@@ -122,14 +122,14 @@ def mesh_mesh_contacts(
122122 if not is_opposite_normal_normal (a_normal , b_normal ):
123123 continue
124124
125- result = polygon_polygon_overlap (a_points , a_normal , b_points , b_normal , tolerance , minimum_area )
125+ result = polygon_polygon_overlap (a_points , b_points , a_normal , tolerance , minimum_area ) # type: ignore
126126
127127 # this is not always an accurate representation of the interface
128128 # if the polygon has holes
129129 # the interface is incorrect
130130
131131 if result :
132- points , frame , area = result
132+ points , frame , area , matrix_to_local , matrix_to_world = result
133133 contact = contacttype (points = points , frame = frame , size = area )
134134 contacts .append (contact )
135135
@@ -200,40 +200,71 @@ def brep_brep_contacts(
200200 if not is_opposite_normal_normal (a_normal , b_normal ):
201201 continue
202202
203- result = polygon_polygon_overlap (a_points , a_normal , b_points , b_normal , tolerance , minimum_area )
203+ result = polygon_polygon_overlap (a_points , b_points , a_normal , tolerance , minimum_area )
204204
205205 # this is not always an accurate representation of the interface
206206 # if the polygon has holes
207207 # the interface is incorrect
208208
209209 if result :
210- # if a_face.area < b_face.area:
211- # c = OCCBrep.from_brepfaces([a_face])
212- # else:
213- # c = OCCBrep.from_brepfaces([b_face])
210+ points , frame , area , matrix_to_local , matrix_to_world = result
214211
215- points , frame , area = result
216- contact = contacttype (points = points , frame = frame , size = area )
212+ # if the result exists, but the faces have holes
213+ # compute the holes in the intersection polygon
214+
215+ holes = brepface_brepface_overlap_holes (a_face , b_face , matrix_to_local , matrix_to_world , minimum_area )
216+
217+ contact = contacttype (points = points , frame = frame , size = area , holes = holes )
217218 contacts .append (contact )
218219
219220 return contacts
220221
221222
222- def polygon_polygon_overlap (a_points , a_normal , b_points , b_normal , tolerance , minimum_area ):
223+ def polygon_polygon_overlap (
224+ a_points : list [Point ] | list [list [float ]],
225+ b_points : list [Point ] | list [list [float ]],
226+ normal : Vector ,
227+ tolerance : float ,
228+ minimum_area : float ,
229+ ) -> Optional [tuple [list [Point ], Frame , float , Transformation , Transformation ]]:
230+ """Compute the overlap between two polygons defined by their corner points.
231+
232+ Parameters
233+ ----------
234+ a_points
235+ The corner points of the first polygon.
236+ b_points
237+ The corner points of the second polygon.
238+ normal
239+ The normal vector defining the desired orientation of the local coordinate frame.
240+ tolerance
241+ Maximum deviation from the perfectly flat interface plane.
242+ minimum_area
243+ Minimum area of the overlap polygon.
244+
245+ Returns
246+ -------
247+ tuple[list[Point], Frame, float, Transformation, Transformation] | None
248+ The corner points of the overlap polygon, the local coordinate frame, the area of the overlap polygon,
249+ the transformation to local coordinates, and the transformation to world coordinates.
250+ Returns None if there is no valid overlap.
251+
252+ """
223253 # this ensures that a shared frame is used to do the interface calculations
224254 frame = Frame (* bestfit_frame_numpy (a_points + b_points ))
225255
226256 # the frame should be oriented along the normal of the "a" face
227257 # this will align the interface frame with the resulting interaction edge
228258 # which is important for calculations with solvers such as CRA
229- if frame .zaxis .dot (a_normal ) < 0 :
259+ if frame .zaxis .dot (normal ) < 0 :
230260 frame .invert ()
231261
232262 # compute the transformation to frame coordinates
233- matrix = Transformation .from_change_of_basis (Frame .worldXY (), frame )
263+ matrix_to_local = Transformation .from_change_of_basis (Frame .worldXY (), frame )
264+ matrix_to_world = matrix_to_local .inverted ()
234265
235- a_projected = transform_points (a_points , matrix )
236- b_projected = transform_points (b_points , matrix )
266+ a_projected = transform_points (a_points , matrix_to_local )
267+ b_projected = transform_points (b_points , matrix_to_local )
237268
238269 p0 = ShapelyPolygon (a_projected )
239270 p1 = ShapelyPolygon (b_projected )
@@ -258,7 +289,65 @@ def polygon_polygon_overlap(a_points, a_normal, b_points, b_normal, tolerance, m
258289 return
259290
260291 coords = [[x , y , 0.0 ] for x , y , _ in intersection .exterior .coords ]
261- points = [Point (* xyz ) for xyz in transform_points (coords , matrix .inverted ())[:- 1 ]]
292+ points = [Point (* xyz ) for xyz in transform_points (coords , matrix_to_world )[:- 1 ]]
293+
262294 frame = Frame (centroid_polygon (points ), frame .xaxis , frame .yaxis )
263295
264- return points , frame , area
296+ return points , frame , area , matrix_to_local , matrix_to_world
297+
298+
299+ def brepface_brepface_overlap_holes (
300+ a ,
301+ b ,
302+ matrix_to_local ,
303+ matrix_to_world ,
304+ minimum_area ,
305+ ) -> Optional [list [Polygon ]]:
306+ """Compute the holes in the overlap between two brep faces.
307+
308+ Parameters
309+ ----------
310+ a : BrepFace
311+ The first brep face.
312+ b : BrepFace
313+ The second brep face.
314+ matrix_to_local : Transformation
315+ The transformation to local coordinates.
316+ matrix_to_world : Transformation
317+ The transformation to world coordinates.
318+ minimum_area : float
319+ Minimum area of a hole to be considered.
320+
321+ Returns
322+ -------
323+ list[Polygon] | None
324+ The holes in the overlap polygon, or None if there are no holes.
325+
326+ """
327+ a_points = a .loops [0 ].to_polygon ().points
328+ b_points = b .loops [0 ].to_polygon ().points
329+
330+ a_holes = []
331+ if len (a .loops ) > 1 :
332+ for loop in a .loops [1 :]:
333+ a_holes .append (transform_points (loop .to_polygon ().points , matrix_to_local ))
334+
335+ b_holes = []
336+ if len (b .loops ) > 1 :
337+ for loop in b .loops [1 :]:
338+ b_holes .append (transform_points (loop .to_polygon ().points , matrix_to_local ))
339+
340+ a_shapely = ShapelyPolygon (transform_points (a_points , matrix_to_local ), holes = a_holes )
341+ b_shapely = ShapelyPolygon (transform_points (b_points , matrix_to_local ), holes = b_holes )
342+
343+ intersection : ShapelyPolygon = a_shapely .intersection (b_shapely ) # type: ignore
344+ area = intersection .area
345+
346+ if area < minimum_area :
347+ # the interface area is too small
348+ return
349+
350+ holes = [[[x , y , 0.0 ] for x , y , _ in interior .coords ] for interior in intersection .interiors ]
351+ holes = [Polygon (transform_points (hole , matrix_to_world )[:- 1 ]) for hole in holes ]
352+
353+ return holes
0 commit comments