Skip to content

Commit

Permalink
fix(face): Add a method to get Face3D to and from array
Browse files Browse the repository at this point in the history
  • Loading branch information
chriswmackey authored and Chris Mackey committed Jun 20, 2024
1 parent b11a9ad commit 1c3fc58
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 1 deletion.
2 changes: 1 addition & 1 deletion ladybug_geometry/geometry2d/polygon.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
29 changes: 29 additions & 0 deletions ladybug_geometry/geometry3d/face.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
19 changes: 19 additions & 0 deletions tests/face3d_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down

0 comments on commit 1c3fc58

Please sign in to comment.