From 404c483cc7c220b2521ab06011bf612303ca2029 Mon Sep 17 00:00:00 2001 From: Kaalleen <36401965+kaalleen@users.noreply.github.com> Date: Sat, 17 Aug 2024 17:28:08 +0200 Subject: [PATCH] fix fills without underpath and bad start-end positions (#3141) --- lib/stitches/auto_fill.py | 38 ++++++++++++++++++++-------- lib/stitches/circular_fill.py | 2 +- lib/stitches/guided_fill.py | 2 +- lib/stitches/linear_gradient_fill.py | 2 +- lib/stitches/tartan_fill.py | 2 +- lib/tartan/svg.py | 2 +- 6 files changed, 32 insertions(+), 16 deletions(-) diff --git a/lib/stitches/auto_fill.py b/lib/stitches/auto_fill.py index a1e88d5d2..4800c994c 100644 --- a/lib/stitches/auto_fill.py +++ b/lib/stitches/auto_fill.py @@ -111,7 +111,7 @@ def auto_fill(shape, if not travel_graph: return fallback(shape, running_stitch_length, running_stitch_tolerance) - path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point) + path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point, underpath) path = fill_gaps(path, round_to_multiple_of_2(gap_fill_rows)) result = path_to_stitches(shape, path, travel_graph, fill_stitch_graph, angle, row_spacing, max_stitch_length, running_stitch_length, running_stitch_tolerance, @@ -375,13 +375,13 @@ def graph_make_valid(graph): graph.add_edge(start, end, key=f'segment-{key}', **data) elif 'outline' in graph_edges.keys(): data = graph_edges['outline'] - graph.add_edge(start, end, key='outline-{key}', **data) + graph.add_edge(start, end, key=f'outline-{key}', **data) elif 'extra' in graph_edges.keys(): data = graph_edges['extra'] - graph.add_edge(start, end, key='extra-{key}', **data) + graph.add_edge(start, end, key=f'extra-{key}', **data) elif 'initial' in graph_edges.keys(): data = graph_edges['initial'] - graph.add_edge(start, end, key='initial-{key}', **data) + graph.add_edge(start, end, key=f'initial-{key}', **data) def fallback(shape, running_stitch_length, running_stitch_tolerance): @@ -600,7 +600,7 @@ def nearest_node(nodes, point, attr=None): @debug.time -def find_stitch_path(graph, travel_graph, starting_point=None, ending_point=None): +def find_stitch_path(graph, travel_graph, starting_point=None, ending_point=None, underpath=True): """find a path that visits every grating segment exactly once Theoretically, we just need to find an Eulerian Path in the graph. @@ -623,6 +623,7 @@ def find_stitch_path(graph, travel_graph, starting_point=None, ending_point=None """ graph = graph.copy() + composed_graph = networkx.compose(graph, travel_graph) if not starting_point: starting_point = list(graph.nodes.keys())[0] @@ -641,6 +642,10 @@ def find_stitch_path(graph, travel_graph, starting_point=None, ending_point=None last_vertex = None last_key = None + # get the outline index for starting and ending node + outline_start = travel_graph[starting_node][list(travel_graph[starting_node])[0]]['outline']['outline'] + outline_end = travel_graph[ending_node][list(travel_graph[ending_node])[0]]['outline']['outline'] + while vertex_stack: current_vertex, current_key = vertex_stack[-1] if graph.degree(current_vertex) == 0: @@ -664,8 +669,21 @@ def find_stitch_path(graph, travel_graph, starting_point=None, ending_point=None # Note, it's quite possible that part of this PathEdge will be eliminated by # collapse_sequential_outline_edges(). - if starting_node is not ending_node: - path.insert(0, PathEdge((starting_node, ending_node), key="initial")) + if underpath or outline_start == outline_end: + if starting_node is not ending_node: + path.insert(0, PathEdge((starting_node, ending_node), key="initial")) + else: + # If underpath is disabled, we only have the option travel along the outline edges. + # The user chose to start and end on different oultines, so there is no way to travel + # along the edge from start to end. Add an additional path from start to end along existing + # graph and travel edges (they will be duplicated). + start_path = networkx.shortest_path(composed_graph, starting_node, ending_node, weight='weight') + for i, edge in enumerate(list(zip(start_path, start_path[1:]))): + # add as segment so it won't be collapsed + if 'segment' in composed_graph[edge[0]][edge[1]].keys(): + path.insert(i, PathEdge(edge, key=f'segment-start{i}')) + else: + path.insert(i, PathEdge(edge, key=f'outline-start{i}')) # If the starting and/or ending point falls far away from the end of a row # of stitches (like can happen at the top of a square), then we need to @@ -872,10 +890,8 @@ def travel(shape, travel_graph, edge, running_stitch_length, running_stitch_tole try: path = networkx.shortest_path(travel_graph, start, end, weight='weight') except networkx.NetworkXNoPath: - # TODO: find a better solution, this may produce unwanted jump stitches - # but at least it renders the requested shape - # test case: underpath disabled, starts and ends on different outlines - return + # This may not look good, but it prevents the fill from failing (which hopefully never happens) + path = [start, end] if underpath and path != (start, end): path = smooth_path(path, 2) diff --git a/lib/stitches/circular_fill.py b/lib/stitches/circular_fill.py index 7be9469a5..8b7fae415 100644 --- a/lib/stitches/circular_fill.py +++ b/lib/stitches/circular_fill.py @@ -93,7 +93,7 @@ def circular_fill(shape, graph_make_valid(fill_stitch_graph) travel_graph = build_travel_graph(fill_stitch_graph, shape, angle, underpath) - path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point) + path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point, underpath) result = path_to_stitches(shape, path, travel_graph, fill_stitch_graph, running_stitch_length, running_stitch_tolerance, skip_last, underpath) result = _apply_bean_stitch_and_repeats(result, repeats, bean_stitch_repeats) return result diff --git a/lib/stitches/guided_fill.py b/lib/stitches/guided_fill.py index dcf9971a0..79eff6b55 100644 --- a/lib/stitches/guided_fill.py +++ b/lib/stitches/guided_fill.py @@ -53,7 +53,7 @@ def guided_fill(shape, graph_make_valid(fill_stitch_graph) travel_graph = build_travel_graph(fill_stitch_graph, shape, angle, underpath) - path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point) + path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point, underpath) result = path_to_stitches(shape, path, travel_graph, fill_stitch_graph, max_stitch_length, running_stitch_length, running_stitch_tolerance, skip_last, underpath) diff --git a/lib/stitches/linear_gradient_fill.py b/lib/stitches/linear_gradient_fill.py index c0c07e0dc..3b2d09386 100644 --- a/lib/stitches/linear_gradient_fill.py +++ b/lib/stitches/linear_gradient_fill.py @@ -289,7 +289,7 @@ def _get_stitch_groups(fill, shape, colors, color_lines, starting_point, ending_ graph_make_valid(fill_stitch_graph) travel_graph = build_travel_graph(fill_stitch_graph, shape, fill.angle, False) - path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point) + path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point, False) stitches = path_to_stitches( shape, path, diff --git a/lib/stitches/tartan_fill.py b/lib/stitches/tartan_fill.py index ad3e0370b..5eeed473f 100644 --- a/lib/stitches/tartan_fill.py +++ b/lib/stitches/tartan_fill.py @@ -781,7 +781,7 @@ def _segments_to_stitch_group( return None graph_make_valid(fill_stitch_graph) travel_graph = build_travel_graph(fill_stitch_graph, shape, fill.angle, False) - path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point) + path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point, False) stitches = path_to_stitches( shape, path, diff --git a/lib/tartan/svg.py b/lib/tartan/svg.py index a1f78fc99..0fd8b5792 100644 --- a/lib/tartan/svg.py +++ b/lib/tartan/svg.py @@ -224,7 +224,7 @@ class TartanSvgGroup: return [] graph_make_valid(fill_stitch_graph) travel_graph = build_travel_graph(fill_stitch_graph, outline_shape, 0, False) - path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point) + path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point, False) return self._path_to_shapes(path, fill_stitch_graph, polygons, geometry_type, outline_shape) def _path_to_shapes(