summaryrefslogtreecommitdiff
path: root/lib/tartan
diff options
context:
space:
mode:
authorKaalleen <36401965+kaalleen@users.noreply.github.com>2024-06-30 22:49:18 +0200
committerGitHub <noreply@github.com>2024-06-30 22:49:18 +0200
commite52886a64a4e76c3fdc49df95c85655da3c4f7f4 (patch)
tree88a4edee630df0947553ced79fb6eb9dc510ccea /lib/tartan
parent7c06dee44ac7951a5db406eb829100a5bc3e5887 (diff)
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)
Diffstat (limited to 'lib/tartan')
-rw-r--r--lib/tartan/svg.py28
-rw-r--r--lib/tartan/utils.py145
2 files changed, 131 insertions, 42 deletions
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(