|
42 | 42 | from compas.geometry import distance_line_line |
43 | 43 | from compas.geometry import distance_point_plane |
44 | 44 | from compas.geometry import distance_point_point |
| 45 | +from compas.geometry import dot_vectors |
45 | 46 | from compas.geometry import length_vector |
46 | 47 | from compas.geometry import midpoint_line |
47 | 48 | from compas.geometry import normal_polygon |
@@ -3882,6 +3883,75 @@ def area(self): |
3882 | 3883 | """ |
3883 | 3884 | return sum(self.face_area(fkey) for fkey in self.faces()) |
3884 | 3885 |
|
| 3886 | + def volume(self, copy=True, unify_cycles=True): |
| 3887 | + """Calculate the volume of the mesh. |
| 3888 | +
|
| 3889 | + Parameters |
| 3890 | + ---------- |
| 3891 | + copy : bool, optional |
| 3892 | + If True, a copy of the mesh is made before computation to avoid modifying the original. |
| 3893 | + Default is True. |
| 3894 | + unify_cycles : bool, optional |
| 3895 | + If True, face cycles are unified to ensure consistent orientation. |
| 3896 | + Default is True. |
| 3897 | +
|
| 3898 | + Returns |
| 3899 | + ------- |
| 3900 | + float | None |
| 3901 | + The volume of the mesh if the mesh is closed, None otherwise. |
| 3902 | +
|
| 3903 | + Notes |
| 3904 | + ----- |
| 3905 | + The volume is computed using the signed volume of tetrahedra formed by each |
| 3906 | + triangulated face and the origin. This method works for both convex and |
| 3907 | + non-convex meshes, as long as they are closed and properly oriented. |
| 3908 | +
|
| 3909 | + The volume is only meaningful for closed meshes. For open meshes, this method |
| 3910 | + returns None. |
| 3911 | +
|
| 3912 | + When faces are non-convex, the triangulation might not be correct, since it uses |
| 3913 | + the centroid of the face. For accurate results with non-convex faces, consider |
| 3914 | + using a mesh with triangulated faces. |
| 3915 | +
|
| 3916 | + By default, the mesh is copied internally and face cycles are unified to ensure |
| 3917 | + correct orientation before computing the volume. These operations can be disabled |
| 3918 | + by setting ``copy=False`` and ``unify_cycles=False`` for performance in cases where |
| 3919 | + the mesh is already correctly oriented or when the original mesh can be modified. |
| 3920 | +
|
| 3921 | + Examples |
| 3922 | + -------- |
| 3923 | + >>> from compas.datastructures import Mesh |
| 3924 | + >>> mesh = Mesh.from_polyhedron(6) # Create a cube |
| 3925 | + >>> volume = mesh.volume() |
| 3926 | + >>> volume is not None |
| 3927 | + True |
| 3928 | +
|
| 3929 | + """ |
| 3930 | + if not self.is_closed(): |
| 3931 | + return None |
| 3932 | + |
| 3933 | + # Make a copy to avoid modifying the original mesh |
| 3934 | + mesh_to_use = self.copy() if copy else self |
| 3935 | + |
| 3936 | + # Unify cycles to ensure consistent face orientation |
| 3937 | + if unify_cycles: |
| 3938 | + mesh_to_use.unify_cycles() |
| 3939 | + |
| 3940 | + # Use built-in triangulation to get triangulated faces |
| 3941 | + vertices, faces = mesh_to_use.to_vertices_and_faces(triangulated=True) |
| 3942 | + |
| 3943 | + volume = 0.0 |
| 3944 | + for face in faces: |
| 3945 | + # Each face is now a triangle (3 vertices) |
| 3946 | + a, b, c = [vertices[i] for i in face] |
| 3947 | + # Signed volume of tetrahedron formed by triangle and origin |
| 3948 | + # V = (1/6) * (a dot (b cross c)) where a, b, c are the vertices |
| 3949 | + bc = cross_vectors(b, c) |
| 3950 | + vol = dot_vectors(a, bc) / 6.0 |
| 3951 | + volume += vol |
| 3952 | + |
| 3953 | + return abs(volume) |
| 3954 | + |
3885 | 3955 | def centroid(self): |
3886 | 3956 | """Calculate the mesh centroid. |
3887 | 3957 |
|
|
0 commit comments