Skip to content

Commit 3451007

Browse files
dovadam-urbanczyk
andauthored
Add support for 2D and 3D Wire fillet (#1549)
* Add support for 2D and 3D Wire fillet * Change wire fillet signature to vertex coordinate list instead of index list * Fix type error in Wire.fillet * Simplify logic a little bit * black fix * Trying to fix the test * Additional methods for Wire * Fix formatting and logic * Fix for closed wires * Fix test * Remove unused methods * Fix typo in shapes.Wire.fillet comment --------- Co-authored-by: AU <[email protected]>
1 parent 4f2cec0 commit 3451007

File tree

2 files changed

+110
-0
lines changed

2 files changed

+110
-0
lines changed

cadquery/occ_impl/shapes.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,8 @@
265265

266266
from OCP.BRepAlgo import BRepAlgo
267267

268+
from OCP.ChFi2d import ChFi2d_FilletAPI # For Wire.Fillet()
269+
268270
from math import pi, sqrt, inf, radians, cos
269271

270272
import warnings
@@ -2379,6 +2381,90 @@ def chamfer2D(self, d: float, vertices: Iterable[Vertex]) -> "Wire":
23792381

23802382
return f.chamfer2D(d, vertices).outerWire()
23812383

2384+
def fillet(
2385+
self, radius: float, vertices: Optional[Iterable[Vertex]] = None
2386+
) -> "Wire":
2387+
"""
2388+
Apply 2D or 3D fillet to a wire
2389+
:param wire: The input wire to fillet. Currently only open wires are supported
2390+
:param radius: the radius of the fillet, must be > zero
2391+
:param vertices: Optional list of vertices to fillet. By default all vertices are fillet.
2392+
:return: A wire with filleted corners
2393+
"""
2394+
2395+
edges = list(self)
2396+
all_vertices = self.Vertices()
2397+
newEdges = []
2398+
currentEdge = edges[0]
2399+
2400+
verticesSet = set(vertices) if vertices else set()
2401+
2402+
for i in range(len(edges) - 1):
2403+
nextEdge = edges[i + 1]
2404+
2405+
# Create a plane that is spanned by currentEdge and nextEdge
2406+
currentDir = currentEdge.tangentAt(1)
2407+
nextDir = nextEdge.tangentAt(0)
2408+
normalDir = currentDir.cross(nextDir)
2409+
2410+
# Check conditions for skipping fillet:
2411+
# 1. The edges are parallel
2412+
# 2. The vertex is not in the vertices white list
2413+
if normalDir.Length == 0 or (
2414+
all_vertices[i + 1] not in verticesSet and bool(verticesSet)
2415+
):
2416+
newEdges.append(currentEdge)
2417+
currentEdge = nextEdge
2418+
continue
2419+
2420+
# Prepare for using ChFi2d_FilletAPI
2421+
pointInPlane = currentEdge.Center().toPnt()
2422+
cornerPlane = gp_Pln(pointInPlane, normalDir.toDir())
2423+
2424+
filletMaker = ChFi2d_FilletAPI(
2425+
currentEdge.wrapped, nextEdge.wrapped, cornerPlane
2426+
)
2427+
2428+
ok = filletMaker.Perform(radius)
2429+
if not ok:
2430+
raise ValueError(f"Failed fillet at vertex {i+1}!")
2431+
2432+
# Get the result of the fillet operation
2433+
thePoint = next(iter(nextEdge)).Center().toPnt()
2434+
res_arc = filletMaker.Result(
2435+
thePoint, currentEdge.wrapped, nextEdge.wrapped
2436+
)
2437+
2438+
newEdges.append(currentEdge)
2439+
newEdges.append(Edge(res_arc))
2440+
2441+
currentEdge = nextEdge
2442+
2443+
# Add the last edge
2444+
newEdges.append(currentEdge)
2445+
2446+
return Wire.assembleEdges(newEdges)
2447+
2448+
def Vertices(self) -> List[Vertex]:
2449+
"""
2450+
Ordered list of vertices of the wire.
2451+
"""
2452+
2453+
rv = []
2454+
2455+
exp = BRepTools_WireExplorer(self.wrapped)
2456+
rv.append(Vertex(exp.CurrentVertex()))
2457+
2458+
while exp.More():
2459+
exp.Next()
2460+
rv.append(Vertex(exp.CurrentVertex()))
2461+
2462+
# handle closed wires correclty
2463+
if self.IsClosed():
2464+
rv = rv[:-1]
2465+
2466+
return rv
2467+
23822468
def __iter__(self) -> Iterator[Edge]:
23832469
"""
23842470
Iterate over edges in an ordered way.

tests/test_cad_objects.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,30 @@ def testEdgeWrapperRadius(self):
742742
)
743743
self.assertAlmostEqual(many_rad.radius(), 1.0)
744744

745+
def testWireFillet(self):
746+
points = [
747+
(0.000, 0.000, 0.000),
748+
(-0.287, 1.183, -0.592),
749+
(-1.404, 4.113, -2.787),
750+
(-1.332, 1.522, 0.553),
751+
(7.062, 0.433, -0.097),
752+
(8.539, -0.000, -0.000),
753+
]
754+
wire = Wire.makePolygon(points, close=False)
755+
756+
# Fillet the wire
757+
wfillet = wire.fillet(radius=0.560)
758+
assert len(wfillet.Edges()) == 2 * len(points) - 3
759+
760+
# Fillet a single vertex
761+
wfillet = wire.fillet(radius=0.560, vertices=wire.Vertices()[1:2])
762+
assert len(wfillet.Edges()) == len(points)
763+
764+
# Assert exception if trying to fillet with too big
765+
# a radius
766+
with self.assertRaises(ValueError):
767+
wfillet = wire.fillet(radius=1.0)
768+
745769

746770
@pytest.mark.parametrize(
747771
"points, close, expected_edges",

0 commit comments

Comments
 (0)