From 8e70f3d2feaab8a14f775a5ef84002bdab9688f0 Mon Sep 17 00:00:00 2001 From: Kaalleen <36401965+kaalleen@users.noreply.github.com> Date: Tue, 26 Mar 2024 07:10:40 +0100 Subject: Add object based min stitch length (#2792) * add object based min stitch length (overwrites global) * add object based minimum jump stitch (overwrites global) * rename patches to stitch_groups --- lib/api/stitch_plan.py | 4 +- lib/elements/element.py | 28 ++++++++++- lib/elements/empty_d_object.py | 2 +- lib/elements/fill_stitch.py | 54 ++++++++++++--------- lib/elements/image.py | 2 +- lib/elements/marker.py | 2 +- lib/elements/polyline.py | 8 ++-- lib/elements/satin_column.py | 88 ++++++++++++++++++----------------- lib/elements/stroke.py | 48 ++++++++++--------- lib/elements/text.py | 2 +- lib/extensions/base.py | 12 ++--- lib/extensions/density_map.py | 4 +- lib/extensions/output.py | 4 +- lib/extensions/print_pdf.py | 4 +- lib/extensions/stitch_plan_preview.py | 4 +- lib/extensions/zip.py | 4 +- lib/stitch_plan/color_block.py | 4 +- lib/stitch_plan/stitch.py | 42 +++++++++++++---- lib/stitch_plan/stitch_group.py | 21 +++++++-- lib/stitch_plan/stitch_plan.py | 24 +++++++--- lib/svg/tags.py | 2 + 21 files changed, 229 insertions(+), 134 deletions(-) diff --git a/lib/api/stitch_plan.py b/lib/api/stitch_plan.py index 5e9a57c1..0267a70a 100644 --- a/lib/api/stitch_plan.py +++ b/lib/api/stitch_plan.py @@ -20,8 +20,8 @@ def get_stitch_plan(): metadata = g.extension.get_inkstitch_metadata() collapse_len = metadata['collapse_len_mm'] min_stitch_len = metadata['min_stitch_len_mm'] - patches = g.extension.elements_to_stitch_groups(g.extension.elements) - stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len, min_stitch_len=min_stitch_len) + stitch_groups = g.extension.elements_to_stitch_groups(g.extension.elements) + stitch_plan = stitch_groups_to_stitch_plan(stitch_groups, collapse_len=collapse_len, min_stitch_len=min_stitch_len) return jsonify(stitch_plan) except InkstitchException as exc: return jsonify({"error_message": str(exc)}), 500 diff --git a/lib/elements/element.py b/lib/elements/element.py index 7145571b..6c2fa15a 100644 --- a/lib/elements/element.py +++ b/lib/elements/element.py @@ -218,6 +218,28 @@ class EmbroideryElement(object): width = convert_length(width) return width * self.stroke_scale + @property + @param('min_stitch_length_mm', + _('Minimum stitch length'), + tooltip=_('Overwrite global minimum stitch length setting. Shorter stitches than that will be removed.'), + type='float', + default=None, + sort_index=48) + @cache + def min_stitch_length(self): + return self.get_float_param("min_stitch_length_mm") + + @property + @param('min_jump_stitch_length_mm', + _('Minimum jump stitch length'), + tooltip=_('Overwrite global minimum jump stitch length setting. Shorter distances to the next object will have no lock stitches.'), + type='float', + default=None, + sort_index=49) + @cache + def min_jump_stitch_length(self): + return self.get_float_param("min_jump_stitch_length_mm") + @property @param('ties', _('Allow lock stitches'), @@ -472,7 +494,7 @@ class EmbroideryElement(object): return lock_start, lock_end - def to_stitch_groups(self, last_patch): + def to_stitch_groups(self, last_stitch_group): raise NotImplementedError("%s must implement to_stitch_groups()" % self.__class__.__name__) @debug.time @@ -576,6 +598,10 @@ class EmbroideryElement(object): stitch_groups[-1].trim_after = self.has_command("trim") or self.trim_after stitch_groups[-1].stop_after = self.has_command("stop") or self.stop_after + for stitch_group in stitch_groups: + stitch_group.min_jump_stitch_length = self.min_jump_stitch_length + stitch_group.set_minimum_stitch_length(self.min_stitch_length) + self._save_cached_stitch_groups(stitch_groups, previous_stitch) debug.log(f"ending {self.node.get('id')} {self.node.get(INKSCAPE_LABEL)}") diff --git a/lib/elements/empty_d_object.py b/lib/elements/empty_d_object.py index 6f010488..c2e56eac 100644 --- a/lib/elements/empty_d_object.py +++ b/lib/elements/empty_d_object.py @@ -27,5 +27,5 @@ class EmptyDObject(EmbroideryElement): def shape(self): return - def to_stitch_groups(self, last_patch): + def to_stitch_groups(self, last_stitch_group): return [] diff --git a/lib/elements/fill_stitch.py b/lib/elements/fill_stitch.py index 203c9fe8..9ba649d7 100644 --- a/lib/elements/fill_stitch.py +++ b/lib/elements/fill_stitch.py @@ -714,7 +714,7 @@ class FillStitch(EmbroideryElement): def get_starting_point(self, previous_stitch_group): # If there is a "fill_start" Command, then use that; otherwise pick - # the point closest to the end of the last patch. + # the point closest to the end of the last stitch_group. if self.get_command('fill_start'): return self.get_command('fill_start').target_point @@ -792,18 +792,23 @@ class FillStitch(EmbroideryElement): return stitch_groups def do_legacy_fill(self): - stitch_lists = legacy_fill(self.shape, - self.angle, - self.row_spacing, - self.end_row_spacing, - self.max_stitch_length, - self.flip, - self.staggers, - self.skip_last) - return [StitchGroup(stitches=stitch_list, - color=self.color, - force_lock_stitches=self.force_lock_stitches, - lock_stitches=self.lock_stitches) for stitch_list in stitch_lists] + stitch_lists = legacy_fill( + self.shape, + self.angle, + self.row_spacing, + self.end_row_spacing, + self.max_stitch_length, + self.flip, + self.staggers, + self.skip_last + ) + + return [StitchGroup( + stitches=stitch_list, + color=self.color, + force_lock_stitches=self.force_lock_stitches, + lock_stitches=self.lock_stitches + ) for stitch_list in stitch_lists] def do_underlay(self, shape, starting_point): color = self.color @@ -833,7 +838,7 @@ class FillStitch(EmbroideryElement): starting_point = underlay.stitches[-1] return [stitch_groups, starting_point] - def do_auto_fill(self, shape, last_patch, starting_point, ending_point): + def do_auto_fill(self, shape, last_stitch_group, starting_point, ending_point): stitch_group = StitchGroup( color=self.color, tags=("auto_fill", "auto_fill_top"), @@ -851,10 +856,12 @@ class FillStitch(EmbroideryElement): self.skip_last, starting_point, ending_point, - self.underpath)) + self.underpath + ) + ) return [stitch_group] - def do_contour_fill(self, polygon, last_patch, starting_point): + def do_contour_fill(self, polygon, last_stitch_group, starting_point): if not starting_point: starting_point = (0, 0) starting_point = shgeo.Point(starting_point) @@ -894,17 +901,17 @@ class FillStitch(EmbroideryElement): tags=("auto_fill", "auto_fill_top"), stitches=stitches, force_lock_stitches=self.force_lock_stitches, - lock_stitches=self.lock_stitches,) + lock_stitches=self.lock_stitches) stitch_groups.append(stitch_group) return stitch_groups - def do_guided_fill(self, shape, last_patch, starting_point, ending_point): + def do_guided_fill(self, shape, last_stitch_group, starting_point, ending_point): guide_line = self._get_guide_lines() # No guide line: fallback to normal autofill if not guide_line: - return self.do_auto_fill(shape, last_patch, starting_point, ending_point) + return self.do_auto_fill(shape, last_stitch_group, starting_point, ending_point) stitch_group = StitchGroup( color=self.color, @@ -947,11 +954,11 @@ class FillStitch(EmbroideryElement): tags=("meander_fill", "meander_fill_top"), stitches=meander_fill(self, shape, original_shape, i, starting_point, ending_point), force_lock_stitches=self.force_lock_stitches, - lock_stitches=self.lock_stitches, + lock_stitches=self.lock_stitches ) return [stitch_group] - def do_circular_fill(self, shape, last_patch, starting_point, ending_point): + def do_circular_fill(self, shape, last_stitch_group, starting_point, ending_point): # get target position command = self.get_command('ripple_target') if command: @@ -983,8 +990,9 @@ class FillStitch(EmbroideryElement): tags=("circular_fill", "auto_fill_top"), stitches=stitches, force_lock_stitches=self.force_lock_stitches, - lock_stitches=self.lock_stitches,) + lock_stitches=self.lock_stitches + ) return [stitch_group] - def do_linear_gradient_fill(self, shape, last_patch, start, end): + def do_linear_gradient_fill(self, shape, last_stitch_group, start, end): return linear_gradient_fill(self, shape, start, end) diff --git a/lib/elements/image.py b/lib/elements/image.py index 73a46871..9352a73a 100644 --- a/lib/elements/image.py +++ b/lib/elements/image.py @@ -29,5 +29,5 @@ class ImageObject(EmbroideryElement): def validation_warnings(self): yield ImageTypeWarning(self.center()) - def to_stitch_groups(self, last_patch): + def to_stitch_groups(self, last_stitch_group): return [] diff --git a/lib/elements/marker.py b/lib/elements/marker.py index 574ce91e..7085d31f 100644 --- a/lib/elements/marker.py +++ b/lib/elements/marker.py @@ -28,5 +28,5 @@ class MarkerObject(EmbroideryElement): repr_point = next(inkex.Path(self.parse_path()).end_points) yield MarkerWarning(repr_point) - def to_stitch_groups(self, last_patch): + def to_stitch_groups(self, last_stitch_group): return [] diff --git a/lib/elements/polyline.py b/lib/elements/polyline.py index 4bc71dc0..9d17132a 100644 --- a/lib/elements/polyline.py +++ b/lib/elements/polyline.py @@ -94,10 +94,10 @@ class Polyline(EmbroideryElement): def validation_warnings(self): yield PolylineWarning(self.path[0][0][0]) - def to_stitch_groups(self, last_patch): - patch = StitchGroup(color=self.color, lock_stitches=(None, None)) + def to_stitch_groups(self, last_stitch_group): + stitch_group = StitchGroup(color=self.color, lock_stitches=(None, None)) for stitch in self.stitches: - patch.add_stitch(Point(*stitch)) + stitch_group.add_stitch(Point(*stitch)) - return [patch] + return [stitch_group] diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index 10de9f82..ec999ec7 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -1145,7 +1145,8 @@ class SatinColumn(EmbroideryElement): stitch_group = StitchGroup( color=self.color, tags=("satin_column", "satin_column_underlay", "satin_contour_underlay"), - stitches=first_side) + stitches=first_side + ) self.add_running_stitches(first_side[-1], second_side[0], stitch_group) stitch_group.stitches += second_side @@ -1172,7 +1173,8 @@ class SatinColumn(EmbroideryElement): return StitchGroup( color=self.color, tags=("satin_column", "satin_column_underlay", "satin_center_walk"), - stitches=stitches) + stitches=stitches + ) def do_zigzag_underlay(self): # zigzag underlay, usually done at a much lower density than the @@ -1185,7 +1187,7 @@ class SatinColumn(EmbroideryElement): # "German underlay" described here: # http://www.mrxstitch.com/underlay-what-lies-beneath-machine-embroidery/ - patch = StitchGroup(color=self.color) + stitch_group = StitchGroup(color=self.color) pairs = self.plot_points_on_rails(self.zigzag_underlay_spacing / 2.0, -self.zigzag_underlay_inset_px, @@ -1206,12 +1208,12 @@ class SatinColumn(EmbroideryElement): if last_point.distance(point) > max_len: split_points = running_stitch.split_segment_even_dist(last_point, point, max_len) for p in split_points: - patch.add_stitch(p) + stitch_group.add_stitch(p) last_point = point - patch.add_stitch(point) + stitch_group.add_stitch(point) - patch.add_tags(("satin_column", "satin_column_underlay", "satin_zigzag_underlay")) - return patch + stitch_group.add_tags(("satin_column", "satin_column_underlay", "satin_zigzag_underlay")) + return stitch_group def do_satin(self): # satin: do a zigzag pattern, alternating between the paths. The @@ -1222,7 +1224,7 @@ class SatinColumn(EmbroideryElement): # print >> dbg, "satin", self.zigzag_spacing, self.pull_compensation - patch = StitchGroup(color=self.color) + stitch_group = StitchGroup(color=self.color) # pull compensation is automatically converted from mm to pixels by get_float_param pairs = self.plot_points_on_rails( @@ -1248,25 +1250,25 @@ class SatinColumn(EmbroideryElement): split_points, _ = self.get_split_points( last_point, a, last_short_point, a_short, max_stitch_length, last_count, length_sigma, random_phase, min_split_length, prng.join_args(seed, 'satin-split', 2 * i), row_num=2 * i, from_end=True) - patch.add_stitches(split_points, ("satin_column", "satin_split_stitch")) + stitch_group.add_stitches(split_points, ("satin_column", "satin_split_stitch")) - patch.add_stitch(a_short) - patch.stitches[-1].add_tags(("satin_column", "satin_column_edge")) + stitch_group.add_stitch(a_short) + stitch_group.stitches[-1].add_tags(("satin_column", "satin_column_edge")) split_points, last_count = self.get_split_points( a, b, a_short, b_short, max_stitch_length, None, length_sigma, random_phase, min_split_length, prng.join_args(seed, 'satin-split', 2 * i + 1), row_num=2 * i + 1) - patch.add_stitches(split_points, ("satin_column", "satin_split_stitch")) + stitch_group.add_stitches(split_points, ("satin_column", "satin_split_stitch")) - patch.add_stitch(b_short) - patch.stitches[-1].add_tags(("satin_column", "satin_column_edge")) + stitch_group.add_stitch(b_short) + stitch_group.stitches[-1].add_tags(("satin_column", "satin_column_edge")) last_point = b last_short_point = b_short if self._center_walk_is_odd(): - patch.stitches = list(reversed(patch.stitches)) + stitch_group.stitches = list(reversed(stitch_group.stitches)) - return patch + return stitch_group def do_e_stitch(self): # e stitch: do a pattern that looks like the letter "E". It looks like @@ -1274,7 +1276,7 @@ class SatinColumn(EmbroideryElement): # # _|_|_|_|_|_|_|_|_|_|_|_| - patch = StitchGroup(color=self.color) + stitch_group = StitchGroup(color=self.color) pairs = self.plot_points_on_rails( self.zigzag_spacing, @@ -1302,21 +1304,21 @@ class SatinColumn(EmbroideryElement): # zigzag spacing is wider than stitch length, subdivide if last_point is not None and max_stitch_length is not None and self.zigzag_spacing > max_stitch_length: points, _ = self.get_split_points(last_point, left, last_point, left, max_stitch_length) - patch.add_stitches(points) + stitch_group.add_stitches(points) - patch.add_stitch(a_short, ("edge", "left")) - patch.add_stitches(split_points, ("split_stitch",)) - patch.add_stitch(b_short, ("edge",)) - patch.add_stitches(split_points[::-1], ("split_stitch",)) - patch.add_stitch(a_short, ("edge",)) + stitch_group.add_stitch(a_short, ("edge", "left")) + stitch_group.add_stitches(split_points, ("split_stitch",)) + stitch_group.add_stitch(b_short, ("edge",)) + stitch_group.add_stitches(split_points[::-1], ("split_stitch",)) + stitch_group.add_stitch(a_short, ("edge",)) last_point = a_short if self._center_walk_is_odd(): - patch.stitches = list(reversed(patch.stitches)) + stitch_group.stitches = list(reversed(stitch_group.stitches)) - patch.add_tags(("satin_column", "e_stitch")) - return patch + stitch_group.add_tags(("satin_column", "e_stitch")) + return stitch_group def do_s_stitch(self): # S stitch: do a pattern that looks like the letter "S". It looks like @@ -1324,7 +1326,7 @@ class SatinColumn(EmbroideryElement): # _ _ _ _ _ _ # _| |_| |_| |_| |_| |_| | - patch = StitchGroup(color=self.color) + stitch_group = StitchGroup(color=self.color) pairs = self.plot_points_on_rails( self.zigzag_spacing, @@ -1357,17 +1359,17 @@ class SatinColumn(EmbroideryElement): if last_point is not None and max_stitch_length is not None and self.zigzag_spacing > max_stitch_length: initial_points, _ = self.get_split_points(last_point, points[0], last_point, points[0], max_stitch_length) - patch.add_stitches(points) + stitch_group.add_stitches(points) last_point = points[-1] if self._center_walk_is_odd(): - patch.stitches = list(reversed(patch.stitches)) + stitch_group.stitches = list(reversed(stitch_group.stitches)) - patch.add_tags(("satin", "s_stitch")) - return patch + stitch_group.add_tags(("satin", "s_stitch")) + return stitch_group def do_zigzag(self): - patch = StitchGroup(color=self.color) + stitch_group = StitchGroup(color=self.color) # calculate pairs at double the requested density pairs = self.plot_points_on_rails( @@ -1401,24 +1403,24 @@ class SatinColumn(EmbroideryElement): split_points, _ = self.get_split_points( last_point, a, last_point_short, a_short, max_stitch_length, None, length_sigma, random_phase, min_split_length, prng.join_args(seed, 'satin-split', 2 * i), row_num=2 * i, from_end=True) - patch.add_stitches(split_points, ("satin_column", "zigzag_split_stitch")) + stitch_group.add_stitches(split_points, ("satin_column", "zigzag_split_stitch")) - patch.add_stitch(a_short) + stitch_group.add_stitch(a_short) split_points, _ = self.get_split_points( a, b, a_short, b_short, max_stitch_length, None, length_sigma, random_phase, min_split_length, prng.join_args(seed, 'satin-split', 2 * i + 1), row_num=2 * i + 1) - patch.add_stitches(split_points, ("satin_column", "zigzag_split_stitch")) + stitch_group.add_stitches(split_points, ("satin_column", "zigzag_split_stitch")) - patch.add_stitch(b_short) + stitch_group.add_stitch(b_short) last_point = b last_point_short = b_short if self._center_walk_is_odd(): - patch.stitches = list(reversed(patch.stitches)) + stitch_group.stitches = list(reversed(stitch_group.stitches)) - return patch + return stitch_group def get_split_points(self, *args, **kwargs): if self.split_method == "default": @@ -1526,15 +1528,17 @@ class SatinColumn(EmbroideryElement): stitch_group += next_stitch_group return stitch_group - def to_stitch_groups(self, last_patch=None): + def to_stitch_groups(self, last_stitch_group=None): # Stitch a variable-width satin column, zig-zagging between two paths. # The algorithm will draw zigzags between each consecutive pair of # beziers. The boundary points between beziers serve as "checkpoints", # allowing the user to control how the zigzags flow around corners. - stitch_group = StitchGroup(color=self.color, - force_lock_stitches=self.force_lock_stitches, - lock_stitches=self.lock_stitches) + stitch_group = StitchGroup( + color=self.color, + force_lock_stitches=self.force_lock_stitches, + lock_stitches=self.lock_stitches + ) if self.center_walk_underlay: stitch_group += self.do_center_walk() diff --git a/lib/elements/stroke.py b/lib/elements/stroke.py index a4df5118..2ce02dbd 100644 --- a/lib/elements/stroke.py +++ b/lib/elements/stroke.py @@ -443,11 +443,10 @@ class Stroke(EmbroideryElement): # `self.zigzag_spacing` is the length for a zig and a zag # together (a V shape). Start with running stitch at half # that length: - patch = self.running_stitch(path, zigzag_spacing / 2.0, self.running_stitch_tolerance) + stitch_group = self.running_stitch(path, zigzag_spacing / 2.0, self.running_stitch_tolerance) + stitch_group.stitches = zigzag_stitch(stitch_group.stitches, zigzag_spacing, stroke_width, pull_compensation) - patch.stitches = zigzag_stitch(patch.stitches, zigzag_spacing, stroke_width, pull_compensation) - - return patch + return stitch_group def running_stitch(self, path, stitch_length, tolerance): stitches = running_stitch(path, stitch_length, tolerance) @@ -463,7 +462,12 @@ class Stroke(EmbroideryElement): repeated_stitches.extend(this_path) - return StitchGroup(self.color, repeated_stitches, lock_stitches=self.lock_stitches, force_lock_stitches=self.force_lock_stitches) + return StitchGroup( + self.color, + stitches=repeated_stitches, + lock_stitches=self.lock_stitches, + force_lock_stitches=self.force_lock_stitches + ) def apply_max_stitch_length(self, path): # apply max distances @@ -491,16 +495,16 @@ class Stroke(EmbroideryElement): def do_bean_repeats(self, stitches): return bean_stitch(stitches, self.bean_stitch_repeats) - def to_stitch_groups(self, last_patch): # noqa: C901 - patches = [] + def to_stitch_groups(self, last_stitch_group): # noqa: C901 + stitch_groups = [] # ripple stitch if self.stroke_method == 'ripple_stitch': - patch = self.ripple_stitch() - if patch: + stitch_group = self.ripple_stitch() + if stitch_group: if any(self.bean_stitch_repeats): - patch.stitches = self.do_bean_repeats(patch.stitches) - patches.append(patch) + stitch_group.stitches = self.do_bean_repeats(stitch_group.stitches) + stitch_groups.append(stitch_group) else: for path in self.paths: path = [Point(x, y) for x, y in path] @@ -514,24 +518,26 @@ class Stroke(EmbroideryElement): else: # manual stitch disables lock stitches unless they force them lock_stitches = (None, None) - patch = StitchGroup(color=self.color, - stitches=path, - lock_stitches=lock_stitches, - force_lock_stitches=self.force_lock_stitches) + stitch_group = StitchGroup( + color=self.color, + stitches=path, + lock_stitches=lock_stitches, + force_lock_stitches=self.force_lock_stitches + ) # simple satin elif self.stroke_method == 'zigzag_stitch': - patch = self.simple_satin(path, self.zigzag_spacing, self.stroke_width, self.pull_compensation) + stitch_group = self.simple_satin(path, self.zigzag_spacing, self.stroke_width, self.pull_compensation) # running stitch else: - patch = self.running_stitch(path, self.running_stitch_length, self.running_stitch_tolerance) + stitch_group = self.running_stitch(path, self.running_stitch_length, self.running_stitch_tolerance) # bean stitch if any(self.bean_stitch_repeats): - patch.stitches = self.do_bean_repeats(patch.stitches) + stitch_group.stitches = self.do_bean_repeats(stitch_group.stitches) - if patch: - patches.append(patch) + if stitch_group: + stitch_groups.append(stitch_group) - return patches + return stitch_groups @cache def get_guide_line(self): diff --git a/lib/elements/text.py b/lib/elements/text.py index 8a3846c0..4203c8f9 100644 --- a/lib/elements/text.py +++ b/lib/elements/text.py @@ -29,5 +29,5 @@ class TextObject(EmbroideryElement): def validation_warnings(self): yield TextTypeWarning(self.pointer()) - def to_stitch_groups(self, last_patch): + def to_stitch_groups(self, last_stitch_group): return [] diff --git a/lib/extensions/base.py b/lib/extensions/base.py index e0bf4131..4d5aacfe 100644 --- a/lib/extensions/base.py +++ b/lib/extensions/base.py @@ -124,16 +124,16 @@ class InkstitchExtension(inkex.EffectExtension): return False def elements_to_stitch_groups(self, elements): - patches = [] + stitch_groups = [] for element in elements: - if patches: - last_patch = patches[-1] + if stitch_groups: + last_stitch_group = stitch_groups[-1] else: - last_patch = None + last_stitch_group = None - patches.extend(element.embroider(last_patch)) + stitch_groups.extend(element.embroider(last_stitch_group)) - return patches + return stitch_groups def get_inkstitch_metadata(self): return InkStitchMetadata(self.svg) diff --git a/lib/extensions/density_map.py b/lib/extensions/density_map.py index 854a4ebe..e4963bf9 100644 --- a/lib/extensions/density_map.py +++ b/lib/extensions/density_map.py @@ -39,8 +39,8 @@ class DensityMap(InkstitchExtension): self.metadata = self.get_inkstitch_metadata() collapse_len = self.metadata['collapse_len_mm'] min_stitch_len = self.metadata['min_stitch_len_mm'] - patches = self.elements_to_stitch_groups(self.elements) - stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len, min_stitch_len=min_stitch_len) + stitch_groups = self.elements_to_stitch_groups(self.elements) + stitch_plan = stitch_groups_to_stitch_plan(stitch_groups, collapse_len=collapse_len, min_stitch_len=min_stitch_len) layer = svg.find(".//*[@id='__inkstitch_density_plan__']") color_groups = create_color_groups(layer) diff --git a/lib/extensions/output.py b/lib/extensions/output.py index ef542277..5f2b485d 100644 --- a/lib/extensions/output.py +++ b/lib/extensions/output.py @@ -54,8 +54,8 @@ class Output(InkstitchExtension): self.metadata = self.get_inkstitch_metadata() collapse_len = self.metadata['collapse_len_mm'] min_stitch_len = self.metadata['min_stitch_len_mm'] - patches = self.elements_to_stitch_groups(self.elements) - stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len, disable_ties=self.settings.get('laser_mode', False), + stitch_groups = self.elements_to_stitch_groups(self.elements) + stitch_plan = stitch_groups_to_stitch_plan(stitch_groups, collapse_len=collapse_len, disable_ties=self.settings.get('laser_mode', False), min_stitch_len=min_stitch_len) ThreadCatalog().match_and_apply_palette(stitch_plan, self.metadata['thread-palette']) diff --git a/lib/extensions/print_pdf.py b/lib/extensions/print_pdf.py index ddfdc3a3..c3c14e48 100644 --- a/lib/extensions/print_pdf.py +++ b/lib/extensions/print_pdf.py @@ -310,8 +310,8 @@ class Print(InkstitchExtension): self.metadata = self.get_inkstitch_metadata() collapse_len = self.metadata['collapse_len_mm'] min_stitch_len = self.metadata['min_stitch_len_mm'] - patches = self.elements_to_stitch_groups(self.elements) - stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len, min_stitch_len=min_stitch_len) + stitch_groups = self.elements_to_stitch_groups(self.elements) + stitch_plan = stitch_groups_to_stitch_plan(stitch_groups, collapse_len=collapse_len, min_stitch_len=min_stitch_len) palette = ThreadCatalog().match_and_apply_palette(stitch_plan, self.get_inkstitch_metadata()['thread-palette']) overview_svg, color_block_svgs = self.render_svgs(stitch_plan, realistic=False) diff --git a/lib/extensions/stitch_plan_preview.py b/lib/extensions/stitch_plan_preview.py index 224c8d75..541c42f0 100644 --- a/lib/extensions/stitch_plan_preview.py +++ b/lib/extensions/stitch_plan_preview.py @@ -38,8 +38,8 @@ class StitchPlanPreview(InkstitchExtension): self.metadata = self.get_inkstitch_metadata() collapse_len = self.metadata['collapse_len_mm'] min_stitch_len = self.metadata['min_stitch_len_mm'] - patches = self.elements_to_stitch_groups(self.elements) - stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len, min_stitch_len=min_stitch_len) + stitch_groups = self.elements_to_stitch_groups(self.elements) + stitch_plan = stitch_groups_to_stitch_plan(stitch_groups, collapse_len=collapse_len, min_stitch_len=min_stitch_len) render_stitch_plan(svg, stitch_plan, realistic, visual_commands) # apply options diff --git a/lib/extensions/zip.py b/lib/extensions/zip.py index 24bfc633..9a13ca2d 100644 --- a/lib/extensions/zip.py +++ b/lib/extensions/zip.py @@ -54,8 +54,8 @@ class Zip(InkstitchExtension): self.metadata = self.get_inkstitch_metadata() collapse_len = self.metadata['collapse_len_mm'] min_stitch_len = self.metadata['min_stitch_len_mm'] - patches = self.elements_to_stitch_groups(self.elements) - stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len, min_stitch_len=min_stitch_len) + stitch_groups = self.elements_to_stitch_groups(self.elements) + stitch_plan = stitch_groups_to_stitch_plan(stitch_groups, collapse_len=collapse_len, min_stitch_len=min_stitch_len) ThreadCatalog().match_and_apply_palette(stitch_plan, self.get_inkstitch_metadata()['thread-palette']) if self.options.x_repeats != 1 or self.options.y_repeats != 1: diff --git a/lib/stitch_plan/color_block.py b/lib/stitch_plan/color_block.py index a0f9d43f..3cec826d 100644 --- a/lib/stitch_plan/color_block.py +++ b/lib/stitch_plan/color_block.py @@ -112,6 +112,7 @@ class ColorBlock(object): if min_stitch_len is None: min_stitch_len = 0.1 + min_stitch_len *= PIXELS_PER_MM stitches = [self.stitches[0]] for stitch in self.stitches[1:]: @@ -123,7 +124,8 @@ class ColorBlock(object): pass else: length = (stitch - stitches[-1]).length() - if length <= min_stitch_len * PIXELS_PER_MM: + min_length = stitch.min_stitch_length or min_stitch_len + if length <= min_length: # duplicate stitch, skip this one continue diff --git a/lib/stitch_plan/stitch.py b/lib/stitch_plan/stitch.py index 8ad699c7..ffa944ae 100644 --- a/lib/stitch_plan/stitch.py +++ b/lib/stitch_plan/stitch.py @@ -11,7 +11,17 @@ from ..utils.geometry import Point class Stitch(Point): """A stitch is a Point with extra information telling how to sew it.""" - def __init__(self, x, y=None, color=None, jump=False, stop=False, trim=False, color_change=False, tags=None): + def __init__( + self, + x, y=None, + color=None, + jump=False, + stop=False, + trim=False, + color_change=False, + min_stitch_length=None, + tags=None + ): # DANGER: if you add new attributes, you MUST also set their default # values in __new__() below. Otherwise, cached stitch plans can be # loaded and create objects without those properties defined, because @@ -37,6 +47,7 @@ class Stitch(Point): self._set('trim', trim, base_stitch) self._set('stop', stop, base_stitch) self._set('color_change', color_change, base_stitch) + self._set('min_stitch_length', min_stitch_length, base_stitch) self.tags = set() self.add_tags(tags or []) @@ -52,13 +63,16 @@ class Stitch(Point): return instance def __repr__(self): - return "Stitch(%s, %s, %s, %s, %s, %s, %s)" % (self.x, - self.y, - self.color, - "JUMP" if self.jump else " ", - "TRIM" if self.trim else " ", - "STOP" if self.stop else " ", - "COLOR CHANGE" if self.color_change else " ") + return "Stitch(%s, %s, %s, %s, %s, %s, %s, %s)" % ( + self.x, + self.y, + self.color, + self.min_stitch_length, + "JUMP" if self.jump else " ", + "TRIM" if self.trim else " ", + "STOP" if self.stop else " ", + "COLOR CHANGE" if self.color_change else " " + ) def _set(self, attribute, value, base_stitch): # Set an attribute. If the caller passed a Stitch object, use its value, unless @@ -95,7 +109,17 @@ class Stitch(Point): return tag in self.tags def copy(self): - return Stitch(self.x, self.y, self.color, self.jump, self.stop, self.trim, self.color_change, self.tags) + return Stitch( + self.x, + self.y, + self.color, + self.jump, + self.stop, + self.trim, + self.color_change, + self.min_stitch_length, + self.tags + ) def offset(self, offset: Point): out = self.copy() diff --git a/lib/stitch_plan/stitch_group.py b/lib/stitch_plan/stitch_group.py index c85fc5f5..0730101c 100644 --- a/lib/stitch_plan/stitch_group.py +++ b/lib/stitch_plan/stitch_group.py @@ -17,8 +17,17 @@ class StitchGroup: between them by the stitch plan generation code. """ - def __init__(self, color=None, stitches=None, trim_after=False, stop_after=False, - lock_stitches=(None, None), force_lock_stitches=False, tags=None): + def __init__( + self, + color=None, + stitches=None, + min_jump_stitch_length=False, + trim_after=False, + stop_after=False, + lock_stitches=(None, None), + force_lock_stitches=False, + tags=None + ): # DANGER: if you add new attributes, you MUST also set their default # values in __new__() below. Otherwise, cached stitch plans can be # loaded and create objects without those properties defined, because @@ -29,6 +38,7 @@ class StitchGroup: self.stop_after = stop_after self.lock_stitches = lock_stitches self.force_lock_stitches = force_lock_stitches + self.min_jump_stitch_length = min_jump_stitch_length self.stitches = [] if stitches: @@ -55,9 +65,13 @@ class StitchGroup: raise TypeError("StitchGroup can only be added to another StitchGroup") def __len__(self): - # This method allows `len(patch)` and `if patch: + # This method allows `len(stitch_group)` and `if stitch_group: return len(self.stitches) + def set_minimum_stitch_length(self, min_stitch_length): + for stitch in self.stitches: + stitch.min_stitch_length = min_stitch_length + def add_stitches(self, stitches, tags=None): for stitch in stitches: self.add_stitch(stitch, tags=tags) @@ -66,7 +80,6 @@ class StitchGroup: if not isinstance(stitch, Stitch): # probably a Point stitch = Stitch(stitch, tags=tags) - self.stitches.append(stitch) def reverse(self): diff --git a/lib/stitch_plan/stitch_plan.py b/lib/stitch_plan/stitch_plan.py index 8067749a..f6e3f0de 100644 --- a/lib/stitch_plan/stitch_plan.py +++ b/lib/stitch_plan/stitch_plan.py @@ -59,13 +59,23 @@ def stitch_groups_to_stitch_plan(stitch_groups, collapse_len=None, min_stitch_le # make a new block of our color color_block = stitch_plan.new_color_block(color=stitch_group.color) else: - if (len(color_block) and not need_tie_in and - ((stitch_group.stitches[0] - color_block.stitches[-1]).length() > collapse_len or - previous_stitch_group.force_lock_stitches)): - lock_stitches = previous_stitch_group.get_lock_stitches("end", disable_ties) - if lock_stitches: - color_block.add_stitches(stitches=lock_stitches) - need_tie_in = True + add_lock = False + if len(color_block) and not need_tie_in: + distance_to_previous_stitch = (stitch_group.stitches[0] - color_block.stitches[-1]).length() + if previous_stitch_group.force_lock_stitches: + add_lock = True + elif previous_stitch_group.min_jump_stitch_length: + # object based minimum jump stitch length overrides the global collapse_len setting + if distance_to_previous_stitch > previous_stitch_group.min_jump_stitch_length: + add_lock = True + elif distance_to_previous_stitch > collapse_len: + add_lock = True + + if add_lock: + lock_stitches = previous_stitch_group.get_lock_stitches("end", disable_ties) + need_tie_in = True + if lock_stitches: + color_block.add_stitches(stitches=lock_stitches) if need_tie_in is True: lock_stitches = stitch_group.get_lock_stitches("start", disable_ties) diff --git a/lib/svg/tags.py b/lib/svg/tags.py index 36721a1a..1da2eb40 100644 --- a/lib/svg/tags.py +++ b/lib/svg/tags.py @@ -54,6 +54,8 @@ SVG_OBJECT_TAGS = (SVG_ELLIPSE_TAG, SVG_CIRCLE_TAG, SVG_RECT_TAG) INKSTITCH_ATTRIBS = {} inkstitch_attribs = [ + 'min_stitch_length_mm', + 'min_jump_stitch_length_mm', 'ties', 'force_lock_stitches', 'lock_start', -- cgit v1.2.3