summaryrefslogtreecommitdiff
path: root/lib/stitches
diff options
context:
space:
mode:
Diffstat (limited to 'lib/stitches')
-rw-r--r--lib/stitches/auto_satin.py91
1 files changed, 74 insertions, 17 deletions
diff --git a/lib/stitches/auto_satin.py b/lib/stitches/auto_satin.py
index 59bf6b0a..7bc3e67c 100644
--- a/lib/stitches/auto_satin.py
+++ b/lib/stitches/auto_satin.py
@@ -1,4 +1,4 @@
-from itertools import chain
+from itertools import chain, izip
import math
import cubicsuperpath
@@ -9,8 +9,8 @@ import simplestyle
import networkx as nx
-from ..elements import Stroke
-from ..svg import PIXELS_PER_MM, line_strings_to_csp
+from ..elements import Stroke, SatinColumn
+from ..svg import PIXELS_PER_MM, line_strings_to_csp, get_correction_transform
from ..svg.tags import SVG_PATH_TAG
from ..utils import Point as InkstitchPoint, cut, cache
@@ -25,7 +25,7 @@ class SatinSegment(object):
reverse -- if True, reverse the direction of the satin
"""
- def __init__(self, satin, start=0.0, end=1.0, reverse=False):
+ def __init__(self, satin, start=0.0, end=1.0, reverse=False, original_satin=None):
"""Initialize a SatinEdge.
Arguments:
@@ -38,6 +38,7 @@ class SatinSegment(object):
"""
self.satin = satin
+ self.original_satin = original_satin or self.satin
self.reverse = reverse
# start and end are stored as normalized projections
@@ -74,17 +75,19 @@ class SatinSegment(object):
to_element = to_satin
def to_running_stitch(self):
- return RunningStitch(self.center_line, self.satin)
+ return RunningStitch(self.center_line, self.original_satin)
def break_up(self, segment_size):
"""Break this SatinSegment up into SatinSegments of the specified size."""
num_segments = int(math.ceil(self.center_line.length / segment_size))
segments = []
- satin = self.to_satin()
for i in xrange(num_segments):
- segments.append(SatinSegment(satin, float(
- i) / num_segments, float(i + 1) / num_segments, self.reverse))
+ segments.append(SatinSegment(self.satin,
+ float(i) / num_segments,
+ float(i + 1) / num_segments,
+ self.reverse,
+ self.original_satin))
if self.reverse:
segments.reverse()
@@ -121,6 +124,10 @@ class SatinSegment(object):
def end_point(self):
return self.satin.center_line.interpolate(self.end, normalized=True)
+ @property
+ def original_node(self):
+ return self.original_satin.node
+
def is_sequential(self, other):
"""Check if a satin segment immediately follows this one on the same satin."""
@@ -213,10 +220,11 @@ class RunningStitch(object):
style['stroke-dasharray'] = "0.5,0.5"
style = simplestyle.formatStyle(style)
node.set("style", style)
-
node.set("embroider_running_stitch_length_mm", self.running_stitch_length)
- return Stroke(node)
+ stroke = Stroke(node)
+
+ return stroke
@property
@cache
@@ -228,6 +236,10 @@ class RunningStitch(object):
def end_point(self):
return self.path.interpolate(1.0, normalized=True)
+ @property
+ def original_node(self):
+ return self.original_element.node
+
@cache
def reversed(self):
return RunningStitch(shgeo.LineString(reversed(self.path.coords)), self.style)
@@ -236,6 +248,9 @@ class RunningStitch(object):
if not isinstance(other, RunningStitch):
return False
+ if self.original_element is not other.original_element:
+ return False
+
return self.path.distance(other.path) < 0.5
def __add__(self, other):
@@ -277,6 +292,11 @@ def auto_satin(elements, preserve_order=False, starting_point=None, ending_point
instances (that are running stitch) can be included to indicate how to travel
between two SatinColumns. This works best when preserve_order is True.
+ If preserve_order is True, then the elements and any newly-created elements
+ will be in the same position in the SVG DOM. If preserve_order is False, then
+ the elements will be removed from the SVG DOM and it's up to the caller to
+ decide where to put the returned SVG path nodes.
+
Returns: a list of SVG path nodes making up the selected stitch order.
Jumps between objects are implied if they are not right next to each
other.
@@ -289,7 +309,13 @@ def auto_satin(elements, preserve_order=False, starting_point=None, ending_point
path = find_path(graph, starting_node, ending_node)
operations = path_to_operations(graph, path)
operations = collapse_sequential_segments(operations)
- return operations_to_elements_and_trims(operations)
+ new_elements, trims, original_parents = operations_to_elements_and_trims(operations, preserve_order)
+ remove_original_elements(elements)
+
+ if preserve_order:
+ preserve_original_groups(new_elements, original_parents)
+
+ return new_elements, trims
def build_graph(elements, preserve_order=False):
@@ -306,7 +332,7 @@ def build_graph(elements, preserve_order=False):
segments = []
if isinstance(element, Stroke):
segments.append(RunningStitch(element))
- else:
+ elif isinstance(element, SatinColumn):
whole_satin = SatinSegment(element)
segments = whole_satin.break_up(PIXELS_PER_MM)
@@ -548,26 +574,57 @@ def collapse_sequential_segments(old_operations):
return new_operations
-def operations_to_elements_and_trims(operations):
+def operations_to_elements_and_trims(operations, preserve_order):
"""Convert a list of operations to Elements and locations of trims.
Returns:
- (nodes, trims)
+ (elements, trims, original_parents)
- element -- a list of Element instances
+ elements -- a list of Element instances
trims -- indices of nodes after which the thread should be trimmed
+ original_parents -- a parallel list of the original SVG parent nodes that spawned each element
"""
elements = []
trims = []
+ original_parent_nodes = []
for operation in operations:
- # Ignore JumpStitch opertions. Jump stitches in Ink/Stitch are
+ # Ignore JumpStitch operations. Jump stitches in Ink/Stitch are
# implied and added by Embroider if needed.
if isinstance(operation, (SatinSegment, RunningStitch)):
elements.append(operation.to_element())
+ original_parent_nodes.append(operation.original_node.getparent())
elif isinstance(operation, (JumpStitch)):
if elements and operation.length > PIXELS_PER_MM:
trims.append(len(elements) - 1)
- return elements, list(set(trims))
+ return elements, list(set(trims)), original_parent_nodes
+
+
+def remove_original_elements(elements):
+ for element in elements:
+ for command in element.commands:
+ remove_from_parent(command.connector)
+ remove_from_parent(command.use)
+ remove_from_parent(element.node)
+
+
+def remove_from_parent(node):
+ if node.getparent() is not None:
+ node.getparent().remove(node)
+
+
+def preserve_original_groups(elements, original_parent_nodes):
+ """Ensure that all elements are contained in the original SVG group elements.
+
+ When preserve_order is True, no SatinColumn or Stroke elements will be
+ reordered in the XML tree. This makes it possible to preserve original SVG
+ group membership. We'll ensure that each newly-created Element is added
+ to the group that contained the original SatinColumn that spawned it.
+ """
+
+ for element, parent in izip(elements, original_parent_nodes):
+ if parent is not None:
+ parent.append(element.node)
+ element.node.set('transform', get_correction_transform(parent, child=True))