kopia lustrzana https://github.com/inkstitch/inkstitch
major revamp
* properly process transform parameters (ungrouping no longer necessary!) * handle satin on beziers properly * previously beziers were stroked as straight line segments that included control points * allow overriding parameters on individual paths by adding extra svg params * embroider_angle, embroider_stitch_length, embroider_zigzag_spacing, embroider_row_spacing, etc * set using "Edit XML" * default to 10 pixels per millimeter * properly write CSV files in millimeters (was dividing by 10) * always translate pattern to origin to fit in hoop * add "running stitch length" for < 0.5 stroke width) * don't traceback if no paths were selected * add "repeats" option for stroke (satin/running stitch) to go back and forth over the line * good for a double line of center-line underlay below satinpull/1/head
rodzic
142f0a5681
commit
3e3d540089
15
PyEmb.py
15
PyEmb.py
|
@ -63,9 +63,14 @@ class Embroidery:
|
||||||
maxy = max(maxy,p.y)
|
maxy = max(maxy,p.y)
|
||||||
sx = maxx-minx
|
sx = maxx-minx
|
||||||
sy = maxy-miny
|
sy = maxy-miny
|
||||||
|
|
||||||
|
self.translate(-minx, -miny)
|
||||||
|
return (minx, miny)
|
||||||
|
|
||||||
|
def translate(self, dx, dy):
|
||||||
for p in self.coords:
|
for p in self.coords:
|
||||||
p.x -= minx
|
p.x += dx
|
||||||
p.y -= miny
|
p.y += dy
|
||||||
|
|
||||||
def scale(self, sc):
|
def scale(self, sc):
|
||||||
if not isinstance(sc, (tuple, list)):
|
if not isinstance(sc, (tuple, list)):
|
||||||
|
@ -160,11 +165,11 @@ class Embroidery:
|
||||||
int(stitch.color[3:5], 16),
|
int(stitch.color[3:5], 16),
|
||||||
int(stitch.color[5:7], 16))
|
int(stitch.color[5:7], 16))
|
||||||
if stitch.jumpStitch:
|
if stitch.jumpStitch:
|
||||||
self.str += '"*","JUMP","%f","%f"\n' % (stitch.x/10, stitch.y/10)
|
self.str += '"*","JUMP","%f","%f"\n' % (stitch.x, stitch.y)
|
||||||
if lastColor != None and stitch.color != lastColor:
|
if lastColor != None and stitch.color != lastColor:
|
||||||
# not first color choice, add color change record
|
# not first color choice, add color change record
|
||||||
self.str += '"*","COLOR","%f","%f"\n' % (stitch.x/10, stitch.y/10)
|
self.str += '"*","COLOR","%f","%f"\n' % (stitch.x, stitch.y)
|
||||||
self.str += '"*","STITCH","%f","%f"\n' % (stitch.x/10, stitch.y/10)
|
self.str += '"*","STITCH","%f","%f"\n' % (stitch.x, stitch.y)
|
||||||
lastColor = stitch.color
|
lastColor = stitch.color
|
||||||
return self.str
|
return self.str
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
<param name="zigzag_spacing_mm" type="float" min="0.01" max="5.00" precision="2" _gui-text="Zigzag spacing (mm)">1.00</param>
|
<param name="zigzag_spacing_mm" type="float" min="0.01" max="5.00" precision="2" _gui-text="Zigzag spacing (mm)">1.00</param>
|
||||||
<param name="row_spacing_mm" type="float" min="0.01" max="5.00" precision="2" _gui-text="Row spacing (mm)">0.40</param>
|
<param name="row_spacing_mm" type="float" min="0.01" max="5.00" precision="2" _gui-text="Row spacing (mm)">0.40</param>
|
||||||
<param name="max_stitch_len_mm" type="float" min="0.1" max="100.0" _gui-text="Maximum stitch length (mm)">3.0</param>
|
<param name="max_stitch_len_mm" type="float" min="0.1" max="100.0" _gui-text="Maximum stitch length (mm)">3.0</param>
|
||||||
|
<param name="running_stitch_len_mm" type="float" min="0.1" max="100.0" _gui-text="Running stitch length (mm)">3.0</param>
|
||||||
<param name="collapse_len_mm" type="float" min="0.0" max="10.0" _gui-text="Maximum collapse length (mm)">0.0</param>
|
<param name="collapse_len_mm" type="float" min="0.0" max="10.0" _gui-text="Maximum collapse length (mm)">0.0</param>
|
||||||
<param name="preserve_order" type="boolean" _gui-text="Preserve stacking order" description="if false, sorts by color, which saves thread changes. True preserves stacking order, important if you're laying colors over each other.">false</param>
|
<param name="preserve_order" type="boolean" _gui-text="Preserve stacking order" description="if false, sorts by color, which saves thread changes. True preserves stacking order, important if you're laying colors over each other.">false</param>
|
||||||
<param name="hatch_filled_paths" type="boolean" _gui-text="Hatch filled paths" description="If false, filled paths are filled using equally-spaced lines. If true, filled paths are filled using hatching lines.">false</param>
|
<param name="hatch_filled_paths" type="boolean" _gui-text="Hatch filled paths" description="If false, filled paths are filled using equally-spaced lines. If true, filled paths are filled using hatching lines.">false</param>
|
||||||
|
|
245
embroider.py
245
embroider.py
|
@ -27,7 +27,8 @@ import time
|
||||||
import inkex
|
import inkex
|
||||||
import simplepath
|
import simplepath
|
||||||
import simplestyle
|
import simplestyle
|
||||||
import cspsubdiv
|
import simpletransform
|
||||||
|
from cspsubdiv import cspsubdiv
|
||||||
import cubicsuperpath
|
import cubicsuperpath
|
||||||
import PyEmb
|
import PyEmb
|
||||||
import math
|
import math
|
||||||
|
@ -37,13 +38,83 @@ import lxml.etree as etree
|
||||||
from lxml.builder import E
|
from lxml.builder import E
|
||||||
import shapely.geometry as shgeo
|
import shapely.geometry as shgeo
|
||||||
import shapely.affinity as affinity
|
import shapely.affinity as affinity
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
dbg = open("/tmp/embroider-debug.txt", "w")
|
dbg = open("/tmp/embroider-debug.txt", "w")
|
||||||
PyEmb.dbg = dbg
|
PyEmb.dbg = dbg
|
||||||
#pixels_per_millimeter = 90.0 / 25.4
|
#pixels_per_millimeter = 90.0 / 25.4
|
||||||
|
|
||||||
#this actually makes each pixel worth one tenth of a millimeter
|
#this actually makes each pixel worth one tenth of a millimeter
|
||||||
pixels_per_millimeter = 1
|
pixels_per_millimeter = 10
|
||||||
|
|
||||||
|
# a 0.5pt stroke becomes a straight line.
|
||||||
|
STROKE_MIN = 0.5
|
||||||
|
|
||||||
|
def parse_boolean(s):
|
||||||
|
if isinstance(s, bool):
|
||||||
|
return s
|
||||||
|
else:
|
||||||
|
return s and s.lower in ('yes', 'y', 'true', 't', '1')
|
||||||
|
|
||||||
|
def get_param(node, param, default):
|
||||||
|
value = node.get("embroider_" + param)
|
||||||
|
|
||||||
|
if value is None or not value.strip():
|
||||||
|
return default
|
||||||
|
|
||||||
|
return value.strip()
|
||||||
|
|
||||||
|
def get_boolean_param(node, param, default=False):
|
||||||
|
value = get_param(node, param, default)
|
||||||
|
|
||||||
|
return parse_boolean(value)
|
||||||
|
|
||||||
|
def get_float_param(node, param, default=None):
|
||||||
|
value = get_param(node, param, default)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return float(value)
|
||||||
|
except ValueError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def get_int_param(node, param, default=None):
|
||||||
|
value = get_param(node, param, default)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return int(value)
|
||||||
|
except ValueError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def parse_path(node):
|
||||||
|
path = cubicsuperpath.parsePath(node.get("d"))
|
||||||
|
|
||||||
|
# print >> sys.stderr, pformat(path)
|
||||||
|
|
||||||
|
# start with the identity transform
|
||||||
|
transform = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]
|
||||||
|
|
||||||
|
# combine this node's transform with all parent groups' transforms
|
||||||
|
transform = simpletransform.composeParents(node, transform)
|
||||||
|
|
||||||
|
# apply the combined transform to this node's path
|
||||||
|
simpletransform.applyTransformToPath(transform, path)
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
def flatten(path, flatness):
|
||||||
|
"""approximate a path containing beziers with a series of points"""
|
||||||
|
|
||||||
|
cspsubdiv(path, flatness)
|
||||||
|
|
||||||
|
flattened = []
|
||||||
|
|
||||||
|
for comp in path:
|
||||||
|
vertices = []
|
||||||
|
for ctl in comp:
|
||||||
|
vertices.append((ctl[1][0], ctl[1][1]))
|
||||||
|
flattened.append(vertices)
|
||||||
|
|
||||||
|
return flattened
|
||||||
|
|
||||||
def bboxarea(poly):
|
def bboxarea(poly):
|
||||||
x0=None
|
x0=None
|
||||||
|
@ -68,8 +139,7 @@ def cspToShapelyPolygon(path):
|
||||||
for sub_path in path:
|
for sub_path in path:
|
||||||
point_ary = []
|
point_ary = []
|
||||||
last_pt = None
|
last_pt = None
|
||||||
for csp in sub_path:
|
for pt in sub_path:
|
||||||
pt = (csp[1][0],csp[1][1])
|
|
||||||
if (last_pt!=None):
|
if (last_pt!=None):
|
||||||
vp = (pt[0]-last_pt[0],pt[1]-last_pt[1])
|
vp = (pt[0]-last_pt[0],pt[1]-last_pt[1])
|
||||||
dp = math.sqrt(math.pow(vp[0],2.0)+math.pow(vp[1],2.0))
|
dp = math.sqrt(math.pow(vp[0],2.0)+math.pow(vp[1],2.0))
|
||||||
|
@ -132,6 +202,9 @@ class PatchList:
|
||||||
def __init__(self, patches):
|
def __init__(self, patches):
|
||||||
self.patches = patches
|
self.patches = patches
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.patches)
|
||||||
|
|
||||||
def sort_by_sortorder(self):
|
def sort_by_sortorder(self):
|
||||||
def by_sort_order(a,b):
|
def by_sort_order(a,b):
|
||||||
return cmp(a.sortorder, b.sortorder)
|
return cmp(a.sortorder, b.sortorder)
|
||||||
|
@ -414,7 +487,7 @@ class EmbroideryObject:
|
||||||
lastStitch = newStitch
|
lastStitch = newStitch
|
||||||
lastColor = patch.color
|
lastColor = patch.color
|
||||||
|
|
||||||
emb.translate_to_origin()
|
dx, dy = emb.translate_to_origin()
|
||||||
emb.scale(1.0/pixels_per_millimeter)
|
emb.scale(1.0/pixels_per_millimeter)
|
||||||
|
|
||||||
fp = open(filename, "wb")
|
fp = open(filename, "wb")
|
||||||
|
@ -427,6 +500,7 @@ class EmbroideryObject:
|
||||||
fp.write(emb.export_gcode(dbg))
|
fp.write(emb.export_gcode(dbg))
|
||||||
fp.close()
|
fp.close()
|
||||||
emb.scale(pixels_per_millimeter)
|
emb.scale(pixels_per_millimeter)
|
||||||
|
emb.translate(dx, dy)
|
||||||
return emb
|
return emb
|
||||||
|
|
||||||
def emit_inkscape(self, parent, emb):
|
def emit_inkscape(self, parent, emb):
|
||||||
|
@ -484,6 +558,10 @@ class Embroider(inkex.Effect):
|
||||||
action="store", type="float",
|
action="store", type="float",
|
||||||
dest="max_stitch_len_mm", default=3.0,
|
dest="max_stitch_len_mm", default=3.0,
|
||||||
help="max stitch length (mm)")
|
help="max stitch length (mm)")
|
||||||
|
self.OptionParser.add_option("--running_stitch_len_mm",
|
||||||
|
action="store", type="float",
|
||||||
|
dest="running_stitch_len_mm", default=3.0,
|
||||||
|
help="running stitch length (mm)")
|
||||||
self.OptionParser.add_option("-c", "--collapse_len_mm",
|
self.OptionParser.add_option("-c", "--collapse_len_mm",
|
||||||
action="store", type="float",
|
action="store", type="float",
|
||||||
dest="collapse_len_mm", default=0.0,
|
dest="collapse_len_mm", default=0.0,
|
||||||
|
@ -519,23 +597,27 @@ class Embroider(inkex.Effect):
|
||||||
self.patches = []
|
self.patches = []
|
||||||
self.stacking_order = {}
|
self.stacking_order = {}
|
||||||
|
|
||||||
def get_sort_order(self, threadcolor, id):
|
def get_sort_order(self, threadcolor, node):
|
||||||
return SortOrder(threadcolor, self.stacking_order.get(id), self.options.preserve_order=="true")
|
return SortOrder(threadcolor, self.stacking_order.get(node.get("id")), self.options.preserve_order=="true")
|
||||||
|
|
||||||
def process_one_path(self, shpath, threadcolor, sortorder, angle):
|
def process_one_path(self, node, shpath, threadcolor, sortorder, angle):
|
||||||
#self.add_shapely_geo_to_svg(shpath.boundary, color="#c0c000")
|
#self.add_shapely_geo_to_svg(shpath.boundary, color="#c0c000")
|
||||||
|
|
||||||
rows_of_segments = self.intersect_region_with_grating(shpath, angle)
|
hatching = get_boolean_param(node, "hatching", self.hatching)
|
||||||
|
row_spacing_px = get_float_param(node, "row_spacing", self.row_spacing_px)
|
||||||
|
max_stitch_len_px = get_float_param(node, "max_stitch_length", self.max_stitch_len_px)
|
||||||
|
|
||||||
|
rows_of_segments = self.intersect_region_with_grating(shpath, row_spacing_px, angle)
|
||||||
segments = self.visit_segments_one_by_one(rows_of_segments)
|
segments = self.visit_segments_one_by_one(rows_of_segments)
|
||||||
|
|
||||||
def small_stitches(patch, beg, end):
|
def small_stitches(patch, beg, end):
|
||||||
vector = (end-beg)
|
vector = (end-beg)
|
||||||
patch.addStitch(beg)
|
patch.addStitch(beg)
|
||||||
old_dist = vector.length()
|
old_dist = vector.length()
|
||||||
if (old_dist < self.max_stitch_len_px):
|
if (old_dist < max_stitch_len_px):
|
||||||
patch.addStitch(end)
|
patch.addStitch(end)
|
||||||
return
|
return
|
||||||
one_stitch = vector.mul(1.0 / old_dist * self.max_stitch_len_px * random.random())
|
one_stitch = vector.mul(1.0 / old_dist * max_stitch_len_px * random.random())
|
||||||
beg = beg + one_stitch
|
beg = beg + one_stitch
|
||||||
while (True):
|
while (True):
|
||||||
vector = (end-beg)
|
vector = (end-beg)
|
||||||
|
@ -543,11 +625,11 @@ class Embroider(inkex.Effect):
|
||||||
assert(old_dist==None or dist<old_dist)
|
assert(old_dist==None or dist<old_dist)
|
||||||
old_dist = dist
|
old_dist = dist
|
||||||
patch.addStitch(beg)
|
patch.addStitch(beg)
|
||||||
if (dist < self.max_stitch_len_px):
|
if (dist < max_stitch_len_px):
|
||||||
patch.addStitch(end)
|
patch.addStitch(end)
|
||||||
return
|
return
|
||||||
|
|
||||||
one_stitch = vector.mul(1.0/dist*self.max_stitch_len_px)
|
one_stitch = vector.mul(1.0/dist*max_stitch_len_px)
|
||||||
beg = beg + one_stitch
|
beg = beg + one_stitch
|
||||||
|
|
||||||
swap = False
|
swap = False
|
||||||
|
@ -555,22 +637,22 @@ class Embroider(inkex.Effect):
|
||||||
for (beg,end) in segments:
|
for (beg,end) in segments:
|
||||||
if (swap):
|
if (swap):
|
||||||
(beg,end)=(end,beg)
|
(beg,end)=(end,beg)
|
||||||
if not self.hatching:
|
if not hatching:
|
||||||
swap = not swap
|
swap = not swap
|
||||||
small_stitches(patch, PyEmb.Point(*beg),PyEmb.Point(*end))
|
small_stitches(patch, PyEmb.Point(*beg),PyEmb.Point(*end))
|
||||||
return [patch]
|
return [patch]
|
||||||
|
|
||||||
def intersect_region_with_grating(self, shpath, angle):
|
def intersect_region_with_grating(self, shpath, row_spacing_px, angle):
|
||||||
#dbg.write("bounds = %s\n" % str(shpath.bounds))
|
#dbg.write("bounds = %s\n" % str(shpath.bounds))
|
||||||
rotated_shpath = affinity.rotate(shpath, angle, use_radians = True)
|
rotated_shpath = affinity.rotate(shpath, angle, use_radians = True)
|
||||||
bbox = rotated_shpath.bounds
|
bbox = rotated_shpath.bounds
|
||||||
delta = self.row_spacing_px * 50 # *2 should be enough but isn't. TODO: find out why, and if this always works.
|
delta = row_spacing_px * 50 # *2 should be enough but isn't. TODO: find out why, and if this always works.
|
||||||
bbox = affinity.rotate(shgeo.LinearRing(((bbox[0] - delta, bbox[1] - delta), (bbox[2] + delta, bbox[1] - delta), (bbox[2] + delta, bbox[3] + delta), (bbox[0] - delta, bbox[3] + delta))), -angle, use_radians = True).coords
|
bbox = affinity.rotate(shgeo.LinearRing(((bbox[0] - delta, bbox[1] - delta), (bbox[2] + delta, bbox[1] - delta), (bbox[2] + delta, bbox[3] + delta), (bbox[0] - delta, bbox[3] + delta))), -angle, use_radians = True).coords
|
||||||
|
|
||||||
p0 = PyEmb.Point(bbox[0][0], bbox[0][1])
|
p0 = PyEmb.Point(bbox[0][0], bbox[0][1])
|
||||||
p1 = PyEmb.Point(bbox[1][0], bbox[1][1])
|
p1 = PyEmb.Point(bbox[1][0], bbox[1][1])
|
||||||
p2 = PyEmb.Point(bbox[3][0], bbox[3][1])
|
p2 = PyEmb.Point(bbox[3][0], bbox[3][1])
|
||||||
count = (p2 - p0).length() / self.row_spacing_px
|
count = (p2 - p0).length() / row_spacing_px
|
||||||
p_inc = (p2 - p0).mul(1 / count)
|
p_inc = (p2 - p0).mul(1 / count)
|
||||||
count += 2
|
count += 2
|
||||||
|
|
||||||
|
@ -622,9 +704,8 @@ class Embroider(inkex.Effect):
|
||||||
if (count>100): raise "kablooey"
|
if (count>100): raise "kablooey"
|
||||||
return linearized_runs
|
return linearized_runs
|
||||||
|
|
||||||
def handle_node(self, node, id):
|
def handle_node(self, node):
|
||||||
|
if (node.tag == inkex.addNS('g', 'svg')):
|
||||||
if (node.tag != self.svgpath):
|
|
||||||
#dbg.write("%s\n"%str((id, etree.tostring(node, pretty_print=True))))
|
#dbg.write("%s\n"%str((id, etree.tostring(node, pretty_print=True))))
|
||||||
#dbg.write("not a path; recursing:\n")
|
#dbg.write("not a path; recursing:\n")
|
||||||
for child in node.iter(self.svgpath):
|
for child in node.iter(self.svgpath):
|
||||||
|
@ -633,26 +714,14 @@ class Embroider(inkex.Effect):
|
||||||
|
|
||||||
#dbg.write("Node: %s\n"%str((id, etree.tostring(node, pretty_print=True))))
|
#dbg.write("Node: %s\n"%str((id, etree.tostring(node, pretty_print=True))))
|
||||||
|
|
||||||
israw = False
|
israw = parse_boolean(node.get('embroider_raw'))
|
||||||
desc = node.findtext(inkex.addNS('desc', 'svg'))
|
|
||||||
if desc is None:
|
|
||||||
desc = ''
|
|
||||||
descparts = {}
|
|
||||||
for part in desc.split(';'):
|
|
||||||
if '=' in part:
|
|
||||||
k, v = part.split('=', 1)
|
|
||||||
else:
|
|
||||||
k, v = part, ''
|
|
||||||
descparts[k] = v
|
|
||||||
israw = 'embroider_raw' in descparts
|
|
||||||
if (israw):
|
if (israw):
|
||||||
self.patchList.patches.extend(self.path_to_patch_list(node))
|
self.patchList.patches.extend(self.path_to_patch_list(node))
|
||||||
else:
|
else:
|
||||||
if (self.get_style(node, "fill")!=None):
|
if (self.get_style(node, "fill")!=None):
|
||||||
angle = math.radians(float(descparts.get('embroider_angle', 0)))
|
self.patchList.patches.extend(self.filled_region_to_patchlist(node))
|
||||||
self.patchList.patches.extend(self.filled_region_to_patchlist(node, id, angle))
|
|
||||||
if (self.get_style(node, "stroke")!=None):
|
if (self.get_style(node, "stroke")!=None):
|
||||||
self.patchList.patches.extend(self.path_to_patch_list(node, id))
|
self.patchList.patches.extend(self.path_to_patch_list(node))
|
||||||
|
|
||||||
def get_style(self, node, style_name):
|
def get_style(self, node, style_name):
|
||||||
style = simplestyle.parseStyle(node.get("style"))
|
style = simplestyle.parseStyle(node.get("style"))
|
||||||
|
@ -676,13 +745,18 @@ class Embroider(inkex.Effect):
|
||||||
self.row_spacing_px = self.options.row_spacing_mm * pixels_per_millimeter
|
self.row_spacing_px = self.options.row_spacing_mm * pixels_per_millimeter
|
||||||
self.zigzag_spacing_px = self.options.zigzag_spacing_mm * pixels_per_millimeter
|
self.zigzag_spacing_px = self.options.zigzag_spacing_mm * pixels_per_millimeter
|
||||||
self.max_stitch_len_px = self.options.max_stitch_len_mm*pixels_per_millimeter
|
self.max_stitch_len_px = self.options.max_stitch_len_mm*pixels_per_millimeter
|
||||||
|
self.running_stitch_len_px = self.options.running_stitch_len_mm*pixels_per_millimeter
|
||||||
self.collapse_len_px = self.options.collapse_len_mm*pixels_per_millimeter
|
self.collapse_len_px = self.options.collapse_len_mm*pixels_per_millimeter
|
||||||
self.hatching = self.options.hatch_filled_paths == "true"
|
self.hatching = self.options.hatch_filled_paths == "true"
|
||||||
|
|
||||||
self.svgpath = inkex.addNS('path', 'svg')
|
self.svgpath = inkex.addNS('path', 'svg')
|
||||||
self.patchList = PatchList([])
|
self.patchList = PatchList([])
|
||||||
for id, node in self.selected.iteritems():
|
for node in self.selected.itervalues():
|
||||||
self.handle_node(node, id)
|
self.handle_node(node)
|
||||||
|
|
||||||
|
if not self.patchList:
|
||||||
|
inkex.errormsg("No paths selected.")
|
||||||
|
return
|
||||||
|
|
||||||
self.patchList = self.patchList.tsp_by_color()
|
self.patchList = self.patchList.tsp_by_color()
|
||||||
#dbg.write("patch count: %d\n" % len(self.patchList.patches))
|
#dbg.write("patch count: %d\n" % len(self.patchList.patches))
|
||||||
|
@ -714,7 +788,7 @@ class Embroider(inkex.Effect):
|
||||||
'd':simplepath.formatPath(new_path),
|
'd':simplepath.formatPath(new_path),
|
||||||
})
|
})
|
||||||
|
|
||||||
def path_to_patch_list(self, node, id):
|
def path_to_patch_list(self, node):
|
||||||
threadcolor = simplestyle.parseStyle(node.get("style"))["stroke"]
|
threadcolor = simplestyle.parseStyle(node.get("style"))["stroke"]
|
||||||
stroke_width_str = simplestyle.parseStyle(node.get("style"))["stroke-width"]
|
stroke_width_str = simplestyle.parseStyle(node.get("style"))["stroke-width"]
|
||||||
if (stroke_width_str.endswith("px")):
|
if (stroke_width_str.endswith("px")):
|
||||||
|
@ -724,85 +798,76 @@ class Embroider(inkex.Effect):
|
||||||
stroke_width = float(stroke_width_str)
|
stroke_width = float(stroke_width_str)
|
||||||
#dbg.write("stroke_width is <%s>\n" % repr(stroke_width))
|
#dbg.write("stroke_width is <%s>\n" % repr(stroke_width))
|
||||||
#dbg.flush()
|
#dbg.flush()
|
||||||
sortorder = self.get_sort_order(threadcolor, id)
|
|
||||||
path = simplepath.parsePath(node.get("d"))
|
running_stitch_len_px = get_float_param(node, "stitch_length", self.running_stitch_len_px)
|
||||||
|
zigzag_spacing_px = get_float_param(node, "zigzag_spacing", self.zigzag_spacing_px)
|
||||||
|
repeats = get_int_param(node, "repeats", 1)
|
||||||
|
|
||||||
|
sortorder = self.get_sort_order(threadcolor, node)
|
||||||
|
paths = flatten(parse_path(node), self.options.flat)
|
||||||
|
|
||||||
# regularize the points lists.
|
# regularize the points lists.
|
||||||
# (If we're parsing beziers, there will be a list of multi-point
|
# (If we're parsing beziers, there will be a list of multi-point
|
||||||
# subarrays.)
|
# subarrays.)
|
||||||
|
|
||||||
patches = []
|
patches = []
|
||||||
emb_point_list = []
|
|
||||||
|
for path in paths:
|
||||||
def flush_point_list():
|
path = [PyEmb.Point(x, y) for x, y in path]
|
||||||
STROKE_MIN = 0.5 # a 0.5pt stroke becomes a straight line.
|
|
||||||
if (stroke_width <= STROKE_MIN):
|
if (stroke_width <= STROKE_MIN):
|
||||||
#dbg.write("self.max_stitch_len_px = %s\n" % self.max_stitch_len_px)
|
#dbg.write("self.max_stitch_len_px = %s\n" % self.max_stitch_len_px)
|
||||||
patch = self.stroke_points(emb_point_list, self.max_stitch_len_px, 0.0, threadcolor, sortorder)
|
patch = self.stroke_points(path, running_stitch_len_px, 0.0, repeats, threadcolor, sortorder)
|
||||||
else:
|
else:
|
||||||
patch = self.stroke_points(emb_point_list, self.zigzag_spacing_px*0.5, stroke_width, threadcolor, sortorder)
|
patch = self.stroke_points(path, zigzag_spacing_px*0.5, stroke_width, repeats, threadcolor, sortorder)
|
||||||
patches.extend(patch)
|
patches.extend(patch)
|
||||||
|
|
||||||
close_point = None
|
|
||||||
for (type,points) in path:
|
|
||||||
#dbg.write("path_to_patch_list parses pt %s with type=%s\n" % (points, type))
|
|
||||||
if type == 'M' and len(emb_point_list):
|
|
||||||
flush_point_list()
|
|
||||||
emb_point_list = []
|
|
||||||
|
|
||||||
if type == 'Z':
|
|
||||||
#dbg.write("... closing patch to %s\n" % close_point)
|
|
||||||
emb_point_list.append(close_point)
|
|
||||||
else:
|
|
||||||
pointscopy = list(points)
|
|
||||||
while (len(pointscopy)>0):
|
|
||||||
emb_point_list.append(PyEmb.Point(pointscopy[0], pointscopy[1]))
|
|
||||||
pointscopy = pointscopy[2:]
|
|
||||||
if type == 'M':
|
|
||||||
#dbg.write("latching close_point %s\n" % emb_point_list[-1])
|
|
||||||
close_point = emb_point_list[-1]
|
|
||||||
|
|
||||||
flush_point_list()
|
|
||||||
return patches
|
return patches
|
||||||
|
|
||||||
def stroke_points(self, emb_point_list, zigzag_spacing_px, stroke_width, threadcolor, sortorder):
|
def stroke_points(self, emb_point_list, zigzag_spacing_px, stroke_width, repeats, threadcolor, sortorder):
|
||||||
patch = Patch(color=threadcolor, sortorder=sortorder)
|
patch = Patch(color=threadcolor, sortorder=sortorder)
|
||||||
p0 = emb_point_list[0]
|
p0 = emb_point_list[0]
|
||||||
rho = 0.0
|
rho = 0.0
|
||||||
fact = 1
|
fact = 1
|
||||||
|
|
||||||
for segi in range(1, len(emb_point_list)):
|
for repeat in xrange(repeats):
|
||||||
p1 = emb_point_list[segi]
|
if repeat % 2 == 0:
|
||||||
|
order = range(1, len(emb_point_list))
|
||||||
|
else:
|
||||||
|
order = range(-2, -len(emb_point_list) - 1, -1)
|
||||||
|
|
||||||
# how far we have to go along segment
|
for segi in order:
|
||||||
seg_len = (p1 - p0).length()
|
p1 = emb_point_list[segi]
|
||||||
if (seg_len == 0):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# vector pointing along segment
|
# how far we have to go along segment
|
||||||
along = (p1 - p0).unit()
|
seg_len = (p1 - p0).length()
|
||||||
# vector pointing to edge of stroke width
|
if (seg_len == 0):
|
||||||
perp = along.rotate_left().mul(stroke_width*0.5)
|
continue
|
||||||
|
|
||||||
# iteration variable: how far we are along segment
|
# vector pointing along segment
|
||||||
while (rho <= seg_len):
|
along = (p1 - p0).unit()
|
||||||
left_pt = p0+along.mul(rho)+perp.mul(fact)
|
# vector pointing to edge of stroke width
|
||||||
patch.addStitch(left_pt)
|
perp = along.rotate_left().mul(stroke_width*0.5)
|
||||||
rho += zigzag_spacing_px
|
|
||||||
fact = -fact
|
|
||||||
|
|
||||||
p0 = p1
|
# iteration variable: how far we are along segment
|
||||||
rho -= seg_len
|
while (rho <= seg_len):
|
||||||
|
left_pt = p0+along.mul(rho)+perp.mul(fact)
|
||||||
|
patch.addStitch(left_pt)
|
||||||
|
rho += zigzag_spacing_px
|
||||||
|
fact = -fact
|
||||||
|
|
||||||
|
p0 = p1
|
||||||
|
rho -= seg_len
|
||||||
|
|
||||||
return [patch]
|
return [patch]
|
||||||
|
|
||||||
def filled_region_to_patchlist(self, node, id, angle):
|
def filled_region_to_patchlist(self, node):
|
||||||
p = cubicsuperpath.parsePath(node.get("d"))
|
angle = math.radians(float(get_float_param(node,'angle',0)))
|
||||||
cspsubdiv.cspsubdiv(p, self.options.flat)
|
paths = flatten(parse_path(node), self.options.flat)
|
||||||
shapelyPolygon = cspToShapelyPolygon(p)
|
shapelyPolygon = cspToShapelyPolygon(paths)
|
||||||
threadcolor = simplestyle.parseStyle(node.get("style"))["fill"]
|
threadcolor = simplestyle.parseStyle(node.get("style"))["fill"]
|
||||||
sortorder = self.get_sort_order(threadcolor, id)
|
sortorder = self.get_sort_order(threadcolor, node)
|
||||||
return self.process_one_path(
|
return self.process_one_path(
|
||||||
|
node,
|
||||||
shapelyPolygon,
|
shapelyPolygon,
|
||||||
threadcolor,
|
threadcolor,
|
||||||
sortorder,
|
sortorder,
|
||||||
|
|
Ładowanie…
Reference in New Issue