Skip to content

Commit

Permalink
Add support for 2D and 3D Wire fillet (#1549)
Browse files Browse the repository at this point in the history
* 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 <adam-urbanczyk@users.noreply.github.com>
  • Loading branch information
dov and adam-urbanczyk authored Apr 6, 2024
1 parent 4f2cec0 commit 3451007
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 0 deletions.
86 changes: 86 additions & 0 deletions cadquery/occ_impl/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@

from OCP.BRepAlgo import BRepAlgo

from OCP.ChFi2d import ChFi2d_FilletAPI # For Wire.Fillet()

from math import pi, sqrt, inf, radians, cos

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

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

def fillet(
self, radius: float, vertices: Optional[Iterable[Vertex]] = None
) -> "Wire":
"""
Apply 2D or 3D fillet to a wire
:param wire: The input wire to fillet. Currently only open wires are supported
:param radius: the radius of the fillet, must be > zero
:param vertices: Optional list of vertices to fillet. By default all vertices are fillet.
:return: A wire with filleted corners
"""

edges = list(self)
all_vertices = self.Vertices()
newEdges = []
currentEdge = edges[0]

verticesSet = set(vertices) if vertices else set()

for i in range(len(edges) - 1):
nextEdge = edges[i + 1]

# Create a plane that is spanned by currentEdge and nextEdge
currentDir = currentEdge.tangentAt(1)
nextDir = nextEdge.tangentAt(0)
normalDir = currentDir.cross(nextDir)

# Check conditions for skipping fillet:
# 1. The edges are parallel
# 2. The vertex is not in the vertices white list
if normalDir.Length == 0 or (
all_vertices[i + 1] not in verticesSet and bool(verticesSet)
):
newEdges.append(currentEdge)
currentEdge = nextEdge
continue

# Prepare for using ChFi2d_FilletAPI
pointInPlane = currentEdge.Center().toPnt()
cornerPlane = gp_Pln(pointInPlane, normalDir.toDir())

filletMaker = ChFi2d_FilletAPI(
currentEdge.wrapped, nextEdge.wrapped, cornerPlane
)

ok = filletMaker.Perform(radius)
if not ok:
raise ValueError(f"Failed fillet at vertex {i+1}!")

# Get the result of the fillet operation
thePoint = next(iter(nextEdge)).Center().toPnt()
res_arc = filletMaker.Result(
thePoint, currentEdge.wrapped, nextEdge.wrapped
)

newEdges.append(currentEdge)
newEdges.append(Edge(res_arc))

currentEdge = nextEdge

# Add the last edge
newEdges.append(currentEdge)

return Wire.assembleEdges(newEdges)

def Vertices(self) -> List[Vertex]:
"""
Ordered list of vertices of the wire.
"""

rv = []

exp = BRepTools_WireExplorer(self.wrapped)
rv.append(Vertex(exp.CurrentVertex()))

while exp.More():
exp.Next()
rv.append(Vertex(exp.CurrentVertex()))

# handle closed wires correclty
if self.IsClosed():
rv = rv[:-1]

return rv

def __iter__(self) -> Iterator[Edge]:
"""
Iterate over edges in an ordered way.
Expand Down
24 changes: 24 additions & 0 deletions tests/test_cad_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,30 @@ def testEdgeWrapperRadius(self):
)
self.assertAlmostEqual(many_rad.radius(), 1.0)

def testWireFillet(self):
points = [
(0.000, 0.000, 0.000),
(-0.287, 1.183, -0.592),
(-1.404, 4.113, -2.787),
(-1.332, 1.522, 0.553),
(7.062, 0.433, -0.097),
(8.539, -0.000, -0.000),
]
wire = Wire.makePolygon(points, close=False)

# Fillet the wire
wfillet = wire.fillet(radius=0.560)
assert len(wfillet.Edges()) == 2 * len(points) - 3

# Fillet a single vertex
wfillet = wire.fillet(radius=0.560, vertices=wire.Vertices()[1:2])
assert len(wfillet.Edges()) == len(points)

# Assert exception if trying to fillet with too big
# a radius
with self.assertRaises(ValueError):
wfillet = wire.fillet(radius=1.0)


@pytest.mark.parametrize(
"points, close, expected_edges",
Expand Down

0 comments on commit 3451007

Please sign in to comment.