summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLex Neva <github.com@lexneva.name>2018-05-01 21:21:07 -0400
committerLex Neva <github.com@lexneva.name>2018-05-01 21:21:07 -0400
commit05daffb7e01db55879eb24f3c00532324a5d41af (patch)
tree43ff5d954e035e0e8b5a507b9c1bf9d6b4d3338d
parent1b31806423c8fec4040fed6d1009db016860b763 (diff)
refactor everything out of lib/__init__.py
-rw-r--r--lib/__init__.py298
-rw-r--r--lib/elements/auto_fill.py8
-rw-r--r--lib/elements/element.py18
-rw-r--r--lib/elements/fill.py9
-rw-r--r--lib/elements/polyline.py3
-rw-r--r--lib/elements/satin_column.py6
-rw-r--r--lib/elements/stroke.py5
-rw-r--r--lib/extensions/base.py3
-rw-r--r--lib/extensions/embroider.py7
-rw-r--r--lib/extensions/input.py10
-rw-r--r--lib/extensions/palettes.py1
-rw-r--r--lib/extensions/params.py2
-rw-r--r--lib/extensions/print_pdf.py15
-rw-r--r--lib/extensions/simulate.py1
-rw-r--r--lib/i18n.py21
-rw-r--r--lib/output.py130
-rw-r--r--lib/simulator.py3
-rw-r--r--lib/stitch_plan/__init__.py1
-rw-r--r--lib/stitch_plan/stitch.py15
-rw-r--r--lib/stitch_plan/stitch_plan.py5
-rw-r--r--lib/stitch_plan/ties.py6
-rw-r--r--lib/stitches/auto_fill.py7
-rw-r--r--lib/stitches/fill.py6
-rw-r--r--lib/svg/__init__.py2
-rw-r--r--lib/svg/svg.py (renamed from lib/svg.py)7
-rw-r--r--lib/svg/tags.py12
-rw-r--r--lib/svg/units.py105
-rw-r--r--lib/threads/catalog.py2
-rw-r--r--lib/threads/palette.py3
-rw-r--r--lib/utils/io.py1
-rw-r--r--messages.po24
31 files changed, 379 insertions, 357 deletions
diff --git a/lib/__init__.py b/lib/__init__.py
index 2c0ee620..e69de29b 100644
--- a/lib/__init__.py
+++ b/lib/__init__.py
@@ -1,298 +0,0 @@
-#!/usr/bin/env python
-# http://www.achatina.de/sewing/main/TECHNICL.HTM
-
-import os
-import sys
-import gettext
-from copy import deepcopy
-import math
-import libembroidery
-from .utils import cache
-from .utils.geometry import Point
-
-import inkex
-import simplepath
-import simplestyle
-import simpletransform
-from bezmisc import bezierlength, beziertatlength, bezierpointatt
-from cspsubdiv import cspsubdiv
-import cubicsuperpath
-from shapely import geometry as shgeo
-
-
-# modern versions of Inkscape use 96 pixels per inch as per the CSS standard
-PIXELS_PER_MM = 96 / 25.4
-
-SVG_PATH_TAG = inkex.addNS('path', 'svg')
-SVG_POLYLINE_TAG = inkex.addNS('polyline', 'svg')
-SVG_DEFS_TAG = inkex.addNS('defs', 'svg')
-SVG_GROUP_TAG = inkex.addNS('g', 'svg')
-INKSCAPE_LABEL = inkex.addNS('label', 'inkscape')
-INKSCAPE_GROUPMODE = inkex.addNS('groupmode', 'inkscape')
-
-EMBROIDERABLE_TAGS = (SVG_PATH_TAG, SVG_POLYLINE_TAG)
-
-dbg = open(os.devnull, "w")
-
-translation = None
-_ = lambda message: message
-
-
-def localize():
- if getattr(sys, 'frozen', False):
- # we are in a pyinstaller installation
- locale_dir = sys._MEIPASS
- else:
- locale_dir = os.path.dirname(__file__)
-
- locale_dir = os.path.join(locale_dir, 'locales')
-
- global translation, _
-
- translation = gettext.translation("inkstitch", locale_dir, fallback=True)
- _ = translation.gettext
-
-localize()
-
-# cribbed from inkscape-silhouette
-def parse_length_with_units( str ):
-
- '''
- Parse an SVG value which may or may not have units attached
- This version is greatly simplified in that it only allows: no units,
- units of px, mm, and %. Everything else, it returns None for.
- There is a more general routine to consider in scour.py if more
- generality is ever needed.
- '''
-
- u = 'px'
- s = str.strip()
- if s[-2:] == 'px':
- s = s[:-2]
- elif s[-2:] == 'mm':
- u = 'mm'
- s = s[:-2]
- elif s[-2:] == 'pt':
- u = 'pt'
- s = s[:-2]
- elif s[-2:] == 'pc':
- u = 'pc'
- s = s[:-2]
- elif s[-2:] == 'cm':
- u = 'cm'
- s = s[:-2]
- elif s[-2:] == 'in':
- u = 'in'
- s = s[:-2]
- elif s[-1:] == '%':
- u = '%'
- s = s[:-1]
- try:
- v = float( s )
- except:
- raise ValueError(_("parseLengthWithUnits: unknown unit %s") % s)
-
- return v, u
-
-
-def convert_length(length):
- value, units = parse_length_with_units(length)
-
- if not units or units == "px":
- return value
-
- if units == 'pt':
- value /= 72
- units = 'in'
-
- if units == 'pc':
- value /= 6
- units = 'in'
-
- if units == 'cm':
- value *= 10
- units = 'mm'
-
- if units == 'mm':
- value = value / 25.4
- units = 'in'
-
- if units == 'in':
- # modern versions of Inkscape use CSS's 96 pixels per inch. When you
- # open an old document, inkscape will add a viewbox for you.
- return value * 96
-
- raise ValueError(_("Unknown unit: %s") % units)
-
-
-@cache
-def get_doc_size(svg):
- doc_width = convert_length(svg.get('width'))
- doc_height = convert_length(svg.get('height'))
-
- return doc_width, doc_height
-
-@cache
-def get_viewbox_transform(node):
- # somewhat cribbed from inkscape-silhouette
- doc_width, doc_height = get_doc_size(node)
-
- viewbox = node.get('viewBox').strip().replace(',', ' ').split()
-
- dx = -float(viewbox[0])
- dy = -float(viewbox[1])
- transform = simpletransform.parseTransform("translate(%f, %f)" % (dx, dy))
-
- try:
- sx = doc_width / float(viewbox[2])
- sy = doc_height / float(viewbox[3])
- scale_transform = simpletransform.parseTransform("scale(%f, %f)" % (sx, sy))
- transform = simpletransform.composeTransform(transform, scale_transform)
- except ZeroDivisionError:
- pass
-
- return transform
-
-@cache
-def get_stroke_scale(node):
- doc_width, doc_height = get_doc_size(node)
- viewbox = node.get('viewBox').strip().replace(',', ' ').split()
- return doc_width / float(viewbox[2])
-
-
-class Stitch(Point):
- def __init__(self, x, y, color=None, jump=False, stop=False, trim=False, no_ties=False):
- self.x = x
- self.y = y
- self.color = color
- self.jump = jump
- self.trim = trim
- self.stop = stop
- self.no_ties = no_ties
-
- def __repr__(self):
- return "Stitch(%s, %s, %s, %s, %s, %s, %s)" % (self.x, self.y, self.color, "JUMP" if self.jump else " ", "TRIM" if self.trim else " ", "STOP" if self.stop else " ", "NO TIES" if self.no_ties else " ")
-
-
-def make_thread(color):
- thread = libembroidery.EmbThread()
- thread.color = libembroidery.embColor_make(*color.rgb)
-
- thread.description = color.name
- thread.catalogNumber = ""
-
- return thread
-
-def add_thread(pattern, thread):
- """Add a thread to a pattern and return the thread's index"""
-
- libembroidery.embPattern_addThread(pattern, thread)
-
- return libembroidery.embThreadList_count(pattern.threadList) - 1
-
-def get_flags(stitch):
- flags = 0
-
- if stitch.jump:
- flags |= libembroidery.JUMP
-
- if stitch.trim:
- flags |= libembroidery.TRIM
-
- if stitch.stop:
- flags |= libembroidery.STOP
-
- return flags
-
-
-def _string_to_floats(string):
- floats = string.split(',')
- return [float(num) for num in floats]
-
-
-def get_origin(svg):
- # The user can specify the embroidery origin by defining two guides
- # named "embroidery origin" that intersect.
-
- namedview = svg.find(inkex.addNS('namedview', 'sodipodi'))
- all_guides = namedview.findall(inkex.addNS('guide', 'sodipodi'))
- label_attribute = inkex.addNS('label', 'inkscape')
- guides = [guide for guide in all_guides
- if guide.get(label_attribute, "").startswith("embroidery origin")]
-
- # document size used below
- doc_size = list(get_doc_size(svg))
-
- # convert the size from viewbox-relative to real-world pixels
- viewbox_transform = get_viewbox_transform(svg)
- simpletransform.applyTransformToPoint(simpletransform.invertTransform(viewbox_transform), doc_size)
-
- default = [doc_size[0] / 2.0, doc_size[1] / 2.0]
- simpletransform.applyTransformToPoint(viewbox_transform, default)
- default = Point(*default)
-
- if len(guides) < 2:
- return default
-
- # Find out where the guides intersect. Only pay attention to the first two.
- guides = guides[:2]
-
- lines = []
- for guide in guides:
- # inkscape's Y axis is reversed from SVG's, and the guide is in inkscape coordinates
- position = Point(*_string_to_floats(guide.get('position')))
- position.y = doc_size[1] - position.y
-
-
- # This one baffles me. I think inkscape might have gotten the order of
- # their vector wrong?
- parts = _string_to_floats(guide.get('orientation'))
- direction = Point(parts[1], parts[0])
-
- # We have a theoretically infinite line defined by a point on the line
- # and a vector direction. Shapely can only deal in concrete line
- # segments, so we'll pick points really far in either direction on the
- # line and call it good enough.
- lines.append(shgeo.LineString((position + 100000 * direction, position - 100000 * direction)))
-
- intersection = lines[0].intersection(lines[1])
-
- if isinstance(intersection, shgeo.Point):
- origin = [intersection.x, intersection.y]
- simpletransform.applyTransformToPoint(viewbox_transform, origin)
- return Point(*origin)
- else:
- # Either the two guides are the same line, or they're parallel.
- return default
-
-
-def write_embroidery_file(file_path, stitch_plan, svg):
- origin = get_origin(svg)
-
- pattern = libembroidery.embPattern_create()
-
- for color_block in stitch_plan:
- add_thread(pattern, make_thread(color_block.color))
-
- for stitch in color_block:
- if stitch.stop and stitch is not color_block.last_stitch:
- # A STOP stitch that is not at the end of a color block
- # occurs when the user specified "STOP after". "STOP" is the
- # same thing as a color change, and the user will assign a
- # special color at the machine that tells it to pause after.
- # We need to add another copy of the same color here so that
- # the stitches after the STOP are still the same color.
- add_thread(pattern, make_thread(color_block.color))
-
- flags = get_flags(stitch)
- libembroidery.embPattern_addStitchAbs(pattern, stitch.x - origin.x, stitch.y - origin.y, flags, 1)
-
- libembroidery.embPattern_addStitchAbs(pattern, stitch.x - origin.x, stitch.y - origin.y, libembroidery.END, 1)
-
- # convert from pixels to millimeters
- libembroidery.embPattern_scale(pattern, 1/PIXELS_PER_MM)
-
- # SVG and embroidery disagree on the direction of the Y axis
- libembroidery.embPattern_flipVertical(pattern)
-
- libembroidery.embPattern_write(pattern, file_path)
diff --git a/lib/elements/auto_fill.py b/lib/elements/auto_fill.py
index 6eb1f10c..08ae67f7 100644
--- a/lib/elements/auto_fill.py
+++ b/lib/elements/auto_fill.py
@@ -1,10 +1,10 @@
import math
-from .. import _
-from .element import param, Patch
-from ..utils import cache
-from .fill import Fill
from shapely import geometry as shgeo
+from ..i18n import _
+from ..utils import cache
from ..stitches import auto_fill
+from .element import param, Patch
+from .fill import Fill
class AutoFill(Fill):
diff --git a/lib/elements/element.py b/lib/elements/element.py
index cfca3782..300136dd 100644
--- a/lib/elements/element.py
+++ b/lib/elements/element.py
@@ -1,9 +1,10 @@
import sys
from copy import deepcopy
+from shapely import geometry as shgeo
+from ..i18n import _
from ..utils import cache
-from shapely import geometry as shgeo
-from .. import _, PIXELS_PER_MM, get_viewbox_transform, get_stroke_scale, convert_length
+from ..svg import PIXELS_PER_MM, get_viewbox_transform, convert_length, get_doc_size
# inkscape-provided utilities
import simpletransform
@@ -11,6 +12,7 @@ import simplestyle
import cubicsuperpath
from cspsubdiv import cspsubdiv
+
class Patch:
"""A raw collection of stitches with attached instructions."""
@@ -146,6 +148,15 @@ class EmbroideryElement(object):
@property
@cache
+ def stroke_scale(self):
+ svg = self.node.getroottree().getroot()
+ doc_width, doc_height = get_doc_size(svg)
+ viewbox = svg.get('viewBox', '0 0 %s %s' % (doc_width, doc_height))
+ viewbox = viewbox.strip().replace(',', ' ').split()
+ return doc_width / float(viewbox[2])
+
+ @property
+ @cache
def stroke_width(self):
width = self.get_style("stroke-width")
@@ -153,8 +164,7 @@ class EmbroideryElement(object):
return 1.0
width = convert_length(width)
-
- return width * get_stroke_scale(self.node.getroottree().getroot())
+ return width * self.stroke_scale
@property
def path(self):
diff --git a/lib/elements/fill.py b/lib/elements/fill.py
index a74a897d..52a42260 100644
--- a/lib/elements/fill.py
+++ b/lib/elements/fill.py
@@ -1,10 +1,13 @@
-from .. import _, PIXELS_PER_MM
-from .element import param, EmbroideryElement, Patch
-from ..utils import cache
from shapely import geometry as shgeo
import math
+
+from .element import param, EmbroideryElement, Patch
+from ..i18n import _
+from ..svg import PIXELS_PER_MM
+from ..utils import cache
from ..stitches import running_stitch, auto_fill, legacy_fill
+
class Fill(EmbroideryElement):
element_name = _("Fill")
diff --git a/lib/elements/polyline.py b/lib/elements/polyline.py
index 6ded9fd1..5c474237 100644
--- a/lib/elements/polyline.py
+++ b/lib/elements/polyline.py
@@ -1,5 +1,6 @@
-from .. import _, Point
from .element import param, EmbroideryElement, Patch
+from ..i18n import _
+from ..utils.geometry import Point
from ..utils import cache
diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py
index d22f5145..3593db64 100644
--- a/lib/elements/satin_column.py
+++ b/lib/elements/satin_column.py
@@ -1,9 +1,9 @@
from itertools import chain, izip
+from shapely import geometry as shgeo, ops as shops
-from .. import _, Point
from .element import param, EmbroideryElement, Patch
-from ..utils import cache
-from shapely import geometry as shgeo, ops as shops
+from ..i18n import _
+from ..utils import cache, Point
class SatinColumn(EmbroideryElement):
diff --git a/lib/elements/stroke.py b/lib/elements/stroke.py
index 360e3744..48662b6d 100644
--- a/lib/elements/stroke.py
+++ b/lib/elements/stroke.py
@@ -1,7 +1,8 @@
import sys
-from .. import _, Point
+
from .element import param, EmbroideryElement, Patch
-from ..utils import cache
+from ..i18n import _
+from ..utils import cache, Point
warned_about_legacy_running_stitch = False
diff --git a/lib/extensions/base.py b/lib/extensions/base.py
index 91e050eb..ff587ca5 100644
--- a/lib/extensions/base.py
+++ b/lib/extensions/base.py
@@ -3,8 +3,9 @@ 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 .. import SVG_POLYLINE_TAG, SVG_GROUP_TAG, SVG_DEFS_TAG, INKSCAPE_GROUPMODE, EMBROIDERABLE_TAGS, PIXELS_PER_MM
from ..utils import cache
diff --git a/lib/extensions/embroider.py b/lib/extensions/embroider.py
index 564e96ca..a213be64 100644
--- a/lib/extensions/embroider.py
+++ b/lib/extensions/embroider.py
@@ -1,12 +1,13 @@
import sys
import traceback
import os
-
import inkex
-from .. import _, PIXELS_PER_MM, write_embroidery_file
+
from .base import InkstitchExtension
+from ..i18n import _
+from ..output import write_embroidery_file
from ..stitch_plan import patches_to_stitch_plan
-from ..svg import render_stitch_plan
+from ..svg import render_stitch_plan, PIXELS_PER_MM
class Embroider(InkstitchExtension):
diff --git a/lib/extensions/input.py b/lib/extensions/input.py
index bd3db0ed..f8bf5a5d 100644
--- a/lib/extensions/input.py
+++ b/lib/extensions/input.py
@@ -1,17 +1,19 @@
import os
from os.path import realpath, dirname, join as path_join
import sys
+from inkex import etree
+import inkex
# help python find libembroidery when running in a local repo clone
if getattr(sys, 'frozen', None) is None:
sys.path.append(realpath(path_join(dirname(__file__), '..', '..')))
from libembroidery import *
-from inkex import etree
-import inkex
-from .. import PIXELS_PER_MM, INKSCAPE_LABEL, _
+
+from ..svg import PIXELS_PER_MM, render_stitch_plan
+from ..svg.tags import INKSCAPE_LABEL
+from ..i18n import _
from ..stitch_plan import StitchPlan
-from ..svg import render_stitch_plan
class Input(object):
diff --git a/lib/extensions/palettes.py b/lib/extensions/palettes.py
index 269dc6dc..f7a6c7a5 100644
--- a/lib/extensions/palettes.py
+++ b/lib/extensions/palettes.py
@@ -10,6 +10,7 @@ import time
import logging
import wx
import inkex
+
from ..utils import guess_inkscape_config_path
diff --git a/lib/extensions/params.py b/lib/extensions/params.py
index 881dab49..03a6f3cc 100644
--- a/lib/extensions/params.py
+++ b/lib/extensions/params.py
@@ -13,8 +13,8 @@ from collections import defaultdict
from functools import partial
from itertools import groupby
-from .. import _
from .base import InkstitchExtension
+from ..i18n import _
from ..stitch_plan import patches_to_stitch_plan
from ..elements import EmbroideryElement, Fill, AutoFill, Stroke, SatinColumn
from ..utils import save_stderr, restore_stderr
diff --git a/lib/extensions/print_pdf.py b/lib/extensions/print_pdf.py
index 5d462c0f..6450ee7c 100644
--- a/lib/extensions/print_pdf.py
+++ b/lib/extensions/print_pdf.py
@@ -10,22 +10,21 @@ from copy import deepcopy
import wx
import appdirs
import json
-
import inkex
-from .. import _, PIXELS_PER_MM, SVG_GROUP_TAG, translation as inkstitch_translation
-from .base import InkstitchExtension
-from ..stitch_plan import patches_to_stitch_plan
-from ..svg import render_stitch_plan
-from ..threads import ThreadCatalog
-
from jinja2 import Environment, FileSystemLoader, select_autoescape
from datetime import date
import base64
-
from flask import Flask, request, Response, send_from_directory, jsonify
import webbrowser
import requests
+from .base import InkstitchExtension
+from ..i18n import _, translation as inkstitch_translation
+from ..svg import PIXELS_PER_MM, render_stitch_plan
+from ..svg.tags import SVG_GROUP_TAG
+from ..stitch_plan import patches_to_stitch_plan
+from ..threads import ThreadCatalog
+
def datetimeformat(value, format='%Y/%m/%d'):
return value.strftime(format)
diff --git a/lib/extensions/simulate.py b/lib/extensions/simulate.py
index 75bc62c7..0c372d4d 100644
--- a/lib/extensions/simulate.py
+++ b/lib/extensions/simulate.py
@@ -1,6 +1,7 @@
import wx
from .base import InkstitchExtension
+from ..i18n import _
from ..simulator import EmbroiderySimulator
from ..stitch_plan import patches_to_stitch_plan
diff --git a/lib/i18n.py b/lib/i18n.py
new file mode 100644
index 00000000..d20f5d2f
--- /dev/null
+++ b/lib/i18n.py
@@ -0,0 +1,21 @@
+import sys
+import os
+import gettext
+
+_ = translation = None
+
+def localize():
+ if getattr(sys, 'frozen', False):
+ # we are in a pyinstaller installation
+ locale_dir = sys._MEIPASS
+ else:
+ locale_dir = os.path.dirname(__file__)
+
+ locale_dir = os.path.join(locale_dir, 'locales')
+
+ global translation, _
+
+ translation = gettext.translation("inkstitch", locale_dir, fallback=True)
+ _ = translation.gettext
+
+localize()
diff --git a/lib/output.py b/lib/output.py
new file mode 100644
index 00000000..f1651357
--- /dev/null
+++ b/lib/output.py
@@ -0,0 +1,130 @@
+import libembroidery
+import inkex
+import simpletransform
+
+from .utils import Point
+from .svg import PIXELS_PER_MM, get_doc_size, get_viewbox_transform
+
+
+def make_thread(color):
+ thread = libembroidery.EmbThread()
+ thread.color = libembroidery.embColor_make(*color.rgb)
+
+ thread.description = color.name
+ thread.catalogNumber = ""
+
+ return thread
+
+def add_thread(pattern, thread):
+ """Add a thread to a pattern and return the thread's index"""
+
+ libembroidery.embPattern_addThread(pattern, thread)
+
+ return libembroidery.embThreadList_count(pattern.threadList) - 1
+
+def get_flags(stitch):
+ flags = 0
+
+ if stitch.jump:
+ flags |= libembroidery.JUMP
+
+ if stitch.trim:
+ flags |= libembroidery.TRIM
+
+ if stitch.stop:
+ flags |= libembroidery.STOP
+
+ return flags
+
+
+def _string_to_floats(string):
+ floats = string.split(',')
+ return [float(num) for num in floats]
+
+
+def get_origin(svg):
+ # The user can specify the embroidery origin by defining two guides
+ # named "embroidery origin" that intersect.
+
+ namedview = svg.find(inkex.addNS('namedview', 'sodipodi'))
+ all_guides = namedview.findall(inkex.addNS('guide', 'sodipodi'))
+ label_attribute = inkex.addNS('label', 'inkscape')
+ guides = [guide for guide in all_guides
+ if guide.get(label_attribute, "").startswith("embroidery origin")]
+
+ # document size used below
+ doc_size = list(get_doc_size(svg))
+
+ # convert the size from viewbox-relative to real-world pixels
+ viewbox_transform = get_viewbox_transform(svg)
+ simpletransform.applyTransformToPoint(simpletransform.invertTransform(viewbox_transform), doc_size)
+
+ default = [doc_size[0] / 2.0, doc_size[1] / 2.0]
+ simpletransform.applyTransformToPoint(viewbox_transform, default)
+ default = Point(*default)
+
+ if len(guides) < 2:
+ return default
+
+ # Find out where the guides intersect. Only pay attention to the first two.
+ guides = guides[:2]
+
+ lines = []
+ for guide in guides:
+ # inkscape's Y axis is reversed from SVG's, and the guide is in inkscape coordinates
+ position = Point(*_string_to_floats(guide.get('position')))
+ position.y = doc_size[1] - position.y
+
+
+ # This one baffles me. I think inkscape might have gotten the order of
+ # their vector wrong?
+ parts = _string_to_floats(guide.get('orientation'))
+ direction = Point(parts[1], parts[0])
+
+ # We have a theoretically infinite line defined by a point on the line
+ # and a vector direction. Shapely can only deal in concrete line
+ # segments, so we'll pick points really far in either direction on the
+ # line and call it good enough.
+ lines.append(shgeo.LineString((position + 100000 * direction, position - 100000 * direction)))
+
+ intersection = lines[0].intersection(lines[1])
+
+ if isinstance(intersection, shgeo.Point):
+ origin = [intersection.x, intersection.y]
+ simpletransform.applyTransformToPoint(viewbox_transform, origin)
+ return Point(*origin)
+ else:
+ # Either the two guides are the same line, or they're parallel.
+ return default
+
+
+def write_embroidery_file(file_path, stitch_plan, svg):
+ origin = get_origin(svg)
+
+ pattern = libembroidery.embPattern_create()
+
+ for color_block in stitch_plan:
+ add_thread(pattern, make_thread(color_block.color))
+
+ for stitch in color_block:
+ if stitch.stop and stitch is not color_block.last_stitch:
+ # A STOP stitch that is not at the end of a color block
+ # occurs when the user specified "STOP after". "STOP" is the
+ # same thing as a color change, and the user will assign a
+ # special color at the machine that tells it to pause after.
+ # We need to add another copy of the same color here so that
+ # the stitches after the STOP are still the same color.
+ add_thread(pattern, make_thread(color_block.color))
+
+ flags = get_flags(stitch)
+ libembroidery.embPattern_addStitchAbs(pattern, stitch.x - origin.x, stitch.y - origin.y, flags, 1)
+
+ libembroidery.embPattern_addStitchAbs(pattern, stitch.x - origin.x, stitch.y - origin.y, libembroidery.END, 1)
+
+ # convert from pixels to millimeters
+ libembroidery.embPattern_scale(pattern, 1/PIXELS_PER_MM)
+
+ # SVG and embroidery disagree on the direction of the Y axis
+ libembroidery.embPattern_flipVertical(pattern)
+
+ libembroidery.embPattern_write(pattern, file_path)
diff --git a/lib/simulator.py b/lib/simulator.py
index cc9442ea..c7e74353 100644
--- a/lib/simulator.py
+++ b/lib/simulator.py
@@ -4,8 +4,7 @@ import wx
import colorsys
from itertools import izip
-from . import PIXELS_PER_MM
-from .svg import color_block_to_point_lists
+from .svg import PIXELS_PER_MM, color_block_to_point_lists
class EmbroiderySimulator(wx.Frame):
diff --git a/lib/stitch_plan/__init__.py b/lib/stitch_plan/__init__.py
index 6c1f418a..791a5f20 100644
--- a/lib/stitch_plan/__init__.py
+++ b/lib/stitch_plan/__init__.py
@@ -1 +1,2 @@
from stitch_plan import patches_to_stitch_plan, StitchPlan, ColorBlock
+from .stitch import Stitch
diff --git a/lib/stitch_plan/stitch.py b/lib/stitch_plan/stitch.py
new file mode 100644
index 00000000..6a8579c2
--- /dev/null
+++ b/lib/stitch_plan/stitch.py
@@ -0,0 +1,15 @@
+from ..utils.geometry import Point
+
+
+class Stitch(Point):
+ def __init__(self, x, y, color=None, jump=False, stop=False, trim=False, no_ties=False):
+ self.x = x
+ self.y = y
+ self.color = color
+ self.jump = jump
+ self.trim = trim
+ self.stop = stop
+ self.no_ties = no_ties
+
+ def __repr__(self):
+ return "Stitch(%s, %s, %s, %s, %s, %s, %s)" % (self.x, self.y, self.color, "JUMP" if self.jump else " ", "TRIM" if self.trim else " ", "STOP" if self.stop else " ", "NO TIES" if self.no_ties else " ")
diff --git a/lib/stitch_plan/stitch_plan.py b/lib/stitch_plan/stitch_plan.py
index fab87876..570a7645 100644
--- a/lib/stitch_plan/stitch_plan.py
+++ b/lib/stitch_plan/stitch_plan.py
@@ -1,8 +1,9 @@
-from .. import Stitch, PIXELS_PER_MM
-from ..utils.geometry import Point
+from .stitch import Stitch
from .stop import process_stop
from .trim import process_trim
from .ties import add_ties
+from ..svg import PIXELS_PER_MM
+from ..utils.geometry import Point
from ..threads import ThreadColor
diff --git a/lib/stitch_plan/ties.py b/lib/stitch_plan/ties.py
index 1207ea51..f9c5b721 100644
--- a/lib/stitch_plan/ties.py
+++ b/lib/stitch_plan/ties.py
@@ -1,7 +1,9 @@
+from copy import deepcopy
+
+from .stitch import Stitch
from ..utils import cut_path
from ..stitches import running_stitch
-from .. import Stitch
-from copy import deepcopy
+
def add_tie(stitches, tie_path):
if stitches[-1].no_ties:
diff --git a/lib/stitches/auto_fill.py b/lib/stitches/auto_fill.py
index 7f265909..518a2812 100644
--- a/lib/stitches/auto_fill.py
+++ b/lib/stitches/auto_fill.py
@@ -1,5 +1,3 @@
-from fill import intersect_region_with_grating, row_num, stitch_row
-from .. import _, PIXELS_PER_MM, Point as InkstitchPoint
import sys
import shapely
import networkx
@@ -7,6 +5,11 @@ import math
from itertools import groupby
from collections import deque
+from .fill import intersect_region_with_grating, row_num, stitch_row
+from ..i18n import _
+from ..svg import PIXELS_PER_MM
+from ..utils.geometry import Point as InkstitchPoint
+
class MaxQueueLengthExceeded(Exception):
pass
diff --git a/lib/stitches/fill.py b/lib/stitches/fill.py
index 1b7377b0..14971cb4 100644
--- a/lib/stitches/fill.py
+++ b/lib/stitches/fill.py
@@ -1,9 +1,10 @@
-from .. import PIXELS_PER_MM
-from ..utils import cache, Point as InkstitchPoint
import shapely
import math
import sys
+from ..svg import PIXELS_PER_MM
+from ..utils import cache, Point as InkstitchPoint
+
def legacy_fill(shape, angle, row_spacing, end_row_spacing, max_stitch_length, flip, staggers):
rows_of_segments = intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing, flip)
@@ -242,4 +243,3 @@ def pull_runs(rows, shape, row_spacing):
count += 1
return runs
-
diff --git a/lib/svg/__init__.py b/lib/svg/__init__.py
new file mode 100644
index 00000000..1895bba4
--- /dev/null
+++ b/lib/svg/__init__.py
@@ -0,0 +1,2 @@
+from .svg import color_block_to_point_lists, render_stitch_plan
+from .units import *
diff --git a/lib/svg.py b/lib/svg/svg.py
index 0728309b..3bc546e7 100644
--- a/lib/svg.py
+++ b/lib/svg/svg.py
@@ -1,5 +1,10 @@
import simpletransform, simplestyle, inkex
-from . import _, get_viewbox_transform, cache, SVG_GROUP_TAG, INKSCAPE_LABEL, INKSCAPE_GROUPMODE, SVG_PATH_TAG
+
+from .units import get_viewbox_transform
+from .tags import SVG_GROUP_TAG, INKSCAPE_LABEL, INKSCAPE_GROUPMODE, SVG_PATH_TAG
+from ..i18n import _
+from ..utils import cache
+
def color_block_to_point_lists(color_block):
point_lists = [[]]
diff --git a/lib/svg/tags.py b/lib/svg/tags.py
new file mode 100644
index 00000000..fee59957
--- /dev/null
+++ b/lib/svg/tags.py
@@ -0,0 +1,12 @@
+import inkex
+
+
+SVG_PATH_TAG = inkex.addNS('path', 'svg')
+SVG_POLYLINE_TAG = inkex.addNS('polyline', 'svg')
+SVG_DEFS_TAG = inkex.addNS('defs', 'svg')
+SVG_GROUP_TAG = inkex.addNS('g', 'svg')
+
+INKSCAPE_LABEL = inkex.addNS('label', 'inkscape')
+INKSCAPE_GROUPMODE = inkex.addNS('groupmode', 'inkscape')
+
+EMBROIDERABLE_TAGS = (SVG_PATH_TAG, SVG_POLYLINE_TAG)
diff --git a/lib/svg/units.py b/lib/svg/units.py
new file mode 100644
index 00000000..015da60e
--- /dev/null
+++ b/lib/svg/units.py
@@ -0,0 +1,105 @@
+import simpletransform
+
+from ..utils import cache
+
+# modern versions of Inkscape use 96 pixels per inch as per the CSS standard
+PIXELS_PER_MM = 96 / 25.4
+
+# cribbed from inkscape-silhouette
+def parse_length_with_units( str ):
+
+ '''
+ Parse an SVG value which may or may not have units attached
+ This version is greatly simplified in that it only allows: no units,
+ units of px, mm, and %. Everything else, it returns None for.
+ There is a more general routine to consider in scour.py if more
+ generality is ever needed.
+ '''
+
+ u = 'px'
+ s = str.strip()
+ if s[-2:] == 'px':
+ s = s[:-2]
+ elif s[-2:] == 'mm':
+ u = 'mm'
+ s = s[:-2]
+ elif s[-2:] == 'pt':
+ u = 'pt'
+ s = s[:-2]
+ elif s[-2:] == 'pc':
+ u = 'pc'
+ s = s[:-2]
+ elif s[-2:] == 'cm':
+ u = 'cm'
+ s = s[:-2]
+ elif s[-2:] == 'in':
+ u = 'in'
+ s = s[:-2]
+ elif s[-1:] == '%':
+ u = '%'
+ s = s[:-1]
+ try:
+ v = float( s )
+ except:
+ raise ValueError(_("parseLengthWithUnits: unknown unit %s") % s)
+
+ return v, u
+
+
+def convert_length(length):
+ value, units = parse_length_with_units(length)
+
+ if not units or units == "px":
+ return value
+
+ if units == 'pt':
+ value /= 72
+ units = 'in'
+
+ if units == 'pc':
+ value /= 6
+ units = 'in'
+
+ if units == 'cm':
+ value *= 10
+ units = 'mm'
+
+ if units == 'mm':
+ value = value / 25.4
+ units = 'in'
+
+ if units == 'in':
+ # modern versions of Inkscape use CSS's 96 pixels per inch. When you
+ # open an old document, inkscape will add a viewbox for you.
+ return value * 96
+
+ raise ValueError(_("Unknown unit: %s") % units)
+
+
+@cache
+def get_doc_size(svg):
+ doc_width = convert_length(svg.get('width'))
+ doc_height = convert_length(svg.get('height'))
+
+ return doc_width, doc_height
+
+@cache
+def get_viewbox_transform(node):
+ # somewhat cribbed from inkscape-silhouette
+ doc_width, doc_height = get_doc_size(node)
+
+ viewbox = node.get('viewBox').strip().replace(',', ' ').split()
+
+ dx = -float(viewbox[0])
+ dy = -float(viewbox[1])
+ transform = simpletransform.parseTransform("translate(%f, %f)" % (dx, dy))
+
+ try:
+ sx = doc_width / float(viewbox[2])
+ sy = doc_height / float(viewbox[3])
+ scale_transform = simpletransform.parseTransform("scale(%f, %f)" % (sx, sy))
+ transform = simpletransform.composeTransform(transform, scale_transform)
+ except ZeroDivisionError:
+ pass
+
+ return transform
diff --git a/lib/threads/catalog.py b/lib/threads/catalog.py
index cebae4ff..d9981dc6 100644
--- a/lib/threads/catalog.py
+++ b/lib/threads/catalog.py
@@ -3,8 +3,10 @@ from os.path import dirname, realpath
import sys
from glob import glob
from collections import Sequence
+
from .palette import ThreadPalette
+
class _ThreadCatalog(Sequence):
"""Holds a set of ThreadPalettes."""
diff --git a/lib/threads/palette.py b/lib/threads/palette.py
index e1f47c7f..785fb082 100644
--- a/lib/threads/palette.py
+++ b/lib/threads/palette.py
@@ -1,9 +1,10 @@
from collections import Set
-from .color import ThreadColor
from colormath.color_objects import sRGBColor, LabColor
from colormath.color_conversions import convert_color
from colormath.color_diff import delta_e_cie1994
+from .color import ThreadColor
+
def compare_thread_colors(color1, color2):
# K_L=2 indicates textiles
diff --git a/lib/utils/io.py b/lib/utils/io.py
index e87b9881..be1fdf24 100644
--- a/lib/utils/io.py
+++ b/lib/utils/io.py
@@ -2,6 +2,7 @@ import os
import sys
from cStringIO import StringIO
+
def save_stderr():
# GTK likes to spam stderr, which inkscape will show in a dialog.
null = open(os.devnull, 'w')
diff --git a/messages.po b/messages.po
index a9fae6ff..7b2ca815 100644
--- a/messages.po
+++ b/messages.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2018-04-29 21:45-0400\n"
+"POT-Creation-Date: 2018-05-01 21:21-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,17 +17,6 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.5.3\n"
-#, python-format
-msgid "parseLengthWithUnits: unknown unit %s"
-msgstr ""
-
-#, python-format
-msgid "Unknown unit: %s"
-msgstr ""
-
-msgid "Stitch Plan"
-msgstr ""
-
msgid "Auto-Fill"
msgstr ""
@@ -333,6 +322,17 @@ msgid ""
"file to lexelby@github."
msgstr ""
+msgid "Stitch Plan"
+msgstr ""
+
+#, python-format
+msgid "parseLengthWithUnits: unknown unit %s"
+msgstr ""
+
+#, python-format
+msgid "Unknown unit: %s"
+msgstr ""
+
msgid "Color"
msgstr ""