summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/extensions/convert_to_satin.py79
-rw-r--r--lib/svg/path.py6
-rw-r--r--messages.po8
3 files changed, 65 insertions, 28 deletions
diff --git a/lib/extensions/convert_to_satin.py b/lib/extensions/convert_to_satin.py
index 715853c4..8af45b3b 100644
--- a/lib/extensions/convert_to_satin.py
+++ b/lib/extensions/convert_to_satin.py
@@ -1,6 +1,10 @@
import inkex
from shapely import geometry as shgeo
from itertools import chain
+import numpy
+from numpy import diff, sign, setdiff1d
+from scipy.signal import argrelmin
+import math
from .base import InkstitchExtension
from ..svg.tags import SVG_PATH_TAG
@@ -62,42 +66,73 @@ class ConvertToSatin(InkstitchExtension):
return (left_rail, right_rail), rungs
- def generate_rungs(self, path, stroke_width):
- rungs = []
+ def get_scores(self, path):
+ scores = numpy.zeros(101, numpy.int32)
+ path_length = path.length
+
+ prev_point = None
+ prev_direction = None
+ length_so_far = 0
+ for point in path.coords:
+ point = Point(*point)
+
+ if prev_point is None:
+ prev_point = point
+ continue
+
+ direction = (point - prev_point).unit()
+
+ if prev_direction is not None:
+ cos_angle_between = prev_direction * direction
+ angle = abs(math.degrees(math.acos(cos_angle_between)))
+
+ scores[int(round(length_so_far / path_length * 100.0))] += angle ** 2
- # approximately 1cm between rungs, and we don't want them at the very start or end
- num_rungs = int(path.length / PIXELS_PER_MM / 10) - 1
+ length_so_far += (point - prev_point).length()
+ prev_direction = direction
+ prev_point = point
- if num_rungs < 1 or num_rungs == 2:
- # avoid 2 rungs because it can be ambiguous (which are the rails and which are the
- # rungs?)
- num_rungs += 1
+ return scores
- distance_between_rungs = path.length / (num_rungs + 1)
+ def local_minima(self, array):
+ # from: https://stackoverflow.com/a/9667121/4249120
+ # finds spots where the curvature (second derivative) is > 0
+ return (diff(sign(diff(array))) > 0).nonzero()[0] + 1
- for i in xrange(num_rungs):
- rung_center = path.interpolate((i + 1) * distance_between_rungs)
+ def generate_rungs(self, path, stroke_width):
+ scores = self.get_scores(path)
+ scores = numpy.convolve(scores, [1, 2, 4, 8, 16, 8, 4, 2, 1], mode='same')
+ rung_locations = self.local_minima(scores)
+ rung_locations = setdiff1d(rung_locations, [0, 100])
+
+ if len(rung_locations) == 0:
+ rung_locations = [50]
+
+ rungs = []
+ last_rung_center = None
+
+ for location in rung_locations:
+ location = location / 100.0
+ rung_center = path.interpolate(location, normalized=True)
rung_center = Point(rung_center.x, rung_center.y)
- # TODO: use bezierslopeatt or whatever
- # we need to calculate the normal at this point so grab another point just a little
- # further down the path, effectively grabbing a small segment of the path
- segment_end = path.interpolate((i + 1) * distance_between_rungs + 0.1)
- segment_end = Point(segment_end.x, segment_end.y)
+ if last_rung_center is not None and \
+ (rung_center - last_rung_center).length() < 2 * PIXELS_PER_MM:
+ continue
+ else:
+ last_rung_center = rung_center
- tangent = segment_end - rung_center
- normal = tangent.unit().rotate_left()
+ tangent_end = path.interpolate(location + 0.001, normalized=True)
+ tangent_end = Point(tangent_end.x, tangent_end.y)
+ tangent = (tangent_end - rung_center).unit()
+ normal = tangent.rotate_left()
offset = normal * stroke_width * 0.75
rung_start = rung_center + offset
rung_end = rung_center - offset
-
rungs.append((rung_start.as_tuple(), rung_end.as_tuple()))
return rungs
- import sys
- print >> sys.stderr, rails, rungs
-
def satin_to_svg_node(self, rails, rungs, correction_transform):
diff --git a/lib/svg/path.py b/lib/svg/path.py
index 00e2c269..52144332 100644
--- a/lib/svg/path.py
+++ b/lib/svg/path.py
@@ -15,8 +15,10 @@ def get_node_transform(node):
# 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)
+ # this if is because sometimes inkscape likes to create paths outside of a layer?!
+ if node.getparent() is not None:
+ # combine this node's transform with all parent groups' transforms
+ transform = simpletransform.composeParents(node, transform)
# add in the transform implied by the viewBox
viewbox_transform = get_viewbox_transform(node.getroottree().getroot())
diff --git a/messages.po b/messages.po
index eb27b8a9..da2748b1 100644
--- a/messages.po
+++ b/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-07-30 14:57-0400\n"
+"POT-Creation-Date: 2018-07-30 16:03-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"
@@ -238,17 +238,17 @@ msgstr ""
msgid "Please choose one or more commands to attach."
msgstr ""
-#: lib/extensions/convert_to_satin.py:20
+#: lib/extensions/convert_to_satin.py:24
msgid "Please select at least one line to convert to a satin column."
msgstr ""
#. : Convert To Satin extension, user selected one or more objects that were
#. not lines.
-#: lib/extensions/convert_to_satin.py:25
+#: lib/extensions/convert_to_satin.py:29
msgid "Only simple lines may be converted to satin columns."
msgstr ""
-#: lib/extensions/convert_to_satin.py:37
+#: lib/extensions/convert_to_satin.py:41
#, python-format
msgid ""
"Cannot convert %s to a satin column because it intersects itself. Try "