avoid NetworkXNoPath error by connecting graph

pull/1803/head
Lex Neva 2023-02-18 17:44:42 -05:00
rodzic 7fa3fec534
commit ae43fb9683
2 zmienionych plików z 32 dodań i 11 usunięć

Wyświetl plik

@ -1,3 +1,5 @@
from itertools import combinations
import networkx as nx
from shapely.geometry import MultiPoint, Point
from shapely.ops import nearest_points
@ -5,11 +7,11 @@ from shapely.ops import nearest_points
from .running_stitch import running_stitch
from .. import tiles
from ..debug import debug
from ..stitch_plan import Stitch
from ..utils.smoothing import smooth_path
from ..utils.clamp_path import clamp_path_to_polygon
from ..utils.geometry import Point as InkStitchPoint, ensure_geometry_collection
from ..utils.list import poprandom
from ..utils.prng import iter_uniform_floats
from ..utils.smoothing import smooth_path
from ..utils.threading import check_stop_flag
@ -24,11 +26,11 @@ def meander_fill(fill, shape, shape_index, starting_point, ending_point):
debug.log_line_strings(lambda: ensure_geometry_collection(shape.boundary).geoms, 'Meander shape')
graph = tile.to_graph(shape, fill.meander_scale)
debug.log_graph(graph, 'Meander graph')
debug.log(lambda: f"graph connected? {nx.is_connected(graph)}")
ensure_connected(graph)
start, end = find_starting_and_ending_nodes(graph, shape, starting_point, ending_point)
rng = iter_uniform_floats(fill.random_seed, 'meander-fill', shape_index)
return post_process(generate_meander_path(graph, start, end, rng), fill)
return post_process(generate_meander_path(graph, start, end, rng), shape, fill)
def get_tile(tile_name):
@ -40,6 +42,24 @@ def get_tile(tile_name):
return None
def ensure_connected(graph):
"""If graph is unconnected, add edges to make it connected."""
# TODO: combine this with possible_jumps() in lib/stitches/utils/autoroute.py
possible_connections = []
for component1, component2 in combinations(nx.connected_components(graph), 2):
points1 = MultiPoint([Point(node) for node in component1])
points2 = MultiPoint([Point(node) for node in component2])
start_point, end_point = nearest_points(points1, points2)
possible_connections.append(((start_point.x, start_point.y), (end_point.x, end_point.y), start_point.distance(end_point)))
if possible_connections:
for start, end in nx.k_edge_augmentation(graph, 1, avail=possible_connections):
check_stop_flag()
graph.add_edge(start, end)
def find_starting_and_ending_nodes(graph, shape, starting_point, ending_point):
if starting_point is None:
starting_point = shape.exterior.coords[0]
@ -137,14 +157,14 @@ def replace_edge_pair(path, edge1, edge2, graph, graph_nodes):
@debug.time
def post_process(points, fill):
def post_process(points, shape, fill):
debug.log(f"smoothness: {fill.smoothness}")
# debug.log_line_string(LineString(points), "pre-smoothed", "#FF0000")
smoothed_points = smooth_path(points, fill.smoothness)
smoothed_points = [InkStitchPoint.from_tuple(point) for point in smoothed_points]
stitches = running_stitch(smoothed_points, fill.running_stitch_length, fill.running_stitch_tolerance)
stitches = [Stitch(point) for point in stitches]
stitches = clamp_path_to_polygon(stitches, shape)
return stitches

Wyświetl plik

@ -3,7 +3,7 @@ from math import ceil, floor
import inkex
import lxml
from networkx import Graph
import networkx as nx
from shapely.geometry import LineString
from shapely.prepared import prep
@ -127,7 +127,7 @@ class Tile:
return self._generate_graph(prepared_shape, shape_center, shape_diagonal)
def _generate_graph(self, shape, shape_center, shape_diagonal):
graph = Graph()
graph = nx.Graph()
tiles0 = ceil(shape_diagonal / self.shift0.length()) + 2
tiles1 = ceil(shape_diagonal / self.shift1.length()) + 2
for repeat0 in range(floor(-tiles0 / 2), ceil(tiles0 / 2)):
@ -147,11 +147,12 @@ class Tile:
return graph
def _remove_dead_ends(self, graph):
graph.remove_edges_from(nx.selfloop_edges(graph))
while True:
nodes_with_degree_1 = [node for node, degree in graph.degree() if degree == 1]
dead_end_nodes = [node for node, degree in graph.degree() if degree <= 1]
if nodes_with_degree_1:
graph.remove_nodes_from(nodes_with_degree_1)
if dead_end_nodes:
graph.remove_nodes_from(dead_end_nodes)
else:
return