kopia lustrzana https://github.com/inkstitch/inkstitch
232 wiersze
7.0 KiB
Python
232 wiersze
7.0 KiB
Python
import inkex
|
|
import re
|
|
import json
|
|
from copy import deepcopy
|
|
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, layer_commands
|
|
|
|
|
|
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
|
|
"""
|
|
|
|
match = re.match('^\{[^}]+\}(.+)$', tag)
|
|
|
|
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:
|
|
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)
|
|
|
|
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):
|
|
item = self._find_item(name)
|
|
item.text = json.dumps(value)
|
|
|
|
def _find_item(self, name, create=True):
|
|
tag = inkex.addNS(name, "inkstitch")
|
|
item = self.metadata.find(tag)
|
|
if item is None and create:
|
|
item = inkex.etree.SubElement(self.metadata, tag)
|
|
|
|
return item
|
|
|
|
def __getitem__(self, name):
|
|
item = self._find_item(name)
|
|
|
|
try:
|
|
return json.loads(item.text)
|
|
except (ValueError, TypeError):
|
|
return None
|
|
|
|
def __delitem__(self, name):
|
|
item = self._find_item(name, create=False)
|
|
|
|
if item is not None:
|
|
self.metadata.remove(item)
|
|
|
|
def __iter__(self):
|
|
for child in self.metadata:
|
|
if child.prefix == "inkstitch":
|
|
yield strip_namespace(child.tag)
|
|
|
|
def __len__(self):
|
|
i = 0
|
|
for i, item in enumerate(self):
|
|
pass
|
|
|
|
return i + 1
|
|
|
|
|
|
class InkstitchExtension(inkex.Effect):
|
|
"""Base class for Inkstitch extensions. Not intended for direct use."""
|
|
|
|
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")
|
|
|
|
def no_elements_error(self):
|
|
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."))
|
|
|
|
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 []
|
|
|
|
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
|
|
|
|
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):
|
|
return self.descendants(self.document.getroot())
|
|
|
|
def detect_classes(self, node):
|
|
if node.tag == SVG_POLYLINE_TAG:
|
|
return [Polyline]
|
|
else:
|
|
element = EmbroideryElement(node)
|
|
|
|
if element.get_boolean_param("satin_column"):
|
|
return [SatinColumn]
|
|
else:
|
|
classes = []
|
|
|
|
if element.get_style("fill", "black"):
|
|
if element.get_boolean_param("auto_fill", True):
|
|
classes.append(AutoFill)
|
|
else:
|
|
classes.append(Fill)
|
|
|
|
if element.get_style("stroke"):
|
|
if not is_command(element.node):
|
|
classes.append(Stroke)
|
|
|
|
if element.get_boolean_param("stroke_first", False):
|
|
classes.reverse()
|
|
|
|
return classes
|
|
|
|
|
|
def get_elements(self):
|
|
self.elements = []
|
|
for node in self.get_nodes():
|
|
classes = self.detect_classes(node)
|
|
self.elements.extend(cls(node) for cls in classes)
|
|
|
|
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
|
|
|
|
def get_inkstitch_metadata(self):
|
|
return InkStitchMetadata(self.document)
|
|
|
|
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
|
|
|
|
|
|
def parse(self):
|
|
"""Override inkex.Effect to add Ink/Stitch xml namespace"""
|
|
|
|
# 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().
|
|
inkex.NSS['inkstitch'] = 'http://inkstitch.org/namespace'
|
|
|
|
# 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".
|
|
inkex.etree.cleanup_namespaces(self.document,
|
|
top_nsmap=inkex.NSS,
|
|
keep_ns_prefixes=inkex.NSS.keys())
|
|
self.original_document = deepcopy(self.document)
|