From d7c5eb2405eb0ba536b194b7831e58a5cb196383 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 24 Nov 2023 10:50:27 -0500 Subject: [PATCH] fix(shademesh): Change method to only traingulate non planar quads --- honeybee/shademesh.py | 28 +++++++++++++++++------- tests/shademesh_test.py | 47 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/honeybee/shademesh.py b/honeybee/shademesh.py index 3a9b98ae..6e37ede7 100644 --- a/honeybee/shademesh.py +++ b/honeybee/shademesh.py @@ -219,7 +219,7 @@ def scale(self, factor, origin=None): self.properties.scale(factor, origin) def triangulate_and_remove_degenerate_faces(self, tolerance=0.01): - """Triangulate all faces in the mesh and remove all degenerate faces from the result. + """Triangulate non-planar faces in the mesh and remove all degenerate faces. This is helpful for certain geometry interfaces that require perfectly planar geometry without duplicate or colinear vertices. @@ -231,16 +231,28 @@ def triangulate_and_remove_degenerate_faces(self, tolerance=0.01): """ new_faces, verts = [], self.geometry.vertices for shd in self.faces: - shades = (shd,) if len(shd) == 3 else \ - ((shd[0], shd[1], shd[2]), (shd[2], shd[3], shd[0])) - for shade in enumerate(shades): - shd_verts = [verts[v] for v in shade] - shade_face = Face3D(shd_verts) + shd_verts = [verts[v] for v in shd] + shf = Face3D(shd_verts) + if not shf.check_planar(tolerance, raise_exception=False): + shades = ((shd[0], shd[1], shd[2]), (shd[2], shd[3], shd[0])) + for shade in shades: + shd_verts = [verts[v] for v in shade] + shade_face = Face3D(shd_verts) + try: + shade_face.remove_colinear_vertices(tolerance) + except AssertionError: + continue # degenerate face to remove + new_faces.append(shade) + else: try: - shade_face.remove_colinear_vertices(tolerance) + new_face = shf.remove_colinear_vertices(tolerance) except AssertionError: continue # degenerate face to remove - new_faces.append(shade) + if len(new_face.vertices) == len(shd): + new_faces.append(shd) + else: # quad face with duplicate or colinear verts + new_sh = tuple(shd[shd_verts.index(v)] for v in new_face.vertices) + new_faces.append(new_sh) self._geometry = Mesh3D(verts, new_faces) def is_geo_equivalent(self, shade_mesh, tolerance=0.01): diff --git a/tests/shademesh_test.py b/tests/shademesh_test.py index 5740c963..1756b46e 100644 --- a/tests/shademesh_test.py +++ b/tests/shademesh_test.py @@ -175,6 +175,53 @@ def test_reflect(): assert test_2.geometry[2].z == pytest.approx(2, rel=1e-3) +def test_triangulate_and_remove_degenerate_faces(): + """Test the triangulate_and_remove_degenerate_faces method.""" + pts = (Point3D(0, 0, 4), Point3D(0, 2, 4), Point3D(2, 2, 4), + Point3D(2, 0, 4), Point3D(4, 0, 4)) + mesh = Mesh3D(pts, [(0, 1, 2, 3), (2, 3, 4)]) + shade = ShadeMesh('Awning_1', mesh) + shade.triangulate_and_remove_degenerate_faces(0.01) + assert len(shade.faces) == 2 + assert len(shade.faces[0]) == 4 + assert len(shade.faces[1]) == 3 + + pts = (Point3D(0, 0, 2), Point3D(0, 2, 4), Point3D(2, 2, 4), + Point3D(2, 0, 4), Point3D(4, 0, 4)) + mesh = Mesh3D(pts, [(0, 1, 2, 3), (2, 3, 4)]) + shade = ShadeMesh('Awning_1', mesh) + shade.triangulate_and_remove_degenerate_faces(0.01) + assert len(shade.faces) == 3 + assert len(shade.faces[0]) == 3 + assert len(shade.faces[1]) == 3 + + pts = (Point3D(0, 2, 4), Point3D(0, 2, 4), Point3D(2, 2, 4), + Point3D(2, 0, 4), Point3D(4, 0, 4)) + mesh = Mesh3D(pts, [(0, 1, 2, 3), (2, 3, 4)]) + shade = ShadeMesh('Awning_1', mesh) + shade.triangulate_and_remove_degenerate_faces(0.01) + assert len(shade.faces) == 2 + assert len(shade.faces[0]) == 3 + assert len(shade.faces[1]) == 3 + + pts = (Point3D(0, 0, 4), Point3D(0, 2, 4), Point3D(2, 2, 4), + Point3D(2, 0, 4), Point3D(2, 0, 4)) + mesh = Mesh3D(pts, [(0, 1, 2, 3), (2, 3, 4)]) + shade = ShadeMesh('Awning_1', mesh) + shade.triangulate_and_remove_degenerate_faces(0.01) + assert len(shade.faces) == 1 + assert len(shade.faces[0]) == 4 + + pts = (Point3D(1, 1, 4), Point3D(0, 2, 4), Point3D(2, 2, 4), + Point3D(2, 0, 4), Point3D(4, 0, 4)) + mesh = Mesh3D(pts, [(0, 1, 2, 3), (2, 3, 4)]) + shade = ShadeMesh('Awning_1', mesh) + shade.triangulate_and_remove_degenerate_faces(0.01) + assert len(shade.faces) == 2 + assert len(shade.faces[0]) == 3 + assert len(shade.faces[1]) == 3 + + def test_to_dict(): """Test the shade to_dict method.""" pts = (Point3D(0, 0, 4), Point3D(0, 2, 4), Point3D(2, 2, 4),