summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/elements/fill_stitch.py15
-rw-r--r--lib/stitches/guided_fill.py16
-rw-r--r--lib/stitches/point_transfer.py2
-rw-r--r--lib/stitches/sample_linestring.py19
-rw-r--r--lib/stitches/tangential_fill_stitch_line_creator.py10
-rw-r--r--lib/stitches/tangential_fill_stitch_pattern_creator.py40
-rw-r--r--lib/svg/tags.py1
7 files changed, 69 insertions, 34 deletions
diff --git a/lib/elements/fill_stitch.py b/lib/elements/fill_stitch.py
index eaddcfe0..e0de0f22 100644
--- a/lib/elements/fill_stitch.py
+++ b/lib/elements/fill_stitch.py
@@ -192,6 +192,19 @@ class FillStitch(EmbroideryElement):
return max(self.get_float_param("max_stitch_length_mm", 3.0), 0.1 * PIXELS_PER_MM)
@property
+ @param('min_stitch_length_mm',
+ _('Minimum fill stitch length'),
+ tooltip=_(
+ 'The minimum length of a stitch in a row. Larger values might introduce deviations from the desired path. Shorter stitch may be used at the start or end of a row.'),
+ unit='mm',
+ sort_index=4,
+ select_items=[('fill_method', 1), ('fill_method', 2)],
+ type='float',
+ default=0.0)
+ def min_stitch_length(self):
+ return self.get_float_param("min_stitch_length_mm", 0.0)
+
+ @property
@param('staggers',
_('Stagger rows this many times before repeating'),
tooltip=_('Setting this dictates how many rows apart the stitches will be before they fall in the same column position.'),
@@ -557,6 +570,7 @@ class FillStitch(EmbroideryElement):
-self.row_spacing,
self.join_style+1,
self.max_stitch_length,
+ min(self.min_stitch_length, self.max_stitch_length),
self.interlaced,
self.tangential_strategy,
shgeo.Point(starting_point))
@@ -585,6 +599,7 @@ class FillStitch(EmbroideryElement):
self.angle,
self.row_spacing,
self.max_stitch_length,
+ min(self.min_stitch_length,self.max_stitch_length),
self.running_stitch_length,
self.skip_last,
starting_point,
diff --git a/lib/stitches/guided_fill.py b/lib/stitches/guided_fill.py
index 4cc250ef..6948a086 100644
--- a/lib/stitches/guided_fill.py
+++ b/lib/stitches/guided_fill.py
@@ -24,6 +24,7 @@ def guided_fill(shape,
angle,
row_spacing,
max_stitch_length,
+ min_stitch_length,
running_stitch_length,
skip_last,
starting_point,
@@ -45,7 +46,7 @@ def guided_fill(shape,
travel_graph = build_travel_graph(fill_stitch_graph, shape, angle, underpath)
path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point)
result = path_to_stitches(path, travel_graph, fill_stitch_graph, angle, row_spacing,
- max_stitch_length, running_stitch_length, skip_last, offset_by_half)
+ max_stitch_length, min_stitch_length, running_stitch_length, skip_last, offset_by_half)
return result
@@ -187,13 +188,13 @@ def process_travel_edges(graph, fill_stitch_graph, shape, travel_edges):
del strtree
-def stitch_line(stitches, stitching_direction, geometry, projected_points, max_stitch_length, row_spacing, skip_last, offset_by_half):
+def stitch_line(stitches, stitching_direction, geometry, projected_points, max_stitch_length, min_stitch_length, row_spacing, skip_last, offset_by_half):
if stitching_direction == 1:
stitched_line, _ = raster_line_string_with_priority_points(
- geometry, 0.0, geometry.length, max_stitch_length, projected_points, abs(row_spacing), offset_by_half, True)
+ geometry, 0.0, geometry.length, max_stitch_length, min_stitch_length, projected_points, abs(row_spacing), offset_by_half, True)
else:
stitched_line, _ = raster_line_string_with_priority_points(
- geometry, geometry.length, 0.0, max_stitch_length, projected_points, abs(row_spacing), offset_by_half, True)
+ geometry, geometry.length, 0.0, max_stitch_length, min_stitch_length, projected_points, abs(row_spacing), offset_by_half, True)
stitches.append(Stitch(*stitched_line[0], tags=('fill_row_start',)))
for i in range(1, len(stitched_line)-1):
@@ -209,7 +210,7 @@ def stitch_line(stitches, stitching_direction, geometry, projected_points, max_s
@debug.time
-def path_to_stitches(path, travel_graph, fill_stitch_graph, angle, row_spacing, max_stitch_length,
+def path_to_stitches(path, travel_graph, fill_stitch_graph, angle, row_spacing, max_stitch_length, min_stitch_length,
running_stitch_length, skip_last, offset_by_half):
path = collapse_sequential_outline_edges(path)
@@ -230,7 +231,7 @@ def path_to_stitches(path, travel_graph, fill_stitch_graph, angle, row_spacing,
abs(edge[0][0]-path_geometry.coords[-1][0])+abs(edge[0][1]-path_geometry.coords[-1][1])):
stitching_direction = -1
stitch_line(new_stitches, stitching_direction, path_geometry, projected_points,
- max_stitch_length, row_spacing, skip_last, offset_by_half)
+ max_stitch_length, min_stitch_length, row_spacing, skip_last, offset_by_half)
current_edge['already_rastered'] = True
transfer_points_to_surrounding_graph(
fill_stitch_graph, current_edge, row_spacing, False, new_stitches, overnext_neighbor=True)
@@ -264,9 +265,8 @@ def extend_line(line, minx, maxx, miny, maxy):
def repair_multiple_parallel_offset_curves(multi_line):
- # TODO: linemerge is overritten by the very next line?!?
lines = linemerge(multi_line)
- lines = list(multi_line.geoms)
+ lines = list(lines.geoms)
max_length = -1
max_length_idx = -1
for idx, subline in enumerate(lines):
diff --git a/lib/stitches/point_transfer.py b/lib/stitches/point_transfer.py
index cf4597dd..5506324d 100644
--- a/lib/stitches/point_transfer.py
+++ b/lib/stitches/point_transfer.py
@@ -70,7 +70,7 @@ def transfer_points_to_surrounding(treenode, used_offset, offset_by_half, to_tra
assert(not transfer_forbidden_points or transfer_forbidden_points and (
offset_by_half or not offset_by_half and overnext_neighbor))
- if len(to_transfer_points) == 0:
+ if len(to_transfer_points) < 3:
return
# Get a list of all possible adjacent nodes which will be considered for transferring the points of treenode:
diff --git a/lib/stitches/sample_linestring.py b/lib/stitches/sample_linestring.py
index b2298984..838b1792 100644
--- a/lib/stitches/sample_linestring.py
+++ b/lib/stitches/sample_linestring.py
@@ -63,7 +63,7 @@ def calculate_line_angles(line):
return Angles
-def raster_line_string_with_priority_points(line, start_distance, end_distance, maxstitch_distance, # noqa: C901
+def raster_line_string_with_priority_points(line, start_distance, end_distance, maxstitch_distance, minstitch_distance, # noqa: C901
must_use_points_deque, abs_offset, offset_by_half, replace_forbidden_points):
"""
Rasters a line between start_distance and end_distance.
@@ -72,6 +72,7 @@ def raster_line_string_with_priority_points(line, start_distance, end_distance,
-start_distance: The distance along the line from which the rastering should start
-end_distance: The distance along the line until which the rastering should be done
-maxstitch_distance: The maximum allowed stitch distance
+ -minstitch_distance: The minimum allowed stitch distance
-Note that start_distance > end_distance for stitching_direction = -1
-must_use_points_deque: deque with projected points on line from its neighbors. An item of the deque
is setup as follows: ((projected point on line, LineStringSampling.PointSource), priority=distance along line)
@@ -84,7 +85,7 @@ def raster_line_string_with_priority_points(line, start_distance, end_distance,
-List which defines the point origin for each point according to the PointSource enum.
"""
- if (abs(end_distance-start_distance) < constants.line_lengh_seen_as_one_point):
+ if (abs(end_distance-start_distance) < max(minstitch_distance, constants.line_lengh_seen_as_one_point)):
return [line.interpolate(start_distance).coords[0]], [PointSource.HARD_EDGE]
deque_points = list(must_use_points_deque)
@@ -103,9 +104,9 @@ def raster_line_string_with_priority_points(line, start_distance, end_distance,
deque_points = deque_points[::-1]
# Remove all points from the deque which do not fall in the segment [start_distance; end_distance]
- while (len(deque_points) > 0 and deque_points[0][1] <= start_distance+min(maxstitch_distance/20, constants.point_spacing_to_be_considered_equal)):
+ while (len(deque_points) > 0 and deque_points[0][1] <= start_distance+min(maxstitch_distance/20, minstitch_distance, constants.point_spacing_to_be_considered_equal)):
deque_points.pop(0)
- while (len(deque_points) > 0 and deque_points[-1][1] >= end_distance-min(maxstitch_distance/20, constants.point_spacing_to_be_considered_equal)):
+ while (len(deque_points) > 0 and deque_points[-1][1] >= end_distance-min(maxstitch_distance/20, minstitch_distance, constants.point_spacing_to_be_considered_equal)):
deque_points.pop()
@@ -185,6 +186,9 @@ def raster_line_string_with_priority_points(line, start_distance, end_distance,
while segment_end_index < len(merged_point_list):
segment_length = merged_point_list[segment_end_index][1] - \
merged_point_list[segment_start_index][1]
+ if segment_length < minstitch_distance:
+ segment_end_index += 1
+ continue
if segment_length > maxstitch_distance+constants.point_spacing_to_be_considered_equal:
new_distance = merged_point_list[segment_start_index][1] + \
maxstitch_distance
@@ -214,6 +218,13 @@ def raster_line_string_with_priority_points(line, start_distance, end_distance,
iter = segment_start_index+1
while (iter <= segment_end_index):
+ segment_length = merged_point_list[iter][1] - \
+ merged_point_list[segment_start_index][1]
+ if segment_length < minstitch_distance and merged_point_list[iter][0].point_source != PointSource.HARD_EDGE_INTERNAL:
+ #We need to create this hard edge exception - otherwise there are some too large deviations posible
+ iter += 1
+ continue
+
if merged_point_list[iter][0].point_source == PointSource.OVERNEXT:
index_overnext = iter
elif merged_point_list[iter][0].point_source == PointSource.DIRECT:
diff --git a/lib/stitches/tangential_fill_stitch_line_creator.py b/lib/stitches/tangential_fill_stitch_line_creator.py
index af14ea0f..4d4377f0 100644
--- a/lib/stitches/tangential_fill_stitch_line_creator.py
+++ b/lib/stitches/tangential_fill_stitch_line_creator.py
@@ -158,7 +158,7 @@ def check_and_prepare_tree_for_valid_spiral(root):
return True
-def offset_poly(poly, offset, join_style, stitch_distance, offset_by_half, strategy, starting_point): # noqa: C901
+def offset_poly(poly, offset, join_style, stitch_distance, min_stitch_distance, offset_by_half, strategy, starting_point): # noqa: C901
"""
Takes a polygon (which can have holes) as input and creates offsetted
versions until the polygon is filled with these smaller offsets.
@@ -173,6 +173,8 @@ def offset_poly(poly, offset, join_style, stitch_distance, offset_by_half, strat
For examples look at
https://shapely.readthedocs.io/en/stable/_images/parallel_offset.png
-stitch_distance maximum allowed stitch distance between two points
+ -min_stitch_distance stitches within a row shall be at least min_stitch_distance apart. Stitches connecting
+ offsetted paths might be shorter.
-offset_by_half: True if the points shall be interlaced
-strategy: According to StitchingStrategy enum class you can select between
different strategies for the connection between parent and childs. In
@@ -315,15 +317,15 @@ def offset_poly(poly, offset, join_style, stitch_distance, offset_by_half, strat
if strategy == StitchingStrategy.CLOSEST_POINT:
(connected_line, connected_line_origin) = tangential_fill_stitch_pattern_creator.connect_raster_tree_nearest_neighbor(
- root, offset, stitch_distance, starting_point, offset_by_half)
+ root, offset, stitch_distance, min_stitch_distance, starting_point, offset_by_half)
elif strategy == StitchingStrategy.INNER_TO_OUTER:
(connected_line, connected_line_origin) = tangential_fill_stitch_pattern_creator.connect_raster_tree_from_inner_to_outer(
- root, offset, stitch_distance, starting_point, offset_by_half)
+ root, offset, stitch_distance, min_stitch_distance, starting_point, offset_by_half)
elif strategy == StitchingStrategy.SPIRAL:
if not check_and_prepare_tree_for_valid_spiral(root):
raise ValueError("Geometry cannot be filled with one spiral!")
(connected_line, connected_line_origin) = tangential_fill_stitch_pattern_creator.connect_raster_tree_spiral(
- root, offset, stitch_distance, starting_point, offset_by_half)
+ root, offset, stitch_distance, min_stitch_distance, starting_point, offset_by_half)
else:
raise ValueError("Invalid stitching stratety!")
diff --git a/lib/stitches/tangential_fill_stitch_pattern_creator.py b/lib/stitches/tangential_fill_stitch_pattern_creator.py
index d7afad0c..95143bce 100644
--- a/lib/stitches/tangential_fill_stitch_pattern_creator.py
+++ b/lib/stitches/tangential_fill_stitch_pattern_creator.py
@@ -52,7 +52,7 @@ def cut(line, distance):
def connect_raster_tree_nearest_neighbor( # noqa: C901
- tree, used_offset, stitch_distance, close_point, offset_by_half):
+ tree, used_offset, stitch_distance, min_stitch_distance, close_point, offset_by_half):
"""
Takes the offsetted curves organized as tree, connects and samples them.
Strategy: A connection from parent to child is made where both curves
@@ -63,6 +63,8 @@ def connect_raster_tree_nearest_neighbor( # noqa: C901
-used_offset: used offset when the offsetted curves were generated
-stitch_distance: maximum allowed distance between two points
after sampling
+ -min_stitch_distance stitches within a row shall be at least min_stitch_distance apart. Stitches connecting
+ offsetted paths might be shorter.
-close_point: defines the beginning point for stitching
(stitching starts always from the undisplaced curve)
-offset_by_half: If true the resulting points are interlaced otherwise not.
@@ -136,6 +138,7 @@ def connect_raster_tree_nearest_neighbor( # noqa: C901
# points for start and end)
end_distance,
stitch_distance,
+ min_stitch_distance,
tree.transferred_point_priority_deque,
abs_offset,
offset_by_half,
@@ -230,6 +233,7 @@ def connect_raster_tree_nearest_neighbor( # noqa: C901
item.child_node,
used_offset,
stitch_distance,
+ min_stitch_distance,
item.nearest_point_child,
offset_by_half,
)
@@ -432,7 +436,7 @@ def calculate_replacing_middle_point(line_segment, abs_offset, max_stitch_distan
return line_segment.coords[1]
-def connect_raster_tree_from_inner_to_outer(tree, used_offset, stitch_distance, close_point, offset_by_half): # noqa: C901
+def connect_raster_tree_from_inner_to_outer(tree, used_offset, stitch_distance, min_stitch_distance, close_point, offset_by_half): # noqa: C901
"""
Takes the offsetted curves organized as tree, connects and samples them.
Strategy: A connection from parent to child is made as fast as possible to
@@ -444,6 +448,8 @@ def connect_raster_tree_from_inner_to_outer(tree, used_offset, stitch_distance,
-used_offset: used offset when the offsetted curves were generated
-stitch_distance: maximum allowed distance between two points
after sampling
+ -min_stitch_distance stitches within a row shall be at least min_stitch_distance apart. Stitches connecting
+ offsetted paths might be shorter.
-close_point: defines the beginning point for stitching
(stitching starts always from the undisplaced curve)
-offset_by_half: If true the resulting points are interlaced otherwise not.
@@ -514,11 +520,12 @@ def connect_raster_tree_from_inner_to_outer(tree, used_offset, stitch_distance,
if stitching_direction == 1:
(own_coords, own_coords_origin) = sample_linestring.raster_line_string_with_priority_points(
current_coords,
- start_offset, # We add start_offset to not sample the same
- # point again (avoid double points for start
+ start_offset, # We add start_offset to not sample the initial/end
+ # point twice (avoid double points for start
# and end)
end_offset,
stitch_distance,
+ min_stitch_distance,
tree.transferred_point_priority_deque,
abs_offset,
offset_by_half,
@@ -529,12 +536,13 @@ def connect_raster_tree_from_inner_to_outer(tree, used_offset, stitch_distance,
current_coords,
current_coords.length - start_offset, # We subtract
# start_offset to not
- # sample the same point
- # again (avoid double
+ # sample the initial/end point
+ # twice (avoid double
# points for start
# and end)
current_coords.length - end_offset,
stitch_distance,
+ min_stitch_distance,
tree.transferred_point_priority_deque,
abs_offset,
offset_by_half,
@@ -639,6 +647,7 @@ def connect_raster_tree_from_inner_to_outer(tree, used_offset, stitch_distance,
item.child_node,
used_offset,
stitch_distance,
+ min_stitch_distance,
item.nearest_point_child,
offset_by_half,
)
@@ -683,19 +692,12 @@ def connect_raster_tree_from_inner_to_outer(tree, used_offset, stitch_distance,
if cur_item < len(nearest_points_list) - 1:
d = min(
d,
- abs(
- nearest_points_list[cur_item +
- 1].proj_distance_parent
- - item.proj_distance_parent
- ),
+ abs(nearest_points_list[cur_item + 1].proj_distance_parent - item.proj_distance_parent),
)
if d > constants.factor_offset_starting_points * abs_offset:
result_coords.append(
- current_coords.interpolate(
- item.proj_distance_parent
- + 2 * constants.factor_offset_starting_points * abs_offset
- ).coords[0]
+ current_coords.interpolate(item.proj_distance_parent + 2 * constants.factor_offset_starting_points * abs_offset).coords[0]
)
result_coords_origin.append(
sample_linestring.PointSource.ENTER_LEAVING_POINT
@@ -792,7 +794,7 @@ def interpolate_LinearRings(a, b, start=None, step=.005):
def connect_raster_tree_spiral(
- tree, used_offset, stitch_distance, close_point, offset_by_half):
+ tree, used_offset, stitch_distance, min_stitch_distance, close_point, offset_by_half):
"""
Takes the offsetted curves organized as tree, connects and samples them as a spiral.
It expects that each node in the tree has max. one child
@@ -802,6 +804,8 @@ def connect_raster_tree_spiral(
-used_offset: used offset when the offsetted curves were generated
-stitch_distance: maximum allowed distance between two points
after sampling
+ -min_stitch_distance stitches within a row shall be at least min_stitch_distance apart. Stitches connecting
+ offsetted paths might be shorter.
-close_point: defines the beginning point for stitching
(stitching starts always from the undisplaced curve)
-offset_by_half: If true the resulting points are interlaced otherwise not.
@@ -819,6 +823,7 @@ def connect_raster_tree_spiral(
0,
tree.val.length,
stitch_distance,
+ min_stitch_distance,
tree.transferred_point_priority_deque,
abs_offset,
offset_by_half,
@@ -842,6 +847,7 @@ def connect_raster_tree_spiral(
0,
node.val.length,
stitch_distance,
+ min_stitch_distance,
node.transferred_point_priority_deque,
abs_offset,
offset_by_half,
@@ -883,7 +889,7 @@ def connect_raster_tree_spiral(
lineseg = LineString([result_coords[-2], result_coords[-1], own_coords[0], own_coords[1]])
else:
lineseg = LineString([result_coords[-2], result_coords[-1], own_coords[1]])
- (temp_coords, _) = sample_linestring.raster_line_string_with_priority_points(lineseg, 0, lineseg.length, stitch_distance,
+ (temp_coords, _) = sample_linestring.raster_line_string_with_priority_points(lineseg, 0, lineseg.length, stitch_distance, min_stitch_distance,
DEPQ(), abs_offset, offset_by_half, False)
if len(temp_coords) == 2: # only start and end point of lineseg was needed
result_coords.pop()
diff --git a/lib/svg/tags.py b/lib/svg/tags.py
index f3118661..37eb5752 100644
--- a/lib/svg/tags.py
+++ b/lib/svg/tags.py
@@ -64,6 +64,7 @@ inkstitch_attribs = [
'fill_underlay_row_spacing_mm',
'fill_underlay_skip_last',
'max_stitch_length_mm',
+ 'min_stitch_length_mm',
'row_spacing_mm',
'end_row_spacing_mm',
'skip_last',