kopia lustrzana https://github.com/inkstitch/inkstitch
				
				
				
			refactor everything out of lib/__init__.py
							rodzic
							
								
									1b31806423
								
							
						
					
					
						commit
						05daffb7e0
					
				
							
								
								
									
										298
									
								
								lib/__init__.py
								
								
								
								
							
							
						
						
									
										298
									
								
								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)
 | 
			
		||||
| 
						 | 
				
			
			@ -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):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,10 @@
 | 
			
		|||
import sys
 | 
			
		||||
from copy import deepcopy
 | 
			
		||||
 | 
			
		||||
from ..utils import cache
 | 
			
		||||
from shapely import geometry as shgeo
 | 
			
		||||
from .. import _, PIXELS_PER_MM, get_viewbox_transform, get_stroke_scale, convert_length
 | 
			
		||||
 | 
			
		||||
from ..i18n import _
 | 
			
		||||
from ..utils import cache
 | 
			
		||||
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."""
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -144,6 +146,15 @@ class EmbroideryElement(object):
 | 
			
		|||
        style = simplestyle.parseStyle(self.node.get("style"))
 | 
			
		||||
        return style_name in style
 | 
			
		||||
 | 
			
		||||
    @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):
 | 
			
		||||
| 
						 | 
				
			
			@ -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):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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")
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,10 @@
 | 
			
		|||
from itertools import chain, izip
 | 
			
		||||
 | 
			
		||||
from .. import _, Point
 | 
			
		||||
from .element import param, EmbroideryElement, Patch
 | 
			
		||||
from ..utils import cache
 | 
			
		||||
from shapely import geometry as shgeo, ops as shops
 | 
			
		||||
 | 
			
		||||
from .element import param, EmbroideryElement, Patch
 | 
			
		||||
from ..i18n import _
 | 
			
		||||
from ..utils import cache, Point
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SatinColumn(EmbroideryElement):
 | 
			
		||||
    element_name = _("Satin Column")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,6 +10,7 @@ import time
 | 
			
		|||
import logging
 | 
			
		||||
import wx
 | 
			
		||||
import inkex
 | 
			
		||||
 | 
			
		||||
from ..utils import guess_inkscape_config_path
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			@ -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):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1 +1,2 @@
 | 
			
		|||
from stitch_plan import patches_to_stitch_plan, StitchPlan, ColorBlock
 | 
			
		||||
from .stitch import Stitch
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 " ")
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
from .svg import color_block_to_point_lists, render_stitch_plan
 | 
			
		||||
from .units import *
 | 
			
		||||
| 
						 | 
				
			
			@ -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 = [[]]
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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."""
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										24
									
								
								messages.po
								
								
								
								
							
							
						
						
									
										24
									
								
								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 ""
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Ładowanie…
	
		Reference in New Issue