inkstitch/lib/extensions/base.py

223 wiersze
6.7 KiB
Python
Czysty Zwykły widok Historia

import inkex
2018-04-14 01:23:00 +00:00
import re
import json
2018-04-24 19:41:39 +00:00
from copy import deepcopy
2018-04-14 01:23:00 +00:00
from collections import MutableMapping
from ..elements import AutoFill, Fill, Stroke, SatinColumn, Polyline, EmbroideryElement
from .. import SVG_POLYLINE_TAG, SVG_GROUP_TAG, SVG_DEFS_TAG, INKSCAPE_GROUPMODE, EMBROIDERABLE_TAGS, PIXELS_PER_MM
from ..utils import cache
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
"""
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:
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)
if value:
item.text = json.dumps(value)
else:
item.getparent().remove(item)
2018-04-14 01:23:00 +00:00
def _find_item(self, name):
2018-04-14 01:23:00 +00:00
tag = inkex.addNS(name, "inkstitch")
item = self.metadata.find(tag)
if item is None:
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
2018-04-14 01:23:00 +00:00
def __delitem__(self, name):
item = self._find_item(name)
2018-04-14 01:23:00 +00:00
if item:
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
2018-04-14 01:23:00 +00:00
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 before embroidering."))
def descendants(self, node):
nodes = []
element = EmbroideryElement(node)
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 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
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"):
if element.get_boolean_param("auto_fill", True):
classes.append(AutoFill)
else:
classes.append(Fill)
if element.get_style("stroke"):
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
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-04-13 00:05:01 +00:00
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().
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)