Skip to content

Commit

Permalink
Merge 7ff5d06 into 2bccbfe
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Mackey committed Jun 9, 2019
2 parents 2bccbfe + 7ff5d06 commit 26d67c8
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 10 deletions.
32 changes: 28 additions & 4 deletions ladybug_geometry/geometry2d/polygon.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,15 +333,21 @@ def remove_colinear_vertices(self, tolerance):

def reverse(self):
"""Get a copy of this polygon where the vertices are reversed."""
return Polygon2D(tuple(pt for pt in reversed(self.vertices)))
_new_poly = Polygon2D(tuple(pt for pt in reversed(self.vertices)))
self._transfer_properties(_new_poly)
if self._is_clockwise is not None:
_new_poly._is_clockwise = not self._is_clockwise
return _new_poly

def move(self, moving_vec):
"""Get a polygon that has been moved along a vector.
Args:
moving_vec: A Vector2D with the direction and distance to move the polygon.
"""
return Polygon2D(tuple(pt.move(moving_vec) for pt in self.vertices))
_new_poly = Polygon2D(tuple(pt.move(moving_vec) for pt in self.vertices))
self._transfer_properties(_new_poly)
return _new_poly

def rotate(self, angle, origin):
"""Get a polygon that is rotated counterclockwise by a certain angle.
Expand All @@ -350,7 +356,9 @@ def rotate(self, angle, origin):
angle: An angle for rotation in radians.
origin: A Point2D for the origin around which the point will be rotated.
"""
return Polygon2D(tuple(pt.rotate(angle, origin) for pt in self.vertices))
_new_poly = Polygon2D(tuple(pt.rotate(angle, origin) for pt in self.vertices))
self._transfer_properties(_new_poly)
return _new_poly

def reflect(self, normal, origin):
"""Get a polygon reflected across a plane with the input normal vector and origin.
Expand All @@ -360,7 +368,11 @@ def reflect(self, normal, origin):
which the polygon will be reflected. THIS VECTOR MUST BE NORMALIZED.
origin: A Point2D representing the origin from which to reflect.
"""
return Polygon2D(tuple(pt.reflect(normal, origin) for pt in self.vertices))
_new_poly = Polygon2D(tuple(pt.reflect(normal, origin) for pt in self.vertices))
self._transfer_properties(_new_poly)
if self._is_clockwise is not None:
_new_poly._is_clockwise = not self._is_clockwise
return _new_poly

def scale(self, factor, origin=None):
"""Scale a polygon by a factor from an origin point.
Expand Down Expand Up @@ -618,6 +630,18 @@ def to_dict(self):
"""Get Polygon2D as a dictionary."""
return {'vertices': [pt.to_dict() for pt in self.vertices]}

def _transfer_properties(self, new_polygon):
"""Transfer properties from this polygon to a new polygon.
This is used by the transform methods that don't alter the relationship of
face vertices to one another (move, rotate, reflect).
"""
new_polygon._perimeter = self._perimeter
new_polygon._area = self._area
new_polygon._is_convex = self._is_convex
new_polygon._is_self_intersecting = self._is_self_intersecting
new_polygon._is_clockwise = self._is_clockwise

@staticmethod
def _segments_from_vertices(vertices):
_segs = []
Expand Down
21 changes: 16 additions & 5 deletions ladybug_geometry/geometry3d/face.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,14 @@ def upper_left_counter_clockwise_vertices(self):
verts3d = verts3d[first_pt_index:] + verts3d[:first_pt_index]
return verts3d

def counterclockwise_face(self):
"""Get a version of this face that's counterclockwise within the face's plane."""
return self.reverse() if self.is_clockwise else self

def clockwise_face(self):
"""Get a version of this face that's clockwise within the face's plane."""
return self.reverse() if not self.is_clockwise else self

def is_geometrically_equivalent(self, face, tolerance):
"""Check whether a given face is geometrically equivalent to this Face.
Expand Down Expand Up @@ -671,14 +679,14 @@ def remove_colinear_vertices(self, tolerance):
for i, hole in enumerate(self._holes))
return _new_face

def flip(self, preserve_clockwise=False):
def flip(self, preserve_orientation=False):
"""Get a face with a flipped direction from this one.
Note that, by default, this only flips the plane of the face and does not
change the vertices (meaning clockwise property will be inverted). Set
preserve_clockwise to True to also reverse the vertices.
change the vertices (meaning the is_clockwise property will be inverted).
Set preserve_orientation to True to also reverse the vertices.
"""
if preserve_clockwise is False:
if preserve_orientation is False:
_new_face = Face3D(self.vertices, self.plane.flip())
self._transfer_properties(_new_face)
if self._is_clockwise is not None:
Expand All @@ -696,13 +704,16 @@ def flip(self, preserve_clockwise=False):
def reverse(self):
"""Reverse the direction of vertices in the face while keeping the same normal.
Note that this does not chance the face normal. Only the is_clockwise property.
Note that this does not change the face plane. Only the vertices and the
is_clockwise property.
"""
_new_face = Face3D(tuple(reversed(self.vertices)), self.plane)
self._transfer_properties(_new_face)
if self._holes is not None:
_new_face._boundary = reversed(self._boundary)
_new_face._holes = tuple(reversed(hole) for hole in self._holes)
if self._polygon2d is not None:
_new_face._polygon2d = self._polygon2d.reverse()
if self._is_clockwise is not None:
_new_face._is_clockwise = not self._is_clockwise
return _new_face
Expand Down
30 changes: 29 additions & 1 deletion tests/face3d_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,10 +573,12 @@ def test_flip(self):
plane_1 = Plane(Vector3D(0, 0, 1), Point3D(0, 0, 2))
face_1 = Face3D(pts_1, plane_1)
face_2 = face_1.flip()
face_3 = face_1.flip(preserve_orientation=True)

assert face_1.normal == face_2.normal.reverse()
assert face_1.normal == face_2.normal.reverse() == face_3.normal.reverse()
assert face_1.is_clockwise is False
assert face_2.is_clockwise is True
assert face_3.is_clockwise is False
for i, pt in enumerate(face_1.vertices):
assert pt == face_2[i]

Expand All @@ -593,6 +595,32 @@ def test_reverse(self):
for i, pt in enumerate(face_1.vertices):
assert pt == face_2[-i - 1]

def test_clockwise_counterclockwise_face(self):
"""Test the counterclockwise_face and clockwise_face method of Face3D."""
plane_1 = Plane(Vector3D(0, 0, 1))
plane_2 = Plane(Vector3D(0, 0, -1))
pts_1 = (Point3D(0, 0), Point3D(2, 0), Point3D(2, 2), Point3D(0, 2))
pts_2 = (Point3D(0, 0), Point3D(0, 2), Point3D(2, 2), Point3D(2, 0))
face_1 = Face3D(pts_1, plane_1)
face_2 = Face3D(pts_2, plane_1)
face_3 = Face3D(pts_1, plane_2)
face_4 = Face3D(pts_2, plane_2)

assert face_1.is_clockwise is face_1.polygon2d.is_clockwise is False
assert face_2.is_clockwise is face_2.polygon2d.is_clockwise is True
assert face_3.is_clockwise is face_3.polygon2d.is_clockwise is True
assert face_4.is_clockwise is face_4.polygon2d.is_clockwise is False

assert face_1.counterclockwise_face().is_clockwise is False
assert face_2.counterclockwise_face().is_clockwise is False
assert face_3.counterclockwise_face().is_clockwise is False
assert face_4.counterclockwise_face().is_clockwise is False

assert face_1.clockwise_face().is_clockwise is True
assert face_2.clockwise_face().is_clockwise is True
assert face_3.clockwise_face().is_clockwise is True
assert face_4.clockwise_face().is_clockwise is True

def test_move(self):
"""Test the Face3D move method."""
pts_1 = (Point3D(0, 0, 0), Point3D(2, 0, 0), Point3D(2, 2, 0), Point3D(0, 2, 0))
Expand Down
22 changes: 22 additions & 0 deletions tests/polygon2d_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,18 @@ def test_polygon2d_duplicate(self):
assert new_polygon.is_convex == new_polygon_2.is_convex
assert new_polygon.is_self_intersecting == new_polygon_2.is_self_intersecting

def test_reverse(self):
"""Test the reverse property."""
pts_1 = (Point2D(0, 0), Point2D(2, 0), Point2D(2, 2), Point2D(0, 2))
polygon = Polygon2D(pts_1)
new_polygon = polygon.reverse()

assert polygon.area == new_polygon.area
assert polygon.perimeter == new_polygon.perimeter
assert polygon.is_clockwise is not new_polygon.is_clockwise
assert polygon.is_convex == new_polygon.is_convex
assert polygon.is_self_intersecting == new_polygon.is_self_intersecting

def test_move(self):
"""Test the Polygon2D move method."""
pts = (Point2D(0, 0), Point2D(2, 0), Point2D(2, 2), Point2D(0, 2))
Expand Down Expand Up @@ -305,6 +317,11 @@ def test_rotate(self):
assert test_1[0].y == pytest.approx(1, rel=1e-3)
assert test_1[2].x == pytest.approx(0, rel=1e-3)
assert test_1[2].y == pytest.approx(0, rel=1e-3)
assert polygon.area == pytest.approx(test_1.area, rel=1e-3)
assert polygon.perimeter == pytest.approx(test_1.perimeter, rel=1e-3)
assert polygon.is_clockwise is test_1.is_clockwise
assert polygon.is_convex is test_1.is_convex
assert polygon.is_self_intersecting is test_1.is_self_intersecting

test_2 = polygon.rotate(math.pi/2, origin_1)
assert test_2[0].x == pytest.approx(1, rel=1e-3)
Expand All @@ -326,6 +343,11 @@ def test_reflect(self):
assert test_1[0].y == pytest.approx(1, rel=1e-3)
assert test_1[2].x == pytest.approx(0, rel=1e-3)
assert test_1[2].y == pytest.approx(2, rel=1e-3)
assert polygon.area == pytest.approx(test_1.area, rel=1e-3)
assert polygon.perimeter == pytest.approx(test_1.perimeter, rel=1e-3)
assert polygon.is_clockwise is not test_1.is_clockwise
assert polygon.is_convex is test_1.is_convex
assert polygon.is_self_intersecting is test_1.is_self_intersecting

test_1 = polygon.reflect(normal_2, Point2D(0, 0))
assert test_1[0].x == pytest.approx(-1, rel=1e-3)
Expand Down

0 comments on commit 26d67c8

Please sign in to comment.