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):
"""Line Object: Fillet Polyline
Create a sequence of straight lines defined by successive points that are filleted
to a given radius.
Args:
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.
Defaults to False
mode (Mode, optional): combination mode. Defaults to Mode.ADD
Raises:
ValueError: Two or more points not provided
ValueError: radius must be positive
ValueError: radius must be non-negative
"""
_applies_to = [BuildLine._tag]
@ -812,9 +814,9 @@ class FilletPolyline(BaseLineObject):
close: bool = False,
mode: Mode = Mode.ADD,
):
context: BuildLine | None = BuildLine._get_context(self)
validate_inputs(context, self)
points = flatten_sequence(*pts)
if len(points) < 2:
@ -822,30 +824,35 @@ class FilletPolyline(BaseLineObject):
if isinstance(radius, (int, float)):
radius_list = [radius] * len(points) # Single radius for all points
else:
radius_list = list(radius)
if len(radius_list) != len(points) - int(not close) * 2:
raise ValueError(
f"radius list length ({len(radius_list)}) must match angle count ({ len(points) - int(not close) * 2})"
)
for r in radius_list:
if r <= 0:
raise ValueError(f"radius {r} must be positive")
if r < 0:
raise ValueError(f"radius {r} must be non-negative")
lines_pts = WorkplaneList.localize(*points)
# Create the polyline
new_edges = [
Edge.make_line(lines_pts[i], lines_pts[i + 1])
for i in range(len(lines_pts) - 1)
]
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))
wire_of_lines = Wire(new_edges)
# Create a list of vertices from wire_of_lines in the same order as
# the original points so the resulting fillet edges are ordered
ordered_vertices = []
for pnts in lines_pts:
distance = {
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])
# Fillet the corners
# Create a map of vertices to edges containing that vertex
vertex_to_edges = {
v: [e for e in wire_of_lines.edges() if v in e.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 = []
for i, (vertex, edges) in enumerate(vertex_to_edges.items()):
if len(edges) != 2:
continue
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(
radius_list[i - int(not close)], [vertex]
)
fillets.append(fillet_face.edges().filter_by(GeomType.CIRCLE)[0])
current_radius = radius_list[i - int(not close)]
if current_radius == 0:
# For 0 radius, store the vertex as a marker for a sharp corner
fillets.append(None)
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
if close:
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),
]
interior_edges = []
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)
class JernArc(BaseEdgeObject):
"""Line Object: Jern Arc