diff options
Diffstat (limited to 'lib/elements/satin_column.py')
| -rw-r--r-- | lib/elements/satin_column.py | 121 |
1 files changed, 79 insertions, 42 deletions
diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index d3c4d3d3..ccad234a 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -7,7 +7,54 @@ from shapely import geometry as shgeo, affinity as shaffinity from ..i18n import _ from ..svg import line_strings_to_csp, point_lists_to_csp from ..utils import cache, Point, cut, collapse_duplicate_point + from .element import param, EmbroideryElement, Patch +from .validation import ValidationError + + +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).") + steps_to_solve = [ + _("* Add another subpath (select two rails and do Path > Combine)"), + _("* Convert to running stitch or simple satin (Params extension)") + ] + + +class UnequalPointsError(ValidationError): + name = _("Unequal number of points") + description = _("Satin column: There are no rungs and rails have an an unequal number of points.") + steps_to_solve = [ + _('The easiest way to solve this issue is to add one or more rungs. '), + _('Rungs control the stitch direction in satin columns.'), + _('* With the selected object press "P" to activate the pencil tool.'), + _('* Hold "Shift" while drawing the rung.') + ] + + +rung_message = _("Each rung should intersect both rails once.") + + +class DanglingRungError(ValidationError): + name = _("Rung doesn't intersect rails") + description = _("Satin column: A rung doesn't intersect both rails.") + " " + rung_message + + +class TooManyIntersectionsError(ValidationError): + name = _("Rung intersects too many times") + description = _("Satin column: A rung intersects a rail more than once.") + " " + rung_message class SatinColumn(EmbroideryElement): @@ -146,13 +193,25 @@ class SatinColumn(EmbroideryElement): @property @cache def rails(self): - """The rails in order, as LineStrings""" + """The rails in order, as point lists""" return [subpath for i, subpath in enumerate(self.csp) if i in self.rail_indices] @property @cache + def flattened_rails(self): + """The rails, as LineStrings.""" + return tuple(shgeo.LineString(self.flatten_subpath(rail)) for rail in self.rails) + + @property + @cache + def flattened_rungs(self): + """The rungs, as LineStrings.""" + return tuple(shgeo.LineString(self.flatten_subpath(rung)) for rung in self.rungs) + + @property + @cache def rungs(self): - """The rungs, as LineStrings. + """The rungs, as point lists. If there are no rungs, then this is an old-style satin column. The rails are expected to have the same number of path nodes. The path @@ -238,8 +297,6 @@ class SatinColumn(EmbroideryElement): return indices_by_length[:2] def _cut_rail(self, rail, rung): - intersections = 0 - for segment_index, rail_segment in enumerate(rail[:]): if rail_segment is None: continue @@ -252,14 +309,6 @@ class SatinColumn(EmbroideryElement): intersection = collapse_duplicate_point(intersection) if not intersection.is_empty: - if isinstance(intersection, shgeo.MultiLineString): - intersections += len(intersection) - break - elif not isinstance(intersection, shgeo.Point): - self.fatal("INTERNAL ERROR: intersection is: %s %s" % (intersection, getattr(intersection, 'geoms', None))) - else: - intersections += 1 - cut_result = cut(rail_segment, rail_segment.project(intersection)) rail[segment_index:segment_index + 1] = cut_result @@ -269,29 +318,17 @@ class SatinColumn(EmbroideryElement): # segment break - return intersections - @property @cache def flattened_sections(self): """Flatten the rails, cut with the rungs, and return the sections in pairs.""" - if len(self.csp) < 2: - self.fatal(_("satin column: %(id)s: at least two subpaths required (%(num)d found)") % dict(num=len(self.csp), id=self.node.get('id'))) - - rails = [[shgeo.LineString(self.flatten_subpath(rail))] for rail in self.rails] - rungs = [shgeo.LineString(self.flatten_subpath(rung)) for rung in self.rungs] + rails = [[rail] for rail in self.flattened_rails] + rungs = self.flattened_rungs for rung in rungs: - for rail_index, rail in enumerate(rails): - intersections = self._cut_rail(rail, rung) - - if intersections == 0: - self.fatal(_("satin column: One or more of the rungs doesn't intersect both rails.") + - " " + _("Each rail should intersect both rungs once.")) - elif intersections > 1: - self.fatal(_("satin column: One or more of the rungs intersects the rails more than once.") + - " " + _("Each rail should intersect both rungs once.")) + for rail in rails: + self._cut_rail(rail, rung) for rail in rails: for i in xrange(len(rail)): @@ -314,24 +351,27 @@ class SatinColumn(EmbroideryElement): return sections - def validate_satin_column(self): + 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. - node_id = self.node.get("id") - if self.get_style("fill") is not None: - self.fatal(_("satin column: object %s has a fill (but should not)") % node_id) - - if not self.rungs: - if len(self.rails) < 2: - self.fatal(_("satin column: object %(id)s has too few paths. A satin column should have at least two paths (the rails).") % - dict(id=node_id)) + yield SatinHasFillError(self.shape.centroid) + if len(self.rails) < 2: + yield TooFewPathsError(self.shape.centroid) + elif len(self.csp) == 2: if len(self.rails[0]) != len(self.rails[1]): - self.fatal(_("satin column: object %(id)s has two paths with an unequal number of points (%(length1)d and %(length2)d)") % - dict(id=node_id, length1=len(self.rails[0]), length2=len(self.rails[1]))) + yield UnequalPointsError(self.flattened_rails[0].interpolate(0.5, normalized=True)) + else: + for rung in self.flattened_rungs: + for rail in self.flattened_rails: + intersection = rung.intersection(rail) + if intersection.is_empty: + yield DanglingRungError(rung.interpolate(0.5, normalized=True)) + elif not isinstance(intersection, shgeo.Point): + yield TooManyIntersectionsError(rung.interpolate(0.5, normalized=True)) def reverse(self): """Return a new SatinColumn like this one but in the opposite direction. @@ -772,9 +812,6 @@ class SatinColumn(EmbroideryElement): # beziers. The boundary points between beziers serve as "checkpoints", # allowing the user to control how the zigzags flow around corners. - # First, verify that we have valid paths. - self.validate_satin_column() - patch = Patch(color=self.color) if self.center_walk_underlay: |
