From 1c3fc585228aac975b36a45c88b8cdaa9e5cc12c Mon Sep 17 00:00:00 2001 From: Chris Mackey Date: Thu, 20 Jun 2024 16:08:22 -0700 Subject: [PATCH] fix(face): Add a method to get Face3D to and from array --- ladybug_geometry/geometry2d/polygon.py | 2 +- ladybug_geometry/geometry3d/face.py | 29 ++++++++++++++++++++++++++ tests/face3d_test.py | 19 +++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/ladybug_geometry/geometry2d/polygon.py b/ladybug_geometry/geometry2d/polygon.py index a8e58772..445de1ff 100644 --- a/ladybug_geometry/geometry2d/polygon.py +++ b/ladybug_geometry/geometry2d/polygon.py @@ -1189,7 +1189,7 @@ def to_dict(self): 'vertices': [pt.to_array() for pt in self.vertices]} def to_array(self): - """Get a list of lists where each sub-list represents a Point2D vertex.""" + """Get a tuple of tuples where each sub-tuple represents a Point2D vertex.""" return tuple(pt.to_array() for pt in self.vertices) def _to_bool_poly(self): diff --git a/ladybug_geometry/geometry3d/face.py b/ladybug_geometry/geometry3d/face.py index 10829da7..1d6863d6 100644 --- a/ladybug_geometry/geometry3d/face.py +++ b/ladybug_geometry/geometry3d/face.py @@ -185,6 +185,22 @@ def from_dict(cls, data): return cls(tuple(Point3D.from_array(pt) for pt in data['boundary']), plane, holes) + @classmethod + def from_array(cls, point_array): + """Create a Face3D from a nested array of vertex coordinates. + + Args: + point_array: A nested array of arrays where each sub-array represents + a loop of the Face3D. The first array is the boundary and subsequent + arrays represent holes in the Face3D. point arrays. Each sub-array + is composed of arrays that each have a length of 3 and denote 3D + points that define the face. + """ + boundary = tuple(Point3D(*point) for point in point_array[0]) + holes = None if len(point_array) == 1 else \ + tuple(tuple(Point3D(*point) for point in hole) for hole in point_array[1:]) + return cls(boundary, None, holes) + @classmethod def from_extrusion(cls, line_segment, extrusion_vector): """Initialize Face3D by extruding a line segment. @@ -2713,6 +2729,19 @@ def to_dict(self, include_plane=True, enforce_upper_left=False): for hole in self.holes] return base + def to_array(self): + """Get Face3D as a nested list of tuples where each sub-tuple represents loop. + + The first loop is always the outer boundary and successive loops represent + holes in the face (if they exist). Each sub-tuple is composed of tuples + that each have a length of 3 and denote 3D points that define the face. + """ + if self.has_holes: + return (tuple(pt.to_array() for pt in self.boundary),) + \ + tuple(tuple(pt.to_array() for pt in hole) for hole in self.holes) + else: + return (tuple(pt.to_array() for pt in self.boundary),) + @staticmethod def extract_all_from_stl(file_path): """Get a list of Face3Ds imported from all of the triangles in an STL file. diff --git a/tests/face3d_test.py b/tests/face3d_test.py index 8a80b086..1c7f4256 100644 --- a/tests/face3d_test.py +++ b/tests/face3d_test.py @@ -112,6 +112,25 @@ def test_face3d_to_from_dict(): assert new_face.to_dict() == face_dict +def test_face3d_to_from_array(): + """Test the to/from array of Face3D objects.""" + pts = (Point3D(0, 0, 2), Point3D(0, 2, 2), Point3D(2, 2, 2), Point3D(2, 0, 2)) + plane = Plane(Vector3D(0, 0, 1), Point3D(0, 0, 2)) + face = Face3D(pts, plane) + face_array = face.to_array() + new_face = Face3D.from_array(face_array) + assert isinstance(new_face, Face3D) + assert new_face.to_array() == face_array + + bound_pts = [Point3D(0, 0), Point3D(4, 0), Point3D(4, 4), Point3D(0, 4)] + hole_pts = [Point3D(1, 1), Point3D(3, 1), Point3D(3, 3), Point3D(1, 3)] + face = Face3D(bound_pts, None, [hole_pts]) + face_array = face.to_array() + new_face = Face3D.from_array(face_array) + assert isinstance(new_face, Face3D) + assert new_face.to_array() == face_array + + def test_face3d_init_from_vertices(): """Test the initialization of Face3D objects without a plane.""" pts = (Point3D(0, 0, 2), Point3D(0, 2, 2), Point3D(2, 2, 2), Point3D(2, 0, 2))