Files
IfcOpenShell/src/pyodide/demo-app/context.py
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

306 lines
11 KiB
Python
Raw Normal View History

2025-01-12 14:45:25 +01:00
import ifcopenshell
2025-12-19 18:53:04 +05:00
import ifcopenshell.api
2025-11-17 12:39:52 +05:00
import ifcopenshell.api.aggregate
import ifcopenshell.api.context
2025-01-28 15:20:48 +05:00
import ifcopenshell.api.feature
2025-11-17 12:39:52 +05:00
import ifcopenshell.api.geometry
import ifcopenshell.api.root
import ifcopenshell.api.spatial
import ifcopenshell.api.type
import ifcopenshell.api.unit
2025-01-12 14:45:25 +01:00
import ifcopenshell.geom
2025-11-17 12:39:52 +05:00
import ifcopenshell.util.representation
2025-01-12 14:45:25 +01:00
import ifcopenshell.util.unit
import numpy as np
import propertygroups
O = 0.0, 0.0, 0.0
X = 1.0, 0.0, 0.0
Y = 0.0, 1.0, 0.0
Z = 0.0, 0.0, 1.0
class Context:
model = None
body = None
storey = None
def __init__(self):
self._create_empty_model()
def clear(self):
self._create_empty_model()
def open(self, file_content):
self.model = ifcopenshell.file.from_string(file_content)
2025-11-17 12:41:27 +05:00
body = [
ctx for ctx in self.model.by_type("IfcGeometricRepresentationContext") if ctx.ContextIdentifier == "Body"
]
2025-01-12 14:45:25 +01:00
if body:
self.body = body[0]
else:
2025-11-17 12:39:52 +05:00
context = ifcopenshell.api.context.add_context(self.model, context_type="Model")
self.body = ifcopenshell.api.context.add_context(
2025-01-12 14:45:25 +01:00
self.model,
context_type="Model",
context_identifier="Body",
target_view="MODEL_VIEW",
parent=context,
)
2025-11-17 12:41:27 +05:00
axis = [
ctx for ctx in self.model.by_type("IfcGeometricRepresentationContext") if ctx.ContextIdentifier == "Axis"
]
2025-01-12 14:45:25 +01:00
if body:
self.axis = axis[0]
else:
2025-11-17 12:39:52 +05:00
context = ifcopenshell.api.context.add_context(self.model, context_type="Model")
self.axis = ifcopenshell.api.context.add_context(
2025-01-12 14:45:25 +01:00
self.model,
context_type="Model",
context_identifier="Axis",
target_view="GRAPH_VIEW",
parent=context,
)
2025-11-17 12:41:27 +05:00
self.storey = self.model.by_type("IfcBuildingStorey")[0]
2025-01-12 14:45:25 +01:00
def _create_empty_model(self):
# Create a blank model
self.model = ifcopenshell.file()
# All projects must have one IFC Project element
2025-11-17 12:39:52 +05:00
project = ifcopenshell.api.root.create_entity(self.model, ifc_class="IfcProject", name="My Project")
2025-01-12 14:45:25 +01:00
# Geometry is optional in IFC, but because we want to use geometry in this example, let's define units
# Assigning without arguments defaults to metric units
2025-11-17 12:39:52 +05:00
ifcopenshell.api.unit.assign_unit(self.model)
2025-01-12 14:45:25 +01:00
# Let's create a modeling geometry context, so we can store 3D geometry (note: IFC supports 2D too!)
2025-11-17 12:39:52 +05:00
context = ifcopenshell.api.context.add_context(self.model, context_type="Model")
2025-01-12 14:45:25 +01:00
# In particular, in this example we want to store the 3D "body" geometry of objects, i.e. the body shape
2025-11-17 12:39:52 +05:00
self.body = ifcopenshell.api.context.add_context(
2025-01-12 14:45:25 +01:00
self.model,
context_type="Model",
context_identifier="Body",
target_view="MODEL_VIEW",
parent=context,
)
2025-11-17 12:39:52 +05:00
self.axis = ifcopenshell.api.context.add_context(
2025-01-12 14:45:25 +01:00
self.model,
context_type="Model",
context_identifier="Axis",
target_view="GRAPH_VIEW",
parent=context,
)
# Create a site, building, and storey. Many hierarchies are possible.
2025-11-17 12:39:52 +05:00
site = ifcopenshell.api.root.create_entity(self.model, ifc_class="IfcSite", name="My Site")
building = ifcopenshell.api.root.create_entity(self.model, ifc_class="IfcBuilding", name="Building A")
self.storey = ifcopenshell.api.root.create_entity(
2025-01-12 14:45:25 +01:00
self.model,
ifc_class="IfcBuildingStorey",
name="Ground Floor",
)
# Since the site is our top level location, assign it to the project
# Then place our building on the site, and our storey in the building
2025-11-17 12:39:52 +05:00
ifcopenshell.api.aggregate.assign_object(
2025-01-12 14:45:25 +01:00
self.model,
relating_object=project,
products=[site],
)
2025-11-17 12:39:52 +05:00
ifcopenshell.api.aggregate.assign_object(
2025-01-12 14:45:25 +01:00
self.model,
relating_object=site,
products=[building],
)
2025-11-17 12:39:52 +05:00
ifcopenshell.api.aggregate.assign_object(
2025-01-12 14:45:25 +01:00
self.model,
relating_object=building,
products=[self.storey],
)
2025-11-17 12:41:27 +05:00
def create_2pt_wall(self, p1, p2, elevation, height, thickness, container, wall_type=None):
2025-01-12 14:45:25 +01:00
p1 = np.array([p1[0], p1[1]])
p2 = np.array([p2[0], p2[1]])
2025-11-17 12:39:52 +05:00
wall = ifcopenshell.api.root.create_entity(self.model, ifc_class="IfcWall")
2025-01-12 14:45:25 +01:00
length = float(np.linalg.norm(p2 - p1))
2025-11-17 12:39:52 +05:00
representation = ifcopenshell.api.geometry.add_wall_representation(
2025-01-12 14:45:25 +01:00
self.model,
context=self.body,
length=length,
height=height,
thickness=thickness,
)
2025-11-17 12:39:52 +05:00
ifcopenshell.api.geometry.assign_representation(
2025-01-12 14:45:25 +01:00
self.model,
product=wall,
representation=representation,
)
2025-11-17 12:39:52 +05:00
representation = ifcopenshell.api.geometry.add_axis_representation(
2025-01-12 14:45:25 +01:00
self.model,
context=self.axis,
axis=[(0.0, 0.0), (length, 0.0)],
)
2025-11-17 12:39:52 +05:00
ifcopenshell.api.geometry.assign_representation(
2025-01-12 14:45:25 +01:00
self.model,
product=wall,
representation=representation,
)
v = p2 - p1
v = np.divide(v, float(np.linalg.norm(v)), casting="unsafe")
matrix = np.array(
[
[v[0], -v[1], 0, p1[0]],
[v[1], v[0], 0, p1[1]],
[0, 0, 1, elevation],
[0, 0, 0, 1],
]
)
2025-11-17 12:39:52 +05:00
ifcopenshell.api.geometry.edit_object_placement(self.model, product=wall, matrix=matrix)
ifcopenshell.api.spatial.assign_container(
2025-01-12 14:45:25 +01:00
self.model,
relating_structure=container,
products=[wall],
)
if wall_type:
2025-11-17 12:39:52 +05:00
ifcopenshell.api.type.assign_type(
2025-01-12 14:45:25 +01:00
self.model,
related_object=wall,
relating_type=wall_type,
)
return wall
def get_element(self, guid):
return self.model[guid]
def get_model(self):
return self.model
def create_fill(self, ty, pt, wall):
if isinstance(wall, str):
wall = self.model[wall]
2025-11-17 12:41:27 +05:00
if not wall.is_a("IfcWall"):
2025-01-12 14:45:25 +01:00
raise ValueError("Only 'wall' hosts are supported")
2025-11-17 12:41:27 +05:00
if ty == "door":
2025-01-12 14:45:25 +01:00
props = propertygroups.BIMDoorProperties()
2025-11-17 12:41:27 +05:00
elif ty == "window":
2025-01-12 14:45:25 +01:00
props = propertygroups.BIMWindowProperties()
else:
raise ValueError("Only 'door' or 'window' fills are supported")
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(self.model)
2025-11-17 12:41:27 +05:00
body = ifcopenshell.util.representation.get_context(self.model, "Model", "Body", "MODEL_VIEW")
2025-01-12 14:45:25 +01:00
representation_data = props.to_dict(si_conversion=si_conversion)
representation_data["context"] = body
2025-11-17 12:39:52 +05:00
if ty == "door":
door_representation = ifcopenshell.api.geometry.add_door_representation(self.model, **representation_data)
else:
door_representation = ifcopenshell.api.geometry.add_window_representation(self.model, **representation_data)
door = ifcopenshell.api.root.create_entity(self.model, ifc_class=f"ifc{ty}")
2025-01-12 14:45:25 +01:00
door.OverallWidth = props.overall_width / si_conversion
door.OverallHeight = props.overall_height / si_conversion
2025-11-17 12:39:52 +05:00
ifcopenshell.api.geometry.assign_representation(
2025-01-12 14:45:25 +01:00
self.model,
product=door,
representation=door_representation,
)
2025-11-17 12:39:52 +05:00
ifcopenshell.api.spatial.assign_container(
2025-01-12 14:45:25 +01:00
self.model,
relating_structure=self.storey,
products=[door],
)
2025-11-17 12:41:27 +05:00
r = [r for r in wall.Representation.Representations if r.RepresentationIdentifier == "Axis"]
2025-01-12 14:45:25 +01:00
if not r:
raise ValueError("Axis representation is needed")
r = r[0]
axis_geometry = ifcopenshell.geom.create_shape(
ifcopenshell.geom.settings(
DIMENSIONALITY=ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS,
USE_WORLD_COORDS=True,
),
wall,
r,
)
vs = np.array(axis_geometry.geometry.verts).reshape((-1, 3))
es = np.array(axis_geometry.geometry.edges).reshape((-1, 2))
A, B = vs[es[0]]
v = B - A
P = np.zeros(3)
P[0 : len(pt)] = pt
AP = P - A
AP_dot_v = np.dot(AP, v)
v_dot_v = np.dot(v, v)
t = AP_dot_v / v_dot_v * np.linalg.norm(v) / si_conversion
2025-11-17 12:39:52 +05:00
opening = ifcopenshell.api.root.create_entity(
2025-01-12 14:45:25 +01:00
self.model,
ifc_class="IfcOpeningElement",
predefined_type="OPENING",
name="Opening",
)
position_3d = None
if self.model.schema == "IFC2X3":
2025-11-17 12:41:27 +05:00
position_3d = self.model.createIfcAxis2Placement2D(self.model.createIfcCartesianPoint([0.0, 0.0, 0.0]))
2025-01-12 14:45:25 +01:00
position_2d = self.model.createIfcAxis2Placement2D(
self.model.createIfcCartesianPoint([door.OverallWidth / 2.0, 0.0])
)
opening.Representation = self.model.createIfcProductDefinitionShape(
Representations=[
self.model.createIfcShapeRepresentation(
2025-01-12 14:45:25 +01:00
body,
"Body",
"SweptSolid",
Items=[
self.model.createIfcExtrudedAreaSolid(
self.model.createIfcRectangleProfileDef(
"AREA",
None,
position_2d,
door.OverallWidth,
1.2 / si_conversion,
),
position_3d,
self.model.createIfcDirection((0.0, 0.0, 1.0)),
door.OverallHeight,
)
],
)
]
)
2025-01-28 15:20:48 +05:00
ifcopenshell.api.feature.add_feature(self.model, feature=opening, element=wall)
ifcopenshell.api.feature.add_filling(self.model, opening=opening, element=door)
2025-01-12 14:45:25 +01:00
2025-11-17 12:41:27 +05:00
z_offsets = {"door": 0, "window": 1}
2025-01-12 14:45:25 +01:00
opening.ObjectPlacement = self.model.createIfcLocalPlacement(
wall.ObjectPlacement,
self.model.createIfcAxis2Placement3D(
self.model.createIfcCartesianPoint((float(t), 0.0, z_offsets[ty] / si_conversion))
),
)
door.ObjectPlacement = self.model.createIfcLocalPlacement(
opening.ObjectPlacement,
2025-11-17 12:41:27 +05:00
self.model.createIfcAxis2Placement3D(self.model.createIfcCartesianPoint((0.0, 0.0, 0.0))),
2025-01-12 14:45:25 +01:00
)
return door
def to_obj_file(self, fn):
st = ifcopenshell.geom.settings(USE_WORLD_COORDS=True, WELD_VERTICES=False)
it = ifcopenshell.geom.iterator(st, self.model, exclude=("IfcOpeningElement",))
2025-11-17 12:41:27 +05:00
sr = ifcopenshell.geom.serializers.obj(fn, fn + ".mtl", st, ifcopenshell.geom.serializer_settings())
2025-01-12 14:45:25 +01:00
if it.initialize():
for el in ifcopenshell.geom.consume_iterator(it):
sr.write(el)
sr.finalize()
if __name__ == "__main__":
m = Context()
w1 = m.create_2pt_wall((0.0, 0.0), (4.0, 0.0), 0.0, 3.0, 0.1, m.storey)
w2 = m.create_2pt_wall((1.0, 3.0), (1.0, 0.0), 0.0, 3.0, 0.1, m.storey)
2025-11-17 12:41:27 +05:00
m.create_fill("door", [2, 0.0], w1)
m.create_fill("window", [1, 1.5], w2)
2025-01-12 14:45:25 +01:00
m.model.write("out.ifc")
m.to_obj_file("out.obj")