diff --git a/cadquery/cq.py b/cadquery/cq.py index e2d6bba2a..5760056e7 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -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 """ @@ -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() @@ -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 """ @@ -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() diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index b0628ebbe..a3d4a8e9d 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -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( @@ -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() @@ -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( @@ -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( @@ -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)) diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index ea9e00784..55424f9ec 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -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) @@ -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))