Skip to content

compas_tf.base_model ¤

A local copy of compas_model.models.model.

compas_tf inherits from this instead of :class:compas_model.models.Model because the Model compas_tf needs does not exist in any compas_model release. duplicate, merge, compute_contacts_between_groups and find_group_with_name live only on an unmerged branch - verified by reading compas_model/models/model.py out of the published 0.9.3 wheel, where all four are absent. Depending on that branch meant compas_tf only worked against a local editable checkout.

Everything this module imports FROM compas_model (Element, Group, Contact, Material, Modifier, KDTree, ElementTree, ElementNode, InteractionGraph, the BVH node types) does ship in 0.9.3, so only the model class itself is copied.

Kept as a verbatim copy rather than a patch so it can be diffed against upstream. :class:compas_tf.model.TFModel subclasses it. Delete this module and go back to inheriting from compas_model once that branch is released.

find_groups_with_names is the one method here that is compas_tf's own rather than the branch's, and belongs upstream with the other four.

Classes¤

BaseModel ¤

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

Class representing a general model of hierarchically organised elements, with interactions.

Notes

A model has an element tree to store the hierarchical relationships between elements, and an interaction graph to store the interactions between pairs of elements. BaseModel elements are contained in the tree hierarchy in tree nodes, and in the interaction graph in graph nodes.

Every model element can appear only once in the tree, and only once in the graph. This means that every element can have only one hierarchical parent. At the same time, independently of the hierarchy, every element can have many interactions with other elements.

Methods:¤

add_element ¤
add_element(element: Element | ElementType, parent: Element | None = None, material: Material | None = None) -> Element | ElementType

Add an element to the model.

Parameters:

Name Type Description Default
element Element | ElementType

The element to add.

required
parent Element | None

The parent element of the element. If None, the element will be added directly under the root element.

None
material Material | None

A material to assign to the element. Note that the material should have already been added to the model before it can be assigned.

None

Returns:

Type Description
Element

The element added to the model.

Raises:

Type Description
ValueError

If the parent node is not a GroupNode.

ValueError

If a material is provided that is not part of the model.

add_elements ¤
add_elements(elements: list[Element | ElementType], parent: Element | None = None, material: Material | None = None) -> list[Element | ElementType]

Add a list of elements to the model.

Parameters:

Name Type Description Default
elements list[Element | ElementType]

The elements to add.

required
parent Element | None

The parent element of the elements. If None, the elements will be added directly under the root element.

None
material Material | None

A material to assign to the elements. Note that the material should have already been added to the model before it can be assigned.

None

Returns:

Type Description
list[Element]

The list of elements added to the model.

Raises:

Type Description
ValueError

If the parent node is not a GroupNode.

ValueError

If a material is provided that is not part of the model.

add_group ¤
add_group(name: str | None = None) -> Group

Add a group to the model.

Parameters:

Name Type Description Default
name str | None

The name of the group.

None

Returns:

Type Description
Group

The group added to the model.

add_interaction ¤
add_interaction(a: Element, b: Element, modifier: Modifier | None = None) -> tuple[int, int]

Add an interaction between two elements of the model.

Parameters:

Name Type Description Default
a Element

The first element.

required
b Element

The second element.

required

Returns:

Type Description
tuple[int, int]

The edge of the interaction graph representing the interaction between the two elements.

Raises:

Type Description
Exception

If one or both of the elements are not in the graph.

Notes

In future implementations, adding an interaction should implicitly take care of adding modifiers onto the interaction edges, based on the registered modifiers of the source nodes.

In the current implementation, modifiers have to be added explicitly using :meth:add_modifiers. This method will add an interaction edge from the source of the modifier to its target if needed and store the modifier object on it.

add_material ¤
add_material(material: Material) -> None

Add a material to the model.

Parameters:

Name Type Description Default
material Material

A material.

required
add_modifier ¤
add_modifier(source: Element, target: Element, modifier: Modifier) -> list[Modifier]

Add a modifier between two elements, with one the source of the modifier and the other the target.

Parameters:

Name Type Description Default
source Element

The source element.

required
target Element

The target element.

required
modifier Modifier

The modifier.

required

Returns:

Type Description
list[Modifier]

All modifiers stored on the interaction edge between source and target.

Notes

This element should implement the protocol specified by the modifier. The methods of the source element defined by the protocol are used to compute the tools involved in the modification. The tools are used by the modifier to apply the modification to the model geometry of the target element.

The modifier defines the protocol for the modification. The protocol should be implemented by the source element. The protocol methods of the source element are used to compute the modification tool. The modifier applies the modification to the target using this tool.

add_or_get_material ¤
add_or_get_material(material: Material) -> Material

Add a material to the model or retrieve an existing instance of the same type.

Parameters:

Name Type Description Default
material Material

A material.

required

Returns:

Type Description
Material

The added or existing material.

assign_material ¤
assign_material(material: Material, element: Element | None = None, elements: list[Element] | None = None) -> None

Assign a material to an element or a list of elements.

Parameters:

Name Type Description Default
material Material

The material.

required
element Element | None

The element to assign the material to.

None
elements list[Element] | None

The list of elements to assign the material to.

None

Raises:

Type Description
ValueError

If neither element or elements is provided.

ValueError

If both element and elements are provided.

ValueError

If the material is not part of the model.

ValueError

If the provided element or one of the elements in the provided element list is not part of the model.

compute_bvh ¤
compute_bvh(nodetype: type[ElementAABBNode] | type[ElementOBBNode] = ElementAABBNode, max_depth: int | None = None, leafsize: int = 1) -> ElementBVH

Compute the Bounding Volume Hierarchy (BVH) of the elements for fast collision checks.

Parameters:

Name Type Description Default
nodetype type[ElementAABBNode] | type[ElementOBBNode]

The type of bounding volume node used in the tree.

ElementAABBNode
max_depth int | None

The maximum depth used for constructing the BVH.

None
leafsize int

The number of elements contained in a BVH leaf node.

1

Returns:

Type Description
ElementBVH

The computed BVH.

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

Compute the contacts between the block elements of this 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
compute_contacts_between_groups ¤
compute_contacts_between_groups(groups: list[str], groups_b: list[str] | None = None, tolerance: float = 1e-06, minimum_area: float = 0.01, contacttype: type[Contact] = Contact, contactmethod: Callable | None = None) -> None

Compute contacts only between elements of different named groups.

Like :meth:compute_contacts, this is a spatial (BVH) search that adds a contact interaction wherever two element geometries touch. Unlike it, only the named groups take part, and pairs within a single group are never tested.

Two modes:

  • All-pairs (groups_b omitted): detection runs between every unordered pair of distinct groups in groups. This is the natural companion to :meth:merge - every merged sub-model becomes its own group, so passing those names finds the seams between the sub-models.

  • Two-sided (groups_b given): detection runs only between an element of a groups group and an element of a groups_b group. Pairs within groups and pairs within groups_b are skipped. Use this to contact one set against another - e.g. columns against only the outer ribs - without the ribs also contacting each other.

Each element is assigned to the nearest enclosing group whose name is requested, so nested groups behave predictably: pass an outer group name to treat all its descendants as one group, or the inner names to keep them apart.

Parameters:

Name Type Description Default
groups list[str]

Names of the groups on the first side.

required
groups_b list[str] | None

Names of the groups on the second side. If None, groups is tested against itself (all distinct pairs).

None
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 actually detects the contacts of one accepted 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.

None

Raises:

Type Description
ValueError

All-pairs: if fewer than two named groups contain any elements. Two-sided: if either side has no elements.

compute_kdtree ¤
compute_kdtree() -> KDTree

Compute the KD tree of the elements for fast nearest neighbour queries.

The KD tree is built using the reference points of the elements of the model.

Returns:

Type Description
KDTree

The computed KD tree.

duplicate ¤
duplicate() -> BaseModel

Return a fully independent copy of this model (elements get new guids).

Unlike :meth:copy (and deepcopy), which reproduce each element's guid verbatim - so two copies share guids and cannot coexist in one model - duplicate re-clones every element with a fresh guid and rewires the tree/graph. The result is independent, so it can be placed and :meth:merge-d alongside the original.

Returns:

Type Description
BaseModel

An independent copy of this model.

element_nnbrs ¤
element_nnbrs(element: Element, k: int = 1) -> list[tuple[Element, float]]

Find the nearest neighbours to a root element.

Parameters:

Name Type Description Default
element Element

The root element.

required
k int

The number of nearest neighbours that should be returned.

1

Returns:

Type Description
list[tuple[Element, float]]

A list of nearest neighbours, with each neighbour defined as an element and the distance of that element to the root element.

elements ¤
elements() -> Iterator[Element]

Iterate over the elements contained in the model.

Returns:

Type Description
Iterator[Element]

The elements contained in the model.

find_all_elements_of_type ¤
find_all_elements_of_type(elementtype: type[Element]) -> list[Element]

Find all model elements of a given type.

Parameters:

Name Type Description Default
elementtype type[Element]

The type of element.

required

Returns:

Type Description
list[Element]

The elements of the requested type.

find_element_with_name ¤
find_element_with_name(name: str) -> Element | None

Returns True if the model contains an element with the given name.

Parameters:

Name Type Description Default
name str

The name to check.

required

Returns:

Type Description
Element or None

The element with the name, if found.

find_group_with_name ¤
find_group_with_name(name: str) -> BaseModel | None

Extract a named group's subtree as a new, independent model.

Search the element tree for a :class:Group with the given name and return a fresh model holding an independent copy of that group's contents - its elements re-cloned with new guids (like :meth:duplicate), their hierarchy preserved, and any interactions (with their modifiers/contacts) that fall entirely within the subtree carried along. The source model is left untouched.

This is the inverse of :meth:merge: merge nests a model under a group named after it, while find_group_with_name lifts such a group back out into a standalone model (named after the group). The group's full placement in the source hierarchy - the accumulated transformation of its ancestors and the source model itself - is baked into the new model's transformation, so the extracted geometry keeps its world position. The first group matching the name is used.

Parameters:

Name Type Description Default
name str

The name of the group to extract.

required

Returns:

Type Description
BaseModel or None

A new model rooted at the group's contents, or None if no group with that name exists.

find_groups_with_names ¤
find_groups_with_names(names: list[str], name: str | None = None, neighbors: bool | tuple | Callable = False) -> BaseModel

Extract several named groups at once, as one new independent model.

The multi-group counterpart of :meth:find_group_with_name, for lifting an assembly out of a big model - one column plus the quarter it carries, say. Because the groups are extracted together, the interactions BETWEEN them survive; extracting them one at a time and merging the results would lose exactly those, which is the joint you wanted.

Where :meth:find_group_with_name drops the group node and folds its placement into the new model's transformation, this keeps each group's chain of ancestors - pruned to the groups asked for. So a quarter_model_0 under floor_model/quarters_model comes back at the same path, and every group transformation on the way down still applies. The elements are re-cloned with fresh guids and the source model is left untouched.

Parameters:

Name Type Description Default
names list[str]

The names of the groups to extract. The first group matching each name is used.

required
name str | None

Name for the new model. Defaults to the names joined by +.

None
neighbors bool | tuple | Callable

Also bring in elements outside the named groups that belong with one inside - the loose fasteners a bay is bolted together with, which live in their own top-level group rather than in the bay's. Two passes: every element that interacts with one inside, and then, for the elements that have no interaction at all, every one whose bounding box lands inside the bay. Their ancestor groups are recreated the same way. The second pass reads modelgeometry.

True admits anything that touches, which is rarely what an assembly means - the ribs and beams of the quarters next door touch a bay across the seam, and so does the oculus. Pass a tuple of element types (or a predicate taking an element) to admit only the hardware::

bay = model.find_groups_with_names(
    ["column_model_0", "quarter_model_0"],
    neighbors=(ConnectorElement, ConnectorWedgeElement, ...),
)
False

Returns:

Type Description
BaseModel

A new model holding an independent copy of those groups.

Raises:

Type Description
ModelElementNotFound

If any of the names does not match a group.

has_element ¤
has_element(element: Element) -> bool

Returns True if the model contains the given element.

Parameters:

Name Type Description Default
element Element

The element to check.

required

Returns:

Type Description
bool

True if the model contains the element.

has_element_with_name ¤
has_element_with_name(name: str) -> bool

Returns True if the model contains an element with the given name.

Parameters:

Name Type Description Default
name str

The name to check.

required

Returns:

Type Description
bool

True if the model contains an element with the name.

has_interaction ¤
has_interaction(a: Element, b: Element) -> bool

Returns True if two elements have an interaction set between them.

Parameters:

Name Type Description Default
a Element

The first element.

required
b Element

The second element.

required

Returns:

Type Description
bool

True if the elements have an interaction.

has_material ¤
has_material(material: Material) -> bool

Verify that the model contains a specific material.

Parameters:

Name Type Description Default
material Material

A model material.

required

Returns:

Type Description
bool

True if the model contains the material.

materials ¤
materials() -> Iterator[Material]

Iterate over the materials stored in the model.

Returns:

Type Description
Iterator[Material]

The materials stored in the model.

merge ¤
merge(models: list[BaseModel], parent: Element | None = None) -> BaseModel

Merge a list of models into this model, each under its own group.

Each input is added under a new group (named after it) nested under parent - or under the model root when parent is None. For every input its materials, element tree (hierarchy preserved) and interactions (with their modifiers/contacts) are brought in. The inputs are consumed - their elements are moved in, not copied - so :meth:duplicate them first if you need to keep the originals (or to merge several instances of one model).

Build a new combined model by merging into a fresh one, optionally in one line: BaseModel(name="columns").merge(column_models). Insert into an existing model under a chosen group/element by passing parent.

Parameters:

Name Type Description Default
models list[BaseModel]

The list of models to merge into this one.

required
parent Element | None

The group/element to nest the merged models under. Root if None.

None

Returns:

Type Description
BaseModel

This model (returned for chaining).

point_nnbrs ¤
point_nnbrs(point: Point, k: int = 1) -> list[tuple[Element, float]]

Find the nearest neighbours to a point.

Parameters:

Name Type Description Default
point Point

The root point.

required
k int

The number of nearest neighbours that should be returned.

1

Returns:

Type Description
list[tuple[Element, float]]

A list of nearest neighbours, with each neighbour defined as an element and the distance of that element to the root element.

remove_element ¤
remove_element(element: Element) -> None

Remove an element from the model.

Parameters:

Name Type Description Default
element Element

The element to remove.

required
remove_elements_of_type ¤
remove_elements_of_type(elementtype: type[Element]) -> list[Element]

Remove all model elements of a given type.

Parameters:

Name Type Description Default
elementtype type[Element]

The type of element.

required

Returns:

Type Description
list[Element]

The removed elements.

remove_interaction ¤
remove_interaction(a: Element, b: Element) -> None

Remove the interaction between two elements.

Parameters:

Name Type Description Default
a Element
required
b Element
required
transform ¤
transform(transformation: Transformation) -> None

Transform the model and all that it contains.

Parameters:

Name Type Description Default
transformation Transformation

The transformation to apply.

required