Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(mesh): Add Mesh3D constructor with tolerance check #405

Merged
merged 3 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions ladybug_geometry/_mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,37 @@ def _interpret_input_from_face_vertices(faces, purge):
face_collector.append(tuple(ind))
return vertices, face_collector

@staticmethod
def _interpret_input_from_face_vertices_with_tolerance(faces, tolerance):
"""Get faces and vertices from a list of faces as points.

Args:
faces: A list of faces where each face is a list of points.
tolerance: A number for the tolerance to use when checking for duplicate vertices.
Returns:
A tuple of vertices and faces.
"""

def index_of_equivalent_point(vertix_list, vertix):
"""Get the index of a vertix in a list of vertices using the is_equivalent test."""
for i, other_vert in enumerate(vertix_list):
if vertix.is_equivalent(other_vert, tolerance):
return i
raise ValueError()

vertices = []
face_collector = []
for f in faces:
ind = []
for v in f:
try:
ind.append(index_of_equivalent_point(vertices, v))
except ValueError: # add new point
vertices.append(v)
ind.append(len(vertices) - 1)
face_collector.append(tuple(ind))
return vertices, face_collector

def __len__(self):
return len(self._vertices)

Expand Down
19 changes: 19 additions & 0 deletions ladybug_geometry/geometry3d/mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,25 @@ def from_face_vertices(cls, faces, purge=True):
vertices, face_collector = cls._interpret_input_from_face_vertices(faces, purge)
return cls(tuple(vertices), tuple(face_collector))

@classmethod
def from_purged_face_vertices(cls, faces, tolerance):
"""Create a mesh from a list of faces with each face defined by Point3Ds.

This method is slower than 'from_face_vertices' but will result in a mesh
with fewer vertices and a smaller size in memory. This method is similar to
using the 'purge' option in 'from_face_vertices' but will result in more shared
vertices since it uses the `is_equivalent` test to check the vertices rather than
the more strict `__eq__` comparison.

Args:
faces: A list of faces with each face defined as a list of 3 or 4 Point3D.
tolerance: A number for the tolerance to use when checking for duplicate vertices.
"""
vertices, faces = cls._interpret_input_from_face_vertices_with_tolerance(
faces, tolerance
)
return cls(tuple(vertices), tuple(faces))

@classmethod
def from_mesh2d(cls, mesh_2d, plane=None):
"""Create a Mesh3D from a Mesh2D and a Plane in which the mesh exists.
Expand Down
27 changes: 27 additions & 0 deletions tests/mesh3d_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,33 @@ def test_mesh3d_init_from_face_vertices():
assert mesh_1.colors is mesh_1.colors is None


def test_mesh3d_init_from_purged_face_vertices():
"""Test the initialization of Mesh3D from_purged_face_vertices."""
face_1 = (Point3D(0, 0, 2), Point3D(0, 2, 2), Point3D(2, 2, 2), Point3D(2, 0, 2))
face_2 = (Point3D(2, 2, 2), Point3D(2, 0, 2), Point3D(4, 0, 2))
mesh_1 = Mesh3D.from_purged_face_vertices([face_1, face_2], 0.01)

assert len(mesh_1.vertices) == 5
assert len(mesh_1.faces) == 2
assert mesh_1.area == 6.0

assert mesh_1.min == Point3D(0, 0, 2)
assert mesh_1.max == Point3D(4, 2, 2)
assert mesh_1.center == Point3D(2, 1, 2)

assert len(mesh_1.face_areas) == 2
assert mesh_1.face_areas[0] == 4
assert mesh_1.face_areas[1] == 2
assert len(mesh_1.face_centroids) == 2
assert mesh_1.face_centroids[0] == Point3D(1, 1, 2)
assert mesh_1.face_centroids[1].x == pytest.approx(2.67, rel=1e-2)
assert mesh_1.face_centroids[1].y == pytest.approx(0.67, rel=1e-2)
assert mesh_1.face_centroids[1].z == pytest.approx(2, rel=1e-2)

assert mesh_1._is_color_by_face is False
assert mesh_1.colors is mesh_1.colors is None


def test_mesh3d_from_mesh2d():
"""Test the initialization of Mesh3D objects from_mesh2d."""
pts = (Point2D(0, 0), Point2D(0, 2), Point2D(2, 2), Point2D(2, 0))
Expand Down
Loading