kopia lustrzana https://github.com/inkstitch/inkstitch
new extension to add layer commands
rodzic
751173080b
commit
19bb8a5a6d
|
@ -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>
|
|
@ -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>
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
||||
|
|
22
messages.po
22
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 <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 ""
|
||||
|
|
Ładowanie…
Reference in New Issue