1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
from shapely import geometry as shgeo
from shapely.ops import substring
from ..stitch_plan import Stitch
from ..utils.geometry import reverse_line_string
from .auto_fill import (build_fill_stitch_graph, build_travel_graph,
collapse_sequential_outline_edges, fallback,
find_stitch_path, graph_is_valid, travel)
from .contour_fill import _make_fermat_spiral
from .running_stitch import bean_stitch, running_stitch
def circular_fill(shape,
angle,
row_spacing,
end_row_spacing,
num_staggers,
running_stitch_length,
running_stitch_tolerance,
bean_stitch_repeats,
repeats,
skip_last,
starting_point,
ending_point,
underpath,
target
):
# get furthest distance of the target point to a shape border
# so we know how many circles we will need
distance = shape.hausdorff_distance(target)
radius = row_spacing
center = shgeo.Point(target)
if radius > distance:
# if the shape is smaller than row_spacing, return a simple circle in the size of row_spacing
stitches = running_stitch([Stitch(*point) for point in center.buffer(radius).exterior.coords],
running_stitch_length, running_stitch_tolerance)
return _apply_bean_stitch_and_repeats(stitches, repeats, bean_stitch_repeats)
circles = []
# add a small inner circle to make sure that the spiral ends close to the center
circles.append(shgeo.LineString(center.buffer(0.1).exterior.coords))
# add twice the size of the (end_)row_spacing to make sure we go big enough
stopp_at_distance = distance + (end_row_spacing or row_spacing) * 2
while radius < stopp_at_distance:
circles.append(shgeo.LineString(center.buffer(radius).exterior.coords))
if end_row_spacing:
radius += row_spacing + (end_row_spacing - row_spacing) * (radius / distance)
else:
radius += row_spacing
circles.reverse()
# Use double spiral from contour fill (we don't want to get stuck in the middle of the spiral)
double_spiral = _make_fermat_spiral(circles, running_stitch_length, circles[0].coords[0])
double_spiral = shgeo.LineString(list(double_spiral))
intersection = double_spiral.intersection(shape)
if isinstance(intersection, shgeo.LineString):
# if we get a single linestrig (original shape is a circle), apply start and end commands and return path
path = list(intersection.coords)
path = _apply_start_end_commands(shape, path, starting_point, ending_point)
stitches = running_stitch([Stitch(*point) for point in path], running_stitch_length, running_stitch_tolerance)
return _apply_bean_stitch_and_repeats(stitches, repeats, bean_stitch_repeats)
segments = []
for line in intersection.geoms:
if isinstance(line, shgeo.LineString):
# use running stitch here to adjust the stitch length
coords = running_stitch([Stitch(point[0], point[1]) for point in line.coords],
running_stitch_length,
running_stitch_tolerance)
segments.append([(point.x, point.y) for point in coords])
fill_stitch_graph = build_fill_stitch_graph(shape, segments, starting_point, ending_point)
if not graph_is_valid(fill_stitch_graph, shape, running_stitch_length):
return fallback(shape, running_stitch_length, running_stitch_tolerance)
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, running_stitch_length, running_stitch_tolerance, skip_last)
result = _apply_bean_stitch_and_repeats(result, repeats, bean_stitch_repeats)
return result
def _apply_bean_stitch_and_repeats(stitches, repeats, bean_stitch_repeats):
if any(bean_stitch_repeats):
# add bean stitches, but ignore travel stitches
stitches = bean_stitch(stitches, bean_stitch_repeats, ['auto_fill_travel'])
if repeats:
for i in range(1, repeats):
if i % 2 == 1:
# reverse every other pass
stitches.extend(stitches[::-1])
else:
stitches.extend(stitches)
return stitches
def _apply_start_end_commands(shape, path, starting_point, ending_point):
if starting_point or ending_point:
outline = shape.boundary
if starting_point:
start = _get_start_end_sequence(outline, shgeo.Point(*starting_point), shgeo.Point(*path[0]))
path = list(start.coords) + path
if ending_point:
end = _get_start_end_sequence(outline, shgeo.Point(*path[-1]), shgeo.Point(*ending_point))
path.extend(list(end.coords))
return path
def _get_start_end_sequence(outline, start, end):
start_dist = outline.project(start)
end_dist = outline.project(end)
return substring(outline, start_dist, end_dist)
def path_to_stitches(path, travel_graph, fill_stitch_graph, running_stitch_length, running_stitch_tolerance, skip_last):
path = collapse_sequential_outline_edges(path)
stitches = []
# If the very first stitch is travel, we'll omit it in travel(), so add it here.
if not path[0].is_segment():
stitches.append(Stitch(*path[0].nodes[0]))
for edge in path:
if edge.is_segment():
current_edge = fill_stitch_graph[edge[0]][edge[-1]]['segment']
path_geometry = current_edge['geometry']
if edge[0] != path_geometry.coords[0]:
path_geometry = reverse_line_string(path_geometry)
new_stitches = [Stitch(*point) for point in path_geometry.coords]
# need to tag stitches
if skip_last:
del new_stitches[-1]
stitches.extend(new_stitches)
travel_graph.remove_edges_from(fill_stitch_graph[edge[0]][edge[1]]['segment'].get('underpath_edges', []))
else:
stitches.extend(travel(travel_graph, edge[0], edge[1], running_stitch_length, running_stitch_tolerance, skip_last))
return stitches
|