summaryrefslogtreecommitdiff
path: root/lib/elements/satin_column.py
diff options
context:
space:
mode:
authorGeorge Steel <george.steel@gmail.com>2022-12-10 19:27:20 -0500
committerGeorge Steel <george.steel@gmail.com>2022-12-10 19:29:01 -0500
commit2cec72cbbd73b2dece5d15715e2ab8bddd417c69 (patch)
treeaff4569cb68f626e05489a86511fbd4be8325e17 /lib/elements/satin_column.py
parent495a22ea55234e032ac526450fd7969d9aa799c8 (diff)
parente52f889bb0cb208685b3f3ef8271f63ecd318af1 (diff)
Merge branch 'george-steel/expose-trim-after'
Diffstat (limited to 'lib/elements/satin_column.py')
-rw-r--r--lib/elements/satin_column.py111
1 files changed, 27 insertions, 84 deletions
diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py
index 3a183ab0..0850d313 100644
--- a/lib/elements/satin_column.py
+++ b/lib/elements/satin_column.py
@@ -15,23 +15,11 @@ from shapely.ops import nearest_points
from ..i18n import _
from ..stitch_plan import StitchGroup
from ..svg import line_strings_to_csp, point_lists_to_csp
-from ..utils import Point, cache, collapse_duplicate_point, cut
+from ..utils import Point, cache, cut, cut_multiple
from .element import EmbroideryElement, param, PIXELS_PER_MM
from .validation import ValidationError, ValidationWarning
-class SatinHasFillError(ValidationError):
- name = _("Satin column has fill")
- description = _("Satin column: Object has a fill (but should not)")
- steps_to_solve = [
- _("* Select this object."),
- _("* Open the Fill and Stroke panel"),
- _("* Open the Fill tab"),
- _("* Disable the Fill"),
- _("* Alternative: open Params and switch this path to Stroke to disable Satin Column mode")
- ]
-
-
class TooFewPathsError(ValidationError):
name = _("Too few subpaths")
description = _("Satin column: Object has too few subpaths. A satin column should have at least two subpaths (the rails).")
@@ -185,7 +173,7 @@ class SatinColumn(EmbroideryElement):
@param(
'pull_compensation_percent',
_('Pull compensation percentage'),
- tooltip=_('Additional pull compensation which varries as a percentage of stitch width. '
+ tooltip=_('Additional pull compensation which varies as a percentage of stitch width. '
'Two values separated by a space may be used for an aysmmetric effect.'),
unit='% (each side)',
type='float',
@@ -390,24 +378,6 @@ class SatinColumn(EmbroideryElement):
@cache
def flattened_rungs(self):
"""The rungs, as LineStrings."""
- rungs = []
- for rung in self._raw_rungs:
- # make sure each rung intersects both rails
- if not rung.intersects(self.flattened_rails[0]) or not rung.intersects(self.flattened_rails[1]):
- # the rung does not intersect both rails
- # get nearest points on rungs
- start = nearest_points(rung, self.flattened_rails[0])[1]
- end = nearest_points(rung, self.flattened_rails[1])[1]
- # extend from the nearest points just a little bit to make sure that we get an intersection
- rung = shaffinity.scale(shgeo.LineString([start, end]), 1.01, 1.01)
- rungs.append(rung)
- else:
- rungs.append(rung)
- return tuple(rungs)
-
- @property
- @cache
- def _raw_rungs(self):
return tuple(shgeo.LineString(self.flatten_subpath(rung)) for rung in self.rungs)
@property
@@ -431,21 +401,18 @@ class SatinColumn(EmbroideryElement):
for rail in self.rails:
points = self.strip_control_points(rail)
- # ignore the start and end
- points = points[1:-1]
+ if len(points) > 2:
+ # Don't bother putting rungs at the start and end.
+ points = points[1:-1]
+ else:
+ # But do include one at the start if we wouldn't add one otherwise.
+ # This avoids confusing other parts of the code.
+ points = points[:-1]
rung_endpoints.append(points)
rungs = []
for start, end in zip(*rung_endpoints):
- # Expand the points just a bit to ensure that shapely thinks they
- # intersect with the rails even with floating point inaccuracy.
- start = Point(*start)
- end = Point(*end)
- start, end = self.offset_points(start, end, (0.01, 0.01), (0, 0))
- start = list(start)
- end = list(end)
-
rungs.append([[start, start, start], [end, end, end]])
return rungs
@@ -498,39 +465,22 @@ class SatinColumn(EmbroideryElement):
indices_by_length = sorted(list(range(num_paths)), key=lambda index: paths[index].length, reverse=True)
return indices_by_length[:2]
- def _cut_rail(self, rail, rung):
- for segment_index, rail_segment in enumerate(rail[:]):
- if rail_segment is None:
- continue
-
- intersection = rail_segment.intersection(rung)
-
- # If there are duplicate points in a rung-less satin, then
- # intersection will be a GeometryCollection of multiple copies
- # of the same point. This reduces it that to a single point.
- intersection = collapse_duplicate_point(intersection)
-
- if not intersection.is_empty:
- cut_result = cut(rail_segment, rail_segment.project(intersection))
- rail[segment_index:segment_index + 1] = cut_result
-
- if cut_result[1] is None:
- # if we were exactly at the end of one of the existing rail segments,
- # stop here or we'll get a spurious second intersection on the next
- # segment
- break
-
@property
@cache
def flattened_sections(self):
"""Flatten the rails, cut with the rungs, and return the sections in pairs."""
- rails = [[rail] for rail in self.flattened_rails]
+ rails = list(self.flattened_rails)
rungs = self.flattened_rungs
- for rung in rungs:
- for rail in rails:
- self._cut_rail(rail, rung)
+ for i, rail in enumerate(rails):
+ cut_points = []
+
+ for rung in rungs:
+ point_on_rung, point_on_rail = nearest_points(rung, rail)
+ cut_points.append(rail.project(point_on_rail))
+
+ rails[i] = cut_multiple(rail, cut_points)
for rail in rails:
for i in range(len(rail)):
@@ -554,19 +504,15 @@ class SatinColumn(EmbroideryElement):
return sections
def validation_warnings(self):
- for rung in self._raw_rungs:
+ for rung in self.flattened_rungs:
for rail in self.flattened_rails:
intersection = rung.intersection(rail)
if intersection.is_empty:
yield DanglingRungWarning(rung.interpolate(0.5, normalized=True))
def validation_errors(self):
- # The node should have exactly two paths with no fill. Each
- # path should have the same number of points, meaning that they
- # will both be made up of the same number of bezier curves.
-
- if self.get_style("fill") is not None:
- yield SatinHasFillError(self.shape.centroid)
+ # The node should have exactly two paths with the same number of points - or it should
+ # have two rails and at least one rung
if len(self.rails) < 2:
yield TooFewPathsError(self.shape.centroid)
@@ -574,7 +520,7 @@ class SatinColumn(EmbroideryElement):
if len(self.rails[0]) != len(self.rails[1]):
yield UnequalPointsError(self.flattened_rails[0].interpolate(0.5, normalized=True))
else:
- for rung in self._raw_rungs:
+ for rung in self.flattened_rungs:
for rail in self.flattened_rails:
intersection = rung.intersection(rail)
if not intersection.is_empty and not isinstance(intersection, shgeo.Point):
@@ -732,14 +678,10 @@ class SatinColumn(EmbroideryElement):
for path_list in path_lists:
if len(path_list) in (2, 4):
- # Add the rung just after the start of the satin.
- rung_start = path_list[0].interpolate(0.1)
- rung_end = path_list[1].interpolate(0.1)
+ # Add the rung at the start of the satin.
+ rung_start = path_list[0].coords[0]
+ rung_end = path_list[1].coords[0]
rung = shgeo.LineString((rung_start, rung_end))
-
- # make it a bit bigger so that it definitely intersects
- rung = shaffinity.scale(rung, 1.1, 1.1)
-
path_list.append(rung)
def _path_list_to_satins(self, path_list):
@@ -781,7 +723,7 @@ class SatinColumn(EmbroideryElement):
# don't contract beyond the midpoint, or we'll start expanding
if offset_total < -distance:
- scale = distance / offset_total
+ scale = -distance / offset_total
offset_a = offset_a * scale
offset_b = offset_b * scale
@@ -931,6 +873,7 @@ class SatinColumn(EmbroideryElement):
# center line between the bezier curves.
inset_prop = -np.array([self.center_walk_underlay_position, 100-self.center_walk_underlay_position]) / 100
+
# Do it like contour underlay, but inset all the way to the center.
forward, back = self.plot_points_on_rails(
self.center_walk_underlay_stitch_length,