summaryrefslogtreecommitdiff
path: root/lib/stitches/tangential_fill_stitch_pattern_creator.py
diff options
context:
space:
mode:
authorLex Neva <github.com@lexneva.name>2022-05-03 21:08:48 -0400
committerKaalleen <reni@allenka.de>2022-05-04 19:18:33 +0200
commit330c6be78786b85ed2528cf2788e529cfda714fd (patch)
treed77ba1ec330b4fe91474a935935b05d72c888232 /lib/stitches/tangential_fill_stitch_pattern_creator.py
parentaeeaf72338e2d7645309725be641d552a3c56190 (diff)
refactor, tidy, and C901 fixes
Diffstat (limited to 'lib/stitches/tangential_fill_stitch_pattern_creator.py')
-rw-r--r--lib/stitches/tangential_fill_stitch_pattern_creator.py339
1 files changed, 0 insertions, 339 deletions
diff --git a/lib/stitches/tangential_fill_stitch_pattern_creator.py b/lib/stitches/tangential_fill_stitch_pattern_creator.py
deleted file mode 100644
index a19c0a0a..00000000
--- a/lib/stitches/tangential_fill_stitch_pattern_creator.py
+++ /dev/null
@@ -1,339 +0,0 @@
-from collections import namedtuple
-from itertools import chain
-import networkx as nx
-import numpy as np
-import trimesh
-from shapely.geometry import Point, LineString, LinearRing, MultiLineString
-from shapely.ops import nearest_points
-
-from .running_stitch import running_stitch
-
-from ..debug import debug
-from ..stitches import constants
-from ..stitch_plan import Stitch
-from ..utils.geometry import cut, roll_linear_ring, reverse_line_string
-
-nearest_neighbor_tuple = namedtuple(
- "nearest_neighbor_tuple",
- [
- "nearest_point_parent",
- "nearest_point_child",
- "proj_distance_parent",
- "child_node",
- ],
-)
-
-
-def get_nearest_points_closer_than_thresh(travel_line, next_line, threshold):
- """
- Find the first point along travel_line that is within threshold of next_line.
-
- Input:
- -travel_line: The "parent" line for which the distance should
- be minimized to enter next_line
- -next_line: contains the next_line which need to be entered
- -threshold: The distance between travel_line and next_line needs
- to below threshold to be a valid point for entering
-
- Output:
- -tuple or None
- - the tuple structure is:
- (nearest point in travel_line, nearest point in next_line)
- - None is returned if there is no point that satisfies the threshold.
- """
-
- # We'll buffer next_line and find the intersection with travel_line.
- # Then we'll return the very first point in the intersection,
- # matched with a corresponding point on next_line. Fortunately for
- # us, intersection of a Polygon with a LineString yields pieces of
- # the LineString in the same order as the input LineString.
- threshold_area = next_line.buffer(threshold)
- portion_within_threshold = travel_line.intersection(threshold_area)
-
- if portion_within_threshold.is_empty:
- return None
- else:
- if isinstance(portion_within_threshold, MultiLineString):
- portion_within_threshold = portion_within_threshold.geoms[0]
-
- parent_point = Point(portion_within_threshold.coords[0])
- return nearest_points(parent_point, next_line)
-
-
-def create_nearest_points_list(
- travel_line, tree, children_list, threshold, threshold_hard):
- """
- Takes a line and calculates the nearest distance along this line to
- enter the childs in children_list
- The method calculates the distances along the line and along the
- reversed line to find the best direction which minimizes the overall
- distance for all childs.
- Input:
- -travel_line: The "parent" line for which the distance should
- be minimized to enter the childs
- -children_list: contains the childs of travel_line which need to be entered
- -threshold: The distance between travel_line and a child needs to be
- below threshold to be a valid point for entering
- -preferred_direction: Put a bias on the desired travel direction along
- travel_line. If equals zero no bias is applied.
- preferred_direction=1 means we prefer the direction of travel_line;
- preferred_direction=-1 means we prefer the opposite direction.
- Output:
- -stitching direction for travel_line
- -list of tuples (one tuple per child). The tuple structure is:
- ((nearest point in travel_line, nearest point in child),
- distance along travel_line, belonging child)
- """
-
- children_nearest_points = []
-
- for child in children_list:
- result = get_nearest_points_closer_than_thresh(travel_line, tree.nodes[child].val, threshold)
- if result is None:
- # where holes meet outer borders a distance
- # up to 2 * used offset can arise
- result = get_nearest_points_closer_than_thresh(travel_line, tree.nodes[child].val, threshold_hard)
-
- proj = travel_line.project(result[0])
- children_nearest_points.append(
- nearest_neighbor_tuple(
- nearest_point_parent=result[0],
- nearest_point_child=result[1],
- proj_distance_parent=proj,
- child_node=child,
- )
- )
-
- return children_nearest_points
-
-
-def connect_raster_tree_from_inner_to_outer(tree, node, offset, stitch_distance, starting_point,
- avoid_self_crossing, forward=True):
- """
- Takes the offset curves organized as a tree, connects and samples them.
- Strategy: A connection from parent to child is made as fast as possible to
- reach the innermost child as fast as possible in order to stitch afterwards
- from inner to outer.
- Input:
- -tree: contains the offsetted curves in a hierachical organized
- data structure.
- -used_offset: used offset when the offsetted curves were generated
- -stitch_distance: maximum allowed distance between two points
- after sampling
- -min_stitch_distance stitches within a row shall be at least min_stitch_distance apart. Stitches connecting
- offsetted paths might be shorter.
- -close_point: defines the beginning point for stitching
- (stitching starts always from the undisplaced curve)
- -offset_by_half: If true the resulting points are interlaced otherwise not.
- Returnvalues:
- -All offsetted curves connected to one line and sampled with points obeying
- stitch_distance and offset_by_half
- -Tag (origin) of each point to analyze why a point was placed
- at this position
- """
-
- current_node = tree.nodes[node]
- current_ring = current_node.val
-
- if not forward and avoid_self_crossing:
- current_ring = reverse_line_string(current_ring)
-
- # reorder the coordinates of this ring so that it starts with
- # a point nearest the starting_point
- start_distance = current_ring.project(starting_point)
- current_ring = roll_linear_ring(current_ring, start_distance)
- current_node.val = current_ring
-
- # Find where along this ring to connect to each child.
- nearest_points_list = create_nearest_points_list(
- current_ring,
- tree,
- tree[node],
- constants.offset_factor_for_adjacent_geometry * offset,
- 2.05 * offset
- )
- nearest_points_list.sort(key=lambda tup: tup.proj_distance_parent)
-
- result_coords = []
- if not nearest_points_list:
- # We have no children, so we're at the center of a spiral. Reversing
- # the ring gives a nicer visual appearance.
- # current_ring = reverse_line_string(current_ring)
- pass
- else:
- # This is a recursive algorithm. We'll stitch along this ring, pausing
- # to jump to each child ring in turn and sew it before continuing on
- # this ring. We'll end back where we started.
-
- result_coords.append(current_ring.coords[0])
- distance_so_far = 0
- for child_connection in nearest_points_list:
- # Cut this ring into pieces before and after where this child will connect.
- before, after = cut(current_ring, child_connection.proj_distance_parent - distance_so_far)
- distance_so_far += child_connection.proj_distance_parent
-
- # Stitch the part leading up to this child.
- if before is not None:
- result_coords.extend(before.coords)
-
- # Stitch this child. The child will start and end in the same
- # place, which should be close to our current location.
- child_path = connect_raster_tree_from_inner_to_outer(
- tree,
- child_connection.child_node,
- offset,
- stitch_distance,
- child_connection.nearest_point_child,
- avoid_self_crossing,
- not forward
- )
- result_coords.extend(child_path.coords)
-
- # Skip ahead a little bit on this ring before resuming. This
- # gives a nice spiral pattern, where we spiral out from the
- # innermost child.
- if after is not None:
- skip, after = cut(after, offset)
- distance_so_far += offset
-
- current_ring = after
-
- if current_ring is not None:
- # skip a little at the end so we don't end exactly where we started.
- remaining_length = current_ring.length
- if remaining_length > offset:
- current_ring, skip = cut(current_ring, current_ring.length - offset)
-
- result_coords.extend(current_ring.coords)
-
- return LineString(result_coords)
-
-
-def reorder_linear_ring(ring, start):
- distances = ring - start
- start_index = np.argmin(np.linalg.norm(distances, axis=1))
- return np.roll(ring, -start_index, axis=0)
-
-
-def interpolate_linear_rings(ring1, ring2, max_stitch_length, start=None):
- """
- Interpolate between two LinearRings
-
- Creates a path from start_point on ring1 and around the rings, ending at a
- nearby point on ring2. The path will smoothly transition from ring1 to
- ring2 as it travels around the rings.
-
- Inspired by interpolate() from https://github.com/mikedh/pocketing/blob/master/pocketing/polygons.py
-
- Arguments:
- ring1 -- LinearRing start point will lie on
- ring2 -- LinearRing end point will lie on
- max_stitch_length -- maximum stitch length (used to calculate resampling accuracy)
- start -- Point on ring1 to start at, as a tuple
-
- Return value: Path interpolated between two LinearRings, as a LineString.
- """
-
- # Resample the two LinearRings so that they are the same number of points
- # long. Then take the corresponding points in each ring and interpolate
- # between them, gradually going more toward ring2.
- #
- # This is a little less accurate than the method in interpolate(), but several
- # orders of magnitude faster because we're not building and querying a KDTree.
-
- num_points = int(20 * ring1.length / max_stitch_length)
- ring1_resampled = trimesh.path.traversal.resample_path(np.array(ring1.coords), count=num_points)
- ring2_resampled = trimesh.path.traversal.resample_path(np.array(ring2.coords), count=num_points)
-
- if start is not None:
- ring1_resampled = reorder_linear_ring(ring1_resampled, start)
- ring2_resampled = reorder_linear_ring(ring2_resampled, start)
-
- weights = np.linspace(0.0, 1.0, num_points).reshape((-1, 1))
- points = (ring1_resampled * (1.0 - weights)) + (ring2_resampled * weights)
- result = LineString(points)
-
- # TODO: remove when rastering is cheaper
- return result.simplify(constants.simplification_threshold, False)
-
-
-def connect_raster_tree_single_spiral(tree, used_offset, stitch_distance, close_point): # noqa: C901
- """
- Takes the offsetted curves organized as tree, connects and samples them as a spiral.
- It expects that each node in the tree has max. one child
- Input:
- -tree: contains the offsetted curves in a hierarchical organized
- data structure.
- -used_offset: used offset when the offsetted curves were generated
- -stitch_distance: maximum allowed distance between two points
- after sampling
- -min_stitch_distance stitches within a row shall be at least min_stitch_distance apart. Stitches connecting
- offsetted paths might be shorter.
- -close_point: defines the beginning point for stitching
- (stitching starts always from the undisplaced curve)
- -offset_by_half: If true the resulting points are interlaced otherwise not.
- Return values:
- -All offsetted curves connected to one spiral and sampled with
- points obeying stitch_distance and offset_by_half
- -Tag (origin) of each point to analyze why a point was
- placed at this position
- """
-
- starting_point = close_point.coords[0]
-
- rings = [tree.nodes[node].val for node in nx.dfs_preorder_nodes(tree, 'root')]
-
- path = make_spiral(rings, stitch_distance, starting_point)
- path = [Stitch(*stitch) for stitch in path]
-
- return running_stitch(path, stitch_distance)
-
-
-def connect_raster_tree_double_spiral(tree, used_offset, stitch_distance, close_point): # noqa: C901
- """
- Takes the offsetted curves organized as tree, connects and samples them as a spiral.
- It expects that each node in the tree has max. one child
- Input:
- -tree: contains the offsetted curves in a hierarchical organized
- data structure.
- -used_offset: used offset when the offsetted curves were generated
- -stitch_distance: maximum allowed distance between two points
- after sampling
- -min_stitch_distance stitches within a row shall be at least min_stitch_distance apart. Stitches connecting
- offsetted paths might be shorter.
- -close_point: defines the beginning point for stitching
- (stitching starts always from the undisplaced curve)
- -offset_by_half: If true the resulting points are interlaced otherwise not.
- Return values:
- -All offsetted curves connected to one spiral and sampled with
- points obeying stitch_distance and offset_by_half
- -Tag (origin) of each point to analyze why a point was
- placed at this position
- """
-
- starting_point = close_point.coords[0]
-
- rings = [tree.nodes[node].val for node in nx.dfs_preorder_nodes(tree, 'root')]
-
- path = make_fermat_spiral(rings, stitch_distance, starting_point)
- path = [Stitch(*stitch) for stitch in path]
-
- return running_stitch(path, stitch_distance)
-
-
-def make_fermat_spiral(rings, stitch_distance, starting_point):
- forward = make_spiral(rings[::2], stitch_distance, starting_point)
- back = make_spiral(rings[1::2], stitch_distance, starting_point)
- back.reverse()
-
- return chain(forward, back)
-
-
-def make_spiral(rings, stitch_distance, starting_point):
- path = []
-
- for ring1, ring2 in zip(rings[:-1], rings[1:]):
- spiral_part = interpolate_linear_rings(ring1, ring2, stitch_distance, starting_point)
- path.extend(spiral_part.coords)
-
- return path