summaryrefslogtreecommitdiff
path: root/lib/extensions
diff options
context:
space:
mode:
Diffstat (limited to 'lib/extensions')
-rw-r--r--lib/extensions/__init__.py57
-rw-r--r--lib/extensions/auto_satin.py4
-rw-r--r--lib/extensions/base.py45
-rw-r--r--lib/extensions/break_apart.py12
-rw-r--r--lib/extensions/cleanup.py19
-rw-r--r--lib/extensions/commands.py4
-rw-r--r--lib/extensions/convert_to_satin.py35
-rw-r--r--lib/extensions/cut_satin.py6
-rw-r--r--lib/extensions/embroider.py87
-rw-r--r--lib/extensions/embroider_settings.py17
-rw-r--r--lib/extensions/flip.py9
-rw-r--r--lib/extensions/import_threadlist.py19
-rw-r--r--lib/extensions/input.py11
-rw-r--r--lib/extensions/layer_commands.py30
-rw-r--r--lib/extensions/lettering.py82
-rw-r--r--lib/extensions/lettering_custom_font_dir.py48
-rw-r--r--lib/extensions/lettering_generate_json.py76
-rw-r--r--lib/extensions/lettering_remove_kerning.py30
-rw-r--r--lib/extensions/object_commands.py2
-rw-r--r--lib/extensions/output.py10
-rw-r--r--lib/extensions/params.py38
-rw-r--r--lib/extensions/print_pdf.py35
-rw-r--r--lib/extensions/remove_embroidery_settings.py20
-rw-r--r--lib/extensions/reorder.py36
-rw-r--r--lib/extensions/stitch_plan_preview.py9
-rw-r--r--lib/extensions/troubleshoot.py27
-rw-r--r--lib/extensions/zip.py34
27 files changed, 473 insertions, 329 deletions
diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py
index a5388f19..1758772e 100644
--- a/lib/extensions/__init__.py
+++ b/lib/extensions/__init__.py
@@ -1,28 +1,32 @@
-from auto_satin import AutoSatin
-from break_apart import BreakApart
-from cleanup import Cleanup
-from convert_to_satin import ConvertToSatin
-from cut_satin import CutSatin
-from embroider import Embroider
-from flip import Flip
-from global_commands import GlobalCommands
-from import_threadlist import ImportThreadlist
-from input import Input
-from install import Install
-from layer_commands import LayerCommands
-from lettering import Lettering
from lib.extensions.troubleshoot import Troubleshoot
-from object_commands import ObjectCommands
-from output import Output
-from params import Params
-from print_pdf import Print
-from remove_embroidery_settings import RemoveEmbroiderySettings
-from simulator import Simulator
-from stitch_plan_preview import StitchPlanPreview
-from zip import Zip
-__all__ = extensions = [Embroider,
- StitchPlanPreview,
+from .auto_satin import AutoSatin
+from .break_apart import BreakApart
+from .cleanup import Cleanup
+from .convert_to_satin import ConvertToSatin
+from .cut_satin import CutSatin
+from .flip import Flip
+from .global_commands import GlobalCommands
+from .import_threadlist import ImportThreadlist
+from .input import Input
+from .install import Install
+from .layer_commands import LayerCommands
+from .lettering import Lettering
+from .object_commands import ObjectCommands
+from .output import Output
+from .params import Params
+from .print_pdf import Print
+from .remove_embroidery_settings import RemoveEmbroiderySettings
+from .reorder import Reorder
+from .simulator import Simulator
+from .stitch_plan_preview import StitchPlanPreview
+from .zip import Zip
+from .lettering_generate_json import LetteringGenerateJson
+from .lettering_remove_kerning import LetteringRemoveKerning
+from .lettering_custom_font_dir import LetteringCustomFontDir
+from .embroider_settings import EmbroiderSettings
+
+__all__ = extensions = [StitchPlanPreview,
Install,
Params,
Print,
@@ -37,9 +41,14 @@ __all__ = extensions = [Embroider,
CutSatin,
AutoSatin,
Lettering,
+ LetteringGenerateJson,
+ LetteringRemoveKerning,
+ LetteringCustomFontDir,
Troubleshoot,
RemoveEmbroiderySettings,
Cleanup,
BreakApart,
ImportThreadlist,
- Simulator]
+ Simulator,
+ Reorder,
+ EmbroiderSettings]
diff --git a/lib/extensions/auto_satin.py b/lib/extensions/auto_satin.py
index a447a493..fce4a1fd 100644
--- a/lib/extensions/auto_satin.py
+++ b/lib/extensions/auto_satin.py
@@ -14,7 +14,7 @@ class AutoSatin(CommandsExtension):
def __init__(self, *args, **kwargs):
CommandsExtension.__init__(self, *args, **kwargs)
- self.OptionParser.add_option("-p", "--preserve_order", dest="preserve_order", type="inkbool", default=False)
+ self.arg_parser.add_argument("-p", "--preserve_order", dest="preserve_order", type=inkex.Boolean, default=False)
def get_starting_point(self):
return self.get_point("satin_start")
@@ -39,7 +39,7 @@ class AutoSatin(CommandsExtension):
if not self.get_elements():
return
- if not self.selected:
+ if not self.svg.selected:
# L10N auto-route satin columns extension
inkex.errormsg(_("Please select one or more satin columns."))
return False
diff --git a/lib/extensions/base.py b/lib/extensions/base.py
index 9f6dc5f6..1a38973f 100644
--- a/lib/extensions/base.py
+++ b/lib/extensions/base.py
@@ -1,21 +1,19 @@
import json
import os
import re
-from collections import MutableMapping
-from copy import deepcopy
-
-from stringcase import snakecase
+from collections.abc import MutableMapping
import inkex
+from lxml import etree
+from stringcase import snakecase
from ..commands import is_command, layer_commands
from ..elements import EmbroideryElement, nodes_to_elements
-from ..elements.clone import is_clone, is_embroiderable_clone
+from ..elements.clone import is_clone
from ..i18n import _
from ..svg import generate_unique_id
from ..svg.tags import (CONNECTOR_TYPE, EMBROIDERABLE_TAGS, INKSCAPE_GROUPMODE,
- NOT_EMBROIDERABLE_TAGS, SVG_DEFS_TAG, SVG_GROUP_TAG,
- SVG_PATH_TAG)
+ NOT_EMBROIDERABLE_TAGS, SVG_DEFS_TAG, SVG_GROUP_TAG)
SVG_METADATA_TAG = inkex.addNS("metadata", "svg")
@@ -71,7 +69,7 @@ class InkStitchMetadata(MutableMapping):
tag = inkex.addNS(name, "inkstitch")
item = self.metadata.find(tag)
if item is None and create:
- item = inkex.etree.SubElement(self.metadata, tag)
+ item = etree.SubElement(self.metadata, tag)
return item
@@ -117,7 +115,7 @@ class InkstitchExtension(inkex.Effect):
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():
+ if self.svg.get_current_layer() is self.document.getroot():
try:
self.current_layer = self.document.xpath(".//svg:g[@inkscape:groupmode='layer']", namespaces=inkex.NSS)[0]
except IndexError:
@@ -125,7 +123,7 @@ class InkstitchExtension(inkex.Effect):
pass
def no_elements_error(self):
- if self.selected:
+ if self.svg.selected:
# l10n This was previously: "No embroiderable paths selected."
inkex.errormsg(_("Ink/Stitch doesn't know how to work with any of the objects you've selected.") + "\n")
else:
@@ -154,8 +152,8 @@ class InkstitchExtension(inkex.Effect):
if is_command(node) or node.get(CONNECTOR_TYPE):
return[]
- if self.selected:
- if node.get("id") in self.selected:
+ if self.svg.selected:
+ if node.get("id") in self.svg.selected:
selected = True
else:
# if the user didn't select anything that means we process everything
@@ -165,7 +163,7 @@ class InkstitchExtension(inkex.Effect):
nodes.extend(self.descendants(child, selected, troubleshoot))
if selected:
- if (node.tag in EMBROIDERABLE_TAGS or is_embroiderable_clone(node)) and not (node.tag == SVG_PATH_TAG and not node.get('d', '')):
+ if getattr(node, "get_path", None):
nodes.append(node)
elif troubleshoot and (node.tag in NOT_EMBROIDERABLE_TAGS or node.tag in EMBROIDERABLE_TAGS or is_clone(node)):
nodes.append(node)
@@ -206,24 +204,3 @@ class InkstitchExtension(inkex.Effect):
def uniqueId(self, prefix, make_new_id=True):
"""Override inkex.Effect.uniqueId with a nicer naming scheme."""
return generate_unique_id(self.document, prefix)
-
- def parse(self):
- """Override inkex.Effect.parse 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.
-
- # call the superclass's method first
- inkex.Effect.parse(self)
-
- # Add the inkstitch namespace to the SVG. The inkstitch namespace is
- # added to inkex.NSS in ../svg/tags.py at import time.
-
- # The below 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)
diff --git a/lib/extensions/break_apart.py b/lib/extensions/break_apart.py
index 0b17d3d7..d0ab2619 100644
--- a/lib/extensions/break_apart.py
+++ b/lib/extensions/break_apart.py
@@ -1,11 +1,10 @@
import logging
from copy import copy
+import inkex
from shapely.geometry import LineString, MultiPolygon, Polygon
from shapely.ops import polygonize, unary_union
-import inkex
-
from ..elements import EmbroideryElement
from ..i18n import _
from ..svg import get_correction_transform
@@ -19,10 +18,11 @@ class BreakApart(InkstitchExtension):
'''
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
- self.OptionParser.add_option("-m", "--method", type="int", default=1, dest="method")
+ self.arg_parser.add_argument("-m", "--method", type=int, default=1, dest="method")
+ self.minimum_size = 5
def effect(self): # noqa: C901
- if not self.selected:
+ if not self.svg.selected:
inkex.errormsg(_("Please select one or more fill areas to break apart."))
return
@@ -41,13 +41,12 @@ class BreakApart(InkstitchExtension):
try:
paths.sort(key=lambda point_list: Polygon(point_list).area, reverse=True)
polygon = MultiPolygon([(paths[0], paths[1:])])
- if self.geom_is_valid(polygon):
+ if self.geom_is_valid(polygon) and Polygon(paths[-1]).area > self.minimum_size:
continue
except ValueError:
pass
polygons = self.break_apart_paths(paths)
- polygons = self.ensure_minimum_size(polygons, 5)
if self.options.method == 1:
polygons = self.combine_overlapping_polygons(polygons)
polygons = self.recombine_polygons(polygons)
@@ -106,6 +105,7 @@ class BreakApart(InkstitchExtension):
polygons.sort(key=lambda polygon: polygon.area, reverse=True)
multipolygons = []
holes = []
+ self.ensure_minimum_size(polygons, self.minimum_size)
for polygon in polygons:
if polygon in holes:
continue
diff --git a/lib/extensions/cleanup.py b/lib/extensions/cleanup.py
index e06b4bea..f1965aba 100644
--- a/lib/extensions/cleanup.py
+++ b/lib/extensions/cleanup.py
@@ -1,6 +1,4 @@
-import sys
-
-from inkex import NSS
+from inkex import NSS, Boolean, errormsg
from ..elements import Fill, Stroke
from ..i18n import _
@@ -10,10 +8,10 @@ from .base import InkstitchExtension
class Cleanup(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
- self.OptionParser.add_option("-f", "--rm_fill", dest="rm_fill", type="inkbool", default=True)
- self.OptionParser.add_option("-s", "--rm_stroke", dest="rm_stroke", type="inkbool", default=True)
- self.OptionParser.add_option("-a", "--fill_threshold", dest="fill_threshold", type="int", default=20)
- self.OptionParser.add_option("-l", "--stroke_threshold", dest="stroke_threshold", type="int", default=5)
+ self.arg_parser.add_argument("-f", "--rm_fill", dest="rm_fill", type=Boolean, default=True)
+ self.arg_parser.add_argument("-s", "--rm_stroke", dest="rm_stroke", type=Boolean, default=True)
+ self.arg_parser.add_argument("-a", "--fill_threshold", dest="fill_threshold", type=int, default=20)
+ self.arg_parser.add_argument("-l", "--stroke_threshold", dest="stroke_threshold", type=int, default=5)
def effect(self):
self.rm_fill = self.options.rm_fill
@@ -21,8 +19,7 @@ class Cleanup(InkstitchExtension):
self.fill_threshold = self.options.fill_threshold
self.stroke_threshold = self.options.stroke_threshold
- # Remove selection, we want every element in the document
- self.selected = {}
+ self.svg.selected.clear()
count = 0
svg = self.document.getroot()
@@ -32,7 +29,7 @@ class Cleanup(InkstitchExtension):
count += 1
if not self.get_elements():
- print >> sys.stderr, _("%s elements removed" % count)
+ errormsg(_("%s elements removed" % count))
return
for element in self.elements:
@@ -44,4 +41,4 @@ class Cleanup(InkstitchExtension):
element.node.getparent().remove(element.node)
count += 1
- print >> sys.stderr, _("%s elements removed" % count)
+ errormsg(_("%s elements removed" % count))
diff --git a/lib/extensions/commands.py b/lib/extensions/commands.py
index 86e291fd..19b85e6d 100644
--- a/lib/extensions/commands.py
+++ b/lib/extensions/commands.py
@@ -1,3 +1,5 @@
+from inkex import Boolean
+
from .base import InkstitchExtension
@@ -7,4 +9,4 @@ class CommandsExtension(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
for command in self.COMMANDS:
- self.OptionParser.add_option("--%s" % command, type="inkbool")
+ self.arg_parser.add_argument("--%s" % command, type=Boolean)
diff --git a/lib/extensions/convert_to_satin.py b/lib/extensions/convert_to_satin.py
index e2b287dd..048c08da 100644
--- a/lib/extensions/convert_to_satin.py
+++ b/lib/extensions/convert_to_satin.py
@@ -1,12 +1,13 @@
import math
+import sys
from itertools import chain, groupby
+import inkex
import numpy
+from lxml import etree
from numpy import diff, setdiff1d, sign
from shapely import geometry as shgeo
-import inkex
-
from ..elements import Stroke
from ..i18n import _
from ..svg import PIXELS_PER_MM, get_correction_transform
@@ -26,7 +27,7 @@ class ConvertToSatin(InkstitchExtension):
if not self.get_elements():
return
- if not self.selected:
+ if not self.svg.selected:
inkex.errormsg(_("Please select at least one line to convert to a satin column."))
return
@@ -120,8 +121,15 @@ class ConvertToSatin(InkstitchExtension):
path = shgeo.LineString(path)
- left_rail = path.parallel_offset(stroke_width / 2.0, 'left', **style_args)
- right_rail = path.parallel_offset(stroke_width / 2.0, 'right', **style_args)
+ try:
+ left_rail = path.parallel_offset(stroke_width / 2.0, 'left', **style_args)
+ right_rail = path.parallel_offset(stroke_width / 2.0, 'right', **style_args)
+ except ValueError:
+ # TODO: fix this error automatically
+ # Error reference: https://github.com/inkstitch/inkstitch/issues/964
+ inkex.errormsg(_("Ink/Stitch cannot convert your stroke into a satin column. "
+ "Please break up your path and try again.") + '\n')
+ sys.exit(1)
if not isinstance(left_rail, shgeo.LineString) or \
not isinstance(right_rail, shgeo.LineString):
@@ -304,12 +312,11 @@ class ConvertToSatin(InkstitchExtension):
d += "%s,%s " % (x, y)
d += " "
- return inkex.etree.Element(SVG_PATH_TAG,
- {
- "id": self.uniqueId("path"),
- "style": path_style,
- "transform": correction_transform,
- "d": d,
- INKSTITCH_ATTRIBS['satin_column']: "true",
- }
- )
+ return etree.Element(SVG_PATH_TAG,
+ {
+ "id": self.uniqueId("path"),
+ "style": path_style,
+ "transform": correction_transform,
+ "d": d,
+ INKSTITCH_ATTRIBS['satin_column']: "true",
+ })
diff --git a/lib/extensions/cut_satin.py b/lib/extensions/cut_satin.py
index b776a68c..7cc80295 100644
--- a/lib/extensions/cut_satin.py
+++ b/lib/extensions/cut_satin.py
@@ -1,9 +1,9 @@
import inkex
-from .base import InkstitchExtension
-from ..i18n import _
from ..elements import SatinColumn
+from ..i18n import _
from ..svg import get_correction_transform
+from .base import InkstitchExtension
class CutSatin(InkstitchExtension):
@@ -11,7 +11,7 @@ class CutSatin(InkstitchExtension):
if not self.get_elements():
return
- if not self.selected:
+ if not self.svg.selected:
inkex.errormsg(_("Please select one or more satin columns to cut."))
return
diff --git a/lib/extensions/embroider.py b/lib/extensions/embroider.py
deleted file mode 100644
index b9089612..00000000
--- a/lib/extensions/embroider.py
+++ /dev/null
@@ -1,87 +0,0 @@
-import os
-
-from ..i18n import _
-from ..output import write_embroidery_file
-from ..stitch_plan import patches_to_stitch_plan
-from ..svg import render_stitch_plan, PIXELS_PER_MM
-from .base import InkstitchExtension
-
-
-class Embroider(InkstitchExtension):
- def __init__(self, *args, **kwargs):
- InkstitchExtension.__init__(self, *args, **kwargs)
- self.OptionParser.add_option("-c", "--collapse_len_mm",
- action="store", type="float",
- dest="collapse_length_mm", default=3.0,
- help="max collapse length (mm)")
- self.OptionParser.add_option("--hide_layers",
- action="store", type="choice",
- choices=["true", "false"],
- dest="hide_layers", default="true",
- help="Hide all other layers when the embroidery layer is generated")
- self.OptionParser.add_option("-O", "--output_format",
- action="store", type="string",
- dest="output_format", default="csv",
- help="Output file extenstion (default: csv)")
- self.OptionParser.add_option("-P", "--path",
- action="store", type="string",
- dest="path", default=".",
- help="Directory in which to store output file")
- self.OptionParser.add_option("-F", "--output-file",
- action="store", type="string",
- dest="output_file",
- help="Output filename.")
- self.OptionParser.add_option("-b", "--max-backups",
- action="store", type="int",
- dest="max_backups", default=5,
- help="Max number of backups of output files to keep.")
- self.OptionParser.usage += _("\n\nSeeing a 'no such option' message? Please restart Inkscape to fix.")
-
- def get_output_path(self):
- if self.options.output_file:
- # This is helpful for folks that run the embroider extension
- # manually from the command line (without Inkscape) for
- # debugging purposes.
- output_path = os.path.join(os.path.expanduser(os.path.expandvars(self.options.path.decode("UTF-8"))),
- self.options.output_file.decode("UTF-8"))
- else:
- csv_filename = '%s.%s' % (self.get_base_file_name(), self.options.output_format)
- output_path = os.path.join(self.options.path.decode("UTF-8"), csv_filename)
-
- def add_suffix(path, suffix):
- if suffix > 0:
- path = "%s.%s" % (path, suffix)
-
- return path
-
- def move_if_exists(path, suffix=0):
- source = add_suffix(path, suffix)
-
- if suffix >= self.options.max_backups:
- return
-
- dest = add_suffix(path, suffix + 1)
-
- if os.path.exists(source):
- move_if_exists(path, suffix + 1)
-
- if os.path.exists(dest):
- os.remove(dest)
-
- os.rename(source, dest)
-
- move_if_exists(output_path)
-
- return output_path
-
- def effect(self):
- if not self.get_elements():
- return
-
- if self.options.hide_layers:
- self.hide_all_layers()
-
- patches = self.elements_to_patches(self.elements)
- stitch_plan = patches_to_stitch_plan(patches, self.options.collapse_length_mm * PIXELS_PER_MM)
- write_embroidery_file(self.get_output_path(), stitch_plan, self.document.getroot())
- render_stitch_plan(self.document.getroot(), stitch_plan)
diff --git a/lib/extensions/embroider_settings.py b/lib/extensions/embroider_settings.py
new file mode 100644
index 00000000..88e2ba9b
--- /dev/null
+++ b/lib/extensions/embroider_settings.py
@@ -0,0 +1,17 @@
+from .base import InkstitchExtension
+
+
+class EmbroiderSettings(InkstitchExtension):
+ '''
+ This saves embroider settings into the metadata of the file
+ '''
+ def __init__(self, *args, **kwargs):
+ InkstitchExtension.__init__(self, *args, **kwargs)
+ self.arg_parser.add_argument("-c", "--collapse_len_mm",
+ action="store", type=float,
+ dest="collapse_length_mm", default=3.0,
+ help="max collapse length (mm)")
+
+ def effect(self):
+ self.metadata = self.get_inkstitch_metadata()
+ self.metadata['collapse_len_mm'] = self.options.collapse_length_mm
diff --git a/lib/extensions/flip.py b/lib/extensions/flip.py
index 0864da85..87b8b3f0 100644
--- a/lib/extensions/flip.py
+++ b/lib/extensions/flip.py
@@ -1,9 +1,8 @@
import inkex
-import cubicsuperpath
-from .base import InkstitchExtension
-from ..i18n import _
from ..elements import SatinColumn
+from ..i18n import _
+from .base import InkstitchExtension
class Flip(InkstitchExtension):
@@ -14,13 +13,13 @@ class Flip(InkstitchExtension):
first, second = satin.rail_indices
csp[first], csp[second] = csp[second], csp[first]
- satin.node.set("d", cubicsuperpath.formatPath(csp))
+ satin.node.set("d", inkex.paths.CubicSuperPath.to_path(csp))
def effect(self):
if not self.get_elements():
return
- if not self.selected:
+ if not self.svg.selected:
inkex.errormsg(_("Please select one or more satin columns to flip."))
return
diff --git a/lib/extensions/import_threadlist.py b/lib/extensions/import_threadlist.py
index d31c0d69..029043c2 100644
--- a/lib/extensions/import_threadlist.py
+++ b/lib/extensions/import_threadlist.py
@@ -12,20 +12,23 @@ from .base import InkstitchExtension
class ImportThreadlist(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
- self.OptionParser.add_option("-f", "--filepath", type="str", default="", dest="filepath")
- self.OptionParser.add_option("-m", "--method", type="int", default=1, dest="method")
- self.OptionParser.add_option("-t", "--palette", type="str", default=None, dest="palette")
+ self.arg_parser.add_argument("-f", "--filepath", type=str, default="", dest="filepath")
+ self.arg_parser.add_argument("-m", "--method", type=int, default=1, dest="method")
+ self.arg_parser.add_argument("-t", "--palette", type=str, default=None, dest="palette")
def effect(self):
# Remove selection, we want all the elements in the document
- self.selected = {}
+ self.svg.selected.clear()
if not self.get_elements():
return
path = self.options.filepath
if not os.path.exists(path):
- print >> sys.stderr, _("File not found.")
+ inkex.errormsg(_("File not found."))
+ sys.exit(1)
+ if os.path.isdir(path):
+ inkex.errormsg(_("The filepath specified is not a file but a dictionary.\nPlease choose a threadlist file to import."))
sys.exit(1)
method = self.options.method
@@ -35,11 +38,11 @@ class ImportThreadlist(InkstitchExtension):
colors = self.parse_threadlist_by_catalog_number(path)
if all(c is None for c in colors):
- print >>sys.stderr, _("Couldn't find any matching colors in the file.")
+ inkex.errormsg(_("Couldn't find any matching colors in the file."))
if method == 1:
- print >>sys.stderr, _('Please try to import as "other threadlist" and specify a color palette below.')
+ inkex.errormsg(_('Please try to import as "other threadlist" and specify a color palette below.'))
else:
- print >>sys.stderr, _("Please chose an other color palette for your design.")
+ inkex.errormsg(_("Please chose an other color palette for your design."))
sys.exit(1)
# Iterate through the color blocks to apply colors
diff --git a/lib/extensions/input.py b/lib/extensions/input.py
index 957d355c..c6dcb698 100644
--- a/lib/extensions/input.py
+++ b/lib/extensions/input.py
@@ -1,8 +1,9 @@
import os
-import pyembroidery
-from inkex import etree
import inkex
+from lxml import etree
+
+import pyembroidery
from ..stitch_plan import StitchPlan
from ..svg import PIXELS_PER_MM, render_stitch_plan
@@ -10,7 +11,7 @@ from ..svg.tags import INKSCAPE_LABEL
class Input(object):
- def affect(self, args):
+ def run(self, args):
embroidery_file = args[0]
pattern = pyembroidery.read(embroidery_file)
@@ -47,11 +48,11 @@ class Input(object):
# rename the Stitch Plan layer so that it doesn't get overwritten by Embroider
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
- layer.set(INKSCAPE_LABEL, os.path.basename(embroidery_file.decode("UTF-8")))
+ layer.set(INKSCAPE_LABEL, os.path.basename(embroidery_file))
layer.attrib.pop('id')
# Shift the design so that its origin is at the center of the canvas
# Note: this is NOT the same as centering the design in the canvas!
layer.set('transform', 'translate(%s,%s)' % (extents[0], extents[1]))
- print etree.tostring(svg)
+ print(etree.tostring(svg).decode('utf-8'))
diff --git a/lib/extensions/layer_commands.py b/lib/extensions/layer_commands.py
index e710e351..89726510 100644
--- a/lib/extensions/layer_commands.py
+++ b/lib/extensions/layer_commands.py
@@ -1,9 +1,10 @@
import inkex
+from lxml import etree
-from ..commands import LAYER_COMMANDS, get_command_description, ensure_symbol
+from ..commands import LAYER_COMMANDS, ensure_symbol, get_command_description
from ..i18n import _
from ..svg import get_correction_transform
-from ..svg.tags import SVG_USE_TAG, INKSCAPE_LABEL, XLINK_HREF
+from ..svg.tags import INKSCAPE_LABEL, SVG_USE_TAG, XLINK_HREF
from .commands import CommandsExtension
@@ -17,20 +18,19 @@ class LayerCommands(CommandsExtension):
inkex.errormsg(_("Please choose one or more commands to add."))
return
- self.ensure_current_layer()
- correction_transform = get_correction_transform(self.current_layer, child=True)
+ correction_transform = get_correction_transform(self.svg.get_current_layer(), child=True)
for i, command in enumerate(commands):
ensure_symbol(self.document, command)
- inkex.etree.SubElement(self.current_layer, SVG_USE_TAG,
- {
- "id": self.uniqueId("use"),
- INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
- XLINK_HREF: "#inkstitch_%s" % command,
- "height": "100%",
- "width": "100%",
- "x": str(i * 20),
- "y": "-10",
- "transform": correction_transform
- })
+ etree.SubElement(self.svg.get_current_layer(), SVG_USE_TAG,
+ {
+ "id": self.uniqueId("use"),
+ INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
+ XLINK_HREF: "#inkstitch_%s" % command,
+ "height": "100%",
+ "width": "100%",
+ "x": str(i * 20),
+ "y": "-10",
+ "transform": correction_transform
+ })
diff --git a/lib/extensions/lettering.py b/lib/extensions/lettering.py
index d988778d..ee0dd9a0 100644
--- a/lib/extensions/lettering.py
+++ b/lib/extensions/lettering.py
@@ -1,14 +1,12 @@
-# -*- coding: UTF-8 -*-
-
import json
import os
import sys
-from base64 import b64decode, b64encode
import appdirs
import inkex
import wx
import wx.adv
+from lxml import etree
from ..elements import nodes_to_elements
from ..gui import PresetsPanel, SimulatorPreview, info_dialog
@@ -19,6 +17,7 @@ from ..svg.tags import (INKSCAPE_LABEL, INKSTITCH_LETTERING, SVG_GROUP_TAG,
SVG_PATH_TAG)
from ..utils import DotDict, cache, get_bundled_dir
from .commands import CommandsExtension
+from .lettering_custom_font_dir import get_custom_font_dir
class LetteringFrame(wx.Frame):
@@ -45,6 +44,7 @@ class LetteringFrame(wx.Frame):
# font details
self.font_description = wx.StaticText(self, wx.ID_ANY)
+ self.Bind(wx.EVT_SIZE, self.resize)
# options
self.options_box = wx.StaticBox(self, wx.ID_ANY, label=_("Options"))
@@ -80,7 +80,7 @@ class LetteringFrame(wx.Frame):
"""Load the settings saved into the SVG group element"""
self.settings = DotDict({
- "text": u"",
+ "text": "",
"back_and_forth": False,
"font": None,
"scale": 100
@@ -88,7 +88,7 @@ class LetteringFrame(wx.Frame):
try:
if INKSTITCH_LETTERING in self.group.attrib:
- self.settings.update(json.loads(b64decode(self.group.get(INKSTITCH_LETTERING))))
+ self.settings.update(json.loads(self.group.get(INKSTITCH_LETTERING)))
return
except (TypeError, ValueError):
pass
@@ -103,22 +103,14 @@ class LetteringFrame(wx.Frame):
def save_settings(self):
"""Save the settings into the SVG group element."""
-
- # We base64 encode the string before storing it in an XML attribute.
- # In theory, lxml should properly html-encode the string, using HTML
- # entities like 
 as necessary. However, we've found that Inkscape
- # incorrectly interpolates the HTML entities upon reading the
- # extension's output, rather than leaving them as is.
- #
- # Details:
- # https://bugs.launchpad.net/inkscape/+bug/1804346
- self.group.set(INKSTITCH_LETTERING, b64encode(json.dumps(self.settings)))
+ self.group.set(INKSTITCH_LETTERING, json.dumps(self.settings))
def update_font_list(self):
font_paths = {
get_bundled_dir("fonts"),
os.path.expanduser("~/.inkstitch/fonts"),
os.path.join(appdirs.user_config_dir('inkstitch'), 'fonts'),
+ get_custom_font_dir()
}
self.fonts = {}
@@ -130,13 +122,12 @@ class LetteringFrame(wx.Frame):
except OSError:
continue
- try:
- for font_dir in font_dirs:
- font = Font(os.path.join(font_path, font_dir))
- self.fonts[font.name] = font
- self.fonts_by_id[font.id] = font
- except FontError:
- pass
+ for font_dir in font_dirs:
+ font = Font(os.path.join(font_path, font_dir))
+ if font.name == "" or font.id == "":
+ continue
+ self.fonts[font.name] = font
+ self.fonts_by_id[font.id] = font
if len(self.fonts) == 0:
info_dialog(self, _("Unable to find any fonts! Please try reinstalling Ink/Stitch."))
@@ -165,13 +156,13 @@ class LetteringFrame(wx.Frame):
self.font_chooser.Append(font.name)
def get_font_names(self):
- font_names = [font.name for font in self.fonts.itervalues()]
+ font_names = [font.name for font in self.fonts.values()]
font_names.sort()
return font_names
def get_font_descriptions(self):
- return {font.name: font.description for font in self.fonts.itervalues()}
+ return {font.name: font.description for font in self.fonts.values()}
def set_initial_font(self, font_id):
if font_id:
@@ -191,7 +182,7 @@ class LetteringFrame(wx.Frame):
try:
return self.fonts_by_id[self.DEFAULT_FONT]
except KeyError:
- return self.fonts.values()[0]
+ return list(self.fonts.values())[0]
def on_change(self, attribute, event):
self.settings[attribute] = event.GetEventObject().GetValue()
@@ -202,7 +193,11 @@ class LetteringFrame(wx.Frame):
self.settings.font = font.id
self.scale_spinner.SetRange(int(font.min_scale * 100), int(font.max_scale * 100))
- font_variants = font.has_variants()
+ font_variants = []
+ try:
+ font_variants = font.has_variants()
+ except FontError:
+ pass
# Update font description
color = (0, 0, 0)
@@ -212,7 +207,7 @@ class LetteringFrame(wx.Frame):
description = _('This font has no available font variant. Please update or remove the font.')
self.font_description.SetLabel(description)
self.font_description.SetForegroundColour(color)
- self.font_description.Wrap(self.GetSize().width - 20)
+ self.font_description.Wrap(self.GetSize().width - 35)
if font.reversible:
self.back_and_forth_checkbox.Enable()
@@ -230,18 +225,24 @@ class LetteringFrame(wx.Frame):
self.trim_checkbox.SetValue(False)
self.update_preview()
- self.GetSizer().Layout()
+ self.Layout()
+
+ def resize(self, event=None):
+ description = self.font_description.GetLabel().replace("\n", " ")
+ self.font_description.SetLabel(description)
+ self.font_description.Wrap(self.GetSize().width - 35)
+ self.Layout()
def update_preview(self, event=None):
self.preview.update()
- def update_lettering(self):
+ def update_lettering(self, raise_error=False):
del self.group[:]
if self.settings.scale == 100:
destination_group = self.group
else:
- destination_group = inkex.etree.SubElement(self.group, SVG_GROUP_TAG, {
+ destination_group = etree.SubElement(self.group, SVG_GROUP_TAG, {
# L10N The user has chosen to scale the text by some percentage
# (50%, 200%, etc). If you need to use the percentage symbol,
# make sure to double it (%%).
@@ -249,7 +250,14 @@ class LetteringFrame(wx.Frame):
})
font = self.fonts.get(self.font_chooser.GetValue(), self.default_font)
- font.render_text(self.settings.text, destination_group, back_and_forth=self.settings.back_and_forth, trim=self.settings.trim)
+ try:
+ font.render_text(self.settings.text, destination_group, back_and_forth=self.settings.back_and_forth, trim=self.settings.trim)
+ except FontError as e:
+ if raise_error:
+ inkex.errormsg("Error: Text cannot be applied to the document.\n%s" % e)
+ return
+ else:
+ pass
if self.settings.scale != 100:
destination_group.attrib['transform'] = 'scale(%s)' % (self.settings.scale / 100.0)
@@ -295,7 +303,7 @@ class LetteringFrame(wx.Frame):
def apply(self, event):
self.preview.disable()
- self.update_lettering()
+ self.update_lettering(True)
self.save_settings()
self.close()
@@ -369,10 +377,10 @@ class Lettering(CommandsExtension):
self.cancelled = True
def get_or_create_group(self):
- if self.selected:
+ if self.svg.selected:
groups = set()
- for node in self.selected.itervalues():
+ for node in self.svg.selected.values():
if node.tag == SVG_GROUP_TAG and INKSTITCH_LETTERING in node.attrib:
groups.add(node)
@@ -391,9 +399,9 @@ class Lettering(CommandsExtension):
return list(groups)[0]
else:
self.ensure_current_layer()
- return inkex.etree.SubElement(self.current_layer, SVG_GROUP_TAG, {
+ return etree.SubElement(self.svg.get_current_layer(), SVG_GROUP_TAG, {
INKSCAPE_LABEL: _("Ink/Stitch Lettering"),
- "transform": get_correction_transform(self.current_layer, child=True)
+ "transform": get_correction_transform(self.svg.get_current_layer(), child=True)
})
def effect(self):
@@ -405,7 +413,7 @@ class Lettering(CommandsExtension):
display = wx.Display(current_screen)
display_size = display.GetClientArea()
frame_size = frame.GetSize()
- frame.SetPosition((display_size[0], display_size[3] / 2 - frame_size[1] / 2))
+ frame.SetPosition((int(display_size[0]), int(display_size[3] / 2 - frame_size[1] / 2)))
frame.Show()
app.MainLoop()
diff --git a/lib/extensions/lettering_custom_font_dir.py b/lib/extensions/lettering_custom_font_dir.py
new file mode 100644
index 00000000..0103c7d6
--- /dev/null
+++ b/lib/extensions/lettering_custom_font_dir.py
@@ -0,0 +1,48 @@
+import json
+import os
+
+import appdirs
+from inkex import errormsg
+
+from ..i18n import _
+from .base import InkstitchExtension
+
+
+class LetteringCustomFontDir(InkstitchExtension):
+ '''
+ This extension will create a json file to store a custom directory path for additional user fonts
+ '''
+ def __init__(self, *args, **kwargs):
+ InkstitchExtension.__init__(self, *args, **kwargs)
+ self.arg_parser.add_argument("-d", "--path", type=str, default="", dest="path")
+
+ def effect(self):
+ path = self.options.path
+ if not os.path.isdir(path):
+ errormsg(_("Please specify the directory of your custom fonts."))
+ return
+
+ data = {'custom_font_dir': '%s' % path}
+
+ try:
+ config_path = appdirs.user_config_dir('inkstitch')
+ except ImportError:
+ config_path = os.path.expanduser('~/.inkstitch')
+ config_path = os.path.join(config_path, 'custom_dirs.json')
+
+ with open(config_path, 'w', encoding="utf8") as font_data:
+ json.dump(data, font_data, indent=4, ensure_ascii=False)
+
+
+def get_custom_font_dir():
+ custom_font_dir_path = os.path.join(appdirs.user_config_dir('inkstitch'), 'custom_dirs.json')
+ try:
+ with open(custom_font_dir_path, 'r') as custom_dirs:
+ custom_dir = json.load(custom_dirs)
+ except (IOError, ValueError):
+ return ""
+ try:
+ return custom_dir['custom_font_dir']
+ except KeyError:
+ pass
+ return ""
diff --git a/lib/extensions/lettering_generate_json.py b/lib/extensions/lettering_generate_json.py
new file mode 100644
index 00000000..9b44c367
--- /dev/null
+++ b/lib/extensions/lettering_generate_json.py
@@ -0,0 +1,76 @@
+import json
+import os
+import sys
+
+from inkex import Boolean
+
+from ..i18n import _
+from ..lettering.kerning import FontKerning
+from .base import InkstitchExtension
+
+
+class LetteringGenerateJson(InkstitchExtension):
+ '''
+ This extension helps font creators to generate the json file for the lettering tool
+ '''
+ def __init__(self, *args, **kwargs):
+ InkstitchExtension.__init__(self, *args, **kwargs)
+ self.arg_parser.add_argument("-n", "--font-name", type=str, default="Font", dest="font_name")
+ self.arg_parser.add_argument("-d", "--font-description", type=str, default="Description", dest="font_description")
+ self.arg_parser.add_argument("-s", "--auto-satin", type=Boolean, default="true", dest="auto_satin")
+ self.arg_parser.add_argument("-r", "--reversible", type=Boolean, default="true", dest="reversible")
+ self.arg_parser.add_argument("-g", "--default-glyph", type=str, default="", dest="default_glyph")
+ self.arg_parser.add_argument("-i", "--min-scale", type=float, default=1, dest="min_scale")
+ self.arg_parser.add_argument("-a", "--max-scale", type=float, default=1, dest="max_scale")
+ self.arg_parser.add_argument("-l", "--leading", type=int, default=0, dest="leading")
+ self.arg_parser.add_argument("-p", "--font-file", type=str, default="", dest="path")
+
+ def effect(self):
+ # file paths
+ path = self.options.path
+ if not os.path.isfile(path):
+ print(_("Please specify a font file."), file=sys.stderr)
+ return
+ output_path = os.path.join(os.path.dirname(path), 'font.json')
+
+ # kerning
+ kerning = FontKerning(path)
+
+ horiz_adv_x = kerning.horiz_adv_x()
+ hkern = kerning.hkern()
+ word_spacing = kerning.word_spacing()
+ letter_spacing = kerning.letter_spacing()
+ units_per_em = kerning.units_per_em()
+ # missing_glyph_spacing = kerning.missing_glyph_spacing()
+
+ # if letter spacing returns 0, it hasn't been specified in the font file
+ # Ink/Stitch will calculate the width of each letter automatically
+ if letter_spacing == 0:
+ letter_spacing = None
+
+ # if leading (line height) is set to 0, the font author wants Ink/Stitch to use units_per_em
+ # if units_per_em is not defined in the font file a default value will be returned
+ if self.options.leading == 0:
+ leading = units_per_em
+ else:
+ leading = self.options.leading
+
+ # collect data
+ data = {'name': self.options.font_name,
+ 'description': self.options.font_description,
+ 'leading': leading,
+ 'auto_satin': self.options.auto_satin,
+ 'reversible': self.options.reversible,
+ 'default_glyph': self.options.default_glyph,
+ 'min_scale': self.options.min_scale,
+ 'max_scale': self.options.max_scale,
+ 'horiz_adv_x_default': letter_spacing,
+ 'horiz_adv_x_space': word_spacing,
+ 'units_per_em': units_per_em,
+ 'horiz_adv_x': horiz_adv_x,
+ 'kerning_pairs': hkern
+ }
+
+ # write data to font.json into the same directory as the font file
+ with open(output_path, 'w', encoding="utf8") as font_data:
+ json.dump(data, font_data, indent=4, ensure_ascii=False)
diff --git a/lib/extensions/lettering_remove_kerning.py b/lib/extensions/lettering_remove_kerning.py
new file mode 100644
index 00000000..aec8717e
--- /dev/null
+++ b/lib/extensions/lettering_remove_kerning.py
@@ -0,0 +1,30 @@
+import os
+
+from inkex import NSS
+from lxml import etree
+
+from .base import InkstitchExtension
+
+
+class LetteringRemoveKerning(InkstitchExtension):
+ '''
+ This extension helps font creators to generate the json file for the lettering tool
+ '''
+ def __init__(self, *args, **kwargs):
+ InkstitchExtension.__init__(self, *args, **kwargs)
+ self.arg_parser.add_argument("-p", "--font-files", type=str, default="", dest="paths")
+
+ def effect(self):
+ # file paths
+ paths = self.options.paths.split("|")
+ for path in paths:
+ if not os.path.isfile(path):
+ continue
+ with open(path, 'r+', encoding="utf-8") as fontfile:
+ svg = etree.parse(fontfile)
+ xpath = ".//svg:font[1]"
+ kerning = svg.xpath(xpath, namespaces=NSS)[0]
+ kerning.getparent().remove(kerning)
+ fontfile.seek(0)
+ fontfile.write(etree.tostring(svg).decode('utf-8'))
+ fontfile.truncate()
diff --git a/lib/extensions/object_commands.py b/lib/extensions/object_commands.py
index d33ab2ba..f1c2fb46 100644
--- a/lib/extensions/object_commands.py
+++ b/lib/extensions/object_commands.py
@@ -12,7 +12,7 @@ class ObjectCommands(CommandsExtension):
if not self.get_elements():
return
- if not self.selected:
+ if not self.svg.selected:
inkex.errormsg(_("Please select one or more objects to which to attach commands."))
return
diff --git a/lib/extensions/output.py b/lib/extensions/output.py
index ccf4d7cb..52e9d3a9 100644
--- a/lib/extensions/output.py
+++ b/lib/extensions/output.py
@@ -11,7 +11,7 @@ class Output(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
- def getoptions(self, args=sys.argv[1:]):
+ def parse_arguments(self, args=sys.argv[1:]):
# inkex's option parsing can't handle arbitrary command line arguments
# that may be passed for a given output format, so we'll just parse the
# args ourselves. :P
@@ -39,14 +39,16 @@ class Output(InkstitchExtension):
self.file_extension = self.settings.pop('format')
del sys.argv[1:]
- InkstitchExtension.getoptions(self, extra_args)
+ InkstitchExtension.parse_arguments(self, extra_args)
def effect(self):
if not self.get_elements():
return
+ self.metadata = self.get_inkstitch_metadata()
+ collapse_len = self.metadata['collapse_len_mm']
patches = self.elements_to_patches(self.elements)
- stitch_plan = patches_to_stitch_plan(patches, disable_ties=self.settings.get('laser_mode', False))
+ stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len, disable_ties=self.settings.get('laser_mode', False))
temp_file = tempfile.NamedTemporaryFile(suffix=".%s" % self.file_extension, delete=False)
@@ -62,7 +64,7 @@ class Output(InkstitchExtension):
# inkscape will read the file contents from stdout and copy
# to the destination file that the user chose
with open(temp_file.name, "rb") as output_file:
- sys.stdout.write(output_file.read())
+ sys.stdout.buffer.write(output_file.read())
sys.stdout.flush()
# clean up the temp file
diff --git a/lib/extensions/params.py b/lib/extensions/params.py
index 600a4669..910941fe 100644
--- a/lib/extensions/params.py
+++ b/lib/extensions/params.py
@@ -151,7 +151,7 @@ class ParamsTab(ScrolledPanel):
# because they're grayed out anyway.
return values
- for name, input in self.param_inputs.iteritems():
+ for name, input in self.param_inputs.items():
if input in self.changed_inputs and input != self.toggle_checkbox:
values[name] = input.GetValue()
@@ -161,7 +161,7 @@ class ParamsTab(ScrolledPanel):
values = self.get_values()
for node in self.nodes:
# print >> sys.stderr, "apply: ", self.name, node.id, values
- for name, value in values.iteritems():
+ for name, value in values.items():
node.set_param(name, value)
def on_change(self, callable):
@@ -181,7 +181,7 @@ class ParamsTab(ScrolledPanel):
def load_preset(self, preset):
preset_data = preset.get(self.name, {})
- for name, value in preset_data.iteritems():
+ for name, value in preset_data.items():
if name in self.param_inputs:
self.param_inputs[name].SetValue(value)
self.changed_inputs.add(self.param_inputs[name])
@@ -198,16 +198,16 @@ class ParamsTab(ScrolledPanel):
description = _("These settings will be applied to %d objects.") % len(self.nodes)
if any(len(param.values) > 1 for param in self.params):
- description += u"\n • " + _("Some settings had different values across objects. Select a value from the dropdown or enter a new one.")
+ description += "\n • " + _("Some settings had different values across objects. Select a value from the dropdown or enter a new one.")
if self.dependent_tabs:
if len(self.dependent_tabs) == 1:
- description += u"\n • " + _("Disabling this tab will disable the following %d tabs.") % len(self.dependent_tabs)
+ description += "\n • " + _("Disabling this tab will disable the following %d tabs.") % len(self.dependent_tabs)
else:
- description += u"\n • " + _("Disabling this tab will disable the following tab.")
+ description += "\n • " + _("Disabling this tab will disable the following tab.")
if self.paired_tab:
- description += u"\n • " + _("Enabling this tab will disable %s and vice-versa.") % self.paired_tab.name
+ description += "\n • " + _("Enabling this tab will disable %s and vice-versa.") % self.paired_tab.name
self.description_text = description
@@ -285,7 +285,7 @@ class ParamsTab(ScrolledPanel):
self.settings_grid.Add(input, proportion=1, flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND | wx.LEFT, border=40)
self.settings_grid.Add(wx.StaticText(self, label=param.unit or ""), proportion=1, flag=wx.ALIGN_CENTER_VERTICAL)
- self.inputs_to_params = {v: k for k, v in self.param_inputs.iteritems()}
+ self.inputs_to_params = {v: k for k, v in self.param_inputs.items()}
box.Add(self.settings_grid, proportion=1, flag=wx.ALL, border=10)
self.SetSizer(box)
@@ -443,9 +443,9 @@ class SettingsFrame(wx.Frame):
self.notebook.AddPage(tab, tab.name)
sizer_1.Add(self.notebook, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 10)
sizer_1.Add(self.presets_panel, 0, flag=wx.EXPAND | wx.ALL, border=10)
- sizer_3.Add(self.cancel_button, 0, wx.ALIGN_RIGHT | wx.RIGHT, 5)
- sizer_3.Add(self.use_last_button, 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.BOTTOM, 5)
- sizer_3.Add(self.apply_button, 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.BOTTOM, 5)
+ sizer_3.Add(self.cancel_button, 0, wx.RIGHT, 5)
+ sizer_3.Add(self.use_last_button, 0, wx.RIGHT | wx.BOTTOM, 5)
+ sizer_3.Add(self.apply_button, 0, wx.RIGHT | wx.BOTTOM, 5)
sizer_1.Add(sizer_3, 0, wx.ALIGN_RIGHT, 0)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
@@ -491,7 +491,7 @@ class Params(InkstitchExtension):
element.order = z
nodes_by_class[cls].append(element)
- return sorted(nodes_by_class.items(), key=lambda (cls, nodes): cls.__name__)
+ return sorted(list(nodes_by_class.items()), key=lambda cls_nodes: cls_nodes[0].__name__)
def get_values(self, param, nodes):
getter = 'get_param'
@@ -501,17 +501,16 @@ class Params(InkstitchExtension):
else:
getter = 'get_param'
- values = filter(lambda item: item is not None,
- (getattr(node, getter)(param.name, param.default) for node in nodes))
+ values = [item for item in (getattr(node, getter)(param.name, param.default) for node in nodes) if item is not None]
return values
def group_params(self, params):
def by_group_and_sort_index(param):
- return param.group, param.sort_index
+ return param.group or "", param.sort_index
def by_group(param):
- return param.group
+ return param.group or ""
return groupby(sorted(params, key=by_group_and_sort_index), by_group)
@@ -526,7 +525,7 @@ class Params(InkstitchExtension):
# If multiple tabs are enabled, make sure dependent
# tabs are grouped with the parent.
- parent,
+ parent and parent.name,
# Within parent/dependents, put the parent first.
tab == parent
@@ -565,12 +564,13 @@ class Params(InkstitchExtension):
parent_tab = None
new_tabs = []
+
for group, params in self.group_params(params):
tab_name = group or cls.element_name
tab = ParamsTab(parent, id=wx.ID_ANY, name=tab_name, params=list(params), nodes=nodes)
new_tabs.append(tab)
- if group is None:
+ if group == "":
parent_tab = tab
self.assign_parents(new_tabs, parent_tab)
@@ -594,7 +594,7 @@ class Params(InkstitchExtension):
display = wx.Display(current_screen)
display_size = display.GetClientArea()
frame_size = frame.GetSize()
- frame.SetPosition((display_size[0], display_size[3] / 2 - frame_size[1] / 2))
+ frame.SetPosition((int(display_size[0]), int(display_size[3]/2 - frame_size[1]/2)))
frame.Show()
app.MainLoop()
diff --git a/lib/extensions/print_pdf.py b/lib/extensions/print_pdf.py
index 8af07cf7..1218b5e9 100644
--- a/lib/extensions/print_pdf.py
+++ b/lib/extensions/print_pdf.py
@@ -1,19 +1,19 @@
-from copy import deepcopy
-from datetime import date
import errno
import json
import logging
import os
import socket
import sys
-from threading import Thread
import time
+from copy import deepcopy
+from datetime import date
+from threading import Thread
import appdirs
-from flask import Flask, request, Response, send_from_directory, jsonify
-import inkex
-from jinja2 import Environment, FileSystemLoader, select_autoescape
import requests
+from flask import Flask, Response, jsonify, request, send_from_directory
+from jinja2 import Environment, FileSystemLoader, select_autoescape
+from lxml import etree
from ..gui import open_url
from ..i18n import translation as inkstitch_translation
@@ -72,6 +72,11 @@ class PrintPreviewServer(Thread):
def __setup_app(self): # noqa: C901
self.__set_resources_path()
+
+ # Disable warning about using a development server in a production environment
+ cli = sys.modules['flask.cli']
+ cli.show_server_banner = lambda *x: None
+
self.app = Flask(__name__)
@self.app.route('/')
@@ -211,13 +216,21 @@ class Print(InkstitchExtension):
layers = svg.findall("./g[@%s='layer']" % INKSCAPE_GROUPMODE)
stitch_plan_layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
+ # Make sure there is no leftover translation from stitch plan preview
+ stitch_plan_layer.pop('transform')
+
+ # objects outside of the viewbox are invisible
+ # TODO: if we want them to be seen, we need to redefine document size to fit the design
+ # this is just a quick fix and doesn't work on realistic view
+ svg.set('style', 'overflow:visible;')
+
# First, delete all of the other layers. We don't need them and they'll
# just bulk up the SVG.
for layer in layers:
if layer is not stitch_plan_layer:
svg.remove(layer)
- overview_svg = inkex.etree.tostring(svg)
+ overview_svg = etree.tostring(svg).decode('utf-8')
color_block_groups = stitch_plan_layer.getchildren()
color_block_svgs = []
@@ -229,7 +242,7 @@ class Print(InkstitchExtension):
stitch_plan_layer.append(group)
# save an SVG preview
- color_block_svgs.append(inkex.etree.tostring(svg))
+ color_block_svgs.append(etree.tostring(svg).decode('utf-8'))
return overview_svg, color_block_svgs
@@ -269,13 +282,15 @@ class Print(InkstitchExtension):
# objects. It's almost certain they meant to print the whole design.
# If they really wanted to print just a few objects, they could set
# the rest invisible temporarily.
- self.selected = {}
+ self.svg.selected.clear()
if not self.get_elements():
return
+ self.metadata = self.get_inkstitch_metadata()
+ collapse_len = self.metadata['collapse_len_mm']
patches = self.elements_to_patches(self.elements)
- stitch_plan = patches_to_stitch_plan(patches)
+ stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len)
palette = ThreadCatalog().match_and_apply_palette(stitch_plan, self.get_inkstitch_metadata()['thread-palette'])
overview_svg, color_block_svgs = self.render_svgs(stitch_plan, realistic=False)
diff --git a/lib/extensions/remove_embroidery_settings.py b/lib/extensions/remove_embroidery_settings.py
index 2a4d06dd..6ccdb703 100644
--- a/lib/extensions/remove_embroidery_settings.py
+++ b/lib/extensions/remove_embroidery_settings.py
@@ -1,4 +1,4 @@
-import inkex
+from inkex import NSS, Boolean
from ..commands import find_commands
from ..svg.svg import find_elements
@@ -8,9 +8,9 @@ from .base import InkstitchExtension
class RemoveEmbroiderySettings(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
- self.OptionParser.add_option("-p", "--del_params", dest="del_params", type="inkbool", default=True)
- self.OptionParser.add_option("-c", "--del_commands", dest="del_commands", type="inkbool", default=False)
- self.OptionParser.add_option("-d", "--del_print", dest="del_print", type="inkbool", default=False)
+ self.arg_parser.add_argument("-p", "--del_params", dest="del_params", type=Boolean, default=True)
+ self.arg_parser.add_argument("-c", "--del_commands", dest="del_commands", type=Boolean, default=False)
+ self.arg_parser.add_argument("-d", "--del_print", dest="del_print", type=Boolean, default=False)
def effect(self):
self.svg = self.document.getroot()
@@ -30,24 +30,24 @@ class RemoveEmbroiderySettings(InkstitchExtension):
self.remove_element(print_setting)
def remove_params(self):
- if not self.selected:
+ if not self.svg.selected:
xpath = ".//svg:path"
elements = find_elements(self.svg, xpath)
self.remove_inkstitch_attributes(elements)
else:
- for node in self.selected:
+ for node in self.svg.selected:
elements = self.get_selected_elements(node)
self.remove_inkstitch_attributes(elements)
def remove_commands(self):
- if not self.selected:
+ if not self.svg.selected:
# we are not able to grab commands by a specific id
# so let's move through every object instead and see if it has a command
xpath = ".//svg:path|.//svg:circle|.//svg:rect|.//svg:ellipse"
elements = find_elements(self.svg, xpath)
else:
elements = []
- for node in self.selected:
+ for node in self.svg.selected:
elements.extend(self.get_selected_elements(node))
if elements:
@@ -56,7 +56,7 @@ class RemoveEmbroiderySettings(InkstitchExtension):
group = command.connector.getparent()
group.getparent().remove(group)
- if not self.selected:
+ if not self.svg.selected:
# remove standalone commands
standalone_commands = ".//svg:use[starts-with(@xlink:href, '#inkstitch_')]"
self.remove_elements(standalone_commands)
@@ -84,5 +84,5 @@ class RemoveEmbroiderySettings(InkstitchExtension):
def remove_inkstitch_attributes(self, elements):
for element in elements:
for attrib in element.attrib:
- if attrib.startswith(inkex.NSS['inkstitch'], 1):
+ if attrib.startswith(NSS['inkstitch'], 1):
del element.attrib[attrib]
diff --git a/lib/extensions/reorder.py b/lib/extensions/reorder.py
new file mode 100644
index 00000000..4db02760
--- /dev/null
+++ b/lib/extensions/reorder.py
@@ -0,0 +1,36 @@
+import inkex
+
+from .base import InkstitchExtension
+
+
+class Reorder(InkstitchExtension):
+ # Remove selected objects from the document and readd them in the order they
+ # were selected.
+
+ def get_selected_in_order(self):
+ selected = []
+
+ for i in self.options.ids:
+ path = '//*[@id="%s"]' % i
+ for node in self.document.xpath(path, namespaces=inkex.NSS):
+ selected.append(node)
+
+ return selected
+
+ def effect(self):
+ objects = self.get_selected_in_order()
+
+ for obj in objects[1:]:
+ obj.getparent().remove(obj)
+
+ insert_parent = objects[0].getparent()
+ insert_pos = insert_parent.index(objects[0])
+
+ insert_parent.remove(objects[0])
+
+ insert_parent[insert_pos:insert_pos] = objects
+
+
+if __name__ == '__main__':
+ e = Reorder()
+ e.affect()
diff --git a/lib/extensions/stitch_plan_preview.py b/lib/extensions/stitch_plan_preview.py
index b89e24a7..e1c398b5 100644
--- a/lib/extensions/stitch_plan_preview.py
+++ b/lib/extensions/stitch_plan_preview.py
@@ -4,7 +4,6 @@ from .base import InkstitchExtension
class StitchPlanPreview(InkstitchExtension):
-
def effect(self):
# delete old stitch plan
svg = self.document.getroot()
@@ -15,9 +14,13 @@ class StitchPlanPreview(InkstitchExtension):
# create new stitch plan
if not self.get_elements():
return
+
+ realistic = False
+ self.metadata = self.get_inkstitch_metadata()
+ collapse_len = self.metadata['collapse_len_mm']
patches = self.elements_to_patches(self.elements)
- stitch_plan = patches_to_stitch_plan(patches)
- render_stitch_plan(svg, stitch_plan)
+ stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len)
+ render_stitch_plan(svg, stitch_plan, realistic)
# translate stitch plan to the right side of the canvas
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
diff --git a/lib/extensions/troubleshoot.py b/lib/extensions/troubleshoot.py
index 6b63390a..bd352d8f 100644
--- a/lib/extensions/troubleshoot.py
+++ b/lib/extensions/troubleshoot.py
@@ -1,12 +1,13 @@
import textwrap
-import inkex
+from inkex import errormsg
+from lxml import etree
from ..commands import add_layer_commands
from ..elements.validation import (ObjectTypeWarning, ValidationError,
ValidationWarning)
from ..i18n import _
-from ..svg import get_correction_transform
+from ..svg.path import get_correction_transform
from ..svg.tags import (INKSCAPE_GROUPMODE, INKSCAPE_LABEL, SODIPODI_ROLE,
SVG_GROUP_TAG, SVG_PATH_TAG, SVG_TEXT_TAG,
SVG_TSPAN_TAG)
@@ -43,7 +44,7 @@ class Troubleshoot(InkstitchExtension):
message += "\n\n"
message += _("If you are still having trouble with a shape not being embroidered, "
"check if it is in a layer with an ignore command.")
- inkex.errormsg(message)
+ errormsg(message)
def insert_pointer(self, problem):
correction_transform = get_correction_transform(self.troubleshoot_layer)
@@ -61,7 +62,7 @@ class Troubleshoot(InkstitchExtension):
pointer_style = "stroke:#000000;stroke-width:0.2;fill:%s;" % (fill_color)
text_style = "fill:%s;stroke:#000000;stroke-width:0.2;font-size:8px;text-align:center;text-anchor:middle" % (fill_color)
- path = inkex.etree.Element(
+ path = etree.Element(
SVG_PATH_TAG,
{
"id": self.uniqueId("inkstitch__invalid_pointer__"),
@@ -73,7 +74,7 @@ class Troubleshoot(InkstitchExtension):
)
layer.insert(0, path)
- text = inkex.etree.Element(
+ text = etree.Element(
SVG_TEXT_TAG,
{
INKSCAPE_LABEL: _('Description'),
@@ -85,7 +86,7 @@ class Troubleshoot(InkstitchExtension):
)
layer.append(text)
- tspan = inkex.etree.Element(SVG_TSPAN_TAG)
+ tspan = etree.Element(SVG_TSPAN_TAG)
tspan.text = problem.name
text.append(tspan)
@@ -94,7 +95,7 @@ class Troubleshoot(InkstitchExtension):
layer = svg.find(".//*[@id='__validation_layer__']")
if layer is None:
- layer = inkex.etree.Element(
+ layer = etree.Element(
SVG_GROUP_TAG,
{
'id': '__validation_layer__',
@@ -109,7 +110,7 @@ class Troubleshoot(InkstitchExtension):
add_layer_commands(layer, ["ignore_layer"])
- error_group = inkex.etree.SubElement(
+ error_group = etree.SubElement(
layer,
SVG_GROUP_TAG,
{
@@ -118,7 +119,7 @@ class Troubleshoot(InkstitchExtension):
})
layer.append(error_group)
- warning_group = inkex.etree.SubElement(
+ warning_group = etree.SubElement(
layer,
SVG_GROUP_TAG,
{
@@ -127,7 +128,7 @@ class Troubleshoot(InkstitchExtension):
})
layer.append(warning_group)
- type_warning_group = inkex.etree.SubElement(
+ type_warning_group = etree.SubElement(
layer,
SVG_GROUP_TAG,
{
@@ -145,7 +146,7 @@ class Troubleshoot(InkstitchExtension):
svg = self.document.getroot()
text_x = str(float(svg.get('viewBox', '0 0 800 0').split(' ')[2]) + 5.0)
- text_container = inkex.etree.Element(
+ text_container = etree.Element(
SVG_TEXT_TAG,
{
"x": text_x,
@@ -160,7 +161,7 @@ class Troubleshoot(InkstitchExtension):
["", ""]
]
- for problem_type, problems in problem_types.items():
+ for problem_type, problems in list(problem_types.items()):
if problem_type == "error":
text_color = "#ff0000"
problem_type_header = _("Errors")
@@ -202,7 +203,7 @@ class Troubleshoot(InkstitchExtension):
text = self.split_text(text)
for text_line in text:
- tspan = inkex.etree.Element(
+ tspan = etree.Element(
SVG_TSPAN_TAG,
{
SODIPODI_ROLE: "line",
diff --git a/lib/extensions/zip.py b/lib/extensions/zip.py
index aebff331..fedf8c65 100644
--- a/lib/extensions/zip.py
+++ b/lib/extensions/zip.py
@@ -1,36 +1,34 @@
import os
import sys
import tempfile
+from copy import deepcopy
from zipfile import ZipFile
-import inkex
+from inkex import Boolean
+from lxml import etree
+
import pyembroidery
from ..i18n import _
from ..output import write_embroidery_file
from ..stitch_plan import patches_to_stitch_plan
-from ..svg import PIXELS_PER_MM
-from .base import InkstitchExtension
from ..threads import ThreadCatalog
+from .base import InkstitchExtension
class Zip(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self)
- self.OptionParser.add_option("-c", "--collapse_len_mm",
- action="store", type="float",
- dest="collapse_length_mm", default=3.0,
- help="max collapse length (mm)")
# it's kind of obnoxious that I have to do this...
self.formats = []
for format in pyembroidery.supported_formats():
if 'writer' in format and format['category'] == 'embroidery':
extension = format['extension']
- self.OptionParser.add_option('--format-%s' % extension, type="inkbool", dest=extension)
+ self.arg_parser.add_argument('--format-%s' % extension, type=Boolean, dest=extension)
self.formats.append(extension)
- self.OptionParser.add_option('--format-svg', type="inkbool", dest='svg')
- self.OptionParser.add_option('--format-threadlist', type="inkbool", dest='threadlist')
+ self.arg_parser.add_argument('--format-svg', type=Boolean, dest='svg')
+ self.arg_parser.add_argument('--format-threadlist', type=Boolean, dest='threadlist')
self.formats.append('svg')
self.formats.append('threadlist')
@@ -38,8 +36,10 @@ class Zip(InkstitchExtension):
if not self.get_elements():
return
+ self.metadata = self.get_inkstitch_metadata()
+ collapse_len = self.metadata['collapse_len_mm']
patches = self.elements_to_patches(self.elements)
- stitch_plan = patches_to_stitch_plan(patches, self.options.collapse_length_mm * PIXELS_PER_MM)
+ stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len)
base_file_name = self.get_base_file_name()
path = tempfile.mkdtemp()
@@ -50,10 +50,10 @@ class Zip(InkstitchExtension):
if getattr(self.options, format):
output_file = os.path.join(path, "%s.%s" % (base_file_name, format))
if format == 'svg':
- output = open(output_file, 'w')
- output.write(inkex.etree.tostring(self.document.getroot()))
- output.close()
- if format == 'threadlist':
+ document = deepcopy(self.document.getroot())
+ with open(output_file, 'w', encoding='utf-8') as svg:
+ svg.write(etree.tostring(document).decode('utf-8'))
+ elif format == 'threadlist':
output_file = os.path.join(path, "%s_%s.txt" % (base_file_name, _("threadlist")))
output = open(output_file, 'w')
output.write(self.get_threadlist(stitch_plan, base_file_name))
@@ -76,8 +76,8 @@ class Zip(InkstitchExtension):
# inkscape will read the file contents from stdout and copy
# to the destination file that the user chose
- with open(temp_file.name) as output_file:
- sys.stdout.write(output_file.read())
+ with open(temp_file.name, 'rb') as output_file:
+ sys.stdout.buffer.write(output_file.read())
os.remove(temp_file.name)
for file in files: