Skip to content

Commit

Permalink
fix(polygon): Add properties for inside and outside angles
Browse files Browse the repository at this point in the history
  • Loading branch information
chriswmackey authored and Chris Mackey committed Jul 18, 2023
1 parent 2bdba0a commit e4d3f5e
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 1 deletion.
39 changes: 38 additions & 1 deletion ladybug_geometry/geometry2d/polygon.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class Polygon2D(Base2DIn2D):
Properties:
* vertices
* segments
* inside_angles
* outside_angles
* min
* max
* center
Expand All @@ -43,14 +45,16 @@ class Polygon2D(Base2DIn2D):
* is_self_intersecting
* is_valid
"""
__slots__ = ('_segments', '_perimeter', '_area',
__slots__ = ('_segments', '_inside_angles', '_outside_angles', '_perimeter', '_area',
'_is_clockwise', '_is_convex', '_is_self_intersecting')

def __init__(self, vertices):
"""Initialize Polygon2D."""
Base2DIn2D.__init__(self, vertices)
self._segments = None
self._perimeter = None
self._inside_angles = None
self._outside_angles = None
self._area = None
self._is_clockwise = None
self._is_convex = None
Expand Down Expand Up @@ -308,6 +312,39 @@ def segments(self):
self._segments = tuple(_segs)
return self._segments

@property
def inside_angles(self):
"""Tuple of angles in radians for the interior angles of the polygon.
These are aligned with the vertices such that the first angle corresponds
to the inside angle at the first vertex, the second at the second vertex,
and so on.
"""
if self._inside_angles is None:
angles, is_clock = [], self.is_clockwise
for i, pt in enumerate(self.vertices):
v1 = self.vertices[i - 2] - self.vertices[i - 1]
v2 = pt - self.vertices[i - 1]
v_angle = v1.angle_counterclockwise(v2) if is_clock \
else v1.angle_clockwise(v2)
angles.append(v_angle)
angles.append(angles.pop(0)) # move the start angle to the end
self._inside_angles = tuple(angles)
return self._inside_angles

@property
def outside_angles(self):
"""Tuple of angles in radians for the exterior angles of the polygon.
These are aligned with the vertices such that the first angle corresponds
to the inside angle at the first vertex, the second at the second vertex,
and so on.
"""
if self._outside_angles is None:
pi2 = math.pi * 2
self._outside_angles = tuple(pi2 - a for a in self.inside_angles)
return self._outside_angles

@property
def perimeter(self):
"""The perimeter of the polygon."""
Expand Down
19 changes: 19 additions & 0 deletions tests/polygon2d_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,25 @@ def test_polygon2d_init():
assert isinstance(p_array, tuple)
assert len(arr) == 2

assert isinstance(polygon.inside_angles, tuple)
assert len(polygon.inside_angles) == 4
assert all(math.degrees(a) == 90 for a in polygon.inside_angles)

assert isinstance(polygon.outside_angles, tuple)
assert len(polygon.outside_angles) == 4
assert all(math.degrees(a) == 270 for a in polygon.outside_angles)


def test_polygon2d_inside_angles():
"""Test the Polygon2D.inside_angles property."""
pts = (Point2D(-2, 0), Point2D(2, 0), Point2D(2, 2), Point2D(0, 2))
polygon = Polygon2D(pts)

assert math.degrees(polygon.inside_angles[0]) == pytest.approx(45, rel=1e-3)
assert math.degrees(polygon.inside_angles[1]) == pytest.approx(90, rel=1e-3)
assert math.degrees(polygon.inside_angles[2]) == pytest.approx(90, rel=1e-3)
assert math.degrees(polygon.inside_angles[3]) == pytest.approx(135, rel=1e-3)


def test_polygon2d_is_rectangle():
"""Test the Polygon2D.is_rectangle method."""
Expand Down

0 comments on commit e4d3f5e

Please sign in to comment.