2018-04-14 01:23:00 +00:00
|
|
|
from collections import MutableMapping
|
new extension: Auto-Route Satin Columns (#330)
**video demo:** https://www.youtube.com/watch?v=tbghtqziB1g
This branch adds a new extension, Auto-Route Satin Columns, implementing #214! This is a huge new feature that opens the door wide for exciting stuff like lettering (#142).
To use it, select some satin columns and run the extension. After a few seconds, it will replace your satins with a new set with a logical stitching order. Under-pathing and jump-stitches will be added as necessary, and satins will be broken to facilitate jumps. The resulting satins will retain all of the parameters you had set on the original satins, including underlay, zig-zag spacing, etc.
By default, it will choose the left-most extreme as the starting point and the right-most extreme as the ending point (even if these occur partway through a satin such as the left edge of a letter "o"). You can override this by attaching the new "Auto-route satin stitch starting/ending position" commands.
There's also an option to add trims instead of jump stitches. Any jump stitch over 1mm is trimmed. I might make this configurable in the future but in my tests it seems to do a good job. Trim commands are added to the SVG, so it's easy enough to modify/delete as you see fit.
2018-10-30 23:43:21 +00:00
|
|
|
from copy import deepcopy
|
|
|
|
import json
|
|
|
|
import re
|
|
|
|
|
|
|
|
import inkex
|
2018-08-20 02:14:10 +00:00
|
|
|
from stringcase import snakecase
|
2018-05-02 01:21:07 +00:00
|
|
|
|
2018-11-15 01:23:06 +00:00
|
|
|
from ..commands import layer_commands
|
|
|
|
from ..elements import EmbroideryElement, nodes_to_elements
|
2018-08-22 01:43:09 +00:00
|
|
|
from ..i18n import _
|
2018-11-15 01:23:06 +00:00
|
|
|
from ..svg.tags import SVG_GROUP_TAG, INKSCAPE_GROUPMODE, SVG_DEFS_TAG, EMBROIDERABLE_TAGS
|
2018-04-14 01:23:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
SVG_METADATA_TAG = inkex.addNS("metadata", "svg")
|
|
|
|
|
|
|
|
|
|
|
|
def strip_namespace(tag):
|
|
|
|
"""Remove xml namespace from a tag name.
|
|
|
|
|
|
|
|
>>> {http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}namedview
|
|
|
|
<<< namedview
|
|
|
|
"""
|
|
|
|
|
new extension: Auto-Route Satin Columns (#330)
**video demo:** https://www.youtube.com/watch?v=tbghtqziB1g
This branch adds a new extension, Auto-Route Satin Columns, implementing #214! This is a huge new feature that opens the door wide for exciting stuff like lettering (#142).
To use it, select some satin columns and run the extension. After a few seconds, it will replace your satins with a new set with a logical stitching order. Under-pathing and jump-stitches will be added as necessary, and satins will be broken to facilitate jumps. The resulting satins will retain all of the parameters you had set on the original satins, including underlay, zig-zag spacing, etc.
By default, it will choose the left-most extreme as the starting point and the right-most extreme as the ending point (even if these occur partway through a satin such as the left edge of a letter "o"). You can override this by attaching the new "Auto-route satin stitch starting/ending position" commands.
There's also an option to add trims instead of jump stitches. Any jump stitch over 1mm is trimmed. I might make this configurable in the future but in my tests it seems to do a good job. Trim commands are added to the SVG, so it's easy enough to modify/delete as you see fit.
2018-10-30 23:43:21 +00:00
|
|
|
match = re.match(r'^\{[^}]+\}(.+)$', tag)
|
2018-04-14 01:23:00 +00:00
|
|
|
|
|
|
|
if match:
|
|
|
|
return match.group(1)
|
|
|
|
else:
|
|
|
|
return tag
|
|
|
|
|
|
|
|
|
|
|
|
class InkStitchMetadata(MutableMapping):
|
|
|
|
"""Helper class to get and set inkstitch-specific metadata attributes.
|
|
|
|
|
|
|
|
Operates on a document and acts like a dict. Setting an item adds or
|
|
|
|
updates a metadata element in the document. Getting an item retrieves
|
|
|
|
a metadata element's text contents or None if an element by that name
|
|
|
|
doesn't exist.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, document):
|
|
|
|
self.document = document
|
|
|
|
self.metadata = self._get_or_create_metadata()
|
|
|
|
|
|
|
|
def _get_or_create_metadata(self):
|
|
|
|
metadata = self.document.find(SVG_METADATA_TAG)
|
|
|
|
|
|
|
|
if metadata is None:
|
2018-04-17 00:17:07 +00:00
|
|
|
metadata = inkex.etree.SubElement(self.document.getroot(), SVG_METADATA_TAG)
|
|
|
|
|
|
|
|
# move it so that it goes right after the first element, sodipodi:namedview
|
|
|
|
self.document.getroot().remove(metadata)
|
|
|
|
self.document.getroot().insert(1, metadata)
|
2018-04-14 01:23:00 +00:00
|
|
|
|
|
|
|
return metadata
|
|
|
|
|
|
|
|
# Because this class inherints from MutableMapping, all we have to do is
|
|
|
|
# implement these five methods and we get a full dict-like interface.
|
|
|
|
|
|
|
|
def __setitem__(self, name, value):
|
2018-04-21 20:23:13 +00:00
|
|
|
item = self._find_item(name)
|
2018-07-12 19:16:22 +00:00
|
|
|
item.text = json.dumps(value)
|
2018-04-14 01:23:00 +00:00
|
|
|
|
2018-06-10 01:23:21 +00:00
|
|
|
def _find_item(self, name, create=True):
|
2018-04-14 01:23:00 +00:00
|
|
|
tag = inkex.addNS(name, "inkstitch")
|
|
|
|
item = self.metadata.find(tag)
|
2018-06-10 01:23:21 +00:00
|
|
|
if item is None and create:
|
2018-04-14 01:23:00 +00:00
|
|
|
item = inkex.etree.SubElement(self.metadata, tag)
|
|
|
|
|
2018-04-15 00:39:59 +00:00
|
|
|
return item
|
|
|
|
|
|
|
|
def __getitem__(self, name):
|
|
|
|
item = self._find_item(name)
|
|
|
|
|
|
|
|
try:
|
|
|
|
return json.loads(item.text)
|
2018-04-24 04:22:45 +00:00
|
|
|
except (ValueError, TypeError):
|
2018-04-15 00:39:59 +00:00
|
|
|
return None
|
2018-04-14 01:23:00 +00:00
|
|
|
|
|
|
|
def __delitem__(self, name):
|
2018-06-10 01:23:21 +00:00
|
|
|
item = self._find_item(name, create=False)
|
2018-04-14 01:23:00 +00:00
|
|
|
|
2018-06-10 01:23:21 +00:00
|
|
|
if item is not None:
|
2018-04-14 01:23:00 +00:00
|
|
|
self.metadata.remove(item)
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
for child in self.metadata:
|
|
|
|
if child.prefix == "inkstitch":
|
|
|
|
yield strip_namespace(child.tag)
|
|
|
|
|
|
|
|
def __len__(self):
|
2018-04-15 00:39:59 +00:00
|
|
|
i = 0
|
2018-04-14 01:23:00 +00:00
|
|
|
for i, item in enumerate(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
return i + 1
|
2018-03-31 00:37:11 +00:00
|
|
|
|
|
|
|
|
|
|
|
class InkstitchExtension(inkex.Effect):
|
|
|
|
"""Base class for Inkstitch extensions. Not intended for direct use."""
|
|
|
|
|
2018-08-20 02:14:10 +00:00
|
|
|
@classmethod
|
|
|
|
def name(cls):
|
|
|
|
return snakecase(cls.__name__)
|
|
|
|
|
2018-03-31 00:37:11 +00:00
|
|
|
def hide_all_layers(self):
|
|
|
|
for g in self.document.getroot().findall(SVG_GROUP_TAG):
|
|
|
|
if g.get(INKSCAPE_GROUPMODE) == "layer":
|
|
|
|
g.set("style", "display:none")
|
|
|
|
|
2018-11-15 01:23:06 +00:00
|
|
|
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
|
|
|
|
|
2018-03-31 00:37:11 +00:00
|
|
|
def no_elements_error(self):
|
2018-08-22 00:32:50 +00:00
|
|
|
if self.selected:
|
|
|
|
inkex.errormsg(_("No embroiderable paths selected."))
|
|
|
|
else:
|
|
|
|
inkex.errormsg(_("No embroiderable paths found in document."))
|
|
|
|
inkex.errormsg(_("Tip: use Path -> Object to Path to convert non-paths."))
|
2018-03-31 00:37:11 +00:00
|
|
|
|
2018-08-17 02:50:34 +00:00
|
|
|
def descendants(self, node, selected=False):
|
2018-03-31 00:37:11 +00:00
|
|
|
nodes = []
|
|
|
|
element = EmbroideryElement(node)
|
|
|
|
|
2018-08-01 01:00:30 +00:00
|
|
|
if element.has_command('ignore_object'):
|
2018-07-25 02:15:28 +00:00
|
|
|
return []
|
|
|
|
|
2018-08-17 02:50:34 +00:00
|
|
|
if node.tag == SVG_GROUP_TAG and node.get(INKSCAPE_GROUPMODE) == "layer":
|
2018-08-23 02:13:51 +00:00
|
|
|
if len(list(layer_commands(node, "ignore_layer"))):
|
2018-08-17 02:50:34 +00:00
|
|
|
return []
|
|
|
|
|
2018-03-31 00:37:11 +00:00
|
|
|
if element.has_style('display') and element.get_style('display') is None:
|
|
|
|
return []
|
|
|
|
|
|
|
|
if node.tag == SVG_DEFS_TAG:
|
|
|
|
return []
|
|
|
|
|
2018-08-17 02:50:34 +00:00
|
|
|
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
|
|
|
|
|
2018-03-31 00:37:11 +00:00
|
|
|
for child in node:
|
2018-08-17 02:50:34 +00:00
|
|
|
nodes.extend(self.descendants(child, selected))
|
2018-03-31 00:37:11 +00:00
|
|
|
|
2018-08-17 02:50:34 +00:00
|
|
|
if selected and node.tag in EMBROIDERABLE_TAGS:
|
2018-03-31 00:37:11 +00:00
|
|
|
nodes.append(node)
|
|
|
|
|
|
|
|
return nodes
|
|
|
|
|
|
|
|
def get_nodes(self):
|
2018-08-17 02:50:34 +00:00
|
|
|
return self.descendants(self.document.getroot())
|
2018-03-31 00:37:11 +00:00
|
|
|
|
|
|
|
def get_elements(self):
|
2018-11-15 01:23:06 +00:00
|
|
|
self.elements = nodes_to_elements(self.get_nodes())
|
2018-03-31 00:37:11 +00:00
|
|
|
if self.elements:
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
self.no_elements_error()
|
|
|
|
return False
|
|
|
|
|
|
|
|
def elements_to_patches(self, elements):
|
|
|
|
patches = []
|
|
|
|
for element in elements:
|
|
|
|
if patches:
|
|
|
|
last_patch = patches[-1]
|
|
|
|
else:
|
|
|
|
last_patch = None
|
|
|
|
|
|
|
|
patches.extend(element.embroider(last_patch))
|
|
|
|
|
|
|
|
return patches
|
2018-04-13 00:05:01 +00:00
|
|
|
|
2018-04-14 01:23:00 +00:00
|
|
|
def get_inkstitch_metadata(self):
|
|
|
|
return InkStitchMetadata(self.document)
|
|
|
|
|
2018-06-13 02:15:32 +00:00
|
|
|
def get_base_file_name(self):
|
|
|
|
svg_filename = self.document.getroot().get(inkex.addNS('docname', 'sodipodi'), "embroidery.svg")
|
|
|
|
|
|
|
|
if svg_filename.endswith('.svg'):
|
|
|
|
svg_filename = svg_filename[:-4]
|
|
|
|
|
|
|
|
return svg_filename
|
|
|
|
|
new extension: Auto-Route Satin Columns (#330)
**video demo:** https://www.youtube.com/watch?v=tbghtqziB1g
This branch adds a new extension, Auto-Route Satin Columns, implementing #214! This is a huge new feature that opens the door wide for exciting stuff like lettering (#142).
To use it, select some satin columns and run the extension. After a few seconds, it will replace your satins with a new set with a logical stitching order. Under-pathing and jump-stitches will be added as necessary, and satins will be broken to facilitate jumps. The resulting satins will retain all of the parameters you had set on the original satins, including underlay, zig-zag spacing, etc.
By default, it will choose the left-most extreme as the starting point and the right-most extreme as the ending point (even if these occur partway through a satin such as the left edge of a letter "o"). You can override this by attaching the new "Auto-route satin stitch starting/ending position" commands.
There's also an option to add trims instead of jump stitches. Any jump stitch over 1mm is trimmed. I might make this configurable in the future but in my tests it seems to do a good job. Trim commands are added to the SVG, so it's easy enough to modify/delete as you see fit.
2018-10-30 23:43:21 +00:00
|
|
|
def uniqueId(self, prefix, make_new_id=True):
|
|
|
|
"""Override inkex.Effect.uniqueId with a nicer naming scheme."""
|
|
|
|
i = 1
|
|
|
|
while True:
|
|
|
|
new_id = "%s%d" % (prefix, i)
|
|
|
|
if new_id not in self.doc_ids:
|
|
|
|
break
|
|
|
|
i += 1
|
|
|
|
self.doc_ids[new_id] = 1
|
|
|
|
|
|
|
|
return new_id
|
|
|
|
|
2018-04-13 00:05:01 +00:00
|
|
|
def parse(self):
|
new extension: Auto-Route Satin Columns (#330)
**video demo:** https://www.youtube.com/watch?v=tbghtqziB1g
This branch adds a new extension, Auto-Route Satin Columns, implementing #214! This is a huge new feature that opens the door wide for exciting stuff like lettering (#142).
To use it, select some satin columns and run the extension. After a few seconds, it will replace your satins with a new set with a logical stitching order. Under-pathing and jump-stitches will be added as necessary, and satins will be broken to facilitate jumps. The resulting satins will retain all of the parameters you had set on the original satins, including underlay, zig-zag spacing, etc.
By default, it will choose the left-most extreme as the starting point and the right-most extreme as the ending point (even if these occur partway through a satin such as the left edge of a letter "o"). You can override this by attaching the new "Auto-route satin stitch starting/ending position" commands.
There's also an option to add trims instead of jump stitches. Any jump stitch over 1mm is trimmed. I might make this configurable in the future but in my tests it seems to do a good job. Trim commands are added to the SVG, so it's easy enough to modify/delete as you see fit.
2018-10-30 23:43:21 +00:00
|
|
|
"""Override inkex.Effect.parse to add Ink/Stitch xml namespace"""
|
2018-04-13 00:05:01 +00:00
|
|
|
|
|
|
|
# SVG parsers don't actually look for anything at this URL. They just
|
|
|
|
# care that it's unique. That defines a "namespace" of element and
|
|
|
|
# attribute names to disambiguate conflicts with element and
|
|
|
|
# attribute names other XML namespaces.
|
|
|
|
#
|
|
|
|
# Updating inkex.NSS here allows us to pass 'inkstitch' into
|
|
|
|
# inkex.addNS().
|
2018-04-14 01:23:00 +00:00
|
|
|
inkex.NSS['inkstitch'] = 'http://inkstitch.org/namespace'
|
2018-04-13 00:05:01 +00:00
|
|
|
|
|
|
|
# call the superclass's method first
|
|
|
|
inkex.Effect.parse(self)
|
|
|
|
|
|
|
|
# This is the only way I could find to add a namespace to an existing
|
|
|
|
# element tree at the top without getting ugly prefixes like "ns0".
|
2018-04-14 01:23:00 +00:00
|
|
|
inkex.etree.cleanup_namespaces(self.document,
|
2018-04-13 00:05:01 +00:00
|
|
|
top_nsmap=inkex.NSS,
|
|
|
|
keep_ns_prefixes=inkex.NSS.keys())
|
2018-04-24 19:41:39 +00:00
|
|
|
self.original_document = deepcopy(self.document)
|