summaryrefslogtreecommitdiff
path: root/lib/stitches/guided_fill.py
diff options
context:
space:
mode:
authorLex Neva <github.com@lexneva.name>2022-05-02 14:38:33 -0400
committerKaalleen <reni@allenka.de>2022-05-04 19:18:33 +0200
commitbd8cb0d1ff2ce1f17ed8d3a5eaca19f8e8652ad0 (patch)
tree960ddbd5328c1ee5a3be0fde72c84147d348ae2a /lib/stitches/guided_fill.py
parente6fcf11035d3d953c2b07e6d153a1225f79cb781 (diff)
use running_stitch instead for guided fill
Diffstat (limited to 'lib/stitches/guided_fill.py')
-rw-r--r--lib/stitches/guided_fill.py170
1 files changed, 32 insertions, 138 deletions
diff --git a/lib/stitches/guided_fill.py b/lib/stitches/guided_fill.py
index 9170d66f..40728c53 100644
--- a/lib/stitches/guided_fill.py
+++ b/lib/stitches/guided_fill.py
@@ -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)
@@ -49,108 +43,6 @@ def guided_fill(shape,
@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):
path = collapse_sequential_outline_edges(path)
@@ -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