summaryrefslogtreecommitdiff
path: root/lib/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/__init__.py')
-rw-r--r--lib/__init__.py298
1 files changed, 298 insertions, 0 deletions
diff --git a/lib/__init__.py b/lib/__init__.py
new file mode 100644
index 00000000..2c0ee620
--- /dev/null
+++ b/lib/__init__.py
@@ -0,0 +1,298 @@
+#!/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)