kopia lustrzana https://github.com/inkstitch/inkstitch
parse and use viewBox attribute
The viewBox effectively adds global scaling and translation to all shapes in the SVG. Borrowing from inkscape-silhouette, we construct a transform from the viewBox and apply it to all objects. When adding the stitch plan into the SVG, we need to compensate for this implied transformation, which we do by adding its inverse as a transform on the stitch plan polylines. All of this allows us to do away with the nonstandard 10 pixels per mm that was previously hardcoded into inkstitch. Old designs can add a viewBox to switch from 10 pixels per mm to the standard 96 ppi that Inkscape uses.pull/11/head
rodzic
d4c84a0fdb
commit
7190d98dd4
113
embroider.py
113
embroider.py
|
@ -47,6 +47,8 @@ SVG_GROUP_TAG = inkex.addNS('g', 'svg')
|
|||
|
||||
EMBROIDERABLE_TAGS = (SVG_PATH_TAG, SVG_POLYLINE_TAG)
|
||||
|
||||
# modern versions of Inkscape use 96 pixels per inch as per the CSS standard
|
||||
PIXELS_PER_MM = 96 / 25.4
|
||||
|
||||
class Param(object):
|
||||
def __init__(self, name, description, unit=None, values=[], type=None, group=None, inverse=False, default=None):
|
||||
|
@ -73,6 +75,91 @@ def param(*args, **kwargs):
|
|||
|
||||
return decorator
|
||||
|
||||
# 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 == '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_viewbox_transform(node):
|
||||
# somewhat cribbed from inkscape-silhouette
|
||||
|
||||
doc_width = convert_length(node.get('width'))
|
||||
doc_height = convert_length(node.get('height'))
|
||||
|
||||
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
|
||||
|
||||
class EmbroideryElement(object):
|
||||
def __init__(self, node, options=None):
|
||||
self.node = node
|
||||
|
@ -121,7 +208,7 @@ class EmbroideryElement(object):
|
|||
|
||||
if param.endswith('_mm'):
|
||||
# print >> dbg, "get_float_param", param, value, "*", self.options.pixels_per_mm
|
||||
value = value * getattr(self.options, "pixels_per_mm", 10)
|
||||
value = value * PIXELS_PER_MM
|
||||
|
||||
return value
|
||||
|
||||
|
@ -133,7 +220,7 @@ class EmbroideryElement(object):
|
|||
return default
|
||||
|
||||
if param.endswith('_mm'):
|
||||
value = int(value * getattr(self.options, "pixels_per_mm", 10))
|
||||
value = int(value * PIXELS_PER_MM)
|
||||
|
||||
return value
|
||||
|
||||
|
@ -197,9 +284,14 @@ class EmbroideryElement(object):
|
|||
# combine this node's transform with all parent groups' transforms
|
||||
transform = simpletransform.composeParents(self.node, transform)
|
||||
|
||||
# add in the transform implied by the viewBox
|
||||
viewbox_transform = get_viewbox_transform(self.node.getroottree().getroot())
|
||||
transform = simpletransform.composeTransform(viewbox_transform, transform)
|
||||
|
||||
# apply the combined transform to this node's path
|
||||
simpletransform.applyTransformToPath(transform, path)
|
||||
|
||||
|
||||
return path
|
||||
|
||||
def flatten(self, path):
|
||||
|
@ -500,7 +592,7 @@ class Fill(EmbroideryElement):
|
|||
|
||||
# only stitch the first point if it's a reasonable distance away from the
|
||||
# last stitch
|
||||
if not patch.stitches or (beg - patch.stitches[-1]).length() > 0.5 * getattr(self.options, "pixels_per_mm", 10):
|
||||
if not patch.stitches or (beg - patch.stitches[-1]).length() > 0.5 * PIXELS_PER_MM:
|
||||
patch.add_stitch(beg)
|
||||
|
||||
first_stitch = self.adjust_stagger(beg, angle, row_spacing, max_stitch_length)
|
||||
|
@ -515,7 +607,7 @@ class Fill(EmbroideryElement):
|
|||
patch.add_stitch(beg + offset * row_direction)
|
||||
offset += max_stitch_length
|
||||
|
||||
if (end - patch.stitches[-1]).length() > 0.1 * getattr(self.options, "pixels_per_mm", 10):
|
||||
if (end - patch.stitches[-1]).length() > 0.1 * PIXELS_PER_MM:
|
||||
patch.add_stitch(end)
|
||||
|
||||
|
||||
|
@ -1000,7 +1092,7 @@ class AutoFill(Fill):
|
|||
patch.add_stitch(PyEmb.Point(*outline.interpolate(pos).coords[0]))
|
||||
|
||||
end = PyEmb.Point(*end)
|
||||
if (end - patch.stitches[-1]).length() > 0.1 * getattr(self.options, "pixels_per_mm", 10):
|
||||
if (end - patch.stitches[-1]).length() > 0.1 * PIXELS_PER_MM:
|
||||
patch.add_stitch(end)
|
||||
|
||||
print >> dbg, "end connect_points"
|
||||
|
@ -1761,6 +1853,12 @@ def stitches_to_polylines(stitches):
|
|||
return polylines
|
||||
|
||||
def emit_inkscape(parent, stitches):
|
||||
transform = get_viewbox_transform(parent.getroottree().getroot())
|
||||
|
||||
# we need to correct for the viewbox
|
||||
transform = simpletransform.invertTransform(transform)
|
||||
transform = simpletransform.formatTransform(transform)
|
||||
|
||||
for color, polyline in stitches_to_polylines(stitches):
|
||||
# dbg.write('polyline: %s %s\n' % (color, repr(polyline)))
|
||||
inkex.etree.SubElement(parent,
|
||||
|
@ -1770,6 +1868,7 @@ def emit_inkscape(parent, stitches):
|
|||
'stroke-width': "0.4",
|
||||
'fill': 'none'}),
|
||||
'points': " ".join(",".join(str(coord) for coord in point) for point in polyline),
|
||||
'transform': transform
|
||||
})
|
||||
|
||||
|
||||
|
@ -1916,8 +2015,8 @@ class Embroider(inkex.Effect):
|
|||
|
||||
patches.extend(element.to_patches(last_patch))
|
||||
|
||||
stitches = patches_to_stitches(patches, self.options.collapse_length_mm * self.options.pixels_per_mm)
|
||||
emb = PyEmb.Embroidery(stitches, self.options.pixels_per_mm)
|
||||
stitches = patches_to_stitches(patches, self.options.collapse_length_mm * PIXELS_PER_MM)
|
||||
emb = PyEmb.Embroidery(stitches, PIXELS_PER_MM)
|
||||
emb.export(self.get_output_path(), self.options.output_format)
|
||||
|
||||
new_layer = inkex.etree.SubElement(self.document.getroot(), SVG_GROUP_TAG, {})
|
||||
|
|
Ładowanie…
Reference in New Issue