kopia lustrzana https://github.com/inkstitch/inkstitch
use running_stitch instead for guided fill
rodzic
e6fcf11035
commit
bd8cb0d1ff
|
@ -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):
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue