diff options
| author | Lex Neva <github.com@lexneva.name> | 2019-03-14 21:46:44 -0400 |
|---|---|---|
| committer | Lex Neva <github.com@lexneva.name> | 2019-03-14 21:46:44 -0400 |
| commit | 200e2ac5f73c207b95ff815e754e42c427ccc58e (patch) | |
| tree | 55d00bdf14a48fda85822f3642cc7716a7267498 | |
| parent | e616061e85fda0160ebfc7d071358d730b0c613f (diff) | |
deduplicate and comment code
| -rw-r--r-- | lib/stitches/auto_fill.py | 107 |
1 files changed, 79 insertions, 28 deletions
diff --git a/lib/stitches/auto_fill.py b/lib/stitches/auto_fill.py index 2e424267..82b4a99e 100644 --- a/lib/stitches/auto_fill.py +++ b/lib/stitches/auto_fill.py @@ -1,3 +1,5 @@ +# -*- coding: UTF-8 -*- + from itertools import groupby, chain import math @@ -74,7 +76,7 @@ def which_outline(shape, coords): point = shgeo.Point(*coords) outlines = enumerate(list(shape.boundary)) - closest = min(outlines, key=lambda index_outline: index_outline[1].distance(point)) + closest = min(outlines, key=lambda (index, outline): outline.distance(point)) return closest[0] @@ -135,6 +137,8 @@ def build_graph(shape, angle, row_spacing, end_row_spacing): # mark this one as a grating segment. graph.add_edge(*segment, key="segment") + tag_nodes_with_outline_and_projection(graph, shape, graph.nodes()) + for node in graph.nodes(): outline_index = which_outline(shape, node) outline_projection = project(shape, node, outline_index) @@ -142,54 +146,101 @@ def build_graph(shape, angle, row_spacing, end_row_spacing): # Tag each node with its index and projection. graph.add_node(node, index=outline_index, projection=outline_projection) + add_edges_between_outline_nodes(graph, key="outline") + + for node1, node2, key, data in graph.edges(keys=True, data=True): + if key == "outline": + # duplicate every other edge + if data['index'] % 2 == 0: + graph.add_edge(node1, node2, key="extra") + + return graph + + +def tag_nodes_with_outline_and_projection(graph, shape, nodes): + for node in nodes: + outline_index = which_outline(shape, node) + outline_projection = project(shape, node, outline_index) + + graph.add_node(node, outline=outline_index, projection=outline_projection) + + +def add_edges_between_outline_nodes(graph, key=None): + """Add edges around the outlines of the graph, connecting sequential nodes. + + This function assumes that all nodes in the graph are on the outline of the + shape. It figures out which nodes are next to each other on the shape and + connects them in the graph with an edge. + + Edges are tagged with their outline number and their position on that + outline. + """ + nodes = list(graph.nodes(data=True)) # returns a list of tuples: [(node, {data}), (node, {data}) ...] - nodes.sort(key=lambda node: (node[1]['index'], node[1]['projection'])) + nodes.sort(key=lambda node: (node[1]['outline'], node[1]['projection'])) - for outline_index, nodes in groupby(nodes, key=lambda node: node[1]['index']): + for outline_index, nodes in groupby(nodes, key=lambda node: node[1]['outline']): nodes = [node for node, data in nodes] # add an edge between each successive node for i, (node1, node2) in enumerate(zip(nodes, nodes[1:] + [nodes[0]])): - graph.add_edge(node1, node2, key="outline") + data = dict(outline=outline_index, index=i) + if key: + graph.add_edge(node1, node2, key=key, **data) + else: + graph.add_edge(node1, node2, **data) - # duplicate every other edge - if i % 2 == 0: - graph.add_edge(node1, node2, key="extra") - return graph +def build_travel_graph(top_stitch_graph, shape, top_stitch_angle, underpath): + """Build a graph for travel stitches. + This graph will be used to find a stitch path between two spots on the + outline of the shape. + + If underpath is False, we'll just be traveling + around the outline of the shape, so the graph will only contain outline + edges. + + If underpath is True, we'll also allow travel inside the shape. We'll + fill the shape with a cross-hatched grid of lines 2mm apart, at ±45 + degrees from the fill stitch angle. This will ensure that travel stitches + won't be visible and won't disrupt the fill stitch. + + When underpathing, we "encourage" the travel() function to travel inside + the shape rather than on the boundary. We do this by weighting the + boundary edges extra so that they're more "expensive" in the shortest path + calculation. + """ -def build_travel_graph(top_stitch_graph, shape, top_stitch_angle, underpath): graph = networkx.Graph() + + # Add all the nodes from the main graph. This will be all of the endpoints + # of the rows of stitches. Every node will be on the outline of the shape. + # They'll all already have their `outline` and `projection` tags set. graph.add_nodes_from(top_stitch_graph.nodes(data=True)) if underpath: - # need to concatenate all the rows + # These two MultiLineStrings will make up the cross-hatched grid. grating1 = shgeo.MultiLineString(list(chain(*intersect_region_with_grating(shape, top_stitch_angle + math.pi / 4, 2 * PIXELS_PER_MM)))) grating2 = shgeo.MultiLineString(list(chain(*intersect_region_with_grating(shape, top_stitch_angle - math.pi / 4, 2 * PIXELS_PER_MM)))) + # We'll add the endpoints of the crosshatch grating lines too These + # will all be on the outline of the shape. This will ensure that a + # path traveling inside the shape can reach its target on the outline, + # which will be one of the points added above. endpoints = [coord for mls in (grating1, grating2) for ls in mls for coord in ls.coords] + tag_nodes_with_outline_and_projection(graph, shape, endpoints) - for node in endpoints: - outline_index = which_outline(shape, node) - outline_projection = project(shape, node, outline_index) - - # Tag each node with its index and projection. - graph.add_node(node, index=outline_index, projection=outline_projection) - - nodes = list(graph.nodes(data=True)) # returns a list of tuples: [(node, {data}), (node, {data}) ...] - nodes.sort(key=lambda node: (node[1]['index'], node[1]['projection'])) - - for outline_index, nodes in groupby(nodes, key=lambda node: node[1]['index']): - nodes = [node for node, data in nodes] + add_edges_between_outline_nodes(graph) + for start, end in graph.edges: + p1 = InkstitchPoint(*start) + p2 = InkstitchPoint(*end) - # add an edge between each successive node - for node1, node2 in zip(nodes, nodes[1:] + [nodes[0]]): - p1 = InkstitchPoint(*node1) - p2 = InkstitchPoint(*node2) - graph.add_edge(node1, node2, weight=3 * p1.distance(p2)) + # Set the weight equal to triple the edge length, to encourage travel() + # to avoid them when underpathing is enabled. + graph.add_edge(start, end, weight=3 * p1.distance(p2)) if underpath: interior_edges = grating1.symmetric_difference(grating2) @@ -213,7 +264,7 @@ def check_graph(graph, shape, max_stitch_length): def nearest_node_on_outline(graph, point, outline_index=0): point = shgeo.Point(*point) - outline_nodes = [node for node, data in graph.nodes(data=True) if data['index'] == outline_index] + outline_nodes = [node for node, data in graph.nodes(data=True) if data['outline'] == outline_index] nearest = min(outline_nodes, key=lambda node: shgeo.Point(*node).distance(point)) return nearest |
