From 6a8f385d13ee77910923028e99b01c69adc9e4ba Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 19 Jun 2019 14:01:43 -0400 Subject: [PATCH 01/11] fix IndexError for single-path satin columns (fixes #366) --- lib/elements/satin_column.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index bfa393847..4617860a6 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -325,6 +325,9 @@ class SatinColumn(EmbroideryElement): 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)) + 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]))) From 68609cc912d76e8e51258eeea9999542cb2f0e94 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 19 Jun 2019 14:04:25 -0400 Subject: [PATCH 02/11] don't treat objects without stroke as SatinColumn (fixes #460) --- lib/elements/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elements/utils.py b/lib/elements/utils.py index 87dfa877b..5c71de2e7 100644 --- a/lib/elements/utils.py +++ b/lib/elements/utils.py @@ -16,7 +16,7 @@ def node_to_elements(node): elif node.tag == SVG_PATH_TAG: element = EmbroideryElement(node) - if element.get_boolean_param("satin_column"): + if element.get_boolean_param("satin_column") and element.get_style("stroke"): return [SatinColumn(node)] else: elements = [] From ebb4ebb42ccc89631e6d5830460f455c86e97af7 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 19 Jun 2019 14:47:11 -0400 Subject: [PATCH 03/11] rework fill shape parsing code (fixes #469) --- lib/elements/fill.py | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/lib/elements/fill.py b/lib/elements/fill.py index 357adf4b7..4bdfa3ffc 100644 --- a/lib/elements/fill.py +++ b/lib/elements/fill.py @@ -104,34 +104,12 @@ class Fill(EmbroideryElement): @property @cache def shape(self): - poly_ary = [] - for sub_path in self.paths: - point_ary = [] - last_pt = None - for pt in sub_path: - if (last_pt is not None): - vp = (pt[0] - last_pt[0], pt[1] - last_pt[1]) - dp = math.sqrt(math.pow(vp[0], 2.0) + math.pow(vp[1], 2.0)) - # dbg.write("dp %s\n" % dp) - if (dp > 0.01): - # I think too-close points confuse shapely. - point_ary.append(pt) - last_pt = pt - else: - last_pt = pt - if len(point_ary) > 2: - poly_ary.append(point_ary) - - if not poly_ary: - self.fatal(_("shape %s is so small that it cannot be filled with stitches. Please make it bigger or delete it.") % self.node.get('id')) - # shapely's idea of "holes" are to subtract everything in the second set # from the first. So let's at least make sure the "first" thing is the # biggest path. - # TODO: actually figure out which things are holes and which are shells - poly_ary.sort(key=lambda point_list: shgeo.Polygon(point_list).area, reverse=True) - - polygon = shgeo.MultiPolygon([(poly_ary[0], poly_ary[1:])]) + paths = self.paths + paths.sort(key=lambda point_list: shgeo.Polygon(point_list).area, reverse=True) + polygon = shgeo.MultiPolygon([(paths[0], paths[1:])]) if not polygon.is_valid: self.fatal(_("shape is not valid. This can happen if the border crosses over itself.")) From f0315604291ede89798dc3342eb0c001574e1c5f Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 19 Jun 2019 15:09:56 -0400 Subject: [PATCH 04/11] update embroider_satin_column in all cases (fixes #466) --- lib/extensions/params.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/extensions/params.py b/lib/extensions/params.py index 4d04ba230..a3ba77848 100644 --- a/lib/extensions/params.py +++ b/lib/extensions/params.py @@ -138,8 +138,11 @@ class ParamsTab(ScrolledPanel): if self.toggle: checked = self.enabled() - if self.toggle_checkbox in self.changed_inputs and not self.toggle.inverse: - values[self.toggle.name] = checked + if self.toggle_checkbox in self.changed_inputs: + if self.toggle.inverse: + values[self.toggle.name] = not checked + else: + values[self.toggle.name] = checked if not checked: # Ignore params on this tab if the toggle is unchecked, From 249c876ef559e8b2a614cb19caa426351fa43075 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 19 Jun 2019 15:20:43 -0400 Subject: [PATCH 05/11] better message for unconnected fill shapes (fixes #463) --- lib/elements/fill.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/elements/fill.py b/lib/elements/fill.py index 4bdfa3ffc..7ccf7b27e 100644 --- a/lib/elements/fill.py +++ b/lib/elements/fill.py @@ -1,6 +1,7 @@ import math from shapely import geometry as shgeo +from shapely.validation import explain_validity from ..i18n import _ from ..stitches import legacy_fill @@ -112,7 +113,15 @@ class Fill(EmbroideryElement): polygon = shgeo.MultiPolygon([(paths[0], paths[1:])]) if not polygon.is_valid: - self.fatal(_("shape is not valid. This can happen if the border crosses over itself.")) + why = explain_validity(polygon) + + # I Wish this weren't so brittle... + if "Hole lies outside shell" in why: + self.fatal(_("this object is made up of unconnected shapes. This is not allowed because " + "Ink/Stitch doesn't know what order to stitch them in. Please break this " + "object up into separate shapes.")) + else: + self.fatal(_("shape is not valid. This can happen if the border crosses over itself.")) return polygon From e8bd745dfca8c40c0e688c31cec1b09f1cfc65d2 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 19 Jun 2019 15:46:46 -0400 Subject: [PATCH 06/11] handle single linestrings properly (fixes #471) --- lib/stitches/auto_fill.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/stitches/auto_fill.py b/lib/stitches/auto_fill.py index 9d946ae2e..8c8cdefd1 100644 --- a/lib/stitches/auto_fill.py +++ b/lib/stitches/auto_fill.py @@ -312,6 +312,15 @@ def travel_grating(shape, angle, row_spacing): return shgeo.MultiLineString(segments) +def ensure_multi_line_string(thing): + """Given either a MultiLineString or a single LineString, return a MultiLineString""" + + if isinstance(thing, shgeo.LineString): + return shgeo.MultiLineString([thing]) + else: + return thing + + def build_travel_edges(shape, fill_angle): r"""Given a graph, compute the interior travel edges. @@ -359,10 +368,10 @@ def build_travel_edges(shape, fill_angle): for ls in mls for coord in ls.coords] - diagonal_edges = grating1.symmetric_difference(grating2) + diagonal_edges = ensure_multi_line_string(grating1.symmetric_difference(grating2)) # without this, floating point inaccuracies prevent the intersection points from lining up perfectly. - vertical_edges = snap(grating3.difference(grating1), diagonal_edges, 0.005) + vertical_edges = ensure_multi_line_string(snap(grating3.difference(grating1), diagonal_edges, 0.005)) return endpoints, chain(diagonal_edges, vertical_edges) From 46fc95eea5d7fe0fa496744b69042d6dc66ca34a Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 22 Jun 2019 18:10:05 -0400 Subject: [PATCH 07/11] handle document width/height of 100% (fixes #476) --- lib/svg/units.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/svg/units.py b/lib/svg/units.py index 0de410ab9..739dcbb49 100644 --- a/lib/svg/units.py +++ b/lib/svg/units.py @@ -1,7 +1,8 @@ import simpletransform -from ..utils import cache from ..i18n import _ +from ..utils import cache + # modern versions of Inkscape use 96 pixels per inch as per the CSS standard PIXELS_PER_MM = 96 / 25.4 @@ -91,6 +92,15 @@ def get_doc_size(svg): width = svg.get('width') height = svg.get('height') + if width == "100%" and height == "100%": + # Some SVG editors set width and height to "100%". I can't find any + # solid documentation on how one is supposed to interpret that, so + # just ignore it and use the viewBox. That seems to have the intended + # result anyway. + + width = None + height = None + if width is None or height is None: # fall back to the dimensions from the viewBox viewbox = get_viewbox(svg) From 18f50a93eb50f910f9229ef20e2a620d07cc7ec4 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 22 Jun 2019 18:12:11 -0400 Subject: [PATCH 08/11] handle unicode node names (fixes #467) --- lib/elements/element.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elements/element.py b/lib/elements/element.py index 10b1852ad..a2658d888 100644 --- a/lib/elements/element.py +++ b/lib/elements/element.py @@ -283,5 +283,5 @@ class EmbroideryElement(object): # L10N used when showing an error message to the user such as # "Some Path (path1234): error: satin column: One or more of the rungs doesn't intersect both rails." - print >> sys.stderr, "%s: %s %s" % (name, _("error:"), message.encode("UTF-8")) + print >> sys.stderr, "%s: %s %s" % (name.encode("UTF-8"), _("error:"), message.encode("UTF-8")) sys.exit(1) From 2258bf76ca25dde1fb25364a333770e8e3966c4d Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 22 Jun 2019 18:46:39 -0400 Subject: [PATCH 09/11] partial fix for unicode filenames (#478) --- lib/extensions/embroider.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/extensions/embroider.py b/lib/extensions/embroider.py index 1a5780315..b90896120 100644 --- a/lib/extensions/embroider.py +++ b/lib/extensions/embroider.py @@ -39,10 +39,14 @@ class Embroider(InkstitchExtension): def get_output_path(self): if self.options.output_file: - output_path = os.path.join(os.path.expanduser(os.path.expandvars(self.options.path)), self.options.output_file) + # This is helpful for folks that run the embroider extension + # manually from the command line (without Inkscape) for + # debugging purposes. + output_path = os.path.join(os.path.expanduser(os.path.expandvars(self.options.path.decode("UTF-8"))), + self.options.output_file.decode("UTF-8")) else: csv_filename = '%s.%s' % (self.get_base_file_name(), self.options.output_format) - output_path = os.path.join(self.options.path, csv_filename) + output_path = os.path.join(self.options.path.decode("UTF-8"), csv_filename) def add_suffix(path, suffix): if suffix > 0: From 54179d76bc4338c5089a6d826e4b36790b202e45 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 22 Jun 2019 19:05:57 -0400 Subject: [PATCH 10/11] fix style --- lib/elements/satin_column.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py index 4617860a6..d3c4d3d3a 100644 --- a/lib/elements/satin_column.py +++ b/lib/elements/satin_column.py @@ -326,7 +326,8 @@ class SatinColumn(EmbroideryElement): 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)) + 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)) 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)") % From e81e819602a3823ec344d412ba2e5213349fb2c4 Mon Sep 17 00:00:00 2001 From: Kaalleen <36401965+kaalleen@users.noreply.github.com> Date: Mon, 24 Jun 2019 18:54:43 +0200 Subject: [PATCH 11/11] fix unicode error message --- lib/elements/element.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/elements/element.py b/lib/elements/element.py index a2658d888..e85657cd1 100644 --- a/lib/elements/element.py +++ b/lib/elements/element.py @@ -283,5 +283,6 @@ class EmbroideryElement(object): # L10N used when showing an error message to the user such as # "Some Path (path1234): error: satin column: One or more of the rungs doesn't intersect both rails." - print >> sys.stderr, "%s: %s %s" % (name.encode("UTF-8"), _("error:"), message.encode("UTF-8")) + error_msg = "%s: %s %s" % (name, _("error:"), message) + print >> sys.stderr, "%s" % (error_msg.encode("UTF-8")) sys.exit(1)