diff --git a/inx/inkstitch_commands.inx b/inx/inkstitch_attach_commands.inx
similarity index 84%
rename from inx/inkstitch_commands.inx
rename to inx/inkstitch_attach_commands.inx
index 33bd990a4..61cf72135 100644
--- a/inx/inkstitch_commands.inx
+++ b/inx/inkstitch_attach_commands.inx
@@ -8,8 +8,8 @@
false
false
false
- false
- commands
+ false
+ object_commands
all
diff --git a/inx/inkstitch_layer_commands.inx b/inx/inkstitch_layer_commands.inx
new file mode 100644
index 000000000..7eadd094a
--- /dev/null
+++ b/inx/inkstitch_layer_commands.inx
@@ -0,0 +1,19 @@
+
+
+ <_name>Add Layer Commands
+ org.inkstitch.layer_commands
+ inkstitch.py
+ inkex.py
+ Commands will be added to the currently-selected layer.
+ false
+ layer_commands
+
+ all
+
+
+
+
+
+
diff --git a/lib/commands.py b/lib/commands.py
index a9f156e49..cadfa080a 100644
--- a/lib/commands.py
+++ b/lib/commands.py
@@ -75,7 +75,7 @@ class Command(BaseCommand):
class StandaloneCommand(BaseCommand):
def __init__(self, use):
self.node = use
- self.svg = self.use.getroottree().getroot()
+ self.svg = self.node.getroottree().getroot()
self.parse_command()
@@ -113,7 +113,7 @@ def layer_commands(layer, command):
for standalone_command in standalone_commands(layer.getroottree().getroot()):
if standalone_command.command == command:
- if layer in command.iterancestors():
+ if layer in standalone_command.node.iterancestors():
commands.append(command)
return commands
@@ -122,7 +122,7 @@ def standalone_commands(svg):
"""Find all unconnected command symbols in the SVG."""
xpath = ".//svg:use[starts-with(@xlink:href, '#inkstitch_')]"
- symbols = svg.xpath(xpath, namespace=inkex.NSS)
+ symbols = svg.xpath(xpath, namespaces=inkex.NSS)
commands = []
for symbol in symbols:
diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py
index 30a08c9ff..6c8db3186 100644
--- a/lib/extensions/__init__.py
+++ b/lib/extensions/__init__.py
@@ -7,5 +7,6 @@ from input import Input
from output import Output
from zip import Zip
from flip import Flip
-from commands import Commands
+from object_commands import ObjectCommands
+from layer_commands import LayerCommands
from convert_to_satin import ConvertToSatin
diff --git a/lib/extensions/base.py b/lib/extensions/base.py
index 41ed80938..571e3c2d0 100644
--- a/lib/extensions/base.py
+++ b/lib/extensions/base.py
@@ -7,7 +7,7 @@ from collections import MutableMapping
from ..svg.tags import *
from ..elements import AutoFill, Fill, Stroke, SatinColumn, Polyline, EmbroideryElement
from ..utils import cache
-from ..commands import is_command
+from ..commands import is_command, layer_commands
SVG_METADATA_TAG = inkex.addNS("metadata", "svg")
@@ -110,42 +110,40 @@ class InkstitchExtension(inkex.Effect):
inkex.errormsg(_("No embroiderable paths found in document."))
inkex.errormsg(_("Tip: use Path -> Object to Path to convert non-paths."))
- def descendants(self, node):
+ def descendants(self, node, selected=False):
nodes = []
element = EmbroideryElement(node)
if element.has_command('ignore_object'):
return []
+ if node.tag == SVG_GROUP_TAG and node.get(INKSCAPE_GROUPMODE) == "layer":
+ if layer_commands(node, "ignore_layer"):
+ return []
+
if element.has_style('display') and element.get_style('display') is None:
return []
if node.tag == SVG_DEFS_TAG:
return []
- for child in node:
- nodes.extend(self.descendants(child))
+ if self.selected:
+ if node.get("id") in self.selected:
+ selected = True
+ else:
+ # if the user didn't select anything that means we process everything
+ selected = True
- if node.tag in EMBROIDERABLE_TAGS:
+ for child in node:
+ nodes.extend(self.descendants(child, selected))
+
+ if selected and node.tag in EMBROIDERABLE_TAGS:
nodes.append(node)
return nodes
def get_nodes(self):
- """Get all XML nodes, or just those selected
-
- effect is an instance of a subclass of inkex.Effect.
- """
-
- if self.selected:
- nodes = []
- for node in self.document.getroot().iter():
- if node.get("id") in self.selected:
- nodes.extend(self.descendants(node))
- else:
- nodes = self.descendants(self.document.getroot())
-
- return nodes
+ return self.descendants(self.document.getroot())
def detect_classes(self, node):
if node.tag == SVG_POLYLINE_TAG:
diff --git a/lib/extensions/commands.py b/lib/extensions/commands.py
index 0924845c4..e3bfabfeb 100644
--- a/lib/extensions/commands.py
+++ b/lib/extensions/commands.py
@@ -1,23 +1,14 @@
import os
import sys
import inkex
-import simpletransform
-import cubicsuperpath
from copy import deepcopy
-from random import random
-from shapely import geometry as shgeo
from .base import InkstitchExtension
-from ..i18n import _
-from ..elements import SatinColumn
from ..utils import get_bundled_dir, cache
-from ..svg.tags import SVG_DEFS_TAG, SVG_GROUP_TAG, SVG_USE_TAG, SVG_PATH_TAG, INKSCAPE_GROUPMODE, XLINK_HREF, CONNECTION_START, CONNECTION_END, CONNECTOR_TYPE
-from ..svg import get_correction_transform
+from ..svg.tags import SVG_DEFS_TAG
-class Commands(InkstitchExtension):
- COMMANDS = ["fill_start", "fill_end", "stop", "trim", "ignore"]
-
+class CommandsExtension(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
for command in self.COMMANDS:
@@ -47,100 +38,3 @@ class Commands(InkstitchExtension):
path = "./*[@id='inkstitch_%s']" % command
if self.defs.find(path) is None:
self.defs.append(deepcopy(self.symbol_defs.find(path)))
-
- def add_connector(self, symbol, element):
- # I'd like it if I could position the connector endpoint nicely but inkscape just
- # moves it to the element's center immediately after the extension runs.
- start_pos = (symbol.get('x'), symbol.get('y'))
- end_pos = element.shape.centroid
-
- path = inkex.etree.Element(SVG_PATH_TAG,
- {
- "id": self.uniqueId("connector"),
- "d": "M %s,%s %s,%s" % (start_pos[0], start_pos[1], end_pos.x, end_pos.y),
- "style": "stroke:#000000;stroke-width:1px;stroke-opacity:0.5;fill:none;",
- "transform": get_correction_transform(symbol),
- CONNECTION_START: "#%s" % symbol.get('id'),
- CONNECTION_END: "#%s" % element.node.get('id'),
- CONNECTOR_TYPE: "polyline",
- }
- )
-
- symbol.getparent().insert(symbol.getparent().index(symbol), path)
-
- def get_command_pos(self, element, index, total):
- # Put command symbols 30 pixels out from the shape, spaced evenly around it.
-
- # get a line running 30 pixels out from the shape
- outline = element.shape.buffer(30).exterior
-
- # pick this item's spot arond the outline and perturb it a bit to avoid
- # stacking up commands if they run the extension multiple times
- position = index / float(total)
- position += random() * 0.1
-
- return outline.interpolate(position, normalized=True)
-
- def remove_legacy_param(self, element, command):
- if command == "trim" or command == "stop":
- # If they had the old "TRIM after" or "STOP after" attributes set,
- # automatically delete them. THe new commands will do the same
- # thing.
- #
- # If we didn't delete these here, then things would get confusing.
- # If the user were to delete a "trim" symbol added by this extension
- # but the "embroider_trim_after" attribute is still set, then the
- # trim would keep happening.
-
- attribute = "embroider_%s_after" % command
-
- if attribute in element.node.attrib:
- del element.node.attrib[attribute]
-
- def add_command(self, element, commands):
- for i, command in enumerate(commands):
- self.remove_legacy_param(element, command)
-
- pos = self.get_command_pos(element, i, len(commands))
-
- symbol = inkex.etree.SubElement(element.node.getparent(), SVG_USE_TAG,
- {
- "id": self.uniqueId("use"),
- XLINK_HREF: "#inkstitch_%s" % command,
- "height": "100%",
- "width": "100%",
- "x": str(pos.x),
- "y": str(pos.y),
- "transform": get_correction_transform(element.node)
- }
- )
-
- self.add_connector(symbol, element)
-
- def effect(self):
- if not self.get_elements():
- return
-
- if not self.selected:
- inkex.errormsg(_("Please select one or more objects to which to attach commands."))
- return
-
- self.svg = self.document.getroot()
-
- commands = [command for command in self.COMMANDS if getattr(self.options, command)]
-
- if not commands:
- inkex.errormsg(_("Please choose one or more commands to attach."))
- return
-
- for command in commands:
- self.ensure_symbol(command)
-
- # Each object (node) in the SVG may correspond to multiple Elements of different
- # types (e.g. stroke + fill). We only want to process each one once.
- seen_nodes = set()
-
- for element in self.elements:
- if element.node not in seen_nodes:
- self.add_command(element, commands)
- seen_nodes.add(element.node)
diff --git a/lib/extensions/layer_commands.py b/lib/extensions/layer_commands.py
new file mode 100644
index 000000000..88170f660
--- /dev/null
+++ b/lib/extensions/layer_commands.py
@@ -0,0 +1,48 @@
+import os
+import sys
+import inkex
+
+from .commands import CommandsExtension
+from ..i18n import _
+from ..svg.tags import SVG_USE_TAG, XLINK_HREF
+from ..svg import get_correction_transform
+
+
+class LayerCommands(CommandsExtension):
+ COMMANDS = ["ignore_layer"]
+
+ def ensure_current_layer(self):
+ # if no layer is selected, inkex defaults to the root, which isn't
+ # particularly useful
+ if self.current_layer is self.document.getroot():
+ try:
+ self.current_layer = self.document.xpath(".//svg:g[@inkscape:groupmode='layer']", namespaces=inkex.NSS)[0]
+ except IndexError:
+ # No layers at all?? Fine, we'll stick with the default.
+ pass
+
+ def effect(self):
+ commands = [command for command in self.COMMANDS if getattr(self.options, command)]
+
+ if not commands:
+ inkex.errormsg(_("Please choose one or more commands to add."))
+ return
+
+ self.ensure_current_layer()
+ correction_transform = get_correction_transform(self.current_layer, child=True)
+
+ for i, command in enumerate(commands):
+ self.ensure_symbol(command)
+
+ node = inkex.etree.SubElement(self.current_layer, SVG_USE_TAG,
+ {
+ "id": self.uniqueId("use"),
+ XLINK_HREF: "#inkstitch_%s" % command,
+ "height": "100%",
+ "width": "100%",
+ "x": str(i * 20),
+ "y": "-10",
+ "transform": correction_transform
+ })
+
+ namedview = self.document.xpath("//sodipodi:namedview", namespaces=inkex.NSS)
diff --git a/lib/extensions/object_commands.py b/lib/extensions/object_commands.py
new file mode 100644
index 000000000..27a079698
--- /dev/null
+++ b/lib/extensions/object_commands.py
@@ -0,0 +1,114 @@
+import os
+import sys
+import inkex
+import simpletransform
+import cubicsuperpath
+from random import random
+from shapely import geometry as shgeo
+
+from .commands import CommandsExtension
+from ..i18n import _
+from ..elements import SatinColumn
+from ..svg.tags import SVG_GROUP_TAG, SVG_USE_TAG, SVG_PATH_TAG, INKSCAPE_GROUPMODE, XLINK_HREF, CONNECTION_START, CONNECTION_END, CONNECTOR_TYPE
+from ..svg import get_correction_transform
+
+
+class ObjectCommands(CommandsExtension):
+ COMMANDS = ["fill_start", "fill_end", "stop", "trim", "ignore_object"]
+
+ def add_connector(self, symbol, element):
+ # I'd like it if I could position the connector endpoint nicely but inkscape just
+ # moves it to the element's center immediately after the extension runs.
+ start_pos = (symbol.get('x'), symbol.get('y'))
+ end_pos = element.shape.centroid
+
+ path = inkex.etree.Element(SVG_PATH_TAG,
+ {
+ "id": self.uniqueId("connector"),
+ "d": "M %s,%s %s,%s" % (start_pos[0], start_pos[1], end_pos.x, end_pos.y),
+ "style": "stroke:#000000;stroke-width:1px;stroke-opacity:0.5;fill:none;",
+ "transform": get_correction_transform(symbol),
+ CONNECTION_START: "#%s" % symbol.get('id'),
+ CONNECTION_END: "#%s" % element.node.get('id'),
+ CONNECTOR_TYPE: "polyline",
+ }
+ )
+
+ symbol.getparent().insert(symbol.getparent().index(symbol), path)
+
+ def get_command_pos(self, element, index, total):
+ # Put command symbols 30 pixels out from the shape, spaced evenly around it.
+
+ # get a line running 30 pixels out from the shape
+ outline = element.shape.buffer(30).exterior
+
+ # pick this item's spot arond the outline and perturb it a bit to avoid
+ # stacking up commands if they run the extension multiple times
+ position = index / float(total)
+ position += random() * 0.1
+
+ return outline.interpolate(position, normalized=True)
+
+ def remove_legacy_param(self, element, command):
+ if command == "trim" or command == "stop":
+ # If they had the old "TRIM after" or "STOP after" attributes set,
+ # automatically delete them. THe new commands will do the same
+ # thing.
+ #
+ # If we didn't delete these here, then things would get confusing.
+ # If the user were to delete a "trim" symbol added by this extension
+ # but the "embroider_trim_after" attribute is still set, then the
+ # trim would keep happening.
+
+ attribute = "embroider_%s_after" % command
+
+ if attribute in element.node.attrib:
+ del element.node.attrib[attribute]
+
+ def add_commands(self, element, commands):
+ for i, command in enumerate(commands):
+ self.remove_legacy_param(element, command)
+
+ pos = self.get_command_pos(element, i, len(commands))
+
+ symbol = inkex.etree.SubElement(element.node.getparent(), SVG_USE_TAG,
+ {
+ "id": self.uniqueId("use"),
+ XLINK_HREF: "#inkstitch_%s" % command,
+ "height": "100%",
+ "width": "100%",
+ "x": str(pos.x),
+ "y": str(pos.y),
+ "transform": get_correction_transform(element.node)
+ }
+ )
+
+ self.add_connector(symbol, element)
+
+ def effect(self):
+ if not self.get_elements():
+ return
+
+ if not self.selected:
+ inkex.errormsg(_("Please select one or more objects to which to attach commands."))
+ return
+
+ self.svg = self.document.getroot()
+
+ commands = [command for command in self.COMMANDS if getattr(self.options, command)]
+
+ if not commands:
+ inkex.errormsg(_("Please choose one or more commands to attach."))
+ return
+
+ for command in commands:
+ self.ensure_symbol(command)
+
+ # Each object (node) in the SVG may correspond to multiple Elements of different
+ # types (e.g. stroke + fill). We only want to process each one once.
+ seen_nodes = set()
+
+ for element in self.elements:
+ if element.node not in seen_nodes:
+ self.add_commands(element, commands)
+ seen_nodes.add(element.node)
diff --git a/lib/svg/path.py b/lib/svg/path.py
index 521443327..0a8dcb74b 100644
--- a/lib/svg/path.py
+++ b/lib/svg/path.py
@@ -26,16 +26,19 @@ def get_node_transform(node):
return transform
-def get_correction_transform(node):
- """Get a transform to apply to new siblings of this SVG node"""
+def get_correction_transform(node, child=False):
+ """Get a transform to apply to new siblings or children of this SVG node"""
# if we want to place our new nodes in the same group/layer as this node,
# then we'll need to factor in the effects of any transforms set on
# the parents of this node.
- # we can ignore the transform on the node itself since it won't apply
- # to the objects we add
- transform = get_node_transform(node.getparent())
+ if child:
+ transform = get_node_transform(node)
+ else:
+ # we can ignore the transform on the node itself since it won't apply
+ # to the objects we add
+ transform = get_node_transform(node.getparent())
# now invert it, so that we can position our objects in absolute
# coordinates
diff --git a/messages.po b/messages.po
index de74f1a0c..ba50ab626 100644
--- a/messages.po
+++ b/messages.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2018-08-11 22:57-0400\n"
+"POT-Creation-Date: 2018-08-16 22:50-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -344,14 +344,6 @@ msgstr ""
msgid "Tip: use Path -> Object to Path to convert non-paths."
msgstr ""
-#: lib/extensions/commands.py:125
-msgid "Please select one or more objects to which to attach commands."
-msgstr ""
-
-#: lib/extensions/commands.py:133
-msgid "Please choose one or more commands to attach."
-msgstr ""
-
#: lib/extensions/convert_to_satin.py:28
msgid "Please select at least one line to convert to a satin column."
msgstr ""
@@ -429,6 +421,18 @@ msgstr ""
msgid "Ink/Stitch Add-ons Installer"
msgstr ""
+#: lib/extensions/layer_commands.py:28
+msgid "Please choose one or more commands to add."
+msgstr ""
+
+#: lib/extensions/object_commands.py:93
+msgid "Please select one or more objects to which to attach commands."
+msgstr ""
+
+#: lib/extensions/object_commands.py:101
+msgid "Please choose one or more commands to attach."
+msgstr ""
+
#: lib/extensions/params.py:244
msgid "These settings will be applied to 1 object."
msgstr ""