Skip to content

Commit

Permalink
Add tol to cut and intersect (#1332)
Browse files Browse the repository at this point in the history
* Add tol to cut and intersect

* Implement compound overloads

* Black fix

* Add tol to wokrplane methods

* Test fuzzy cut and intersect

* Cover fuzzy ops for compounds too

* More coverage
  • Loading branch information
adam-urbanczyk authored May 10, 2023
1 parent 3c576f4 commit 95f37fd
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 11 deletions.
16 changes: 12 additions & 4 deletions cadquery/cq.py
Original file line number Diff line number Diff line change
Expand Up @@ -3331,13 +3331,17 @@ def __add__(self: T, toUnion: Union["Workplane", Solid, Compound]) -> T:
return self.union(toUnion)

def cut(
self: T, toCut: Union["Workplane", Solid, Compound], clean: bool = True
self: T,
toCut: Union["Workplane", Solid, Compound],
clean: bool = True,
tol: Optional[float] = None,
) -> T:
"""
Cuts the provided solid from the current solid, IE, perform a solid subtraction.
:param toCut: a solid object, or a Workplane object having a solid
:param clean: call :meth:`clean` afterwards to have a clean shape
:param tol: tolerance value for fuzzy bool operation mode (default None)
:raises ValueError: if there is no solid to subtract from in the chain
:return: a Workplane object with the resulting object selected
"""
Expand All @@ -3355,7 +3359,7 @@ def cut(
else:
raise ValueError("Cannot cut type '{}'".format(type(toCut)))

newS = solidRef.cut(*solidToCut)
newS = solidRef.cut(*solidToCut, tol=tol)

if clean:
newS = newS.clean()
Expand All @@ -3377,13 +3381,17 @@ def __sub__(self: T, toUnion: Union["Workplane", Solid, Compound]) -> T:
return self.cut(toUnion)

def intersect(
self: T, toIntersect: Union["Workplane", Solid, Compound], clean: bool = True,
self: T,
toIntersect: Union["Workplane", Solid, Compound],
clean: bool = True,
tol: Optional[float] = None,
) -> T:
"""
Intersects the provided solid from the current solid.
:param toIntersect: a solid object, or a Workplane object having a solid
:param clean: call :meth:`clean` afterwards to have a clean shape
:param tol: tolerance value for fuzzy bool operation mode (default None)
:raises ValueError: if there is no solid to intersect with in the chain
:return: a Workplane object with the resulting object selected
"""
Expand All @@ -3401,7 +3409,7 @@ def intersect(
else:
raise ValueError("Cannot intersect type '{}'".format(type(toIntersect)))

newS = solidRef.intersect(*solidToIntersect)
newS = solidRef.intersect(*solidToIntersect, tol=tol)

if clean:
newS = newS.clean()
Expand Down
36 changes: 29 additions & 7 deletions cadquery/occ_impl/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1053,13 +1053,18 @@ def _bool_op(

return Shape.cast(op.Shape())

def cut(self, *toCut: "Shape") -> "Shape":
def cut(self, *toCut: "Shape", tol: Optional[float] = None) -> "Shape":
"""
Remove the positional arguments from this Shape.
:param tol: Fuzzy mode tolerance
"""

cut_op = BRepAlgoAPI_Cut()

if tol:
cut_op.SetFuzzyValue(tol)

return self._bool_op((self,), toCut, cut_op)

def fuse(
Expand All @@ -1070,7 +1075,7 @@ def fuse(
:param glue: Sets the glue option for the algorithm, which allows
increasing performance of the intersection of the input shapes
:param tol: Additional tolerance
:param tol: Fuzzy mode tolerance
"""

fuse_op = BRepAlgoAPI_Fuse()
Expand All @@ -1083,13 +1088,18 @@ def fuse(

return rv

def intersect(self, *toIntersect: "Shape") -> "Shape":
def intersect(self, *toIntersect: "Shape", tol: Optional[float] = None) -> "Shape":
"""
Intersection of the positional arguments and this Shape.
:param tol: Fuzzy mode tolerance
"""

intersect_op = BRepAlgoAPI_Common()

if tol:
intersect_op.SetFuzzyValue(tol)

return self._bool_op((self,), toIntersect, intersect_op)

def facesIntersectedByLine(
Expand Down Expand Up @@ -3582,13 +3592,18 @@ def __bool__(self) -> bool:

return TopoDS_Iterator(self.wrapped).More()

def cut(self, *toCut: Shape) -> "Compound":
def cut(self, *toCut: "Shape", tol: Optional[float] = None) -> "Compound":
"""
Remove a shape from another one
Remove the positional arguments from this Shape.
:param tol: Fuzzy mode tolerance
"""

cut_op = BRepAlgoAPI_Cut()

if tol:
cut_op.SetFuzzyValue(tol)

return tcast(Compound, self._bool_op(self, toCut, cut_op))

def fuse(
Expand Down Expand Up @@ -3616,13 +3631,20 @@ def fuse(

return tcast(Compound, rv)

def intersect(self, *toIntersect: Shape) -> "Compound":
def intersect(
self, *toIntersect: "Shape", tol: Optional[float] = None
) -> "Compound":
"""
Construct shape intersection
Intersection of the positional arguments and this Shape.
:param tol: Fuzzy mode tolerance
"""

intersect_op = BRepAlgoAPI_Common()

if tol:
intersect_op.SetFuzzyValue(tol)

return tcast(Compound, self._bool_op(self, toIntersect, intersect_op))


Expand Down
27 changes: 27 additions & 0 deletions tests/test_cadquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -4598,6 +4598,7 @@ def testFuzzyBoolOp(self):

eps = 1e-3

# test fuse
box1 = Workplane("XY").box(1, 1, 1)
box2 = Workplane("XY", origin=(1 + eps, 0.0)).box(1, 1, 1)
box3 = Workplane("XY", origin=(2, 0, 0)).box(1, 1, 1)
Expand All @@ -4610,6 +4611,32 @@ def testFuzzyBoolOp(self):
self.assertEqual(res_fuzzy.solids().size(), 1)
self.assertEqual(res_fuzzy2.solids().size(), 1)

# test cut and intersect
box4 = Workplane("XY", origin=(eps, 0.0)).box(1, 1, 1)

res_fuzzy_cut = box1.cut(box4, tol=eps)
res_fuzzy_intersect = box1.intersect(box4, tol=eps)

self.assertAlmostEqual(res_fuzzy_cut.val().Volume(), 0)
self.assertAlmostEqual(res_fuzzy_intersect.val().Volume(), 1)

# test with compounds
box1_cmp = Compound.makeCompound(box1.vals())
box4_cmp = Compound.makeCompound(box4.vals())

res_fuzzy_cut_cmp = box1_cmp.cut(box4_cmp, tol=eps)
res_fuzzy_intersect_cmp = box1_cmp.intersect(box4_cmp, tol=eps)

self.assertAlmostEqual(res_fuzzy_cut_cmp.Volume(), 0)
self.assertAlmostEqual(res_fuzzy_intersect_cmp.Volume(), 1)

# test with solids
res_fuzzy_cut_val = box1.val().cut(box4.val(), tol=eps)
res_fuzzy_intersect_val = box1.val().intersect(box4.val(), tol=eps)

self.assertAlmostEqual(res_fuzzy_cut_val.Volume(), 0)
self.assertAlmostEqual(res_fuzzy_intersect_val.Volume(), 1)

def testLocatedMoved(self):

box = Solid.makeBox(1, 1, 1, Vector(-0.5, -0.5, -0.5))
Expand Down

0 comments on commit 95f37fd

Please sign in to comment.