Skip to content

Commit

Permalink
fix(face): Improve Face3D splitting by snapping within tolerance
Browse files Browse the repository at this point in the history
  • Loading branch information
chriswmackey authored and Chris Mackey committed Mar 17, 2023
1 parent 7c26f47 commit 649f787
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 5 deletions.
35 changes: 33 additions & 2 deletions ladybug_geometry/geometry2d/polygon.py
Original file line number Diff line number Diff line change
Expand Up @@ -897,11 +897,11 @@ def is_polygon_outside(self, polygon):
return True

def polygon_relationship(self, polygon, tolerance):
"""Test whether another Polygon2D lies inside, outside or intersects this one.
"""Test whether another Polygon2D lies inside, outside or overlaps this one.
This method is not usually the fastest for understanding the relationship
between polygons but it accurately accounts for tolerance such that the
case of the two polygons sharing an edge will not determine the outcome.
case of the two polygons sharing edges will not determine the outcome.
Only when the polygon has vertices that are truly outside this polygon
within the tolerance will the relationship become outside (or intersecting
if one of the vertices is already inside within the tolerance).
Expand Down Expand Up @@ -973,6 +973,37 @@ def distance_from_edge_to_point(self, point):
"""
return min(seg.distance_to_point(point) for seg in self.segments)

def snap_to_polygon(self, polygon, tolerance):
"""Snap another Polygon2D to this one for differences smaller than the tolerance.
This is useful to run before performing operations where small tolerance
differences are likely to cause issues, such as in boolean operations.
Args:
polygon: A Polygon2D which will be snapped to the current polygon.
tolerance: The minimum distance at which points will be snapped.
Returns:
A version of the polygon that is snapped to this Polygon2D.
"""
new_verts = []
for pt in polygon.vertices:
# first check if the point can be snapped to a vertex
for s_pt in self.vertices:
if pt.is_equivalent(s_pt, tolerance):
new_verts.append(s_pt)
break
else:
# check if the point can be snapped to a segment
for seg in self.segments:
s_pt = seg.closest_point(pt)
if s_pt.distance_to_point(pt) <= tolerance:
new_verts.append(s_pt)
break
else: # point could not be snapped
new_verts.append(pt)
return Polygon2D(new_verts)

def to_dict(self):
"""Get Polygon2D as a dictionary."""
return {'type': 'Polygon2D',
Expand Down
16 changes: 13 additions & 3 deletions ladybug_geometry/geometry3d/face.py
Original file line number Diff line number Diff line change
Expand Up @@ -1765,9 +1765,16 @@ def coplanar_split(face1, face2, tolerance, angle_tolerance):
return [face1], [face2]
if f1_poly.polygon_relationship(f2_poly, tolerance) == -1:
return [face1], [face2]
# snap the polygons to one another to avoid tolerance issues
try:
f1_poly = f1_poly.remove_colinear_vertices(tolerance)
f2_poly = f2_poly.remove_colinear_vertices(tolerance)
except AssertionError: # degenerate faces input
return [face1], [face2]
s2_poly = f1_poly.snap_to_polygon(f2_poly, tolerance)
# get BooleanPolygons of the two faces
f1_polys = [(pb.BooleanPoint(pt.x, pt.y) for pt in f1_poly.vertices)]
f2_polys = [(pb.BooleanPoint(pt.x, pt.y) for pt in f2_poly.vertices)]
f2_polys = [(pb.BooleanPoint(pt.x, pt.y) for pt in s2_poly.vertices)]
if face1.has_holes:
for hole in face1.hole_polygon2d:
f1_polys.append((pb.BooleanPoint(pt.x, pt.y) for pt in hole.vertices))
Expand All @@ -1778,8 +1785,11 @@ def coplanar_split(face1, face2, tolerance, angle_tolerance):
b_poly1 = pb.BooleanPolygon(f1_polys)
b_poly2 = pb.BooleanPolygon(f2_polys)
# split the two boolean polygons with one another
int_tol = tolerance / 3
int_result, poly1_result, poly2_result = pb.split(b_poly1, b_poly2, int_tol)
int_tol = tolerance / 100
try:
int_result, poly1_result, poly2_result = pb.split(b_poly1, b_poly2, int_tol)
except Exception:
return [face1], [face2] # typically a tolerance issue causing failure
# rebuild the Face3D from the results and return them
int_faces = Face3D._from_bool_poly(int_result, prim_pl)
poly1_faces = Face3D._from_bool_poly(poly1_result, prim_pl)
Expand Down

0 comments on commit 649f787

Please sign in to comment.