diff --git a/src/build123d/topology.py b/src/build123d/topology.py index 628c70a3..14a39e18 100644 --- a/src/build123d/topology.py +++ b/src/build123d/topology.py @@ -658,6 +658,11 @@ def is_closed(self) -> bool: """Are the start and end points equal?""" return BRep_Tool.IsClosed_s(self.wrapped) + @property + def volume(self) -> float: + """volume - the volume of this Edge or Wire, which is always zero""" + return 0.0 + def position_at( self, distance: float, position_mode: PositionMode = PositionMode.LENGTH ) -> Vector: @@ -2244,12 +2249,6 @@ def area(self) -> float: return properties.Mass() - @property - def volume(self) -> float: - """volume - the volume of this Shape""" - # when density == 1, mass == volume - return Shape.compute_mass(self) - def _apply_transform(self, transformation: gp_Trsf) -> Self: """Private Apply Transform @@ -3805,6 +3804,12 @@ def __repr__(self): result = f"{self.__class__.__name__} at {id(self):#x}" return result + @property + def volume(self) -> float: + """volume - the volume of this Compound""" + # when density == 1, mass == volume + return sum(i.volume for i in [*self.get_type(Solid), *self.get_type(Shell)]) + def center(self, center_of: CenterOf = CenterOf.MASS) -> Vector: """Return center of object @@ -5053,6 +5058,11 @@ def length(self) -> float: result = face_vertices[-1].X - face_vertices[0].X return result + @property + def volume(self) -> float: + """volume - the volume of this Face, which is always zero""" + return 0.0 + @property def width(self) -> float: """width of planar face""" @@ -5823,6 +5833,15 @@ class Shell(Shape): _dim = 2 + @property + def volume(self) -> float: + """volume - the volume of this Shell if manifold, otherwise zero""" + # when density == 1, mass == volume + if self.is_manifold: + return Solid.make_solid(self).volume + else: + return 0.0 + @classmethod def make_shell(cls, faces: Iterable[Face]) -> Shell: """Create a Shell from provided faces""" @@ -5848,6 +5867,12 @@ class Solid(Mixin3D, Shape): _dim = 3 + @property + def volume(self) -> float: + """volume - the volume of this Solid""" + # when density == 1, mass == volume + return Shape.compute_mass(self) + @classmethod def make_solid(cls, shell: Shell) -> Solid: """Create a Solid object from the surface shell""" @@ -6590,6 +6615,11 @@ def __init__(self, *args, **kwargs): super().__init__(ocp_vx) self.X, self.Y, self.Z = self.to_tuple() + @property + def volume(self) -> float: + """volume - the volume of this Vertex, which is always zero""" + return 0.0 + def to_tuple(self) -> tuple[float, float, float]: """Return vertex as three tuple of floats""" geom_point = BRep_Tool.Pnt_s(self.wrapped) diff --git a/tests/test_build_generic.py b/tests/test_build_generic.py index a6929b5b..629bf9ae 100644 --- a/tests/test_build_generic.py +++ b/tests/test_build_generic.py @@ -114,7 +114,7 @@ def test_add_to_part(self): ] ) ) - self.assertEqual(test.part.volume, 1125, 5) + self.assertAlmostEqual(test.part.volume, 1125, 5) with BuildPart() as test: add(Compound.make_compound([Edge.make_line((0, 0), (1, 1))])) self.assertEqual(len(test.pending_edges), 1) diff --git a/tests/test_direct_api.py b/tests/test_direct_api.py index c84681bc..90cdaf3d 100644 --- a/tests/test_direct_api.py +++ b/tests/test_direct_api.py @@ -790,6 +790,23 @@ def test_triad(self): self.assertLess(bbox.size.Z, 12.5) self.assertEqual(triad.volume, 0) + def test_volume(self): + e = Edge.make_line((0, 0), (1, 1)) + self.assertAlmostEqual(e.volume, 0, 5) + + f = Face.make_rect(1, 1) + self.assertAlmostEqual(f.volume, 0, 5) + + b = Solid.make_box(1, 1, 1) + self.assertAlmostEqual(b.volume, 1, 5) + + bb = Box(1, 1, 1) + self.assertAlmostEqual(bb.volume, 1, 5) + + c = Compound(children=[e, f, b, bb, b.translate((0, 5, 0))]) + self.assertAlmostEqual(c.volume, 3, 5) + # N.B. b and bb overlap but still add to Compound volume + class TestEdge(DirectApiTestCase): def test_close(self): @@ -1024,6 +1041,10 @@ def test_center(self): 5, ) + def test_face_volume(self): + rect = Face.make_rect(1, 1) + self.assertAlmostEqual(rect.volume, 0, 5) + def test_chamfer_2d(self): test_face = Face.make_rect(10, 10) test_face = test_face.chamfer_2d( @@ -1984,6 +2005,14 @@ def test_common_plane(self): self.assertAlmostEqual(common.z_dir.Y, 0, 5) self.assertAlmostEqual(common.z_dir.Z, 0, 5) + def test_edge_volume(self): + edge = Edge.make_line((0,0),(1,1)) + self.assertAlmostEqual(edge.volume, 0, 5) + + def test_wire_volume(self): + wire = Wire.make_rect(1,1) + self.assertAlmostEqual(wire.volume, 0, 5) + class TestMixin3D(DirectApiTestCase): """Test that 3D add ins""" @@ -3040,6 +3069,17 @@ def test_center(self): box_shell = Shell.make_shell(box_faces) self.assertVectorAlmostEquals(box_shell.center(), (0.5, 0.5, 0.5), 5) + def test_manifold_shell_volume(self): + box_faces = Solid.make_box(1, 1, 1).faces() + box_shell = Shell.make_shell(box_faces) + self.assertAlmostEqual(box_shell.volume, 1, 5) + + def test_nonmanifold_shell_volume(self): + box_faces = Solid.make_box(1, 1, 1).faces() + nm_shell = Shell.make_shell(box_faces) + nm_shell -= nm_shell.faces()[0] + self.assertAlmostEqual(nm_shell.volume, 0, 5) + class TestSolid(DirectApiTestCase): def test_make_solid(self): @@ -3412,6 +3452,10 @@ def test_basic_vertex(self): self.assertVectorAlmostEquals(Vector(Vertex((7,))), (7, 0, 0), 7) self.assertVectorAlmostEquals(Vector(Vertex((8, 9))), (8, 9, 0), 7) + def test_vertex_volume(self): + v = Vertex(1, 1, 1) + self.assertAlmostEqual(v.volume, 0, 5) + def test_vertex_add(self): test_vertex = Vertex(0, 0, 0) self.assertVectorAlmostEquals(