kopia lustrzana https://github.com/inkstitch/inkstitch
Merge pull request #293 from inkstitch/lexelby/origin-and-stop-position
origin and stop positionpull/296/head
commit
6f425f7b3e
|
@ -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)
|
||||
|
||||
return commands
|
||||
yield standalone_command
|
||||
|
||||
|
||||
def standalone_commands(svg):
|
||||
@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."""
|
||||
|
||||
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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
|
@ -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()))
|
||||
|
|
|
@ -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,60 +25,32 @@ 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")]
|
||||
|
||||
# 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
|
||||
|
||||
# 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)
|
||||
if origin_command:
|
||||
return origin_command.point
|
||||
else:
|
||||
# Either the two guides are the same line, or they're parallel.
|
||||
# default: center of the canvas
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
|
98
messages.po
98
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-24 20:45-0400\n"
|
||||
"POT-Creation-Date: 2018-08-24 20:56-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"
|
||||
|
@ -18,35 +18,94 @@ msgstr ""
|
|||
"Generated-By: Babel 2.5.3\n"
|
||||
|
||||
#. command attached to an object
|
||||
#: lib/commands.py:11
|
||||
#: lib/commands.py:13
|
||||
msgid "fill_start"
|
||||
msgstr ""
|
||||
|
||||
#: lib/commands.py:13
|
||||
msgid "Fill stitch starting position"
|
||||
msgstr ""
|
||||
|
||||
#. command attached to an object
|
||||
#: lib/commands.py:14
|
||||
#: lib/commands.py:16
|
||||
msgid "fill_end"
|
||||
msgstr ""
|
||||
|
||||
#: lib/commands.py:16
|
||||
msgid "Fill stitch ending position"
|
||||
msgstr ""
|
||||
|
||||
#. command attached to an object
|
||||
#: lib/commands.py:17
|
||||
#: lib/commands.py:19
|
||||
msgid "stop"
|
||||
msgstr ""
|
||||
|
||||
#: lib/commands.py:19
|
||||
msgid "Stop (pause machine) after sewing this object"
|
||||
msgstr ""
|
||||
|
||||
#. command attached to an object
|
||||
#: lib/commands.py:20
|
||||
#: lib/commands.py:22
|
||||
msgid "trim"
|
||||
msgstr ""
|
||||
|
||||
#: lib/commands.py:22
|
||||
msgid "Trim thread after sewing this object"
|
||||
msgstr ""
|
||||
|
||||
#. command attached to an object
|
||||
#: lib/commands.py:23
|
||||
#: lib/commands.py:25
|
||||
msgid "ignore_object"
|
||||
msgstr ""
|
||||
|
||||
#: lib/commands.py:25
|
||||
msgid "Ignore this object (do not stitch)"
|
||||
msgstr ""
|
||||
|
||||
#. command that affects entire layer
|
||||
#: lib/commands.py:26
|
||||
#. command that affects a layer
|
||||
#: lib/commands.py:28
|
||||
msgid "ignore_layer"
|
||||
msgstr ""
|
||||
|
||||
#: lib/commands.py:28
|
||||
msgid "Ignore layer (do not stitch any objects in this layer)"
|
||||
msgstr ""
|
||||
|
||||
#. command that affects entire document
|
||||
#: lib/commands.py:31
|
||||
msgid "origin"
|
||||
msgstr ""
|
||||
|
||||
#: lib/commands.py:31
|
||||
msgid "Origin for exported embroidery files"
|
||||
msgstr ""
|
||||
|
||||
#. command that affects entire document
|
||||
#: lib/commands.py:34
|
||||
msgid "stop_position"
|
||||
msgstr ""
|
||||
|
||||
#: lib/commands.py:34
|
||||
msgid "Jump destination for Stop commands (a.k.a. \"Frame Out position\")."
|
||||
msgstr ""
|
||||
|
||||
#: lib/commands.py:190
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Error: there is more than one %(command)s command in the document, but "
|
||||
"there can only be one. Please remove all but one."
|
||||
msgstr ""
|
||||
|
||||
#. 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.
|
||||
#: lib/commands.py:197
|
||||
#, python-format
|
||||
msgid "%(command)s: %(description)s"
|
||||
msgstr ""
|
||||
|
||||
#: lib/elements/auto_fill.py:11
|
||||
msgid "Auto-Fill"
|
||||
msgstr ""
|
||||
|
@ -1117,9 +1176,10 @@ msgstr ""
|
|||
#. to your language's word for its language, e.g. "Español" for the spanish
|
||||
#. translation.
|
||||
#: templates/convert_to_satin.inx:12 templates/embroider.inx:24
|
||||
#: templates/flip.inx:12 templates/install.inx:12
|
||||
#: templates/layer_commands.inx:16 templates/object_commands.inx:15
|
||||
#: templates/params.inx:12 templates/print.inx:12 templates/simulate.inx:12
|
||||
#: templates/flip.inx:12 templates/global_commands.inx:16
|
||||
#: templates/install.inx:12 templates/layer_commands.inx:16
|
||||
#: templates/object_commands.inx:15 templates/params.inx:12
|
||||
#: templates/print.inx:12 templates/simulate.inx:12
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1165,6 +1225,20 @@ msgstr ""
|
|||
msgid "Flip Satin Columns"
|
||||
msgstr ""
|
||||
|
||||
#: templates/global_commands.inx:3
|
||||
msgid "Add Commands"
|
||||
msgstr ""
|
||||
|
||||
#: templates/global_commands.inx:7
|
||||
msgid "These commands affect the entire embroidery design."
|
||||
msgstr ""
|
||||
|
||||
#. Inkscape submenu under Extensions -> Ink/Stitch
|
||||
#: templates/global_commands.inx:18 templates/layer_commands.inx:17
|
||||
#: templates/object_commands.inx:16
|
||||
msgid "Commands"
|
||||
msgstr ""
|
||||
|
||||
#: templates/input.inx:11
|
||||
#, python-format
|
||||
msgid "convert %(file_extension)s file to Ink/Stitch manual-stitch paths"
|
||||
|
@ -1183,7 +1257,7 @@ msgid "Commands will be added to the currently-selected layer."
|
|||
msgstr ""
|
||||
|
||||
#: templates/object_commands.inx:3
|
||||
msgid "Attach Commands"
|
||||
msgid "Attach Commands to Selected Objects"
|
||||
msgstr ""
|
||||
|
||||
#: templates/output.inx:11
|
||||
|
|
File diff suppressed because one or more lines are too long
Przed Szerokość: | Wysokość: | Rozmiar: 19 KiB Po Szerokość: | Wysokość: | Rozmiar: 34 KiB |
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>{% trans %}Add Commands{% endtrans %}</name>
|
||||
<id>org.inkstitch.global_commands.{{ locale }}</id>
|
||||
<dependency type="executable" location="extensions">inkstitch.py</dependency>
|
||||
<dependency type="executable" location="extensions">inkex.py</dependency>
|
||||
<param name="description" type="description">{% trans %}These commands affect the entire embroidery design.{% endtrans %}</param>
|
||||
{% for command, description in global_commands %}
|
||||
<param name="{{ command }}" type="boolean" _gui-text="{{ _(description) }}">false</param>
|
||||
{% endfor %}
|
||||
<param name="extension" type="string" gui-hidden="true">global_commands</param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="Ink/Stitch">
|
||||
<submenu name="{% trans %}English{% endtrans %}">
|
||||
{# L10N Inkscape submenu under Extensions -> Ink/Stitch #}
|
||||
<submenu name="{% trans %}Commands{% endtrans %}" />
|
||||
</submenu>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command reldir="extensions" interpreter="python">inkstitch.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
|
@ -13,7 +13,9 @@
|
|||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="Ink/Stitch">
|
||||
<submenu name="{% trans %}English{% endtrans %}" />
|
||||
<submenu name="{% trans %}English{% endtrans %}">
|
||||
<submenu name="{% trans %}Commands{% endtrans %}" />
|
||||
</submenu>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>{% trans %}Attach Commands{% endtrans %}</name>
|
||||
<name>{% trans %}Attach Commands to Selected Objects{% endtrans %}</name>
|
||||
<id>org.inkstitch.commands.{{ locale }}</id>
|
||||
<dependency type="executable" location="extensions">inkstitch.py</dependency>
|
||||
<dependency type="executable" location="extensions">inkex.py</dependency>
|
||||
|
@ -12,7 +12,9 @@
|
|||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="Ink/Stitch">
|
||||
<submenu name="{% trans %}English{% endtrans %}" />
|
||||
<submenu name="{% trans %}English{% endtrans %}">
|
||||
<submenu name="{% trans %}Commands{% endtrans %}" />
|
||||
</submenu>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
|
|
Ładowanie…
Reference in New Issue