summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaalleen <36401965+kaalleen@users.noreply.github.com>2023-04-15 08:48:27 +0200
committerGitHub <noreply@github.com>2023-04-15 08:48:27 +0200
commit6504c72fb73927151c10edcd5e891af53b36826c (patch)
tree2044fb20e785dc2710f0f854719319673c98659a
parent067ef9095a7e77b66257b3f5656a8feac1f028fc (diff)
Add inkstitch svg version tag (#2199)
... to make it easier to update legacy default values
-rw-r--r--lib/elements/element.py62
-rw-r--r--lib/extensions/base.py97
-rw-r--r--lib/metadata.py85
-rw-r--r--lib/svg/tags.py1
-rw-r--r--lib/update.py126
5 files changed, 221 insertions, 150 deletions
diff --git a/lib/elements/element.py b/lib/elements/element.py
index 43ca5095..269cbdc2 100644
--- a/lib/elements/element.py
+++ b/lib/elements/element.py
@@ -58,60 +58,6 @@ def param(*args, **kwargs):
class EmbroideryElement(object):
def __init__(self, node):
self.node = node
- self._update_legacy_params()
-
- def _update_legacy_params(self): # noqa: C901
- # update legacy embroider_ attributes to namespaced attributes
- legacy_attribs = False
- for attrib in self.node.attrib:
- if attrib.startswith('embroider_'):
- self.replace_legacy_param(attrib)
- legacy_attribs = True
-
- # convert legacy tie setting
- legacy_tie = self.get_param('ties', None)
- if legacy_tie == "True":
- self.set_param('ties', 0)
- elif legacy_tie == "False":
- self.set_param('ties', 3)
-
- # convert legacy fill_method
- legacy_fill_method = self.get_int_param('fill_method', None)
- if legacy_fill_method == 0:
- self.set_param('fill_method', 'auto_fill')
- elif legacy_fill_method == 1:
- self.set_param('fill_method', 'contour_fill')
- elif legacy_fill_method == 2:
- self.set_param('fill_method', 'guided_fill')
- elif legacy_fill_method == 3:
- self.set_param('fill_method', 'legacy_fill')
-
- # legacy satin method
- if self.get_boolean_param('e_stitch', False) is True:
- self.remove_param('e_stitch')
- self.set_param('satin_method', 'e_stitch')
-
- # default setting for fill_underlay has changed
- if legacy_attribs and not self.get_param('fill_underlay', ""):
- self.set_param('fill_underlay', False)
-
- # convert legacy stroke_method
- if self.get_style("stroke"):
- # manual stitch
- legacy_manual_stitch = self.get_boolean_param('manual_stitch', False)
- if legacy_manual_stitch is True:
- self.remove_param('manual_stitch')
- self.set_param('stroke_method', 'manual_stitch')
- # stroke_method
- legacy_stroke_method = self.get_int_param('stroke_method', None)
- if legacy_stroke_method == 0:
- self.set_param('stroke_method', 'running_stitch')
- elif legacy_stroke_method == 1:
- self.set_param('stroke_method', 'ripple_stitch')
- if (not self.get_param('stroke_method', None) and
- self.get_param('satin_column', False) is False and
- not self.node.style('stroke-dasharray')):
- self.set_param('stroke_method', 'zigzag_stitch')
@property
def id(self):
@@ -128,14 +74,6 @@ class EmbroideryElement(object):
params.append(prop.fget.param)
return params
- def replace_legacy_param(self, param):
- # remove "embroider_" prefix
- new_param = param[10:]
- if new_param in INKSTITCH_ATTRIBS:
- value = self.node.get(param, "").strip()
- self.set_param(param[10:], value)
- del self.node.attrib[param]
-
@cache
def get_param(self, param, default):
value = self.node.get(INKSTITCH_ATTRIBS[param], "").strip()
diff --git a/lib/extensions/base.py b/lib/extensions/base.py
index 7b3c6f1c..e381e2c1 100644
--- a/lib/extensions/base.py
+++ b/lib/extensions/base.py
@@ -3,112 +3,33 @@
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
-import json
import os
-import re
-from collections.abc import MutableMapping
-from lxml import etree
+import inkex
from lxml.etree import Comment
from stringcase import snakecase
-import inkex
-
from ..commands import is_command, layer_commands
from ..elements import EmbroideryElement, nodes_to_elements
from ..elements.clone import is_clone
from ..i18n import _
from ..marker import has_marker
+from ..metadata import InkStitchMetadata
from ..svg import generate_unique_id
from ..svg.tags import (CONNECTOR_TYPE, EMBROIDERABLE_TAGS, INKSCAPE_GROUPMODE,
NOT_EMBROIDERABLE_TAGS, SVG_CLIPPATH_TAG, SVG_DEFS_TAG,
SVG_GROUP_TAG, SVG_MASK_TAG)
-from ..utils.settings import DEFAULT_METADATA, global_settings
-
-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(r'^\{[^}]+\}(.+)$', 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):
- super().__init__()
- self.document = document
- self.metadata = document.metadata
-
- for setting in DEFAULT_METADATA:
- if self[setting] is None:
- self[setting] = global_settings[f'default_{setting}']
+from ..update import update_inkstitch_document
- # 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 = 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
-
- def __json__(self):
- return dict(self)
-
-
-class InkstitchExtension(inkex.Effect):
+class InkstitchExtension(inkex.EffectExtension):
"""Base class for Inkstitch extensions. Not intended for direct use."""
+ def load(self, *args, **kwargs):
+ document = super().load(*args, **kwargs)
+ update_inkstitch_document(document)
+ return document
+
@classmethod
def name(cls):
return snakecase(cls.__name__)
diff --git a/lib/metadata.py b/lib/metadata.py
new file mode 100644
index 00000000..837fbf00
--- /dev/null
+++ b/lib/metadata.py
@@ -0,0 +1,85 @@
+import json
+import re
+from collections.abc import MutableMapping
+
+import inkex
+from lxml import etree
+
+from .utils.settings import DEFAULT_METADATA, global_settings
+
+
+def strip_namespace(tag):
+ """Remove xml namespace from a tag name.
+
+ >>> {http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}namedview
+ <<< namedview
+ """
+
+ match = re.match(r'^\{[^}]+\}(.+)$', 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):
+ super().__init__()
+ self.document = document
+ self.metadata = document.metadata
+
+ for setting in DEFAULT_METADATA:
+ if self[setting] is None:
+ self[setting] = global_settings[f'default_{setting}']
+
+ # 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 = 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
+
+ def __json__(self):
+ return dict(self)
diff --git a/lib/svg/tags.py b/lib/svg/tags.py
index bbef6ebb..8ce0c8a2 100644
--- a/lib/svg/tags.py
+++ b/lib/svg/tags.py
@@ -27,6 +27,7 @@ SVG_IMAGE_TAG = inkex.addNS('image', 'svg')
SVG_CLIPPATH_TAG = inkex.addNS('clipPath', 'svg')
SVG_MASK_TAG = inkex.addNS('mask', 'svg')
+SVG_METADATA_TAG = inkex.addNS("metadata", "svg")
INKSCAPE_LABEL = inkex.addNS('label', 'inkscape')
INKSCAPE_GROUPMODE = inkex.addNS('groupmode', 'inkscape')
CONNECTION_START = inkex.addNS('connection-start', 'inkscape')
diff --git a/lib/update.py b/lib/update.py
new file mode 100644
index 00000000..cba6f671
--- /dev/null
+++ b/lib/update.py
@@ -0,0 +1,126 @@
+from inkex import errormsg
+
+from .i18n import _
+from .elements import EmbroideryElement
+from .metadata import InkStitchMetadata
+from .svg.tags import INKSTITCH_ATTRIBS
+
+INKSTITCH_SVG_VERSION = 1
+
+
+def update_inkstitch_document(svg):
+ document = svg.getroot()
+ # get the inkstitch svg version from the document
+ search_string = "//*[local-name()='inkstitch_svg_version']//text()"
+ file_version = document.findone(search_string)
+ try:
+ file_version = int(file_version)
+ except (TypeError, ValueError):
+ file_version = 0
+
+ if file_version == INKSTITCH_SVG_VERSION:
+ return
+
+ if file_version > INKSTITCH_SVG_VERSION:
+ errormsg(_("This document was created with a newer Version of Ink/Stitch. "
+ "It is possible that not everything works as expected.\n\n"
+ "Please update your Ink/Stitch version: https://inkstitch.org/docs/install/"))
+ # they may not want to be bothered with this info everytime they call an inkstitch extension
+ # let's udowngrade the file version number
+ _update_inkstitch_svg_version(svg)
+ else:
+ # this document is either a new document or it is outdated
+ # if we cannot find any inkstitch attribute in the document, we assume that this is a new document which doesn't need to be updated
+ search_string = "//*[namespace-uri()='http://inkstitch.org/namespace' or " \
+ "@*[namespace-uri()='http://inkstitch.org/namespace'] or " \
+ "@*[starts-with(name(), 'embroider_')]]"
+ inkstitch_element = document.findone(search_string)
+ if inkstitch_element is None:
+ _update_inkstitch_svg_version(svg)
+ return
+
+ # update elements
+ for element in document.iterdescendants():
+ # We are just checking for params and update them.
+ # No need to check for specific stitch types at this point
+ update_legacy_params(EmbroideryElement(element), file_version, INKSTITCH_SVG_VERSION)
+ _update_inkstitch_svg_version(svg)
+
+
+def _update_inkstitch_svg_version(svg):
+ # set inkstitch svg version
+ metadata = InkStitchMetadata(svg.getroot())
+ metadata['inkstitch_svg_version'] = INKSTITCH_SVG_VERSION
+
+
+def update_legacy_params(element, file_version, inkstitch_svg_version):
+ for version in range(file_version + 1, inkstitch_svg_version + 1):
+ _update_to(version, element)
+
+
+def _update_to(version, element):
+ if version == 1:
+ _update_to_one(element)
+
+
+def _update_to_one(element): # noqa: C901
+ # update legacy embroider_ attributes to namespaced attributes
+ legacy_attribs = False
+ for attrib in element.node.attrib:
+ if attrib.startswith('embroider_'):
+ _replace_legacy_embroider_param(element, attrib)
+ legacy_attribs = True
+
+ # convert legacy tie setting
+ legacy_tie = element.get_param('ties', None)
+ if legacy_tie == "True":
+ element.set_param('ties', 0)
+ elif legacy_tie == "False":
+ element.set_param('ties', 3)
+
+ # convert legacy fill_method
+ legacy_fill_method = element.get_int_param('fill_method', None)
+ if legacy_fill_method == 0:
+ element.set_param('fill_method', 'auto_fill')
+ elif legacy_fill_method == 1:
+ element.set_param('fill_method', 'contour_fill')
+ elif legacy_fill_method == 2:
+ element.set_param('fill_method', 'guided_fill')
+ elif legacy_fill_method == 3:
+ element.set_param('fill_method', 'legacy_fill')
+
+ # legacy satin method
+ if element.get_boolean_param('e_stitch', False) is True:
+ element.remove_param('e_stitch')
+ element.set_param('satin_method', 'e_stitch')
+
+ # default setting for fill_underlay has changed
+ if legacy_attribs and not element.get_param('fill_underlay', ""):
+ element.set_param('fill_underlay', False)
+
+ # convert legacy stroke_method
+ if element.get_style("stroke"):
+ # manual stitch
+ legacy_manual_stitch = element.get_boolean_param('manual_stitch', False)
+ if legacy_manual_stitch is True:
+ element.remove_param('manual_stitch')
+ element.set_param('stroke_method', 'manual_stitch')
+ # stroke_method
+ legacy_stroke_method = element.get_int_param('stroke_method', None)
+ if legacy_stroke_method == 0:
+ element.set_param('stroke_method', 'running_stitch')
+ elif legacy_stroke_method == 1:
+ element.set_param('stroke_method', 'ripple_stitch')
+ if (not element.get_param('stroke_method', None) and
+ element.get_param('satin_column', False) is False and
+ not element.node.style('stroke-dasharray')):
+ element.set_param('stroke_method', 'zigzag_stitch')
+
+
+def _replace_legacy_embroider_param(element, param):
+ # remove "embroider_" prefix
+ new_param = param[10:]
+ if new_param in INKSTITCH_ATTRIBS:
+ value = element.node.get(param, "").strip()
+ element.set_param(param[10:], value)
+ del element.node.attrib[param]