Skip to content

Commit f46be80

Browse files
committed
Add api.structural.assign_product, assign_to_building, and api.geometry.add_topology_representation
assign_product creates IfcRelAssignsToProduct linking a structural member to a physical building element. assign_to_building creates IfcRelServicesBuildings linking a structural analysis model to a building. add_topology_representation creates IfcTopologyRepresentation for structural elements, inferring the representation type from the item class. Generated with the assistance of an AI coding tool.
1 parent be05d77 commit f46be80

8 files changed

Lines changed: 432 additions & 0 deletions

File tree

src/ifcopenshell-python/ifcopenshell/api/geometry/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
from .. import wrap_usecases
2727
from .add_axis_representation import add_axis_representation
28+
from .add_topology_representation import add_topology_representation
2829
from .add_boolean import add_boolean
2930
from .clip_solid import clip_solid
3031
from .clip_solid_bounded import clip_solid_bounded
@@ -63,6 +64,7 @@
6364

6465
__all__ = [
6566
"add_axis_representation",
67+
"add_topology_representation",
6668
"add_boolean",
6769
"clip_solid",
6870
"clip_solid_bounded",
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# IfcOpenShell - IFC toolkit and geometry engine
2+
# Copyright (C) 2026 Dion Moult <dion@thinkmoult.com>
3+
#
4+
# This file is part of IfcOpenShell.
5+
#
6+
# IfcOpenShell is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU Lesser General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# IfcOpenShell is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU Lesser General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU Lesser General Public License
17+
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
18+
# This file was generated with the assistance of an AI coding tool.
19+
20+
from typing import Optional
21+
22+
import ifcopenshell
23+
24+
25+
_ITEM_TYPE_TO_REP_TYPE = {
26+
"IfcVertex": "Vertex",
27+
"IfcVertexPoint": "Vertex",
28+
"IfcEdge": "Edge",
29+
"IfcOrientedEdge": "Edge",
30+
"IfcEdgeCurve": "Edge",
31+
"IfcEdgeLoop": "Edge",
32+
"IfcPath": "Edge",
33+
"IfcFace": "Face",
34+
"IfcFaceSurface": "Face",
35+
"IfcAdvancedFace": "Face",
36+
"IfcClosedShell": "Face",
37+
"IfcOpenShell": "Face",
38+
"IfcConnectedFaceSet": "Face",
39+
}
40+
41+
42+
def add_topology_representation(
43+
file: ifcopenshell.file,
44+
context: ifcopenshell.entity_instance,
45+
item: ifcopenshell.entity_instance,
46+
representation_identifier: Optional[str] = None,
47+
representation_type: Optional[str] = None,
48+
) -> ifcopenshell.entity_instance:
49+
"""Adds an IfcTopologyRepresentation for a structural element
50+
51+
Structural analysis elements (IfcStructuralSurfaceMember,
52+
IfcStructuralCurveMember) use topology representations rather than solid
53+
geometry. This is analogous to :func:`add_axis_representation` and
54+
:func:`add_profile_representation` but produces an
55+
IfcTopologyRepresentation instead of an IfcShapeRepresentation.
56+
57+
The representation type ("Face", "Edge", "Vertex") is inferred from the
58+
item's IFC class if not provided explicitly.
59+
60+
:param context: The IfcGeometricRepresentationContext for the
61+
representation, typically a Reference context.
62+
:param item: The IfcTopologicalRepresentationItem (e.g. IfcFaceSurface,
63+
IfcEdge) to include in the representation.
64+
:param representation_identifier: The RepresentationIdentifier string.
65+
Defaults to the context's ContextIdentifier.
66+
:param representation_type: The RepresentationType string ("Face",
67+
"Edge", "Vertex"). Inferred from item class if not given.
68+
:return: The newly created IfcTopologyRepresentation entity.
69+
70+
Example:
71+
72+
.. code:: python
73+
74+
context = ifcopenshell.util.representation.get_context(
75+
model, "Model", "Reference", "GRAPH_VIEW")
76+
face = model.createIfcFaceSurface(bounds, surface, True)
77+
rep = ifcopenshell.api.geometry.add_topology_representation(
78+
model, context=context, item=face)
79+
ifcopenshell.api.geometry.assign_representation(
80+
model, product=member, representation=rep)
81+
"""
82+
if representation_identifier is None:
83+
representation_identifier = context.ContextIdentifier
84+
85+
if representation_type is None:
86+
for ifc_class, rep_type in _ITEM_TYPE_TO_REP_TYPE.items():
87+
if item.is_a(ifc_class):
88+
representation_type = rep_type
89+
break
90+
else:
91+
representation_type = "Undefined"
92+
93+
return file.createIfcTopologyRepresentation(
94+
context,
95+
representation_identifier,
96+
representation_type,
97+
[item],
98+
)

src/ifcopenshell-python/ifcopenshell/api/structural/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
from .add_structural_load_case import add_structural_load_case
3131
from .add_structural_load_group import add_structural_load_group
3232
from .add_structural_member_connection import add_structural_member_connection
33+
from .assign_product import assign_product
3334
from .assign_structural_analysis_model import assign_structural_analysis_model
35+
from .assign_to_building import assign_to_building
3436
from .edit_structural_analysis_model import edit_structural_analysis_model
3537
from .edit_structural_boundary_condition import edit_structural_boundary_condition
3638
from .edit_structural_connection_cs import edit_structural_connection_cs
@@ -57,7 +59,9 @@
5759
"add_structural_load_case",
5860
"add_structural_load_group",
5961
"add_structural_member_connection",
62+
"assign_product",
6063
"assign_structural_analysis_model",
64+
"assign_to_building",
6165
"edit_structural_analysis_model",
6266
"edit_structural_boundary_condition",
6367
"edit_structural_connection_cs",
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# IfcOpenShell - IFC toolkit and geometry engine
2+
# Copyright (C) 2026 Dion Moult <dion@thinkmoult.com>
3+
#
4+
# This file is part of IfcOpenShell.
5+
#
6+
# IfcOpenShell is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU Lesser General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# IfcOpenShell is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU Lesser General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU Lesser General Public License
17+
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
18+
# This file was generated with the assistance of an AI coding tool.
19+
20+
import ifcopenshell
21+
import ifcopenshell.api.root
22+
23+
24+
def assign_product(
25+
file: ifcopenshell.file,
26+
relating_product: ifcopenshell.entity_instance,
27+
related_object: ifcopenshell.entity_instance,
28+
) -> ifcopenshell.entity_instance:
29+
"""Links an object to a product via IfcRelAssignsToProduct
30+
31+
Typically used to associate a physical building element with a structural
32+
analysis member (IfcStructuralSurfaceMember, IfcStructuralCurveMember) so
33+
that analysis results can be traced back to the physical model.
34+
35+
:param relating_product: The IfcProduct that the object is assigned to,
36+
typically an IfcStructuralMember.
37+
:param related_object: The IfcObjectDefinition being assigned, typically
38+
a physical building element such as an IfcWall or IfcSlab.
39+
:return: The IfcRelAssignsToProduct relationship.
40+
41+
Example:
42+
43+
.. code:: python
44+
45+
wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
46+
member = ifcopenshell.api.root.create_entity(
47+
model, ifc_class="IfcStructuralSurfaceMember")
48+
ifcopenshell.api.structural.assign_product(model,
49+
relating_product=member, related_object=wall)
50+
"""
51+
for rel in relating_product.ReferencedBy or []:
52+
if not rel.is_a("IfcRelAssignsToProduct"):
53+
continue
54+
if related_object in rel.RelatedObjects:
55+
return rel
56+
related_objects = list(rel.RelatedObjects)
57+
related_objects.append(related_object)
58+
rel.RelatedObjects = related_objects
59+
return rel
60+
61+
rel = ifcopenshell.api.root.create_entity(file, ifc_class="IfcRelAssignsToProduct")
62+
rel.RelatingProduct = relating_product
63+
rel.RelatedObjects = [related_object]
64+
return rel
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# IfcOpenShell - IFC toolkit and geometry engine
2+
# Copyright (C) 2026 Dion Moult <dion@thinkmoult.com>
3+
#
4+
# This file is part of IfcOpenShell.
5+
#
6+
# IfcOpenShell is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU Lesser General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# IfcOpenShell is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU Lesser General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU Lesser General Public License
17+
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
18+
# This file was generated with the assistance of an AI coding tool.
19+
20+
import ifcopenshell
21+
import ifcopenshell.api.owner
22+
import ifcopenshell.guid
23+
24+
25+
def assign_to_building(
26+
file: ifcopenshell.file,
27+
structural_analysis_model: ifcopenshell.entity_instance,
28+
building: ifcopenshell.entity_instance,
29+
) -> ifcopenshell.entity_instance:
30+
"""Associates a structural analysis model with a building via IfcRelServicesBuildings
31+
32+
The existing :func:`assign_structural_analysis_model` handles
33+
IfcRelAssignsToGroup (linking structural members to the analysis model).
34+
This function handles the separate model-to-building relationship, which
35+
records which building the structural analysis model serves.
36+
37+
:param structural_analysis_model: The IfcStructuralAnalysisModel to
38+
associate with the building.
39+
:param building: The IfcBuilding (or other IfcSpatialStructureElement)
40+
that the structural analysis model serves.
41+
:return: The IfcRelServicesBuildings relationship.
42+
43+
Example:
44+
45+
.. code:: python
46+
47+
building = ifcopenshell.util.selector.filter_elements(model, "IfcBuilding")[0]
48+
model_ = ifcopenshell.api.structural.add_structural_analysis_model(model)
49+
ifcopenshell.api.structural.assign_to_building(model,
50+
structural_analysis_model=model_, building=building)
51+
"""
52+
for rel in structural_analysis_model.ServicesBuildings or []:
53+
if building in rel.RelatedBuildings:
54+
return rel
55+
rel.RelatedBuildings = list(rel.RelatedBuildings) + [building]
56+
return rel
57+
58+
return file.create_entity(
59+
"IfcRelServicesBuildings",
60+
ifcopenshell.guid.new(),
61+
OwnerHistory=ifcopenshell.api.owner.create_owner_history(file),
62+
RelatingSystem=structural_analysis_model,
63+
RelatedBuildings=[building],
64+
)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# IfcOpenShell - IFC toolkit and geometry engine
2+
# Copyright (C) 2026 Dion Moult <dion@thinkmoult.com>
3+
#
4+
# This file is part of IfcOpenShell.
5+
#
6+
# IfcOpenShell is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU Lesser General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# IfcOpenShell is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU Lesser General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU Lesser General Public License
17+
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
18+
# This file was generated with the assistance of an AI coding tool.
19+
20+
import ifcopenshell.api.context
21+
import ifcopenshell.api.geometry
22+
import ifcopenshell.api.root
23+
import test.bootstrap
24+
25+
26+
class TestAddTopologyRepresentation(test.bootstrap.IFC4):
27+
def setup_context(self):
28+
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
29+
model = ifcopenshell.api.context.add_context(self.file, context_type="Model")
30+
return ifcopenshell.api.context.add_context(
31+
self.file,
32+
context_type="Model",
33+
context_identifier="Reference",
34+
target_view="GRAPH_VIEW",
35+
parent=model,
36+
)
37+
38+
def test_creates_topology_representation(self):
39+
context = self.setup_context()
40+
face = self.file.create_entity("IfcFaceSurface")
41+
rep = ifcopenshell.api.geometry.add_topology_representation(self.file, context=context, item=face)
42+
assert rep.is_a("IfcTopologyRepresentation")
43+
assert rep.ContextOfItems == context
44+
assert face in rep.Items
45+
46+
def test_infers_face_representation_type(self):
47+
context = self.setup_context()
48+
face = self.file.create_entity("IfcFaceSurface")
49+
rep = ifcopenshell.api.geometry.add_topology_representation(self.file, context=context, item=face)
50+
assert rep.RepresentationType == "Face"
51+
52+
def test_infers_edge_representation_type(self):
53+
context = self.setup_context()
54+
edge = self.file.create_entity("IfcEdge")
55+
rep = ifcopenshell.api.geometry.add_topology_representation(self.file, context=context, item=edge)
56+
assert rep.RepresentationType == "Edge"
57+
58+
def test_defaults_representation_identifier_to_context_identifier(self):
59+
context = self.setup_context()
60+
face = self.file.create_entity("IfcFaceSurface")
61+
rep = ifcopenshell.api.geometry.add_topology_representation(self.file, context=context, item=face)
62+
assert rep.RepresentationIdentifier == context.ContextIdentifier
63+
64+
def test_custom_representation_identifier(self):
65+
context = self.setup_context()
66+
face = self.file.create_entity("IfcFaceSurface")
67+
rep = ifcopenshell.api.geometry.add_topology_representation(
68+
self.file, context=context, item=face, representation_identifier="Body"
69+
)
70+
assert rep.RepresentationIdentifier == "Body"
71+
72+
def test_custom_representation_type_overrides_inferred(self):
73+
context = self.setup_context()
74+
face = self.file.create_entity("IfcFaceSurface")
75+
rep = ifcopenshell.api.geometry.add_topology_representation(
76+
self.file, context=context, item=face, representation_type="Undefined"
77+
)
78+
assert rep.RepresentationType == "Undefined"
79+
80+
81+
class TestAddTopologyRepresentationIFC2X3(test.bootstrap.IFC2X3, TestAddTopologyRepresentation):
82+
pass

0 commit comments

Comments
 (0)