new extension to add layer commands

pull/243/head
Lex Neva 2018-08-16 22:50:34 -04:00
rodzic 751173080b
commit 19bb8a5a6d
10 zmienionych plików z 228 dodań i 147 usunięć

Wyświetl plik

@ -8,8 +8,8 @@
<param name="fill_end" type="boolean" _gui-text="Fill ending position">false</param>
<param name="stop" type="boolean" _gui-text="Stop after sewing this object">false</param>
<param name="trim" type="boolean" _gui-text="Trim thread after sewing this object">false</param>
<param name="ignore" type="boolean" _gui-text="Ignore this object (do not stitch)">false</param>
<param name="extension" type="string" gui-hidden="true">commands</param>
<param name="ignore_object" type="boolean" _gui-text="Ignore this object (do not stitch)">false</param>
<param name="extension" type="string" gui-hidden="true">object_commands</param>
<effect>
<object-type>all</object-type>
<effects-menu>

Wyświetl plik

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<_name>Add Layer Commands</_name>
<id>org.inkstitch.layer_commands</id>
<dependency type="executable" location="extensions">inkstitch.py</dependency>
<dependency type="executable" location="extensions">inkex.py</dependency>
<param name="description" type="description">Commands will be added to the currently-selected layer.</param>
<param name="ignore_layer" type="boolean" _gui-text="Ignore layer (do not stitch any objects in this layer)">false</param>
<param name="extension" type="string" gui-hidden="true">layer_commands</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu _name="Embroidery" />
</effects-menu>
</effect>
<script>
<command reldir="extensions" interpreter="python">inkstitch.py</command>
</script>
</inkscape-extension>

Wyświetl plik

@ -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:

Wyświetl plik

@ -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

Wyświetl plik

@ -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:

Wyświetl plik

@ -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)

Wyświetl plik

@ -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)

Wyświetl plik

@ -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)

Wyświetl plik

@ -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

Wyświetl plik

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""