From d4acb52d6983e5e47f526aaf6216372291385016 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 22 Aug 2018 21:56:36 -0400 Subject: add `point` property to StandaloneCommand --- lib/commands.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/commands.py b/lib/commands.py index 7764539c..b9cf9610 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -1,9 +1,9 @@ import inkex import cubicsuperpath -from .svg import apply_transforms +from .svg import apply_transforms, get_node_transform from .svg.tags import SVG_USE_TAG, SVG_SYMBOL_TAG, CONNECTION_START, CONNECTION_END, XLINK_HREF -from .utils import cache +from .utils import cache, Point from .i18n import _, N_ COMMANDS = { @@ -117,6 +117,15 @@ class StandaloneCommand(BaseCommand): self.parse_symbol() + @property + @cache + def point(self): + pos = [float(self.node.get("x", 0)), float(self.node.get("y", 0))] + transform = get_node_transform(self.node) + simpletransform.applyTransformToPoint(transform, pos) + + return Point(*pos) + def get_command_description(command): return _(COMMANDS[command]) -- cgit v1.2.3 From a448b2c0ea4e61b28dccd406ffcc5a5ebb96cdd2 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 22 Aug 2018 22:13:51 -0400 Subject: add origin command and remove guides method --- lib/commands.py | 30 ++++++++++++++----------- lib/extensions/base.py | 2 +- lib/output.py | 59 ++++++++++++-------------------------------------- 3 files changed, 32 insertions(+), 59 deletions(-) (limited to 'lib') diff --git a/lib/commands.py b/lib/commands.py index b9cf9610..37196990 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -1,5 +1,6 @@ import inkex import cubicsuperpath +import simpletransform from .svg import apply_transforms, get_node_transform from .svg.tags import SVG_USE_TAG, SVG_SYMBOL_TAG, CONNECTION_START, CONNECTION_END, XLINK_HREF @@ -22,8 +23,11 @@ COMMANDS = { # L10N command attached to an object "ignore_object": N_("Ignore this object (do not stitch)"), - # L10N command that affects entire layer - "ignore_layer": N_("Ignore layer (do not stitch any objects in this layer)") + # L10N command that affects a layer + "ignore_layer": N_("Ignore layer (do not stitch any objects in this layer)"), + + # L10N command that affects entire document + "origin": N_("Origin for exported embroidery files"), } OBJECT_COMMANDS = ["fill_start", "fill_end", "stop", "trim", "ignore_object"] @@ -153,31 +157,31 @@ def find_commands(node): def layer_commands(layer, command): """Find standalone (unconnected) command symbols in this layer.""" - commands = [] + for global_command in global_commands(layer.getroottree().getroot(), command): + if layer in global_command.node.iterancestors(): + yield global_command - for standalone_command in standalone_commands(layer.getroottree().getroot()): - if standalone_command.command == command: - if layer in standalone_command.node.iterancestors(): - commands.append(command) - return commands +def global_commands(svg, command): + """Find standalone (unconnected) command symbols anywhere in the document.""" + for standalone_command in _standalone_commands(svg): + if standalone_command.command == command: + yield standalone_command -def standalone_commands(svg): + +def _standalone_commands(svg): """Find all unconnected command symbols in the SVG.""" xpath = ".//svg:use[starts-with(@xlink:href, '#inkstitch_')]" symbols = svg.xpath(xpath, namespaces=inkex.NSS) - commands = [] for symbol in symbols: try: - commands.append(StandaloneCommand(symbol)) + yield StandaloneCommand(symbol) except CommandParseError: pass - return commands - def is_command(node): return CONNECTION_START in node.attrib or CONNECTION_END in node.attrib diff --git a/lib/extensions/base.py b/lib/extensions/base.py index 22bc82db..25de441f 100644 --- a/lib/extensions/base.py +++ b/lib/extensions/base.py @@ -123,7 +123,7 @@ class InkstitchExtension(inkex.Effect): return [] if node.tag == SVG_GROUP_TAG and node.get(INKSCAPE_GROUPMODE) == "layer": - if layer_commands(node, "ignore_layer"): + if len(list(layer_commands(node, "ignore_layer"))): return [] if element.has_style('display') and element.get_style('display') is None: diff --git a/lib/output.py b/lib/output.py index eed665ed..ae15ce4e 100644 --- a/lib/output.py +++ b/lib/output.py @@ -5,6 +5,7 @@ import shapely.geometry as shgeo from .utils import Point from .svg import PIXELS_PER_MM, get_doc_size, get_viewbox_transform +from .commands import global_commands def get_command(stitch): @@ -26,57 +27,25 @@ def _string_to_floats(string): def get_origin(svg): - # The user can specify the embroidery origin by defining two guides - # named "embroidery origin" that intersect. + origin_commands = list(global_commands(svg, "origin")) - namedview = svg.find(inkex.addNS('namedview', 'sodipodi')) - all_guides = namedview.findall(inkex.addNS('guide', 'sodipodi')) - label_attribute = inkex.addNS('label', 'inkscape') - guides = [guide for guide in all_guides - if guide.get(label_attribute, "").startswith("embroidery origin")] + if origin_commands: + origin = origin_commands[0].point - # document size used below - doc_size = list(get_doc_size(svg)) - - # convert the size from viewbox-relative to real-world pixels - viewbox_transform = get_viewbox_transform(svg) - simpletransform.applyTransformToPoint(simpletransform.invertTransform(viewbox_transform), doc_size) - - default = [doc_size[0] / 2.0, doc_size[1] / 2.0] - simpletransform.applyTransformToPoint(viewbox_transform, default) - default = Point(*default) - - if len(guides) < 2: - return default - - # Find out where the guides intersect. Only pay attention to the first two. - guides = guides[:2] - - lines = [] - for guide in guides: - # inkscape's Y axis is reversed from SVG's, and the guide is in inkscape coordinates - position = Point(*_string_to_floats(guide.get('position'))) - position.y = doc_size[1] - position.y + return origin + else: + # default: center of the canvas - # This one baffles me. I think inkscape might have gotten the order of - # their vector wrong? - parts = _string_to_floats(guide.get('orientation')) - direction = Point(parts[1], parts[0]) + doc_size = list(get_doc_size(svg)) - # We have a theoretically infinite line defined by a point on the line - # and a vector direction. Shapely can only deal in concrete line - # segments, so we'll pick points really far in either direction on the - # line and call it good enough. - lines.append(shgeo.LineString((position + 100000 * direction, position - 100000 * direction))) + # convert the size from viewbox-relative to real-world pixels + viewbox_transform = get_viewbox_transform(svg) + simpletransform.applyTransformToPoint(simpletransform.invertTransform(viewbox_transform), doc_size) - intersection = lines[0].intersection(lines[1]) + default = [doc_size[0] / 2.0, doc_size[1] / 2.0] + simpletransform.applyTransformToPoint(viewbox_transform, default) + default = Point(*default) - if isinstance(intersection, shgeo.Point): - origin = [intersection.x, intersection.y] - simpletransform.applyTransformToPoint(viewbox_transform, origin) - return Point(*origin) - else: - # Either the two guides are the same line, or they're parallel. return default -- cgit v1.2.3 From a8ac170e87cf9db1b976ca1a068b67f5a70cc571 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 22 Aug 2018 22:48:40 -0400 Subject: implement stop position --- lib/commands.py | 45 +++++++++++++++++++++++++++++++++++++-------- lib/output.py | 18 ++++++++++++------ 2 files changed, 49 insertions(+), 14 deletions(-) (limited to 'lib') diff --git a/lib/commands.py b/lib/commands.py index 37196990..a9b97b35 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -1,3 +1,4 @@ +import sys import inkex import cubicsuperpath import simpletransform @@ -9,25 +10,28 @@ from .i18n import _, N_ COMMANDS = { # L10N command attached to an object - "fill_start": N_("Fill stitch starting position"), + N_("fill_start"): N_("Fill stitch starting position"), # L10N command attached to an object - "fill_end": N_("Fill stitch ending position"), + N_("fill_end"): N_("Fill stitch ending position"), # L10N command attached to an object - "stop": N_("Stop (pause machine) after sewing this object"), + N_("stop"): N_("Stop (pause machine) after sewing this object"), # L10N command attached to an object - "trim": N_("Trim thread after sewing this object"), + N_("trim"): N_("Trim thread after sewing this object"), # L10N command attached to an object - "ignore_object": N_("Ignore this object (do not stitch)"), + N_("ignore_object"): N_("Ignore this object (do not stitch)"), # L10N command that affects a layer - "ignore_layer": N_("Ignore layer (do not stitch any objects in this layer)"), + N_("ignore_layer"): N_("Ignore layer (do not stitch any objects in this layer)"), # L10N command that affects entire document - "origin": N_("Origin for exported embroidery files"), + N_("origin"): N_("Origin for exported embroidery files"), + + # L10N command that affects entire document + N_("stop_point"): N_("Jump destination for Stop commands (a.k.a. \"Frame Out position\")."), } OBJECT_COMMANDS = ["fill_start", "fill_end", "stop", "trim", "ignore_object"] @@ -132,7 +136,7 @@ class StandaloneCommand(BaseCommand): def get_command_description(command): - return _(COMMANDS[command]) + return COMMANDS[command] def find_commands(node): @@ -169,6 +173,31 @@ def global_commands(svg, command): if standalone_command.command == command: yield standalone_command +@cache +def global_command(svg, command): + """Find a single command of the specified type. + + If more than one is found, print an error and exit. + """ + + commands = list(global_commands(svg, command)) + + if len(commands) == 1: + return commands[0] + elif len(commands) > 1: + print >> sys.stderr, _("Error: there is more than one %(command)s command in the document, but there can only be one. " + "Please remove all but one.") % dict(command=command) + + # L10N This is a continuation of the previous error message, letting the user know + # what command we're talking about since we don't normally expose the actual + # command name to them. Contents of %(description)s are in a separate translation + # string. + print >> sys.stderr, _("%(command)s: %(description)s") % dict(command=command, description=_(get_command_description(command))) + + sys.exit(1) + else: + return None + def _standalone_commands(svg): """Find all unconnected command symbols in the SVG.""" diff --git a/lib/output.py b/lib/output.py index ae15ce4e..92f09963 100644 --- a/lib/output.py +++ b/lib/output.py @@ -5,7 +5,7 @@ import shapely.geometry as shgeo from .utils import Point from .svg import PIXELS_PER_MM, get_doc_size, get_viewbox_transform -from .commands import global_commands +from .commands import global_command def get_command(stitch): @@ -27,12 +27,10 @@ def _string_to_floats(string): def get_origin(svg): - origin_commands = list(global_commands(svg, "origin")) + origin_command = global_command(svg, "origin") - if origin_commands: - origin = origin_commands[0].point - - return origin + if origin_command: + return origin_command.point else: # default: center of the canvas @@ -49,6 +47,12 @@ def get_origin(svg): return default +def jump_to_stop_point(pattern, svg): + stop_position = global_command(svg, "stop_position") + if stop_position: + pattern.add_stitch_absolute(pyembroidery.JUMP, stop_position.point.x, stop_position.point.y) + + def write_embroidery_file(file_path, stitch_plan, svg): origin = get_origin(svg) @@ -58,6 +62,8 @@ def write_embroidery_file(file_path, stitch_plan, svg): pattern.add_thread(color_block.color.pyembroidery_thread) for stitch in color_block: + if stitch.stop: + jump_to_stop_point(pattern, svg) command = get_command(stitch) pattern.add_stitch_absolute(command, stitch.x, stitch.y) -- cgit v1.2.3 From 7f9208ae2a97026019ff36d28faf37a5c1b9b270 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Thu, 23 Aug 2018 21:46:22 -0400 Subject: style fixes --- lib/commands.py | 1 + lib/output.py | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/commands.py b/lib/commands.py index a9b97b35..3a4ca230 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -173,6 +173,7 @@ def global_commands(svg, command): if standalone_command.command == command: yield standalone_command + @cache def global_command(svg, command): """Find a single command of the specified type. diff --git a/lib/output.py b/lib/output.py index 92f09963..d5c513e2 100644 --- a/lib/output.py +++ b/lib/output.py @@ -1,7 +1,5 @@ import pyembroidery -import inkex import simpletransform -import shapely.geometry as shgeo from .utils import Point from .svg import PIXELS_PER_MM, get_doc_size, get_viewbox_transform -- cgit v1.2.3 From 53f92df05635668783a6b9407a01326de14a1291 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Fri, 24 Aug 2018 16:29:13 -0400 Subject: add 'Add Commands' extension --- lib/commands.py | 3 ++- lib/extensions/__init__.py | 2 ++ lib/extensions/global_commands.py | 14 ++++++++++++++ lib/inx/extensions.py | 9 +++++++-- 4 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 lib/extensions/global_commands.py (limited to 'lib') diff --git a/lib/commands.py b/lib/commands.py index 3a4ca230..db3c8a71 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -31,11 +31,12 @@ COMMANDS = { N_("origin"): N_("Origin for exported embroidery files"), # L10N command that affects entire document - N_("stop_point"): N_("Jump destination for Stop commands (a.k.a. \"Frame Out position\")."), + N_("stop_position"): N_("Jump destination for Stop commands (a.k.a. \"Frame Out position\")."), } OBJECT_COMMANDS = ["fill_start", "fill_end", "stop", "trim", "ignore_object"] LAYER_COMMANDS = ["ignore_layer"] +GLOBAL_COMMANDS = ["origin", "stop_position"] class CommandParseError(Exception): diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py index cf0313b2..5b72ecb3 100644 --- a/lib/extensions/__init__.py +++ b/lib/extensions/__init__.py @@ -9,6 +9,7 @@ from zip import Zip from flip import Flip from object_commands import ObjectCommands from layer_commands import LayerCommands +from global_commands import GlobalCommands from convert_to_satin import ConvertToSatin __all__ = extensions = [Embroider, @@ -22,4 +23,5 @@ __all__ = extensions = [Embroider, Flip, ObjectCommands, LayerCommands, + GlobalCommands, ConvertToSatin] diff --git a/lib/extensions/global_commands.py b/lib/extensions/global_commands.py new file mode 100644 index 00000000..55848be9 --- /dev/null +++ b/lib/extensions/global_commands.py @@ -0,0 +1,14 @@ +import inkex + +from .layer_commands import LayerCommands +from ..commands import GLOBAL_COMMANDS + + +# It's a bit weird subclassing this from LayerCommands, but global commands +# must still be placed in a layer. That means the two extensions +# do the same thing and the code is the same. We keep this as separate +# extensions because we want the user to understand that global commands +# affect the entire document, not just the current layer. + +class GlobalCommands(LayerCommands): + COMMANDS = GLOBAL_COMMANDS diff --git a/lib/inx/extensions.py b/lib/inx/extensions.py index 4b4b3c13..d1a0c7f3 100755 --- a/lib/inx/extensions.py +++ b/lib/inx/extensions.py @@ -3,7 +3,7 @@ import pyembroidery from .utils import build_environment, write_inx_file from .outputs import pyembroidery_output_formats from ..extensions import extensions, Input, Output -from ..commands import LAYER_COMMANDS, OBJECT_COMMANDS, COMMANDS +from ..commands import LAYER_COMMANDS, OBJECT_COMMANDS, GLOBAL_COMMANDS, COMMANDS def layer_commands(): @@ -13,6 +13,10 @@ def layer_commands(): return [(command, COMMANDS[command]) for command in LAYER_COMMANDS] +def global_commands(): + return [(command, COMMANDS[command]) for command in GLOBAL_COMMANDS] + + def object_commands(): return [(command, COMMANDS[command]) for command in OBJECT_COMMANDS] @@ -35,4 +39,5 @@ def generate_extension_inx_files(): write_inx_file(name, template.render(formats=pyembroidery_output_formats(), debug_formats=pyembroidery_debug_formats(), layer_commands=layer_commands(), - object_commands=object_commands())) + object_commands=object_commands(), + global_commands=global_commands())) -- cgit v1.2.3 From 77177f9b55f57d1373408b3dc619a53fe45d00ce Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Fri, 24 Aug 2018 20:40:56 -0400 Subject: fix style --- lib/extensions/global_commands.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'lib') diff --git a/lib/extensions/global_commands.py b/lib/extensions/global_commands.py index 55848be9..9655c7af 100644 --- a/lib/extensions/global_commands.py +++ b/lib/extensions/global_commands.py @@ -1,5 +1,3 @@ -import inkex - from .layer_commands import LayerCommands from ..commands import GLOBAL_COMMANDS -- cgit v1.2.3