compas_tf.contacts
¤
Contact detection on Brep faces instead of mesh faces.
A contact is two coplanar, oppositely oriented, overlapping polygons. Where those polygons come from decides the answer:
mesh_mesh_contactsuseselement.modelgeometry- triangles, after a boolean. One physical interface comes back as several contact polygons (the column/outer-rib joint splits into 8) from an O(faces_a x faces_b) search over the soup: 626 x 246 faces, ~13 s for that one pair. It also drops area - on one of the 8 identical column/rib joints it returns 50344 mm2 instead of 62632, invisibly, because the interface is already in pieces.brep_brep_contactsuses Brep faces. :func:compas_tf.brep.mesh_to_brepmerges those triangles back into the modelled faces and a Brep face carries its hole loops, so the same joint is 1 contact of the full area in ~0.2 s.
Breps cannot simply be put on the elements - Element.compute_aabb and the
BVH are Mesh-only - so :class:BrepContacts keeps the Mesh model, lets the BVH
prune on the mesh AABBs, and converts only the pairs that survive.
Local, not upstream. :func:brep_brep_contacts is compas_model's function
with the failures contained: upstream lets one bad face pair kill the whole
element pair, and 23 of 2444 pairs here die inside
brepface_brepface_overlap_holes (shapely: GeometryCollection has no
attribute 'interiors', unable to assign free hole to a shell) AFTER the
contact polygon computed fine. Each face pair is guarded on its own, so that
costs the holes, not the contact. The geometry itself is still upstream's.
No tessellation. Brep.overlap would prefilter the face pairs for us, but
it works on OCC's tessellation and triangulates each Brep on first use - at
TOL.lineardeflection (0.001 mm, a 1 micron chord tolerance on a building)
that meshing alone was 200 s of a 205 s search. It is not needed: a contact
needs two faces with exactly opposite normals whose boundary AABBs meet, and
both are exact tests on the face boundaries. :func:prepare_faces takes those
once per Brep and :func:brep_brep_contacts filters on them, for identical
results (585 pairs, 2.0921e+07 mm2) 3.8x faster than overlap() even with its
tessellation already cached.
Do not narrow the prefilter to face.is_plane: these Breps are built from
mesh polygons, so a flat quad comes back as a bilinear surface and the planar
test drops 529 of the 585 pairs.
Classes¤
BrepContacts
¤
Element-pair contact detection on Breps, with a Brep cache.
Callable with the signature the model's contact search expects
((a, b, tolerance=, minimum_area=, contacttype=) -> list[Contact]), so it
goes straight into contactmethod=::
method = BrepContacts()
model.compute_contacts(minimum_area=1.0, contactmethod=method)
print(len(method.breps), "breps,", len(method.errors), "face pairs failed")
or, the same thing in one call,
:meth:compas_tf.model.TFModel.compute_contacts_brep.
Each element is converted to a Brep at most once - conversion is the expensive half (~55 ms per element on the cantilevers model, vs ~85 ms per element PAIR for the detection), and every element takes part in several pairs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
holes
|
bool
|
Compute the hole loops of each contact. |
True
|
skip
|
callable
|
|
None
|
strict
|
bool
|
Let a failing face pair raise instead of being recorded in
:attr: |
False
|
**brepkwargs
|
Forwarded to |
{}
|
Attributes:
| Name | Type | Description |
|---|---|---|
breps |
dict[int, :class:`compas_occt.brep.OCCBrep`]
|
The cache, keyed by |
errors |
list[tuple[str, str, str, Exception]]
|
|
skipped |
int
|
How many pairs |
Functions:¤
between
¤
between(*types) -> Callable
A skip predicate that rejects a pair only if BOTH elements are one of types.
The narrower companion to :func:involving: it removes contacts within a
set of elements while keeping the contacts each of them makes with the rest
of the model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*types
|
type
|
Element classes, matched with |
()
|
Returns:
| Type | Description |
|---|---|
callable
|
|
brep_brep_contacts
¤
brep_brep_contacts(a, b, tolerance: float = 1e-06, minimum_area: float = 0.1, contacttype: type[Contact] = Contact, holes: bool = True, errors: list | None = None, faces_a: list | None = None, faces_b: list | None = None)
Face-face contacts between two Breps - one contact polygon per interface.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
:class:`compas_occt.brep.OCCBrep`
|
The two solids. |
required |
b
|
:class:`compas_occt.brep.OCCBrep`
|
The two solids. |
required |
tolerance
|
float
|
Distance tolerance: maximum deviation from the perfectly flat interface plane. |
1e-06
|
minimum_area
|
float
|
Contacts (and holes) smaller than this are dropped. |
0.1
|
contacttype
|
type[:class:`compas_model.interactions.Contact`]
|
The contact class to instantiate. |
Contact
|
holes
|
bool
|
Compute the hole loops of each contact. Turn off to skip the one part of the computation that is allowed to fail (see the module docstring). Ignored on a compas_model too old to return the transformations. |
True
|
errors
|
list
|
Appended to with |
None
|
faces_a
|
list
|
:func: |
None
|
faces_b
|
list
|
:func: |
None
|
Returns:
| Type | Description |
|---|---|
list[:class:`compas_model.interactions.Contact`]
|
|
contact_holes
¤
contact_holes(contact: Contact) -> list
The hole loops of a contact, or an empty list.
Contact stores them (and serializes them, and builds them into
contact.brep) but exposes no accessor, unlike contact.polygon.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
contact
|
:class:`compas_model.interactions.Contact`
|
|
required |
Returns:
| Type | Description |
|---|---|
list[:class:`compas.geometry.Polygon`]
|
|
involving
¤
involving(*types) -> Callable
A skip predicate that rejects a pair if EITHER element is one of types.
For dropping whole classes of element from a contact search - typically fasteners, whose contacts are a shaft sitting in its own hole. On the cantilevers model the dowels and connector cylinders account for 2072 of the 2805 contacts (each faceted shaft touches its host once per facet), and none of them touch each other, so they are pure noise over the 733 real interfaces::
model.compute_contacts_brep(skip=involving(DowelCylinderElement, ConnectorCylinderElement))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*types
|
type
|
Element classes, matched with |
()
|
Returns:
| Type | Description |
|---|---|
callable
|
|
prepare_faces
¤
(face, points, normal, aabb) per face of a Brep, for the pair loop.
Do this once per Brep and reuse it across every pair the Brep takes part in;
:class:BrepContacts caches it. Recomputing per pair is what makes the
naive loop slow, not the intersections.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
brep
|
:class:`compas_occt.brep.OCCBrep`
|
|
required |
minimum_area
|
float
|
Faces smaller than this cannot carry a contact and are dropped here. |
0.1
|
Returns:
| Type | Description |
|---|---|
list[tuple]
|
|