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