Skip to content

compas_tf.model ¤

Classes¤

TFModel ¤

TFModel(name: str | None = None, **kwargs: object)

A :class:compas_tf.base_model.BaseModel whose geometry survives serialization.

Two additions, matching the ones on :class:compas_tf.element.TFElement:

  • :meth:bake walks the model and computes every element's geometry once. After that compas.json_dump(model, path) writes a file that compas.json_load brings back with the booleans already applied to the base geometry - no boolean backend needed, and no wait.
  • :meth:get_brep converts the whole model into one Brep compound of solid, coplanar-merged parts (and :meth:to_step writes it out).

Serialization itself is inherited: Model.__data__ already carries the elements, the tree and the interaction graph, and every compas_tf element puts its baked geometry in its own __data__.

Attributes¤

is_baked property ¤
is_baked: bool

True if every geometry-carrying element has geometry stored on it.

Methods:¤

bake ¤
bake(modelgeometry: bool = True) -> TFModel

Compute every element's default geometry once, and store it on the elements.

Elements that are not compas_tf elements are still given their modelgeometry, which is what the viewer and the Brep/STEP export read - they just cannot carry extra variants.

Parameters:

Name Type Description Default
modelgeometry bool

Also bake the model-space geometry of every element.

True

Returns:

Type Description
class:`TFModel`

Self, so it chains.

brep_meshes ¤
brep_meshes(variant: str | None = None) -> list

The model-space mesh of every element - what :meth:get_brep converts.

Parameters:

Name Type Description Default
variant str

A baked variant key to convert instead of the finished geometry. Elements that do not carry it are skipped - including any element that is not a compas_tf element, since only those hold variants.

None
clear_contacts ¤
clear_contacts() -> TFModel

Drop every contact stored on the interaction graph, keeping the edges.

Contact searches never remove a contact - they only fill in edges that have none (see :meth:compute_contacts). So a model loaded from file keeps whatever contacts were computed when it was written, and a fresh search has to start from a clean graph to report only its own result.

compute_bvh ¤
compute_bvh(nodetype=None, max_depth: int | None = None, leafsize: int = 1) -> ElementBVH

Bounding-volume hierarchy over the geometry elements only.

Model.compute_bvh feeds self.elements() to the BVH, which includes the :class:Groups. A Group carries no geometry, so its compute_aabb is the base-class raise NotImplementedError - and the BVH asks every element for its aabb. Any model built with :meth:merge therefore has groups in it and cannot build a BVH at all, which makes compute_contacts() fail outright.

compute_contacts_between_groups never hit this because it builds its own BVH from the participating elements, skipping groups explicitly. Filtering here fixes plain compute_contacts() the same way.

compute_contacts ¤
compute_contacts(tolerance: float = 1e-06, minimum_area: float = 0.01, contacttype: type[Contact] = Contact, contactmethod: Callable | None = None) -> None

Compute the contacts between the geometry elements of this model.

Overridden only to iterate :meth:geometry_elements instead of elements(): a :class:Group has no geometry, so asking the BVH for its neighbours calls the base-class compute_aabb and raises NotImplementedError. See :meth:compute_bvh for the same fix on the other side. Body otherwise verbatim from compas_model.

Computing contacts is done independently of the edges of the interaction graph. If contacts are found between two elements with an existing edge, the contacts attribute of the edge will be replaced. If there is no pre-existing edge, one will be added. No element pairs are excluded in the search based on the existence of an edge between their nodes in the interaction graph.

The search is conducted entirely based on the BVH of the elements contained in the model. It is a spatial search that creates topological connections between elements based on their geometrical interaction.

Parameters:

Name Type Description Default
tolerance float

The distance tolerance.

1e-06
minimum_area float

The minimum contact size.

0.01
contacttype type[Contact]

The contact class to use for the generated contacts.

Contact
contactmethod Callable | None

What detects the contacts of one candidate pair, called as contactmethod(a, b, tolerance=, minimum_area=, contacttype=). Default is a.compute_contacts(b, ...), i.e. mesh faces. Pass a :class:compas_tf.contacts.BrepContacts to run on Brep faces instead, or use :meth:compute_contacts_brep.

None
compute_contacts_brep ¤
compute_contacts_brep(tolerance: float = 1e-06, minimum_area: float = 0.1, groups: list[str] | None = None, groups_b: list[str] | None = None, contacttype: type[Contact] = Contact, clear: bool = False, **kwargs)

Contacts on Brep faces rather than mesh faces - one polygon per interface.

The same spatial search as :meth:compute_contacts, with :class:compas_tf.contacts.BrepContacts doing the detection: the BVH still prunes on the mesh AABBs, but each surviving pair is converted to a solid Brep with its coplanar faces merged (element.get_brep()) and intersected face against face. A boolean-triangulated interface therefore comes back as ONE contact carrying its hole loops, instead of one contact per triangle - see :mod:compas_tf.contacts for the numbers.

Parameters:

Name Type Description Default
tolerance float

The distance tolerance.

1e-06
minimum_area float

The minimum contact size. The 1e-2 default of the mesh search is below the noise of a merged Brep face; 1.0 mm2 is a sane floor for this model.

0.1
groups list[str]

Restrict the search to these named groups, as in :meth:compas_tf.base_model.BaseModel.compute_contacts_between_groups. Default searches every pair of geometry elements.

None
groups_b list[str]

The second side of a two-sided group query. Requires groups.

None
contacttype type[:class:`compas_model.interactions.Contact`]

The contact class to instantiate.

Contact
clear bool

Clear the contacts already on the graph first, so the result holds only what this search found. See :meth:clear_contacts.

False
**kwargs

Forwarded to :class:compas_tf.contacts.BrepContacts - holes, strict, skip, and any get_brep() keyword. skip=involving(DowelCylinderElement, ConnectorCylinderElement) drops the fastener contacts, which on this model are 74% of the total and are all a shaft touching its own hole.

{}

Returns:

Type Description
class:`compas_tf.contacts.BrepContacts`

The detector, holding the Brep cache it built (.breps) and the face pairs that failed (.errors).

contact_adjacency ¤
contact_adjacency() -> list[dict]

Which element each contact joins to which, one record per contact.

Returns:

Type Description
list[dict]

index, a / b (element names), a_type / b_type, a_guid / b_guid, and area. index is the position of the matching face in :meth:contacts_to_step's output.

contact_breps ¤
contact_breps() -> list

One planar-face Brep per contact, named contact_<i>__<a>__<b>.

Boundary loop only - holes are dropped, since a contact written this way is a surface for inspection, not a solid. The name is for use in memory: STEP does not carry it (see :meth:contacts_to_json).

contact_pairs ¤
contact_pairs() -> Iterator

(index, element_a, element_b, contact) for every contact.

The order is the one :meth:contacts yields, and it is what makes the adjacency sidecar work - see :meth:contacts_to_json. Everything that writes contacts out goes through here, so the indices always agree.

contacts_to_json ¤
contacts_to_json(path: str) -> str

Write the contact adjacency beside the contact STEP.

Record i describes face i of the file :meth:contacts_to_step wrote, which is the only way to know which two elements a face in that file joins.

contacts_to_step ¤
contacts_to_step(path: str, author: str = 'compas_tf') -> str

Write the contacts to their own STEP file, one face each.

Separate from :meth:to_step on purpose: that file is the shop's, and a reader splits it with .solids, which would drop loose faces.

The faces carry no adjacency - STEP drops the per-shape name (a round-trip through to_step / from_step returns them all as OCCBrepFace, and from_step_with_attributes collapses the compound into one unnamed entry). What it does preserve is the ORDER, so pair the file with :meth:contacts_to_json and match on index.

element_breps ¤
element_breps(variant: str | None = None, cache: dict | None = None, **kwargs) -> list

One solid, coplanar-merged Brep per element, named after the element.

Parameters:

Name Type Description Default
variant str

A baked variant key to convert instead of each element's finished geometry. Elements that do not carry it are skipped - including any element that is not a compas_tf element, since only those can hold variants.

None
cache dict[int, :class:`compas_occt.brep.OCCBrep`]

Already-converted Breps keyed by id(element), reused instead of being rebuilt. This is the .breps of a :class:compas_tf.contacts.BrepContacts, so a model that has just had its contacts computed on Breps does not pay for the conversion twice. Ignored when variant is given, since the cache holds the finished geometry.

None
**kwargs

Forwarded to :meth:compas_tf.brep.BrepMixin.get_brep.

{}

Returns:

Type Description
list[:class:`compas_occt.brep.OCCBrep`]
from_model classmethod ¤
from_model(model: BaseModel, name: str | None = None) -> TFModel

Re-wrap a plain model as a :class:TFModel.

Accepts a :class:compas_model.models.Model too - only __data__ is read, and that is the same on both.

The elements are shared, not copied - the tree and the interaction graph are rebuilt around them by Model.__from_data__.

Parameters:

Name Type Description Default
model :class:`compas_tf.base_model.BaseModel` or :class:`compas_model.models.Model`
required
name str
None

Returns:

Type Description
class:`TFModel`
geometry_elements ¤
geometry_elements() -> Iterator

Every element in the model that carries geometry (groups excluded).

to_step ¤
to_step(path: str, author: str = 'compas_tf', variant: str | None = None, cache: dict | None = None, **kwargs) -> str

Write the whole model to a STEP file, one solid per element.

Parameters:

Name Type Description Default
path str

Destination .stp / .step file.

required
author str

Author recorded in the STEP header.

'compas_tf'
variant str

A baked variant key to write instead of the finished geometry (see :meth:element_breps).

None
cache dict[int, :class:`compas_occt.brep.OCCBrep`]

Already-converted Breps to reuse - see :meth:element_breps.

None
**kwargs

Forwarded to :meth:compas_tf.brep.BrepMixin.get_brep.

{}

Returns:

Type Description
str

The path written.

unbake ¤
unbake() -> TFModel

Drop the baked geometry of every element.