diff --git a/cadquery/cq.py b/cadquery/cq.py index 0b1e4d657..1fceb9edb 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -1118,8 +1118,6 @@ def shell(self, thickness: float) -> "Workplane": solidRef = self.findSolid() faces = [f for f in self.objects if isinstance(f, Face)] - if len(faces) < len(self.objects): - raise ValueError("Shelling requires that faces are selected") s = solidRef.shell(faces, thickness) return self.newObject([s]) diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index 6b5aa6b85..e401478ee 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -10,7 +10,6 @@ Any, overload, TypeVar, - Callable, cast as tcast, ) from typing_extensions import Literal, Protocol @@ -51,6 +50,7 @@ BRepBuilderAPI_Transformed, BRepBuilderAPI_RightCorner, BRepBuilderAPI_RoundCorner, + BRepBuilderAPI_MakeSolid, ) # properties used to store mass calculation result @@ -158,10 +158,9 @@ from OCP.IFSelect import IFSelect_ReturnStatus -from OCP.TopAbs import TopAbs_ShapeEnum +from OCP.TopAbs import TopAbs_ShapeEnum, TopAbs_Orientation from math import pi, sqrt -from functools import reduce import warnings TOLERANCE = 1e-6 @@ -1563,16 +1562,35 @@ def shell( """ occ_faces_list = TopTools_ListOfShape() - for f in faceList: - occ_faces_list.Append(f.wrapped) - shell_builder = BRepOffsetAPI_MakeThickSolid( - self.wrapped, occ_faces_list, thickness, tolerance - ) + if faceList: + for f in faceList: + occ_faces_list.Append(f.wrapped) + + shell_builder = BRepOffsetAPI_MakeThickSolid( + self.wrapped, occ_faces_list, thickness, tolerance + ) + + shell_builder.Build() + rv = shell_builder.Shape() - shell_builder.Build() + else: # if no faces provided a watertight solid will be constructed + shell_builder = BRepOffsetAPI_MakeThickSolid( + self.wrapped, occ_faces_list, thickness, tolerance + ) + + shell_builder.Build() + s1 = self.__class__(shell_builder.Shape()).Shells()[0].wrapped + s2 = self.Shells()[0].wrapped + + # s1 can be outer or inner shell depending on the thickness sign + if thickness > 0: + rv = BRepBuilderAPI_MakeSolid(s1, s2).Shape() + else: + rv = BRepBuilderAPI_MakeSolid(s2, s1).Shape() - return self.__class__(shell_builder.Shape()) + # fix needed for the orientations + return self.__class__(rv) if faceList else self.__class__(rv).fix() def isInside( self: ShapeProtocol, point: VectorLike, tolerance: float = 1.0e-6 diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index c14ebc34e..23100716d 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -1816,6 +1816,18 @@ def testSimpleShell(self): self.saveModel(s) self.assertEqual(23, s.faces().size()) + def testClosedShell(self): + """ + Create a hollow box + """ + s1 = Workplane("XY").box(2, 2, 2).shell(-0.1) + self.assertEqual(12, s1.faces().size()) + self.assertTrue(s1.val().isValid()) + + s2 = Workplane("XY").box(2, 2, 2).shell(0.1) + self.assertEqual(32, s2.faces().size()) + self.assertTrue(s2.val().isValid()) + def testOpenCornerShell(self): s = Workplane("XY").box(1, 1, 1) s1 = s.faces("+Z")