From e52886a64a4e76c3fdc49df95c85655da3c4f7f4 Mon Sep 17 00:00:00 2001 From: Kaalleen <36401965+kaalleen@users.noreply.github.com> Date: Sun, 30 Jun 2024 22:49:18 +0200 Subject: Various fixes (#3028) * several thread palette extension fixes * fix svg tartan when original shape is invalid * tartan stroke spaces * style * fix tartan color substituion at pattern start * ripple: do not render too small paths * use less space for params warning headline * fix clone shape path * zip export template fix (typo) * add realistic stitch plan output warning (help tab) --- lib/tartan/svg.py | 28 +++++++--- lib/tartan/utils.py | 145 +++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 131 insertions(+), 42 deletions(-) (limited to 'lib/tartan') diff --git a/lib/tartan/svg.py b/lib/tartan/svg.py index 739315cf..93d33253 100644 --- a/lib/tartan/svg.py +++ b/lib/tartan/svg.py @@ -24,7 +24,7 @@ from ..stitches.auto_fill import (PathEdge, build_fill_stitch_graph, from ..svg import PIXELS_PER_MM, get_correction_transform from ..utils import DotDict, ensure_multi_line_string from .palette import Palette -from .utils import sort_fills_and_strokes, stripes_to_shapes +from .utils import sort_fills_and_strokes, stripes_to_shapes, get_palette_width class TartanSvgGroup: @@ -51,6 +51,8 @@ class TartanSvgGroup: self.symmetry = self.palette.symmetry self.stripes = self.palette.palette_stripes self.warp, self.weft = self.stripes + self.warp_width = get_palette_width(settings) + self.weft_width = get_palette_width(settings) if self.palette.get_palette_width(self.scale, self.min_stripe_width) == 0: self.warp = [] if self.palette.get_palette_width(self.scale, self.min_stripe_width, 1) == 0: @@ -80,8 +82,16 @@ class TartanSvgGroup: index = parent_group.index(outline) parent_group.insert(index, group) - outline_shape = FillStitch(outline).shape transform = get_correction_transform(outline) + outline_shapes = FillStitch(outline).shape + for outline_shape in outline_shapes.geoms: + self._generate_tartan_group_elements(group, outline_shape, transform) + + # set outline invisible + outline.style['display'] = 'none' + group.append(outline) + + def _generate_tartan_group_elements(self, group, outline_shape, transform): dimensions, rotation_center = self._get_dimensions(outline_shape) warp = stripes_to_shapes( @@ -129,11 +139,6 @@ class TartanSvgGroup: for element in stroke_elements: group.append(element) - # set outline invisible - outline.style['display'] = 'none' - group.append(outline) - return group - def _get_command_position(self, fill: FillStitch, point: Tuple[float, float]) -> Point: """ Shift command position out of the element shape @@ -492,6 +497,8 @@ class TartanSvgGroup: """ Calculates the dimensions for the tartan pattern. Make sure it is big enough for pattern rotations. + We also need additional space to ensure fill stripes go to their full extend, this might be problematic if + start or end stripes use render mode 2 (stroke spacing). :param outline: the shape to be filled with a tartan pattern :returns: [0] a list with boundaries and [1] the center point (for rotations) @@ -509,6 +516,13 @@ class TartanSvgGroup: miny = center.y - min_radius maxx = center.x + min_radius maxy = center.y + min_radius + + extra_space = max(self.warp_width * PIXELS_PER_MM, self.weft_width * PIXELS_PER_MM) + minx -= extra_space + maxx += extra_space + miny -= extra_space + maxy += extra_space + return (float(minx), float(miny), float(maxx), float(maxy)), center def _polygon_to_path( diff --git a/lib/tartan/utils.py b/lib/tartan/utils.py index 4f64fc6f..7949505d 100644 --- a/lib/tartan/utils.py +++ b/lib/tartan/utils.py @@ -46,63 +46,138 @@ def stripes_to_shapes( :returns: a dictionary with shapes grouped by color """ + full_sett = _stripes_to_sett(stripes, symmetry, scale, min_stripe_width) + minx, miny, maxx, maxy = dimensions shapes: defaultdict = defaultdict(list) - original_stripes = stripes - if len(original_stripes) == 0: + if len(full_sett) == 0: return shapes left = minx top = miny - add_to_stroke = 0 - add_to_fill = 0 i = -1 while True: i += 1 - stripes = original_stripes - - segments = stripes - if symmetry and i % 2 != 0 and len(stripes) > 1: - segments = list(reversed(stripes[1:-1])) - for stripe in segments: - width = stripe['width'] * PIXELS_PER_MM * (scale / 100) + for stripe in full_sett: + width = stripe['width'] right = left + width bottom = top + width - if ((top > maxy and weft) or (left > maxx and not weft) or - (add_to_stroke > maxy and weft) or (add_to_stroke > maxx and not weft)): + if (top > maxy and weft) or (left > maxx and not weft): return _merge_polygons(shapes, outline, intersect_outline) - if stripe['render'] == 0: - left = right + add_to_stroke - top = bottom + add_to_stroke - add_to_stroke = 0 - continue - elif stripe['render'] == 2: - add_to_stroke += width + if stripe['color'] is None or not stripe['render']: + left = right + top = bottom continue shape_dimensions = [top, bottom, left, right, minx, miny, maxx, maxy] - if width <= min_stripe_width * PIXELS_PER_MM: - add_to_fill = add_to_stroke - shape_dimensions[0] += add_to_stroke - shape_dimensions[2] += add_to_stroke + if stripe['is_stroke']: linestrings = _get_linestrings(outline, shape_dimensions, rotation, rotation_center, weft) shapes[stripe['color']].extend(linestrings) - add_to_stroke += width - continue - add_to_stroke = 0 + else: + polygon = _get_polygon(shape_dimensions, rotation, rotation_center, weft) + shapes[stripe['color']].append(polygon) + left = right + top = bottom + return shapes + + +def _stripes_to_sett( + stripes: List[dict], + symmetry: bool, + scale: int, + min_stripe_width: float, +) -> List[dict]: + """ + Builds a full sett for easier conversion into elements + + :param stripes: a list of dictionaries with stripe information + :param symmetry: reflective sett (True) / repeating sett (False) + :param scale: the scale value (percent) for the pattern + :param min_stripe_width: min stripe width before it is rendered as running stitch + :returns: a list of dictionaries with stripe information (color, width, is_stroke, render) + """ - # add the space of the lines to the following object to avoid bad symmetry - shape_dimensions[1] += add_to_fill - shape_dimensions[3] += add_to_fill + last_fill_color = _get_last_fill_color(stripes, scale, min_stripe_width, symmetry) + first_was_stroke = False + last_was_stroke = False + add_width = 0 + sett = [] + for stripe in stripes: + width = stripe['width'] * PIXELS_PER_MM * (scale / 100) + is_stroke = width <= min_stripe_width * PIXELS_PER_MM + render = stripe['render'] + + if render == 0: + sett.append({'color': None, 'width': width + add_width, 'is_stroke': False, 'render': False}) + last_fill_color = None + add_width = 0 + last_was_stroke = False + continue + + if render == 2: + sett.append({'color': last_fill_color, 'width': width + add_width, 'is_stroke': False, 'render': True}) + add_width = 0 + last_was_stroke = False + continue + + if is_stroke: + if len(sett) == 0: + first_was_stroke = True + width /= 2 + sett.append({'color': last_fill_color, 'width': width + add_width, 'is_stroke': False, 'render': True}) + sett.append({'color': stripe['color'], 'width': 0, 'is_stroke': True, 'render': True}) + add_width = width + last_was_stroke = True + else: + sett.append({'color': stripe['color'], 'width': width + add_width, 'is_stroke': False, 'render': True}) + last_fill_color = stripe['color'] + last_was_stroke = False + + if add_width > 0: + sett.append({'color': last_fill_color, 'width': add_width, 'is_stroke': False, 'render': True}) + + # For symmetric setts we want to mirror the sett and append to receive a full sett + # We do not repeat at pivot points, which means we exclude the first and the last list item from the mirror + if symmetry: + reversed_sett = list(reversed(sett[1:-1])) + if first_was_stroke: + reversed_sett = reversed_sett[:-1] + if last_was_stroke: + reversed_sett = reversed_sett[1:] + sett.extend(reversed_sett) + + return sett + + +def _get_last_fill_color(stripes: List[dict], scale: int, min_stripe_width: float, symmetry: bool,) -> List[dict]: + ''' + Returns the first fill color of a pattern to substitute spaces if the pattern starts with strokes or + stripes with render mode 2 - polygon = _get_polygon(shape_dimensions, rotation, rotation_center, weft) - shapes[stripe['color']].append(polygon) - left = right + add_to_fill - top = bottom + add_to_fill - add_to_fill = 0 + :param stripes: a list of dictionaries with stripe information + :param scale: the scale value (percent) for the pattern + :param min_stripe_width: min stripe width before it is rendered as running stitch + :param symmetry: reflective sett (True) / repeating sett (False) + :returns: a list with fill colors or a list with one None item if there are no fills + ''' + fill_colors = [] + for stripe in stripes: + if stripe['render'] == 0: + fill_colors.append(None) + elif stripe['render'] == 2: + continue + elif stripe['width'] * (scale / 100) > min_stripe_width: + fill_colors.append(stripe['color']) + if len(fill_colors) == 0: + fill_colors = [None] + + if symmetry: + return fill_colors[0] + else: + return fill_colors[-1] def _merge_polygons( -- cgit v1.2.3