summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/commands.py84
-rw-r--r--lib/extensions/__init__.py2
-rw-r--r--lib/extensions/base.py2
-rw-r--r--lib/extensions/global_commands.py12
-rwxr-xr-xlib/inx/extensions.py9
-rw-r--r--lib/output.py65
6 files changed, 105 insertions, 69 deletions
diff --git a/lib/commands.py b/lib/commands.py
index 7764539c..db3c8a71 100644
--- a/lib/commands.py
+++ b/lib/commands.py
@@ -1,33 +1,42 @@
+import sys
import inkex
import cubicsuperpath
+import simpletransform
-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 = {
# 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 entire layer
- "ignore_layer": N_("Ignore layer (do not stitch any objects in this layer)")
+ # L10N command that affects a layer
+ N_("ignore_layer"): N_("Ignore layer (do not stitch any objects in this layer)"),
+
+ # L10N command that affects entire document
+ N_("origin"): N_("Origin for exported embroidery files"),
+
+ # L10N command that affects entire document
+ 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):
@@ -117,9 +126,18 @@ 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])
+ return COMMANDS[command]
def find_commands(node):
@@ -144,31 +162,57 @@ 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()):
+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:
- if layer in standalone_command.node.iterancestors():
- commands.append(command)
+ yield standalone_command
- return commands
+@cache
+def global_command(svg, command):
+ """Find a single command of the specified type.
-def standalone_commands(svg):
+ 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."""
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/__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/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/extensions/global_commands.py b/lib/extensions/global_commands.py
new file mode 100644
index 00000000..9655c7af
--- /dev/null
+++ b/lib/extensions/global_commands.py
@@ -0,0 +1,12 @@
+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()))
diff --git a/lib/output.py b/lib/output.py
index eed665ed..d5c513e2 100644
--- a/lib/output.py
+++ b/lib/output.py
@@ -1,10 +1,9 @@
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
+from .commands import global_command
def get_command(stitch):
@@ -26,58 +25,30 @@ 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_command = global_command(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_command:
+ return origin_command.point
+ else:
+ # default: center of the canvas
- # document size used below
- doc_size = list(get_doc_size(svg))
+ 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)
+ # 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)
+ 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
-
- # 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])
-
- # 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)))
- intersection = lines[0].intersection(lines[1])
-
- 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
+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):
@@ -89,6 +60,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)