kopia lustrzana https://github.com/inkstitch/inkstitch
bug fixing+first spiral implementation
rodzic
8966fa1919
commit
d445b38629
|
@ -61,7 +61,7 @@ class AutoFill(EmbroideryElement):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@param('tangential_strategy', _('Tangential strategy'), type='dropdown', default=1,
|
@param('tangential_strategy', _('Tangential strategy'), type='dropdown', default=1,
|
||||||
options=[_("Closest point"), _("Inner to Outer")], select_items=[('fill_method', 1)], sort_index=2)
|
options=[_("Closest point"), _("Inner to Outer"), _("single Spiral")], select_items=[('fill_method', 1)], sort_index=2)
|
||||||
def tangential_strategy(self):
|
def tangential_strategy(self):
|
||||||
return self.get_int_param('tangential_strategy', 1)
|
return self.get_int_param('tangential_strategy', 1)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,12 @@ from shapely.geometry import Point, MultiPoint
|
||||||
from shapely.ops import nearest_points
|
from shapely.ops import nearest_points
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from depq import DEPQ
|
from depq import DEPQ
|
||||||
|
import trimesh
|
||||||
|
import numpy as np
|
||||||
|
from scipy import spatial
|
||||||
import math
|
import math
|
||||||
|
from shapely.geometry import asLineString
|
||||||
|
from anytree import PreOrderIter
|
||||||
from ..stitches import LineStringSampling
|
from ..stitches import LineStringSampling
|
||||||
from ..stitches import PointTransfer
|
from ..stitches import PointTransfer
|
||||||
from ..stitches import constants
|
from ..stitches import constants
|
||||||
|
@ -48,8 +53,7 @@ def cut(line, distance):
|
||||||
|
|
||||||
|
|
||||||
def connect_raster_tree_nearest_neighbor(
|
def connect_raster_tree_nearest_neighbor(
|
||||||
tree, used_offset, stitch_distance, close_point, offset_by_half
|
tree, used_offset, stitch_distance, close_point, offset_by_half):
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Takes the offsetted curves organized as tree, connects and samples them.
|
Takes the offsetted curves organized as tree, connects and samples them.
|
||||||
Strategy: A connection from parent to child is made where both curves
|
Strategy: A connection from parent to child is made where both curves
|
||||||
|
@ -338,8 +342,7 @@ def get_nearest_points_closer_than_thresh(travel_line, next_line, thresh):
|
||||||
|
|
||||||
|
|
||||||
def create_nearest_points_list(
|
def create_nearest_points_list(
|
||||||
travel_line, children_list, threshold, threshold_hard, preferred_direction=0
|
travel_line, children_list, threshold, threshold_hard, preferred_direction=0):
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Takes a line and calculates the nearest distance along this line to
|
Takes a line and calculates the nearest distance along this line to
|
||||||
enter the childs in children_list
|
enter the childs in children_list
|
||||||
|
@ -456,8 +459,7 @@ def calculate_replacing_middle_point(line_segment, abs_offset, max_stitch_distan
|
||||||
|
|
||||||
|
|
||||||
def connect_raster_tree_from_inner_to_outer(
|
def connect_raster_tree_from_inner_to_outer(
|
||||||
tree, used_offset, stitch_distance, close_point, offset_by_half
|
tree, used_offset, stitch_distance, close_point, offset_by_half):
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Takes the offsetted curves organized as tree, connects and samples them.
|
Takes the offsetted curves organized as tree, connects and samples them.
|
||||||
Strategy: A connection from parent to child is made as fast as possible to
|
Strategy: A connection from parent to child is made as fast as possible to
|
||||||
|
@ -772,3 +774,153 @@ def connect_raster_tree_from_inner_to_outer(
|
||||||
|
|
||||||
assert len(result_coords) == len(result_coords_origin)
|
assert len(result_coords) == len(result_coords_origin)
|
||||||
return result_coords, result_coords_origin
|
return result_coords, result_coords_origin
|
||||||
|
|
||||||
|
|
||||||
|
# Partly taken from https://github.com/mikedh/pocketing/blob/master/pocketing/polygons.py
|
||||||
|
def interpolate_LinearRings(a, b, start=None, step=.005):
|
||||||
|
"""
|
||||||
|
Interpolate between two LinearRings
|
||||||
|
Parameters
|
||||||
|
-------------
|
||||||
|
a : shapely.geometry.Polygon.LinearRing
|
||||||
|
LinearRing start point will lie on
|
||||||
|
b : shapely.geometry.Polygon.LinearRing
|
||||||
|
LinearRing end point will lie on
|
||||||
|
start : (2,) float, or None
|
||||||
|
Point to start at
|
||||||
|
step : float
|
||||||
|
How far apart should points on
|
||||||
|
the path be.
|
||||||
|
Returns
|
||||||
|
-------------
|
||||||
|
path : (n, 2) float
|
||||||
|
Path interpolated between two LinearRings
|
||||||
|
"""
|
||||||
|
|
||||||
|
# resample the first LinearRing so every sample is spaced evenly
|
||||||
|
ra = trimesh.path.traversal.resample_path(
|
||||||
|
a, step=step)
|
||||||
|
if not a.is_ccw:
|
||||||
|
ra = ra[::-1]
|
||||||
|
|
||||||
|
assert trimesh.path.util.is_ccw(ra)
|
||||||
|
if start is not None:
|
||||||
|
# find the closest index on LinerRing 'a'
|
||||||
|
# by creating a KDTree
|
||||||
|
tree_a = spatial.cKDTree(ra)
|
||||||
|
index = tree_a.query(start)[1]
|
||||||
|
ra = np.roll(ra, -index, axis=0)
|
||||||
|
|
||||||
|
# resample the second LinearRing for even spacing
|
||||||
|
rb = trimesh.path.traversal.resample_path(b,
|
||||||
|
step=step)
|
||||||
|
if not b.is_ccw:
|
||||||
|
rb = rb[::-1]
|
||||||
|
|
||||||
|
# we want points on 'b' that correspond index- wise
|
||||||
|
# the resampled points on 'a'
|
||||||
|
tree_b = spatial.cKDTree(rb)
|
||||||
|
# points on b with corresponding indexes to ra
|
||||||
|
pb = rb[tree_b.query(ra)[1]]
|
||||||
|
|
||||||
|
# linearly interpolate between 'a' and 'b'
|
||||||
|
weights = np.linspace(0.0, 1.0, len(ra)).reshape((-1, 1))
|
||||||
|
|
||||||
|
# start on 'a' and end on 'b'
|
||||||
|
points = (ra * (1.0 - weights)) + (pb * weights)
|
||||||
|
|
||||||
|
result = LineString(points)
|
||||||
|
|
||||||
|
return result.simplify(constants.simplification_threshold, False)
|
||||||
|
|
||||||
|
|
||||||
|
def connect_raster_tree_spiral(
|
||||||
|
tree, used_offset, stitch_distance, close_point, offset_by_half):
|
||||||
|
"""
|
||||||
|
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 hierachical organized
|
||||||
|
data structure.
|
||||||
|
-used_offset: used offset when the offsetted curves were generated
|
||||||
|
-stitch_distance: maximum allowed distance between two points
|
||||||
|
after sampling
|
||||||
|
-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 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
|
||||||
|
"""
|
||||||
|
|
||||||
|
abs_offset = abs(used_offset)
|
||||||
|
if tree.is_leaf:
|
||||||
|
return LineStringSampling.raster_line_string_with_priority_points(
|
||||||
|
tree.val,
|
||||||
|
0,
|
||||||
|
tree.val.length,
|
||||||
|
stitch_distance,
|
||||||
|
tree.transferred_point_priority_deque,
|
||||||
|
abs_offset,
|
||||||
|
offset_by_half,
|
||||||
|
False)
|
||||||
|
|
||||||
|
result_coords = []
|
||||||
|
result_coords_origin = []
|
||||||
|
starting_point = close_point.coords[0]
|
||||||
|
# iterate to the second last level
|
||||||
|
for node in PreOrderIter(tree, stop=lambda n: n.is_leaf):
|
||||||
|
ring1 = node.val
|
||||||
|
ring2 = node.children[0].val
|
||||||
|
|
||||||
|
part_spiral = interpolate_LinearRings(
|
||||||
|
ring1, ring2, starting_point)
|
||||||
|
|
||||||
|
(own_coords, own_coords_origin) = LineStringSampling.raster_line_string_with_priority_points(
|
||||||
|
part_spiral,
|
||||||
|
0,
|
||||||
|
part_spiral.length,
|
||||||
|
stitch_distance,
|
||||||
|
node.transferred_point_priority_deque,
|
||||||
|
abs_offset,
|
||||||
|
offset_by_half,
|
||||||
|
False)
|
||||||
|
|
||||||
|
PointTransfer.transfer_points_to_surrounding(
|
||||||
|
node,
|
||||||
|
used_offset,
|
||||||
|
offset_by_half,
|
||||||
|
own_coords,
|
||||||
|
own_coords_origin,
|
||||||
|
overnext_neighbor=False,
|
||||||
|
transfer_forbidden_points=False,
|
||||||
|
transfer_to_parent=False,
|
||||||
|
transfer_to_sibling=False,
|
||||||
|
transfer_to_child=True)
|
||||||
|
|
||||||
|
# We transfer also to the overnext child to get a more straight
|
||||||
|
# arrangement of points perpendicular to the stitching lines
|
||||||
|
if offset_by_half:
|
||||||
|
PointTransfer.transfer_points_to_surrounding(
|
||||||
|
node,
|
||||||
|
used_offset,
|
||||||
|
False,
|
||||||
|
own_coords,
|
||||||
|
own_coords_origin,
|
||||||
|
overnext_neighbor=True,
|
||||||
|
transfer_forbidden_points=False,
|
||||||
|
transfer_to_parent=False,
|
||||||
|
transfer_to_sibling=False,
|
||||||
|
transfer_to_child=True)
|
||||||
|
|
||||||
|
result_coords.extend(own_coords)
|
||||||
|
result_coords_origin.extend(own_coords_origin)
|
||||||
|
|
||||||
|
# make sure the next section starts where this
|
||||||
|
# section of the curve ends
|
||||||
|
starting_point = own_coords[-1]
|
||||||
|
|
||||||
|
assert len(result_coords) == len(result_coords_origin)
|
||||||
|
return result_coords, result_coords_origin
|
||||||
|
|
|
@ -139,32 +139,35 @@ def raster_line_string_with_priority_points(line, start_distance, end_distance,
|
||||||
angles[0] = 1.1*constants.limiting_angle
|
angles[0] = 1.1*constants.limiting_angle
|
||||||
angles[-1] = 1.1*constants.limiting_angle
|
angles[-1] = 1.1*constants.limiting_angle
|
||||||
|
|
||||||
current_distance = start_distance
|
current_distance = 0
|
||||||
|
last_point = Point(path_coords.coords[0])
|
||||||
# Next we merge the line points and the projected (deque) points into one list
|
# Next we merge the line points and the projected (deque) points into one list
|
||||||
merged_point_list = []
|
merged_point_list = []
|
||||||
dq_iter = 0
|
dq_iter = 0
|
||||||
for point, angle in zip(aligned_line.coords, angles):
|
for point, angle in zip(path_coords.coords, angles):
|
||||||
# if abs(point[0]-52.9) < 0.2 and abs(point[1]-183.4)< 0.2:
|
# if abs(point[0]-7) < 0.2 and abs(point[1]-3.3) < 0.2:
|
||||||
# print("GEFUNDEN")
|
# print("GEFUNDEN")
|
||||||
current_distance = aligned_line.project(Point(point))
|
current_distance += last_point.distance(Point(point))
|
||||||
while dq_iter < len(deque_points) and deque_points[dq_iter][1] < current_distance:
|
last_point = Point(point)
|
||||||
|
while dq_iter < len(deque_points) and deque_points[dq_iter][1] < current_distance+start_distance:
|
||||||
# We want to avoid setting points at soft edges close to forbidden points
|
# We want to avoid setting points at soft edges close to forbidden points
|
||||||
if deque_points[dq_iter][0].point_source == PointSource.FORBIDDEN_POINT:
|
if deque_points[dq_iter][0].point_source == PointSource.FORBIDDEN_POINT:
|
||||||
# Check whether a previous added point is a soft edge close to the forbidden point
|
# Check whether a previous added point is a soft edge close to the forbidden point
|
||||||
if (merged_point_list[-1][0].point_source == PointSource.SOFT_EDGE_INTERNAL and
|
if (merged_point_list[-1][0].point_source == PointSource.SOFT_EDGE_INTERNAL and
|
||||||
abs(merged_point_list[-1][1]-deque_points[dq_iter][1] < abs_offset*constants.factor_offset_forbidden_point)):
|
abs(merged_point_list[-1][1]-deque_points[dq_iter][1]+start_distance < abs_offset*constants.factor_offset_forbidden_point)):
|
||||||
item = merged_point_list.pop()
|
item = merged_point_list.pop()
|
||||||
merged_point_list.append((PointTransfer.projected_point_tuple(
|
merged_point_list.append((PointTransfer.projected_point_tuple(
|
||||||
point=item[0].point, point_source=PointSource.FORBIDDEN_POINT), item[1]))
|
point=item[0].point, point_source=PointSource.FORBIDDEN_POINT), item[1]-start_distance))
|
||||||
else:
|
else:
|
||||||
merged_point_list.append(deque_points[dq_iter])
|
merged_point_list.append(
|
||||||
|
(deque_points[dq_iter][0], deque_points[dq_iter][1]-start_distance))
|
||||||
|
# merged_point_list.append(deque_points[dq_iter])
|
||||||
dq_iter += 1
|
dq_iter += 1
|
||||||
# Check whether the current point is close to a forbidden point
|
# Check whether the current point is close to a forbidden point
|
||||||
if (dq_iter < len(deque_points) and
|
if (dq_iter < len(deque_points) and
|
||||||
deque_points[dq_iter-1][0].point_source == PointSource.FORBIDDEN_POINT and
|
deque_points[dq_iter-1][0].point_source == PointSource.FORBIDDEN_POINT and
|
||||||
angle < constants.limiting_angle and
|
angle < constants.limiting_angle and
|
||||||
abs(deque_points[dq_iter-1][1]-current_distance) < abs_offset*constants.factor_offset_forbidden_point):
|
abs(deque_points[dq_iter-1][1]-current_distance-start_distance) < abs_offset*constants.factor_offset_forbidden_point):
|
||||||
point_source = PointSource.FORBIDDEN_POINT
|
point_source = PointSource.FORBIDDEN_POINT
|
||||||
else:
|
else:
|
||||||
if angle < constants.limiting_angle:
|
if angle < constants.limiting_angle:
|
||||||
|
|
|
@ -9,12 +9,13 @@ from ..stitches import LineStringSampling
|
||||||
projected_point_tuple = namedtuple(
|
projected_point_tuple = namedtuple(
|
||||||
'projected_point_tuple', ['point', 'point_source'])
|
'projected_point_tuple', ['point', 'point_source'])
|
||||||
|
|
||||||
# Calculated the nearest interserction point of "bisectorline" with the coordinates of child (child.val).
|
|
||||||
# It returns the intersection point and its distance along the coordinates of the child or "None, None" if no
|
|
||||||
# intersection was found.
|
|
||||||
|
|
||||||
|
|
||||||
def calc_transferred_point(bisectorline, child):
|
def calc_transferred_point(bisectorline, child):
|
||||||
|
"""
|
||||||
|
Calculates the nearest interserction point of "bisectorline" with the coordinates of child (child.val).
|
||||||
|
It returns the intersection point and its distance along the coordinates of the child or "None, None" if no
|
||||||
|
intersection was found.
|
||||||
|
"""
|
||||||
result = bisectorline.intersection(child.val)
|
result = bisectorline.intersection(child.val)
|
||||||
if result.is_empty:
|
if result.is_empty:
|
||||||
return None, None
|
return None, None
|
||||||
|
@ -279,11 +280,10 @@ def transfer_points_to_surrounding(treenode, used_offset, offset_by_half, to_tra
|
||||||
|
|
||||||
assert(len(point_list) == len(point_source_list))
|
assert(len(point_list) == len(point_source_list))
|
||||||
|
|
||||||
# Calculated the nearest interserction point of "bisectorline" with the coordinates of child.
|
|
||||||
|
# Calculates the nearest interserction point of "bisectorline" with the coordinates of child.
|
||||||
# It returns the intersection point and its distance along the coordinates of the child or "None, None" if no
|
# It returns the intersection point and its distance along the coordinates of the child or "None, None" if no
|
||||||
# intersection was found.
|
# intersection was found.
|
||||||
|
|
||||||
|
|
||||||
def calc_transferred_point_graph(bisectorline, edge_geometry):
|
def calc_transferred_point_graph(bisectorline, edge_geometry):
|
||||||
result = bisectorline.intersection(edge_geometry)
|
result = bisectorline.intersection(edge_geometry)
|
||||||
if result.is_empty:
|
if result.is_empty:
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
from anytree.render import RenderTree
|
||||||
from shapely.geometry.polygon import LinearRing, LineString
|
from shapely.geometry.polygon import LinearRing, LineString
|
||||||
from shapely.geometry import Polygon, MultiLineString
|
from shapely.geometry import Polygon, MultiLineString
|
||||||
from shapely.ops import polygonize
|
from shapely.ops import polygonize
|
||||||
from shapely.geometry import MultiPolygon
|
from shapely.geometry import MultiPolygon
|
||||||
from anytree import AnyNode, PreOrderIter
|
from anytree import AnyNode, PreOrderIter, LevelOrderGroupIter
|
||||||
from shapely.geometry.polygon import orient
|
from shapely.geometry.polygon import orient
|
||||||
from depq import DEPQ
|
from depq import DEPQ
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
|
@ -126,6 +127,38 @@ class StitchingStrategy(IntEnum):
|
||||||
SPIRAL = 2
|
SPIRAL = 2
|
||||||
|
|
||||||
|
|
||||||
|
def check_and_prepare_tree_for_valid_spiral(root):
|
||||||
|
"""
|
||||||
|
Takes a tree consisting of offsetted curves. If a parent has more than one child we
|
||||||
|
cannot create a spiral. However, to make the routine more robust, we allow more than
|
||||||
|
one child if only one of the childs has own childs. The other childs are removed in this
|
||||||
|
routine then. If the routine returns true, the tree will have been cleaned up from unwanted
|
||||||
|
childs. If the routine returns false even under the mentioned weaker conditions the
|
||||||
|
tree cannot be connected by one spiral.
|
||||||
|
"""
|
||||||
|
for children in LevelOrderGroupIter(root):
|
||||||
|
if len(children) > 1:
|
||||||
|
count = 0
|
||||||
|
child_with_children = None
|
||||||
|
for child in children:
|
||||||
|
if not child.is_leaf:
|
||||||
|
count += 1
|
||||||
|
child_with_children = child
|
||||||
|
if count > 1:
|
||||||
|
return False
|
||||||
|
elif count == 1:
|
||||||
|
child_with_children.parent.children = [child_with_children]
|
||||||
|
else: # count == 0 means all childs have no children so we take only the longest child
|
||||||
|
max_length = 0
|
||||||
|
longest_child = None
|
||||||
|
for child in children:
|
||||||
|
if child.val.length > max_length:
|
||||||
|
max_length = child.val.length
|
||||||
|
longest_child = child
|
||||||
|
longest_child.parent.children = [longest_child]
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def offset_poly(
|
def offset_poly(
|
||||||
poly, offset, join_style, stitch_distance, offset_by_half, strategy, starting_point):
|
poly, offset, join_style, stitch_distance, offset_by_half, strategy, starting_point):
|
||||||
"""
|
"""
|
||||||
|
@ -144,13 +177,21 @@ def offset_poly(
|
||||||
-stitch_distance maximum allowed stitch distance between two points
|
-stitch_distance maximum allowed stitch distance between two points
|
||||||
-offset_by_half: True if the points shall be interlaced
|
-offset_by_half: True if the points shall be interlaced
|
||||||
-strategy: According to StitchingStrategy enum class you can select between
|
-strategy: According to StitchingStrategy enum class you can select between
|
||||||
different strategies for the connection between parent and childs
|
different strategies for the connection between parent and childs. In
|
||||||
|
addition it offers the option "SPIRAL" which creates a real spiral towards inner.
|
||||||
|
In contrast to the other two options, "SPIRAL" does not end at the starting point
|
||||||
|
but at the innermost point
|
||||||
-starting_point: Defines the starting point for the stitching
|
-starting_point: Defines the starting point for the stitching
|
||||||
Output:
|
Output:
|
||||||
-List of point coordinate tuples
|
-List of point coordinate tuples
|
||||||
-Tag (origin) of each point to analyze why a point was placed
|
-Tag (origin) of each point to analyze why a point was placed
|
||||||
at this position
|
at this position
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if strategy == StitchingStrategy.SPIRAL and len(poly.interiors) > 1:
|
||||||
|
raise ValueError(
|
||||||
|
"Single spiral geometry must not have more than one hole!")
|
||||||
|
|
||||||
ordered_poly = orient(poly, -1)
|
ordered_poly = orient(poly, -1)
|
||||||
ordered_poly = ordered_poly.simplify(
|
ordered_poly = ordered_poly.simplify(
|
||||||
constants.simplification_threshold, False)
|
constants.simplification_threshold, False)
|
||||||
|
@ -276,20 +317,105 @@ def offset_poly(
|
||||||
make_tree_uniform_ccw(root)
|
make_tree_uniform_ccw(root)
|
||||||
# print(RenderTree(root))
|
# print(RenderTree(root))
|
||||||
if strategy == StitchingStrategy.CLOSEST_POINT:
|
if strategy == StitchingStrategy.CLOSEST_POINT:
|
||||||
(
|
(connected_line, connected_line_origin) = ConnectAndSamplePattern.connect_raster_tree_nearest_neighbor(
|
||||||
connected_line,
|
root, offset, stitch_distance, starting_point, offset_by_half)
|
||||||
connected_line_origin,
|
|
||||||
) = ConnectAndSamplePattern.connect_raster_tree_nearest_neighbor(
|
|
||||||
root, offset, stitch_distance, starting_point, offset_by_half
|
|
||||||
)
|
|
||||||
elif strategy == StitchingStrategy.INNER_TO_OUTER:
|
elif strategy == StitchingStrategy.INNER_TO_OUTER:
|
||||||
(
|
(connected_line, connected_line_origin) = ConnectAndSamplePattern.connect_raster_tree_from_inner_to_outer(
|
||||||
connected_line,
|
root, offset, stitch_distance, starting_point, offset_by_half)
|
||||||
connected_line_origin,
|
elif strategy == StitchingStrategy.SPIRAL:
|
||||||
) = ConnectAndSamplePattern.connect_raster_tree_from_inner_to_outer(
|
if not check_and_prepare_tree_for_valid_spiral(root):
|
||||||
root, offset, stitch_distance, starting_point, offset_by_half
|
raise ValueError("Geometry cannot be filled with one spiral!")
|
||||||
)
|
(connected_line, connected_line_origin) = ConnectAndSamplePattern.connect_raster_tree_spiral(
|
||||||
|
root, offset, stitch_distance, starting_point, offset_by_half)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid stitching stratety!")
|
raise ValueError("Invalid stitching stratety!")
|
||||||
|
|
||||||
return connected_line, connected_line_origin
|
return connected_line, connected_line_origin
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
line1 = LineString([(0, 0), (1, 0)])
|
||||||
|
line2 = LineString([(0, 0), (3, 0)])
|
||||||
|
|
||||||
|
root = AnyNode(
|
||||||
|
id="root",
|
||||||
|
val=line1)
|
||||||
|
child1 = AnyNode(
|
||||||
|
id="node",
|
||||||
|
val=line1,
|
||||||
|
parent=root)
|
||||||
|
child2 = AnyNode(
|
||||||
|
id="node",
|
||||||
|
val=line1,
|
||||||
|
parent=root)
|
||||||
|
child3 = AnyNode(
|
||||||
|
id="node",
|
||||||
|
val=line2,
|
||||||
|
parent=root)
|
||||||
|
|
||||||
|
print(RenderTree(root))
|
||||||
|
print(check_and_prepare_tree_for_valid_spiral(root))
|
||||||
|
print(RenderTree(root))
|
||||||
|
print("---------------------------")
|
||||||
|
root = AnyNode(
|
||||||
|
id="root",
|
||||||
|
val=line1)
|
||||||
|
child1 = AnyNode(
|
||||||
|
id="node",
|
||||||
|
val=line1,
|
||||||
|
parent=root)
|
||||||
|
child2 = AnyNode(
|
||||||
|
id="node",
|
||||||
|
val=line1,
|
||||||
|
parent=root)
|
||||||
|
child3 = AnyNode(
|
||||||
|
id="node",
|
||||||
|
val=line2,
|
||||||
|
parent=child1)
|
||||||
|
print(RenderTree(root))
|
||||||
|
print(check_and_prepare_tree_for_valid_spiral(root))
|
||||||
|
print(RenderTree(root))
|
||||||
|
|
||||||
|
print("---------------------------")
|
||||||
|
root = AnyNode(
|
||||||
|
id="root",
|
||||||
|
val=line1)
|
||||||
|
child1 = AnyNode(
|
||||||
|
id="node",
|
||||||
|
val=line1,
|
||||||
|
parent=root)
|
||||||
|
child2 = AnyNode(
|
||||||
|
id="node",
|
||||||
|
val=line1,
|
||||||
|
parent=child1)
|
||||||
|
child3 = AnyNode(
|
||||||
|
id="node",
|
||||||
|
val=line2,
|
||||||
|
parent=child2)
|
||||||
|
print(RenderTree(root))
|
||||||
|
print(check_and_prepare_tree_for_valid_spiral(root))
|
||||||
|
print(RenderTree(root))
|
||||||
|
|
||||||
|
print("---------------------------")
|
||||||
|
root = AnyNode(
|
||||||
|
id="root",
|
||||||
|
val=line1)
|
||||||
|
child1 = AnyNode(
|
||||||
|
id="node",
|
||||||
|
val=line1,
|
||||||
|
parent=root)
|
||||||
|
child2 = AnyNode(
|
||||||
|
id="node",
|
||||||
|
val=line1,
|
||||||
|
parent=root)
|
||||||
|
child3 = AnyNode(
|
||||||
|
id="node",
|
||||||
|
val=line2,
|
||||||
|
parent=child1)
|
||||||
|
child4 = AnyNode(
|
||||||
|
id="node",
|
||||||
|
val=line2,
|
||||||
|
parent=child2)
|
||||||
|
print(RenderTree(root))
|
||||||
|
print(check_and_prepare_tree_for_valid_spiral(root))
|
||||||
|
print(RenderTree(root))
|
||||||
|
|
|
@ -7,7 +7,7 @@ import math
|
||||||
|
|
||||||
import shapely
|
import shapely
|
||||||
from shapely.geometry.linestring import LineString
|
from shapely.geometry.linestring import LineString
|
||||||
from shapely.ops import linemerge
|
from shapely.ops import linemerge, unary_union
|
||||||
from ..svg import PIXELS_PER_MM
|
from ..svg import PIXELS_PER_MM
|
||||||
from ..utils import Point as InkstitchPoint
|
from ..utils import Point as InkstitchPoint
|
||||||
from ..utils import cache
|
from ..utils import cache
|
||||||
|
@ -126,12 +126,34 @@ def repair_multiple_parallel_offset_curves(multi_line):
|
||||||
return lines[max_length_idx].simplify(0.01, False)
|
return lines[max_length_idx].simplify(0.01, False)
|
||||||
|
|
||||||
|
|
||||||
|
def repair_non_simple_lines(line):
|
||||||
|
repaired = unary_union(line)
|
||||||
|
counter = 0
|
||||||
|
# Do several iterations since we might have several concatenated selfcrossings
|
||||||
|
while repaired.geom_type != 'LineString' and counter < 4:
|
||||||
|
line_segments = []
|
||||||
|
for line_seg in repaired.geoms:
|
||||||
|
if not line_seg.is_ring:
|
||||||
|
line_segments.append(line_seg)
|
||||||
|
|
||||||
|
repaired = unary_union(linemerge(line_segments))
|
||||||
|
counter += 1
|
||||||
|
if repaired.geom_type != 'LineString':
|
||||||
|
raise ValueError(
|
||||||
|
"Guide line (or offsetted instance) is self crossing!")
|
||||||
|
else:
|
||||||
|
return repaired
|
||||||
|
|
||||||
|
|
||||||
def intersect_region_with_grating_line(shape, line, row_spacing, end_row_spacing=None, flip=False):
|
def intersect_region_with_grating_line(shape, line, row_spacing, end_row_spacing=None, flip=False):
|
||||||
|
|
||||||
row_spacing = abs(row_spacing)
|
row_spacing = abs(row_spacing)
|
||||||
(minx, miny, maxx, maxy) = shape.bounds
|
(minx, miny, maxx, maxy) = shape.bounds
|
||||||
upper_left = InkstitchPoint(minx, miny)
|
upper_left = InkstitchPoint(minx, miny)
|
||||||
rows = []
|
rows = []
|
||||||
|
|
||||||
|
if line.geom_type != 'LineString' or not line.is_simple:
|
||||||
|
line = repair_non_simple_lines(line)
|
||||||
# extend the line towards the ends to increase probability that all offsetted curves cross the shape
|
# extend the line towards the ends to increase probability that all offsetted curves cross the shape
|
||||||
line = extend_line(line, minx, maxx, miny, maxy)
|
line = extend_line(line, minx, maxx, miny, maxy)
|
||||||
|
|
||||||
|
@ -160,6 +182,8 @@ def intersect_region_with_grating_line(shape, line, row_spacing, end_row_spacing
|
||||||
if line_offsetted.geom_type == 'MultiLineString': # if we got multiple lines take the longest
|
if line_offsetted.geom_type == 'MultiLineString': # if we got multiple lines take the longest
|
||||||
line_offsetted = repair_multiple_parallel_offset_curves(
|
line_offsetted = repair_multiple_parallel_offset_curves(
|
||||||
line_offsetted)
|
line_offsetted)
|
||||||
|
if not line_offsetted.is_simple:
|
||||||
|
line_offsetted = repair_non_simple_lines(line_offsetted)
|
||||||
|
|
||||||
if row_spacing < 0:
|
if row_spacing < 0:
|
||||||
line_offsetted.coords = line_offsetted.coords[::-1]
|
line_offsetted.coords = line_offsetted.coords[::-1]
|
||||||
|
@ -173,6 +197,8 @@ def intersect_region_with_grating_line(shape, line, row_spacing, end_row_spacing
|
||||||
if line_offsetted.geom_type == 'MultiLineString': # if we got multiple lines take the longest
|
if line_offsetted.geom_type == 'MultiLineString': # if we got multiple lines take the longest
|
||||||
line_offsetted = repair_multiple_parallel_offset_curves(
|
line_offsetted = repair_multiple_parallel_offset_curves(
|
||||||
line_offsetted)
|
line_offsetted)
|
||||||
|
if not line_offsetted.is_simple:
|
||||||
|
line_offsetted = repair_non_simple_lines(line_offsetted)
|
||||||
# using negative row spacing leads as a side effect to reversed offsetted lines - here we undo this
|
# using negative row spacing leads as a side effect to reversed offsetted lines - here we undo this
|
||||||
line_offsetted.coords = line_offsetted.coords[::-1]
|
line_offsetted.coords = line_offsetted.coords[::-1]
|
||||||
line_offsetted = line_offsetted.simplify(0.01, False)
|
line_offsetted = line_offsetted.simplify(0.01, False)
|
||||||
|
|
|
@ -21,6 +21,8 @@ flask
|
||||||
fonttools
|
fonttools
|
||||||
anytree
|
anytree
|
||||||
depq
|
depq
|
||||||
|
trimesh
|
||||||
|
scipy
|
||||||
|
|
||||||
pywinutils; sys.platform == 'win32'
|
pywinutils; sys.platform == 'win32'
|
||||||
pywin32; sys.platform == 'win32'
|
pywin32; sys.platform == 'win32'
|
||||||
|
|
Ładowanie…
Reference in New Issue