summaryrefslogtreecommitdiff
path: root/lib/elements/fill.py
diff options
context:
space:
mode:
authorLex Neva <github.com@lexneva.name>2018-05-01 20:37:51 -0400
committerLex Neva <github.com@lexneva.name>2018-05-01 20:37:51 -0400
commit1b31806423c8fec4040fed6d1009db016860b763 (patch)
tree71ccac169471e76685a7fa0b9910f64555dc73a7 /lib/elements/fill.py
parent5b7f14d092456a941dbd189e61ed38d9b16d388b (diff)
rename inkstitch/ to lib/
You can't have a module and a package named the same thing. PyInstaller wants to import the main script as if it were a module, and this doesn't work unless there's no directory of the same name with a __init__.py in it.
Diffstat (limited to 'lib/elements/fill.py')
-rw-r--r--lib/elements/fill.py97
1 files changed, 97 insertions, 0 deletions
diff --git a/lib/elements/fill.py b/lib/elements/fill.py
new file mode 100644
index 00000000..a74a897d
--- /dev/null
+++ b/lib/elements/fill.py
@@ -0,0 +1,97 @@
+from .. import _, PIXELS_PER_MM
+from .element import param, EmbroideryElement, Patch
+from ..utils import cache
+from shapely import geometry as shgeo
+import math
+from ..stitches import running_stitch, auto_fill, legacy_fill
+
+class Fill(EmbroideryElement):
+ element_name = _("Fill")
+
+ def __init__(self, *args, **kwargs):
+ super(Fill, self).__init__(*args, **kwargs)
+
+ @property
+ @param('auto_fill', _('Manually routed fill stitching'), type='toggle', inverse=True, default=True)
+ def auto_fill(self):
+ return self.get_boolean_param('auto_fill', True)
+
+ @property
+ @param('angle', _('Angle of lines of stitches'), unit='deg', type='float', default=0)
+ @cache
+ def angle(self):
+ return math.radians(self.get_float_param('angle', 0))
+
+ @property
+ def color(self):
+ return self.get_style("fill")
+
+ @property
+ @param('flip', _('Flip fill (start right-to-left)'), type='boolean', default=False)
+ def flip(self):
+ return self.get_boolean_param("flip", False)
+
+ @property
+ @param('row_spacing_mm', _('Spacing between rows'), unit='mm', type='float', default=0.25)
+ def row_spacing(self):
+ return max(self.get_float_param("row_spacing_mm", 0.25), 0.1 * PIXELS_PER_MM)
+
+ @property
+ def end_row_spacing(self):
+ return self.get_float_param("end_row_spacing_mm")
+
+ @property
+ @param('max_stitch_length_mm', _('Maximum fill stitch length'), unit='mm', type='float', default=3.0)
+ def max_stitch_length(self):
+ return max(self.get_float_param("max_stitch_length_mm", 3.0), 0.1 * PIXELS_PER_MM)
+
+ @property
+ @param('staggers', _('Stagger rows this many times before repeating'), type='int', default=4)
+ def staggers(self):
+ return self.get_int_param("staggers", 4)
+
+ @property
+ @cache
+ def paths(self):
+ return self.flatten(self.parse_path())
+
+ @property
+ @cache
+ def shape(self):
+ poly_ary = []
+ for sub_path in self.paths:
+ point_ary = []
+ last_pt = None
+ for pt in sub_path:
+ if (last_pt is not None):
+ 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))
+ # dbg.write("dp %s\n" % dp)
+ if (dp > 0.01):
+ # I think too-close points confuse shapely.
+ point_ary.append(pt)
+ last_pt = pt
+ else:
+ last_pt = pt
+ if point_ary:
+ poly_ary.append(point_ary)
+
+ # shapely's idea of "holes" are to subtract everything in the second set
+ # from the first. So let's at least make sure the "first" thing is the
+ # biggest path.
+ # TODO: actually figure out which things are holes and which are shells
+ poly_ary.sort(key=lambda point_list: shgeo.Polygon(point_list).area, reverse=True)
+
+ polygon = shgeo.MultiPolygon([(poly_ary[0], poly_ary[1:])])
+ # print >> sys.stderr, "polygon valid:", polygon.is_valid
+ return polygon
+
+ def to_patches(self, last_patch):
+ stitch_lists = legacy_fill(self.shape,
+ self.angle,
+ self.row_spacing,
+ self.end_row_spacing,
+ self.max_stitch_length,
+ self.flip,
+ self.staggers)
+ return [Patch(stitches=stitch_list, color=self.color) for stitch_list in stitch_lists]