Changed the FilletPolyLine to be compatible with 0-radius fillets, where it should behave like a normal Polyline

This commit is contained in:
Alex Verschoot 2025-11-16 15:48:30 +01:00
parent e92255cefc
commit dc90a4b15a

View file

@ -787,20 +787,22 @@ class Helix(BaseEdgeObject):
class FilletPolyline(BaseLineObject): class FilletPolyline(BaseLineObject):
"""Line Object: Fillet Polyline """Line Object: Fillet Polyline
Create a sequence of straight lines defined by successive points that are filleted Create a sequence of straight lines defined by successive points that are filleted
to a given radius. to a given radius.
Args: Args:
pts (VectorLike | Iterable[VectorLike]): sequence of two or more points pts (VectorLike | Iterable[VectorLike]): sequence of two or more points
radius (float | Iterable[float]): radius to fillet at each vertex or a single value for all vertices radius (float | Iterable[float]): radius to fillet at each vertex or a single value for all vertices.
A radius of 0 will create a sharp corner (vertex without fillet).
close (bool, optional): close end points with extra Edge and corner fillets. close (bool, optional): close end points with extra Edge and corner fillets.
Defaults to False Defaults to False
mode (Mode, optional): combination mode. Defaults to Mode.ADD mode (Mode, optional): combination mode. Defaults to Mode.ADD
Raises: Raises:
ValueError: Two or more points not provided ValueError: Two or more points not provided
ValueError: radius must be positive ValueError: radius must be non-negative
""" """
_applies_to = [BuildLine._tag] _applies_to = [BuildLine._tag]
@ -812,9 +814,9 @@ class FilletPolyline(BaseLineObject):
close: bool = False, close: bool = False,
mode: Mode = Mode.ADD, mode: Mode = Mode.ADD,
): ):
context: BuildLine | None = BuildLine._get_context(self) context: BuildLine | None = BuildLine._get_context(self)
validate_inputs(context, self) validate_inputs(context, self)
points = flatten_sequence(*pts) points = flatten_sequence(*pts)
if len(points) < 2: if len(points) < 2:
@ -822,30 +824,35 @@ class FilletPolyline(BaseLineObject):
if isinstance(radius, (int, float)): if isinstance(radius, (int, float)):
radius_list = [radius] * len(points) # Single radius for all points radius_list = [radius] * len(points) # Single radius for all points
else: else:
radius_list = list(radius) radius_list = list(radius)
if len(radius_list) != len(points) - int(not close) * 2: if len(radius_list) != len(points) - int(not close) * 2:
raise ValueError( raise ValueError(
f"radius list length ({len(radius_list)}) must match angle count ({ len(points) - int(not close) * 2})" f"radius list length ({len(radius_list)}) must match angle count ({ len(points) - int(not close) * 2})"
) )
for r in radius_list: for r in radius_list:
if r <= 0: if r < 0:
raise ValueError(f"radius {r} must be positive") raise ValueError(f"radius {r} must be non-negative")
lines_pts = WorkplaneList.localize(*points) lines_pts = WorkplaneList.localize(*points)
# Create the polyline # Create the polyline
new_edges = [ new_edges = [
Edge.make_line(lines_pts[i], lines_pts[i + 1]) Edge.make_line(lines_pts[i], lines_pts[i + 1])
for i in range(len(lines_pts) - 1) for i in range(len(lines_pts) - 1)
] ]
if close and (new_edges[0] @ 0 - new_edges[-1] @ 1).length > 1e-5: if close and (new_edges[0] @ 0 - new_edges[-1] @ 1).length > 1e-5:
new_edges.append(Edge.make_line(new_edges[-1] @ 1, new_edges[0] @ 0)) new_edges.append(Edge.make_line(new_edges[-1] @ 1, new_edges[0] @ 0))
wire_of_lines = Wire(new_edges) wire_of_lines = Wire(new_edges)
# Create a list of vertices from wire_of_lines in the same order as # Create a list of vertices from wire_of_lines in the same order as
# the original points so the resulting fillet edges are ordered # the original points so the resulting fillet edges are ordered
ordered_vertices = [] ordered_vertices = []
for pnts in lines_pts: for pnts in lines_pts:
distance = { distance = {
v: (Vector(pnts) - Vector(*v)).length for v in wire_of_lines.vertices() v: (Vector(pnts) - Vector(*v)).length for v in wire_of_lines.vertices()
@ -853,46 +860,90 @@ class FilletPolyline(BaseLineObject):
ordered_vertices.append(sorted(distance.items(), key=lambda x: x[1])[0][0]) ordered_vertices.append(sorted(distance.items(), key=lambda x: x[1])[0][0])
# Fillet the corners # Fillet the corners
# Create a map of vertices to edges containing that vertex # Create a map of vertices to edges containing that vertex
vertex_to_edges = { vertex_to_edges = {
v: [e for e in wire_of_lines.edges() if v in e.vertices()] v: [e for e in wire_of_lines.edges() if v in e.vertices()]
for v in ordered_vertices for v in ordered_vertices
} }
# For each corner vertex create a new fillet Edge # For each corner vertex create a new fillet Edge (or keep as vertex if radius is 0)
fillets = [] fillets = []
for i, (vertex, edges) in enumerate(vertex_to_edges.items()): for i, (vertex, edges) in enumerate(vertex_to_edges.items()):
if len(edges) != 2: if len(edges) != 2:
continue continue
other_vertices = {ve for e in edges for ve in e.vertices() if ve != vertex} current_radius = radius_list[i - int(not close)]
third_edge = Edge.make_line(*[v for v in other_vertices])
fillet_face = Face(Wire(edges + [third_edge])).fillet_2d( if current_radius == 0:
radius_list[i - int(not close)], [vertex] # For 0 radius, store the vertex as a marker for a sharp corner
) fillets.append(None)
fillets.append(fillet_face.edges().filter_by(GeomType.CIRCLE)[0])
else:
other_vertices = {ve for e in edges for ve in e.vertices() if ve != vertex}
third_edge = Edge.make_line(*[v for v in other_vertices])
fillet_face = Face(Wire(edges + [third_edge])).fillet_2d(
current_radius, [vertex]
)
fillets.append(fillet_face.edges().filter_by(GeomType.CIRCLE)[0])
# Create the Edges that join the fillets # Create the Edges that join the fillets
if close: if close:
interior_edges = [ interior_edges = []
Edge.make_line(fillets[i - 1] @ 1, fillets[i] @ 0)
for i in range(len(fillets))
]
end_edges = []
else:
interior_edges = [
Edge.make_line(fillets[i] @ 1, f @ 0) for i, f in enumerate(fillets[1:])
]
end_edges = [
Edge.make_line(wire_of_lines @ 0, fillets[0] @ 0),
Edge.make_line(fillets[-1] @ 1, wire_of_lines @ 1),
]
new_wire = Wire(end_edges + interior_edges + fillets) for i in range(len(fillets)):
prev_idx = i - 1
curr_idx = i
# Determine start and end points
if fillets[prev_idx] is None:
start_pt = ordered_vertices[prev_idx]
else:
start_pt = fillets[prev_idx] @ 1
if fillets[curr_idx] is None:
end_pt = ordered_vertices[curr_idx]
else:
end_pt = fillets[curr_idx] @ 0
interior_edges.append(Edge.make_line(start_pt, end_pt))
end_edges = []
else:
interior_edges = []
for i in range(len(fillets) - 1):
curr_idx = i
next_idx = i + 1
# Determine start and end points
if fillets[curr_idx] is None:
start_pt = ordered_vertices[curr_idx + 1] # +1 because first vertex has no fillet
else:
start_pt = fillets[curr_idx] @ 1
if fillets[next_idx] is None:
end_pt = ordered_vertices[next_idx + 1]
else:
end_pt = fillets[next_idx] @ 0
interior_edges.append(Edge.make_line(start_pt, end_pt))
# Handle end edges
if fillets[0] is None:
start_edge = Edge.make_line(wire_of_lines @ 0, ordered_vertices[1])
else:
start_edge = Edge.make_line(wire_of_lines @ 0, fillets[0] @ 0)
if fillets[-1] is None:
end_edge = Edge.make_line(ordered_vertices[-2], wire_of_lines @ 1)
else:
end_edge = Edge.make_line(fillets[-1] @ 1, wire_of_lines @ 1)
end_edges = [start_edge, end_edge]
# Filter out None values from fillets (these are 0-radius corners)
actual_fillets = [f for f in fillets if f is not None]
new_wire = Wire(end_edges + interior_edges + actual_fillets)
super().__init__(new_wire, mode=mode) super().__init__(new_wire, mode=mode)
class JernArc(BaseEdgeObject): class JernArc(BaseEdgeObject):
"""Line Object: Jern Arc """Line Object: Jern Arc