@@ -1824,6 +1824,62 @@ def sub_rects_from_rect_dimensions(
1824
1824
base_plane )]
1825
1825
return final_faces
1826
1826
1827
+ @staticmethod
1828
+ def coplanar_union (face1 , face2 , tolerance , angle_tolerance ):
1829
+ """Boolean Union two coplanar Face3D with one another.
1830
+
1831
+ Args:
1832
+ face1: A Face3D for the first face that will be unioned with the second face.
1833
+ face2: A Face3D for the second face that will be unioned with the first face.
1834
+ tolerance: The minimum distance between points before they are considered
1835
+ distinct from one another.
1836
+ angle_tolerance: The max angle in radians that the plane normals can
1837
+ differ from one another in order for them to be considered coplanar.
1838
+
1839
+ Returns:
1840
+ A single Face3D for the Union of the two input Face3D. When the faces
1841
+ are not coplanar or they do not overlap, None will be returned.
1842
+ """
1843
+ # test whether the faces are coplanar
1844
+ prim_pl = face1 .plane
1845
+ if not prim_pl .is_coplanar_tolerance (face2 .plane , tolerance , angle_tolerance ):
1846
+ return None
1847
+ # test whether the two polygons have any overlap in 2D space
1848
+ f1_poly = face1 .boundary_polygon2d
1849
+ f2_poly = Polygon2D (tuple (prim_pl .xyz_to_xy (pt ) for pt in face2 .boundary ))
1850
+ if not Polygon2D .overlapping_bounding_rect (f1_poly , f2_poly , tolerance ):
1851
+ return None
1852
+ if f1_poly .polygon_relationship (f2_poly , tolerance ) == - 1 :
1853
+ return None
1854
+ # snap the polygons to one another to avoid tolerance issues
1855
+ try :
1856
+ f1_poly = f1_poly .remove_colinear_vertices (tolerance )
1857
+ f2_poly = f2_poly .remove_colinear_vertices (tolerance )
1858
+ except AssertionError : # degenerate faces input
1859
+ return None
1860
+ s2_poly = f1_poly .snap_to_polygon (f2_poly , tolerance )
1861
+ # get BooleanPolygons of the two faces
1862
+ f1_polys = [(pb .BooleanPoint (pt .x , pt .y ) for pt in f1_poly .vertices )]
1863
+ f2_polys = [(pb .BooleanPoint (pt .x , pt .y ) for pt in s2_poly .vertices )]
1864
+ if face1 .has_holes :
1865
+ for hole in face1 .hole_polygon2d :
1866
+ f1_polys .append ((pb .BooleanPoint (pt .x , pt .y ) for pt in hole .vertices ))
1867
+ if face2 .has_holes :
1868
+ for hole in face2 .holes :
1869
+ h_pt2d = (prim_pl .xyz_to_xy (pt ) for pt in hole )
1870
+ f2_polys .append ((pb .BooleanPoint (pt .x , pt .y ) for pt in h_pt2d ))
1871
+ b_poly1 = pb .BooleanPolygon (f1_polys )
1872
+ b_poly2 = pb .BooleanPolygon (f2_polys )
1873
+ # split the two boolean polygons with one another
1874
+ int_tol = tolerance / 100
1875
+ try :
1876
+ poly_result = pb .union (b_poly1 , b_poly2 , int_tol )
1877
+ except Exception :
1878
+ return [face1 ], [face2 ] # typically a tolerance issue causing failure
1879
+ # rebuild the Face3D from the results and return them
1880
+ union_faces = Face3D ._from_bool_poly (poly_result , prim_pl )
1881
+ return union_faces [0 ]
1882
+
1827
1883
@staticmethod
1828
1884
def coplanar_split (face1 , face2 , tolerance , angle_tolerance ):
1829
1885
"""Split two coplanar Face3D with one another (ensuring matching overlapped area)
@@ -1928,6 +1984,45 @@ def _from_bool_poly(bool_polygon, plane):
1928
1984
face_3d .append (Face3D (pg_3d [0 ], plane , holes = pg_3d [1 :]))
1929
1985
return face_3d
1930
1986
1987
+ @staticmethod
1988
+ def group_by_coplanar_overlap (faces , tolerance ):
1989
+ """Group coplanar Face3Ds depending on whether they overlap one another.
1990
+
1991
+ This is useful as a pre-step before running Face3D.coplanar_union()
1992
+ in order to assess whether unionizing is necessary and to ensure that
1993
+ it is only performed among the necessary groups of faces.
1994
+
1995
+ Args:
1996
+ faces: A list of Face3D to be grouped by their overlapping.
1997
+ tolerance: The minimum distance from the edge of a neighboring Face3D
1998
+ at which a point is considered to overlap with that Face3D.
1999
+
2000
+ Returns:
2001
+ A list of lists where each sub-list represents a group of Face3Ds
2002
+ that all overlap with one another.
2003
+ """
2004
+ # create polygons for all of the faces
2005
+ r_plane = faces [0 ].plane
2006
+ polygons = [Polygon2D ([r_plane .xyz_to_xy (pt ) for pt in face .vertices ])
2007
+ for face in faces ]
2008
+ # loop through the polygons and check to see if it overlaps with the others
2009
+ grouped_polys , grouped_faces = [[polygons [0 ]]], [[faces [0 ]]]
2010
+ for poly , face in zip (polygons [1 :], faces [1 :]):
2011
+ group_found = False
2012
+ for poly_group , face_group in zip (grouped_polys , grouped_faces ):
2013
+ for oth_poly in poly_group :
2014
+ if poly .polygon_relationship (oth_poly , tolerance ) >= 0 :
2015
+ poly_group .append (poly )
2016
+ face_group .append (face )
2017
+ group_found = True
2018
+ break
2019
+ if group_found :
2020
+ break
2021
+ if not group_found : # the polygon does not overlap with any of the others
2022
+ grouped_polys .append ([poly ]) # make a new group for the polygon
2023
+ grouped_faces .append ([face ]) # make a new group for the face
2024
+ return grouped_faces
2025
+
1931
2026
@staticmethod
1932
2027
def join_coplanar_faces (faces , tolerance ):
1933
2028
"""Join a list of coplanar Face3Ds together to get as few as possible.
0 commit comments