use running_stitch instead for guided fill

pull/1548/head
Lex Neva 2022-05-02 14:38:33 -04:00 zatwierdzone przez Kaalleen
rodzic e6fcf11035
commit bd8cb0d1ff
4 zmienionych plików z 48 dodań i 157 usunięć

Wyświetl plik

@ -59,9 +59,9 @@ def auto_fill(shape,
starting_point,
ending_point=None,
underpath=True):
fill_stitch_graph = []
try:
fill_stitch_graph = build_fill_stitch_graph(shape, angle, row_spacing, end_row_spacing, starting_point, ending_point)
segments = intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing)
fill_stitch_graph = build_fill_stitch_graph(shape, segments, starting_point, ending_point)
except ValueError:
# Small shapes will cause the graph to fail - min() arg is an empty sequence through insert node
return fallback(shape, running_stitch_length)
@ -109,7 +109,7 @@ def project(shape, coords, outline_index):
@debug.time
def build_fill_stitch_graph(shape, angle, row_spacing, end_row_spacing, starting_point=None, ending_point=None):
def build_fill_stitch_graph(shape, segments, starting_point=None, ending_point=None):
"""build a graph representation of the grating segments
This function builds a specialized graph (as in graph theory) that will
@ -144,10 +144,6 @@ def build_fill_stitch_graph(shape, angle, row_spacing, end_row_spacing, starting
debug.add_layer("auto-fill fill stitch")
# Convert the shape into a set of parallel line segments.
rows_of_segments = intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing)
segments = [segment for row in rows_of_segments for segment in row]
graph = networkx.MultiGraph()
# First, add the grating segments as edges. We'll use the coordinates
@ -155,7 +151,7 @@ def build_fill_stitch_graph(shape, angle, row_spacing, end_row_spacing, starting
for segment in segments:
# networkx allows us to label nodes with arbitrary data. We'll
# mark this one as a grating segment.
graph.add_edge(*segment, key="segment", underpath_edges=[], geometry=shgeo.LineString(segment))
graph.add_edge(segment[0], segment[-1], key="segment", underpath_edges=[], geometry=shgeo.LineString(segment))
tag_nodes_with_outline_and_projection(graph, shape, graph.nodes())
add_edges_between_outline_nodes(graph, duplicate_every_other=True)
@ -177,7 +173,7 @@ def insert_node(graph, shape, point):
point = tuple(point)
outline = which_outline(shape, point)
projection = project(shape, point, outline)
projected_point = list(shape.boundary)[outline].interpolate(projection)
projected_point = list(shape.boundary.geoms)[outline].interpolate(projection)
node = (projected_point.x, projected_point.y)
edges = []
@ -395,10 +391,9 @@ def process_travel_edges(graph, fill_stitch_graph, shape, travel_edges):
def travel_grating(shape, angle, row_spacing):
rows_of_segments = intersect_region_with_grating(shape, angle, row_spacing)
segments = list(chain(*rows_of_segments))
segments = intersect_region_with_grating(shape, angle, row_spacing)
return shgeo.MultiLineString(segments)
return shgeo.MultiLineString(list(segments))
def ensure_multi_line_string(thing):
@ -457,7 +452,7 @@ def build_travel_edges(shape, fill_angle):
debug.log_line_strings(grating3, "grating3")
endpoints = [coord for mls in (grating1, grating2, grating3)
for ls in mls
for ls in mls.geoms
for coord in ls.coords]
diagonal_edges = ensure_multi_line_string(
@ -467,7 +462,7 @@ def build_travel_edges(shape, fill_angle):
vertical_edges = ensure_multi_line_string(
snap(grating3.difference(grating1), diagonal_edges, 0.005))
return endpoints, chain(diagonal_edges, vertical_edges)
return endpoints, chain(diagonal_edges.geoms, vertical_edges.geoms)
def nearest_node(nodes, point, attr=None):

Wyświetl plik

@ -136,8 +136,6 @@ def intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing=Non
# fill regions at the same angle and spacing always line up nicely.
start -= (start + normal * center) % row_spacing
rows = []
current_row_y = start
while current_row_y < end:
@ -165,7 +163,7 @@ def intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing=Non
runs.reverse()
runs = [tuple(reversed(run)) for run in runs]
rows.append(runs)
yield from runs
if end_row_spacing:
current_row_y += row_spacing + \
@ -174,8 +172,6 @@ def intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing=Non
else:
current_row_y += row_spacing
return rows
def section_to_stitches(group_of_segments, angle, row_spacing, max_stitch_length, staggers, skip_last):
stitches = []

Wyświetl plik

@ -1,14 +1,10 @@
import networkx
from depq import DEPQ
from shapely.geometry import GeometryCollection, LineString, MultiLineString
from shapely import geometry as shgeo
from shapely.ops import linemerge, unary_union
from .auto_fill import (add_edges_between_outline_nodes, build_travel_graph,
collapse_sequential_outline_edges, fallback,
find_stitch_path, graph_is_valid, insert_node,
tag_nodes_with_outline_and_projection, travel)
from .point_transfer import transfer_points_to_surrounding_graph
from .sample_linestring import raster_line_string_with_priority_points
from .auto_fill import (build_fill_stitch_graph,
build_travel_graph, collapse_sequential_outline_edges, fallback,
find_stitch_path, graph_is_valid, travel)
from .running_stitch import running_stitch
from ..debug import debug
from ..i18n import _
from ..stitch_plan import Stitch
@ -28,11 +24,9 @@ def guided_fill(shape,
ending_point=None,
underpath=True,
offset_by_half=True):
fill_stitch_graph = []
try:
fill_stitch_graph = build_guided_fill_stitch_graph(
shape, guideline, row_spacing, starting_point, ending_point)
segments = intersect_region_with_grating_guideline(shape, guideline, row_spacing)
fill_stitch_graph = build_fill_stitch_graph(shape, segments, starting_point, ending_point)
except ValueError:
# Small shapes will cause the graph to fail - min() arg is an empty sequence through insert node
return fallback(shape, running_stitch_length)
@ -48,108 +42,6 @@ def guided_fill(shape,
return result
@debug.time
def build_guided_fill_stitch_graph(shape, guideline, row_spacing, starting_point=None, ending_point=None):
"""build a graph representation of the grating segments
This function builds a specialized graph (as in graph theory) that will
help us determine a stitching path. The idea comes from this paper:
http://www.sciencedirect.com/science/article/pii/S0925772100000158
The goal is to build a graph that we know must have an Eulerian Path.
An Eulerian Path is a path from edge to edge in the graph that visits
every edge exactly once and ends at the node it started at. Algorithms
exist to build such a path, and we'll use Hierholzer's algorithm.
A graph must have an Eulerian Path if every node in the graph has an
even number of edges touching it. Our goal here is to build a graph
that will have this property.
Based on the paper linked above, we'll build the graph as follows:
* nodes are the endpoints of the grating segments, where they meet
with the outer outline of the region the outlines of the interior
holes in the region.
* edges are:
* each section of the outer and inner outlines of the region,
between nodes
* double every other edge in the outer and inner hole outlines
Doubling up on some of the edges seems as if it will just mean we have
to stitch those spots twice. This may be true, but it also ensures
that every node has 4 edges touching it, ensuring that a valid stitch
path must exist.
"""
debug.add_layer("auto-fill fill stitch")
rows_of_segments = intersect_region_with_grating_guideline(shape, guideline, row_spacing)
# segments = [segment for row in rows_of_segments for segment in row]
graph = networkx.MultiGraph()
for i in range(len(rows_of_segments)):
for segment in rows_of_segments[i]:
# First, add the grating segments as edges. We'll use the coordinates
# of the endpoints as nodes, which networkx will add automatically.
# networkx allows us to label nodes with arbitrary data. We'll
# mark this one as a grating segment.
# graph.add_edge(*segment, key="segment", underpath_edges=[])
previous_neighbors = [(seg[0], seg[-1])
for seg in rows_of_segments[i-1] if i > 0]
next_neighbors = [(seg[0], seg[-1]) for seg in rows_of_segments[(i+1) %
len(rows_of_segments)] if i < len(rows_of_segments)-1]
graph.add_edge(segment[0], segment[-1], key="segment", underpath_edges=[],
geometry=LineString(segment), previous_neighbors=previous_neighbors, next_neighbors=next_neighbors,
projected_points=DEPQ(iterable=None, maxlen=None), already_rastered=False)
tag_nodes_with_outline_and_projection(graph, shape, graph.nodes())
add_edges_between_outline_nodes(graph, duplicate_every_other=True)
if starting_point:
insert_node(graph, shape, starting_point)
if ending_point:
insert_node(graph, shape, ending_point)
debug.log_graph(graph, "graph")
return graph
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, 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, 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):
stitches.append(Stitch(*stitched_line[i], tags=('fill_row')))
if not skip_last:
if stitching_direction == 1:
stitches.append(
Stitch(*geometry.coords[-1], tags=('fill_row_end',)))
else:
stitches.append(
Stitch(*geometry.coords[0], tags=('fill_row_end',)))
@debug.time
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):
@ -163,23 +55,23 @@ def path_to_stitches(path, travel_graph, fill_stitch_graph, angle, row_spacing,
for edge in path:
if edge.is_segment():
new_stitches = []
current_edge = fill_stitch_graph[edge[0]][edge[-1]]['segment']
path_geometry = current_edge['geometry']
projected_points = current_edge['projected_points']
stitching_direction = 1
if (abs(edge[0][0]-path_geometry.coords[0][0])+abs(edge[0][1]-path_geometry.coords[0][1]) >
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, 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)
transfer_points_to_surrounding_graph(fill_stitch_graph, current_edge, row_spacing, offset_by_half,
new_stitches, overnext_neighbor=False, transfer_forbidden_points=offset_by_half)
if edge[0] != path_geometry.coords[0]:
path_geometry = reverse_line_string(path_geometry)
point_list = [Stitch(*point) for point in path_geometry.coords]
new_stitches = running_stitch(point_list, max_stitch_length)
# 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, skip_last))
@ -195,14 +87,14 @@ def extend_line(line, minx, maxx, miny, maxy):
point1 = InkstitchPoint(*line.coords[0])
point2 = InkstitchPoint(*line.coords[1])
new_starting_point = point1-(point2-point1).unit()*length
new_starting_point = point1 - (point2 - point1).unit() * length
point3 = InkstitchPoint(*line.coords[-2])
point4 = InkstitchPoint(*line.coords[-1])
new_ending_point = point4+(point4-point3).unit()*length
new_ending_point = point4 + (point4 - point3).unit() * length
return LineString([new_starting_point.as_tuple()] +
line.coords[1:-1]+[new_ending_point.as_tuple()])
return shgeo.LineString([new_starting_point.as_tuple()] +
line.coords[1:-1] + [new_ending_point.as_tuple()])
def repair_multiple_parallel_offset_curves(multi_line):
@ -251,15 +143,15 @@ def intersect_region_with_grating_guideline(shape, line, row_spacing, flip=False
line_offsetted = line
res = line_offsetted.intersection(shape)
while isinstance(res, (GeometryCollection, MultiLineString)) or (not res.is_empty and len(res.coords) > 1):
if isinstance(res, (GeometryCollection, MultiLineString)):
while isinstance(res, (shgeo.GeometryCollection, shgeo.MultiLineString)) or (not res.is_empty and len(res.coords) > 1):
if isinstance(res, (shgeo.GeometryCollection, shgeo.MultiLineString)):
runs = [line_string.coords for line_string in res.geoms if (
not line_string.is_empty and len(line_string.coords) > 1)]
not line_string.is_empty and len(line_string.coords) > 1)]
else:
runs = [res.coords]
runs.sort(key=lambda seg: (
InkstitchPoint(*seg[0]) - upper_left).length())
InkstitchPoint(*seg[0]) - upper_left).length())
if flip:
runs.reverse()
runs = [tuple(reversed(run)) for run in runs]
@ -279,7 +171,7 @@ def intersect_region_with_grating_guideline(shape, line, row_spacing, flip=False
line_offsetted = reverse_line_string(line_offsetted)
line_offsetted = line_offsetted.simplify(0.01, False)
res = line_offsetted.intersection(shape)
if row_spacing > 0 and not isinstance(res, (GeometryCollection, MultiLineString)):
if row_spacing > 0 and not isinstance(res, (shgeo.GeometryCollection, shgeo.MultiLineString)):
if (res.is_empty or len(res.coords) == 1):
row_spacing = -row_spacing
@ -293,4 +185,6 @@ def intersect_region_with_grating_guideline(shape, line, row_spacing, flip=False
line_offsetted = reverse_line_string(line_offsetted)
line_offsetted = line_offsetted.simplify(0.01, False)
res = line_offsetted.intersection(shape)
return rows
for row in rows:
yield from row

Wyświetl plik

@ -74,6 +74,12 @@ def get_correction_transform(node, child=False):
def line_strings_to_csp(line_strings):
try:
# This lets us accept a MultiLineString or a list.
line_strings = line_strings.geoms
except AttributeError:
pass
return point_lists_to_csp(ls.coords for ls in line_strings)