Skip to content

Commit d892cb4

Browse files
committed
fix(face): Add a method to find the intersection between two Face3D
1 parent 5c1b7b9 commit d892cb4

File tree

2 files changed

+92
-12
lines changed

2 files changed

+92
-12
lines changed

ladybug_geometry/geometry3d/face.py

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,6 +1477,53 @@ def intersect_plane(self, plane):
14771477
return _int_seg3d
14781478
return None
14791479

1480+
def intersect_face(self, face, tolerance):
1481+
"""Get the intersection between this face and another input Face3D.
1482+
1483+
Args:
1484+
face: A Face3D object for which intersection will be computed.
1485+
tolerance: The maximum difference between point values for them to be
1486+
considered distinct from one another.
1487+
1488+
Returns:
1489+
List of LineSegment3D objects for the intersection.
1490+
Will be None if no intersection exists.
1491+
"""
1492+
# get the intersection of this face with the other plane
1493+
self_int = self.intersect_plane(face.plane)
1494+
if self_int is None:
1495+
return None
1496+
# get the intersection of the other face with this face's plane
1497+
other_int = face.intersect_plane(self.plane)
1498+
if other_int is None:
1499+
return None
1500+
# determine the overlapping parts of the two intersections
1501+
overlap_segs = []
1502+
for seg1 in self_int:
1503+
s1p1, s1p2 = seg1.p1, seg1.p2
1504+
for seg2 in other_int:
1505+
s2p1, s2p2 = seg2.p1, seg2.p2
1506+
use_s1p1 = seg2.distance_to_point(s1p1) <= tolerance
1507+
use_s1p2 = seg2.distance_to_point(s1p2) <= tolerance
1508+
use_s2p1 = seg1.distance_to_point(s2p1) <= tolerance
1509+
use_s2p2 = seg1.distance_to_point(s2p2) <= tolerance
1510+
if use_s1p1 and use_s1p2:
1511+
overlap_segs.append(seg1)
1512+
elif use_s2p1 and use_s2p2:
1513+
overlap_segs.append(seg2)
1514+
elif use_s1p1 and use_s2p1:
1515+
overlap_segs.append(LineSegment3D.from_end_points(s1p1, s2p1))
1516+
elif use_s1p1 and use_s2p2:
1517+
overlap_segs.append(LineSegment3D.from_end_points(s1p1, s2p2))
1518+
elif use_s1p2 and use_s2p1:
1519+
overlap_segs.append(LineSegment3D.from_end_points(s1p2, s2p1))
1520+
elif use_s1p2 and use_s2p2:
1521+
overlap_segs.append(LineSegment3D.from_end_points(s1p2, s2p2))
1522+
overlap_segs = [seg for seg in overlap_segs if seg.length > tolerance]
1523+
if len(overlap_segs) == 0:
1524+
return None
1525+
return overlap_segs
1526+
14801527
def project_point(self, point):
14811528
"""Project a Point3D onto this face.
14821529
@@ -2308,9 +2355,12 @@ def coplanar_difference(self, faces, tolerance, angle_tolerance):
23082355
# subtract the boolean polygons
23092356
try:
23102357
b_poly1 = pb.difference(b_poly1, b_poly2, int_tol)
2311-
except Exception:
2312-
return [self] # typically a tolerance issue causing failure
2313-
2358+
except Exception: # tiny edge caused a failure; try with small tol
2359+
int_tol = int_tol / 100
2360+
try:
2361+
b_poly1 = pb.difference(b_poly1, b_poly2, int_tol)
2362+
except Exception:
2363+
return [self] # the edge is just too tiny
23142364
# rebuild the Face3D from the result of the subtraction
23152365
return Face3D._from_bool_poly(b_poly1, prim_pl, tolerance)
23162366

@@ -2362,8 +2412,12 @@ def coplanar_union(face1, face2, tolerance, angle_tolerance):
23622412
int_tol = tolerance / 1000
23632413
try:
23642414
poly_result = pb.union(b_poly1, b_poly2, int_tol)
2365-
except Exception:
2366-
return None # typically a tolerance issue causing failure
2415+
except Exception: # tiny edge caused a failure; try with small tol
2416+
int_tol = int_tol / 100
2417+
try:
2418+
poly_result = pb.union(b_poly1, b_poly2, int_tol)
2419+
except Exception:
2420+
return None # the edge is just too tiny
23672421
# rebuild the Face3D from the results and return them
23682422
union_faces = Face3D._from_bool_poly(poly_result, prim_pl, tolerance)
23692423
return union_faces[0]
@@ -2419,8 +2473,12 @@ def coplanar_intersection(face1, face2, tolerance, angle_tolerance):
24192473
int_tol = tolerance / 1000
24202474
try:
24212475
poly_result = pb.intersect(b_poly1, b_poly2, int_tol)
2422-
except Exception:
2423-
return None # typically a tolerance issue causing failure
2476+
except Exception: # tiny edge caused a failure; try with small tol
2477+
int_tol = int_tol / 100
2478+
try:
2479+
poly_result = pb.intersect(b_poly1, b_poly2, int_tol)
2480+
except Exception:
2481+
return None # the edge is just too tiny
24242482
# rebuild the Face3D from the results and return them
24252483
int_faces = Face3D._from_bool_poly(poly_result, prim_pl, tolerance)
24262484
return int_faces
@@ -2491,8 +2549,13 @@ def coplanar_split(face1, face2, tolerance, angle_tolerance):
24912549
int_tol = tolerance / 1000
24922550
try:
24932551
int_result, poly1_result, poly2_result = pb.split(b_poly1, b_poly2, int_tol)
2494-
except Exception:
2495-
return [face1], [face2] # typically a tolerance issue causing failure
2552+
except Exception: # tiny edge caused a failure; try one more time with small tol
2553+
int_tol = int_tol / 100
2554+
try:
2555+
int_result, poly1_result, poly2_result = \
2556+
pb.split(b_poly1, b_poly2, int_tol)
2557+
except Exception:
2558+
return [face1], [face2] # the edge is just too tiny
24962559
# rebuild the Face3D from the results and return them
24972560
int_faces = Face3D._from_bool_poly(int_result, prim_pl, tolerance)
24982561
poly1_faces = Face3D._from_bool_poly(poly1_result, prim_pl, tolerance)
@@ -2561,11 +2624,15 @@ def coplanar_union_all(faces, tolerance, angle_tolerance):
25612624
prev_poly.append(bool_pts)
25622625
bool_polys.append(pb.BooleanPolygon(prev_poly))
25632626
# union the boolean polygons with one another
2564-
int_tol = tolerance / 100
2627+
int_tol = tolerance / 1000
25652628
try:
25662629
poly_result = pb.union_all(bool_polys, int_tol)
2567-
except Exception:
2568-
return None # typically a tolerance issue causing failure
2630+
except Exception: # tiny edge caused a failure; try with small tol
2631+
int_tol = int_tol / 100
2632+
try:
2633+
poly_result = pb.union_all(bool_polys, int_tol)
2634+
except Exception:
2635+
return None # the edge is just too tiny
25692636
# rebuild the Face3D from the results and return them
25702637
union_faces = Face3D._from_bool_poly(poly_result, prim_pl, tolerance)
25712638
return union_faces

tests/face3d_test.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1350,6 +1350,19 @@ def test_intersect_plane():
13501350
assert len(face.intersect_plane(plane_5)) == 2
13511351

13521352

1353+
def test_intersect_face():
1354+
"""Test the Face3D intersect_face method."""
1355+
pts1 = (Point3D(0, 0, 2), Point3D(2, 0, 2), Point3D(2, 2, 2), Point3D(0, 2, 2))
1356+
pts2 = (Point3D(1, -1, 0), Point3D(1, -1, 3), Point3D(1, 1, 3), Point3D(1, 1, 0))
1357+
face1 = Face3D(pts1)
1358+
face2 = Face3D(pts2)
1359+
1360+
intersect = face1.intersect_face(face2, 0.01)
1361+
assert len(intersect) == 1
1362+
int_line = intersect[0]
1363+
assert int_line.length == pytest.approx(1.0, rel=1e-3)
1364+
1365+
13531366
def test_project_point():
13541367
"""Test the Face3D project_point method."""
13551368
pts = (Point3D(0, 0), Point3D(2, 0), Point3D(2, 1), Point3D(1, 1),

0 commit comments

Comments
 (0)