BIM-softwarearchitectuur: 3D-modellering en samenwerking voor de AEC-industrie
Il Bouwinformatiemodellering (BIM) heeft de wereld radicaal getransformeerd waar architectuur, engineering en constructie (AEC) ontwerpt, bouwt en beheert gebouwen. In 2024 gebruikt ruim 66% van de architectenbureaus in de VS BIM-tools. terwijl de mondiale BIM-softwaremarkt werd gewaardeerd op 7,96 miljard dollar in 2024, met projecties van 33,64 miljard in 2033 (CAGR 17,37%).
Maar wat gebeurt er achter de schermen van moderne BIM-software? Hoe een 3D-model in geheugen? Hoe werkt samenwerking tussen meerdere gebruikers op een gedeeld model? En hoe heeft de cloud de inzet van deze systemen veranderd? In dit artikel gaan we bouwen de volledige architectuur van een BIM-systeem, waarbij de IFC-standaard, het BCF-formaat, wordt geanalyseerd samenwerking en de technische uitdagingen van 3D-webweergave.
Wat je gaat leren
- IFC 4.3 (ISO 16739-1:2024) standaard en BIM-gegevensschema
- Architectuur van een web-BIM-viewer met Three.js/WebGL
- Samenwerkingslaag: BIM Collaboration Format (BCF) en conflictmanagement
- IFC-parseerpijplijn: van binair bestand naar renderbare 3D-scène
- Automatische botsingsdetectie met 3D-kruispuntalgoritmen
- Cloud BIM: multi-tenant architectuur en geometriestreaming
- Integratie met IoT voor de Digital Twin van gebouwen
De IFC-standaard: het DNA van BIM
L'Industry Foundation-klassen (IFC) is het open dataschema dat maakt interoperabiliteit tussen verschillende BIM-software mogelijk. De IFC 4.3 ADD2-versie is werd ISO 16739-1:2024-standaard, geratificeerd in 2024. Het wordt ontwikkeld en onderhouden van BuildingSMART International.
IFC hanteert een objectmodel waarbij elk element van het gebouw (muur, deur, raam, beam, armatuur) is een klasse met standaardeigenschappen en relaties vooraf gedefinieerd met andere elementen. 3D-geometrie staat los van metadata via STEP-weergave (ISO 10303).
| IFC-versie | Jaar | Belangrijkste kenmerken | Status |
|---|---|---|---|
| IFC2x3 | 2006 | Eerste stabiele release, basisarchitectuur | Legacy (nog steeds populair) |
| IFC4 | 2013 | Uitgebreide MEP, geavanceerde structuren, 4D/5D | Breed ondersteund |
| IFC 4.3 | 2022/2024 | Civiele infrastructuur, spoorwegen, geotechniek | ISO 16739-1:2024 (aanbevolen) |
# Esempio di entità IFC in formato STEP (file .ifc)
# Un semplice muro con le sue proprietà e geometria
#100 = IFCPROJECT('2O2Fr$t4X7Dn_hFJ7mkFuC',
$,'Progetto Residenziale Milano',$,$,$,$,$,
(#101,)); # IfcUnitAssignment
#200 = IFCWALL('0LV8Xs0bzEIeVBuDgFJqEY',
$,'Muro Perimetrale Est','Muro in laterizio',$,
#500, # IfcLocalPlacement
#600, # IfcProductDefinitionShape
$,
.SOLIDWALL.);
#300 = IFCWALLTYPE('1Kl0KQPzj5E9XhKaSyTd5Q',
$,'MuroEsterno300mm',$,$,$,$,$,$,
.SOLIDWALL.);
# Proprietà del muro
#400 = IFCPROPERTYSET('3ZYW59saX3kxpqB_7Pnv3D',
$,'Pset_WallCommon',$,
(#401,#402,#403,#404));
#401 = IFCPROPERTYSINGLEVALUE('Reference',$,
IFCIDENTIFIER('MW-300'),$);
#402 = IFCPROPERTYSINGLEVALUE('IsExternal',$,
IFCBOOLEAN(.T.),$);
#403 = IFCPROPERTYSINGLEVALUE('LoadBearing',$,
IFCBOOLEAN(.T.),$);
#404 = IFCPROPERTYSINGLEVALUE('FireRating',$,
IFCLABEL('REI120'),$);
IFC-parser in Python met ifcopenshell
De opensourcebibliotheek alscopenshell is de standaardreferentie IFC-bestanden lezen, bewerken en maken. Ondersteunt IFC 2x3, IFC 4 en IFC 4.3.
import ifcopenshell
import ifcopenshell.geom
import numpy as np
from dataclasses import dataclass, field
from typing import List, Dict, Optional
@dataclass
class BIMElement:
"""Rappresentazione unificata di un elemento BIM."""
global_id: str
ifc_type: str
name: str
properties: Dict[str, any] = field(default_factory=dict)
geometry: Optional[dict] = None
level: Optional[str] = None
classification: Optional[str] = None
class IFCParser:
"""
Parser IFC per estrarre elementi, proprietà e geometrie.
Supporta IFC 2x3, 4 e 4.3.
"""
# Tipologie di elementi architettonici da estrarre
SPATIAL_ELEMENTS = [
'IfcBuilding', 'IfcBuildingStorey', 'IfcSpace'
]
ARCHITECTURAL_ELEMENTS = [
'IfcWall', 'IfcSlab', 'IfcColumn', 'IfcBeam',
'IfcDoor', 'IfcWindow', 'IfcRoof', 'IfcStair',
'IfcRailing', 'IfcCurtainWall'
]
MEP_ELEMENTS = [
'IfcDuctSegment', 'IfcPipeSegment',
'IfcCableCarrierSegment', 'IfcFlowTerminal'
]
def __init__(self, ifc_path: str):
self.model = ifcopenshell.open(ifc_path)
self.settings = ifcopenshell.geom.settings()
self.settings.set(self.settings.USE_WORLD_COORDS, True)
self.settings.set(self.settings.WELD_VERTICES, True)
def extract_all_elements(self) -> List[BIMElement]:
"""Estrae tutti gli elementi con proprietà e geometria."""
elements = []
all_types = (
self.ARCHITECTURAL_ELEMENTS +
self.MEP_ELEMENTS +
self.SPATIAL_ELEMENTS
)
for ifc_type in all_types:
for entity in self.model.by_type(ifc_type):
element = self._parse_entity(entity)
if element:
elements.append(element)
return elements
def _parse_entity(self, entity) -> Optional[BIMElement]:
"""Converte un'entità IFC in BIMElement."""
try:
# Proprietà di base
props = self._get_properties(entity)
# Geometria 3D
geometry = None
try:
shape = ifcopenshell.geom.create_shape(self.settings, entity)
geometry = self._extract_geometry(shape)
except Exception:
pass # Alcuni elementi non hanno geometria
# Piano di appartenenza
level = self._get_storey(entity)
return BIMElement(
global_id=entity.GlobalId,
ifc_type=entity.is_a(),
name=entity.Name or '',
properties=props,
geometry=geometry,
level=level,
classification=self._get_classification(entity),
)
except Exception as e:
return None
def _get_properties(self, entity) -> dict:
"""Estrae tutti i PropertySet dell'entità."""
props = {}
for definition in entity.IsDefinedBy:
if definition.is_a('IfcRelDefinesByProperties'):
prop_set = definition.RelatingPropertyDefinition
if prop_set.is_a('IfcPropertySet'):
for prop in prop_set.HasProperties:
if prop.is_a('IfcPropertySingleValue') and prop.NominalValue:
props[prop.Name] = prop.NominalValue.wrappedValue
return props
def _extract_geometry(self, shape) -> dict:
"""Converte la geometria IFC in formato Three.js-compatibile."""
geometry = shape.geometry
# Vertici
verts = np.array(geometry.verts).reshape(-1, 3)
# Facce triangolari
faces = np.array(geometry.faces).reshape(-1, 3)
# Normali
normals = np.array(geometry.normals).reshape(-1, 3)
return {
'vertices': verts.tolist(),
'faces': faces.tolist(),
'normals': normals.tolist(),
'vertex_count': len(verts),
'face_count': len(faces),
}
def _get_storey(self, entity) -> Optional[str]:
"""Determina il piano di appartenenza dell'elemento."""
for rel in entity.ContainedInStructure:
container = rel.RelatingStructure
if container.is_a('IfcBuildingStorey'):
return container.Name
return None
def _get_classification(self, entity) -> Optional[str]:
"""Estrae la classificazione (OmniClass, Uniclass, ecc.)."""
for rel in entity.HasAssociations:
if rel.is_a('IfcRelAssociatesClassification'):
ref = rel.RelatingClassification
return f"{ref.ReferencedSource.Name}:{ref.Identification}"
return None
def get_spatial_hierarchy(self) -> dict:
"""
Costruisce la gerarchia spaziale: Building > Storey > Space > Elements
"""
hierarchy = {}
buildings = self.model.by_type('IfcBuilding')
for building in buildings:
b_data = {
'id': building.GlobalId,
'name': building.Name,
'storeys': {},
}
for rel in building.IsDecomposedBy:
for storey in rel.RelatedObjects:
if storey.is_a('IfcBuildingStorey'):
elevation = storey.Elevation or 0
b_data['storeys'][storey.GlobalId] = {
'name': storey.Name,
'elevation': elevation,
'elements': self._get_storey_elements(storey),
}
hierarchy[building.GlobalId] = b_data
return hierarchy
BIM-webviewer met Three.js
Een web-BIM-viewer moet modellen met miljoenen polygonen verwerken, met behoud van 60 fps in de browser. Belangrijke technieken zijn onder meer het afsnijden van afgeknotte exemplaren, LOD (Level of Detail), instanced rendering voor herhalende elementen en progressief laden.
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { MeshBVH, acceleratedRaycast } from 'three-mesh-bvh';
// Abilita raycast accelerato per selezione elementi
THREE.Mesh.prototype.raycast = acceleratedRaycast;
class BIMViewer {
private scene: THREE.Scene;
private camera: THREE.PerspectiveCamera;
private renderer: THREE.WebGLRenderer;
private controls: OrbitControls;
private elementMap: Map<string, THREE.Mesh> = new Map();
private selectedElement: THREE.Mesh | null = null;
// Materiali
private defaultMaterial = new THREE.MeshLambertMaterial({
color: 0xcccccc,
transparent: true,
opacity: 0.85,
});
private selectedMaterial = new THREE.MeshLambertMaterial({
color: 0x007bff,
transparent: true,
opacity: 0.9,
});
constructor(container: HTMLElement) {
// Scene setup
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0xf0f0f0);
// Camera
this.camera = new THREE.PerspectiveCamera(
45,
container.clientWidth / container.clientHeight,
0.1,
10000
);
this.camera.position.set(50, 30, 50);
// Renderer con antialiasing e shadow map
this.renderer = new THREE.WebGLRenderer({
antialias: true,
powerPreference: 'high-performance',
});
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(container.clientWidth, container.clientHeight);
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container.appendChild(this.renderer.domElement);
// Luci
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
dirLight.position.set(100, 100, 50);
dirLight.castShadow = true;
this.scene.add(ambientLight, dirLight);
// Controls
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.enableDamping = true;
this.controls.dampingFactor = 0.05;
// Event listeners
this.renderer.domElement.addEventListener('click', this.onElementClick.bind(this));
window.addEventListener('resize', this.onWindowResize.bind(this));
}
loadModel(elements: BIMElementGeometry[]) {
/**
* Carica il modello BIM ottimizzando per performance.
* Usa InstanedMesh per elementi ripetuti (porte, finestre).
*/
const elementsByType = this.groupByType(elements);
for (const [type, typeElements] of Object.entries(elementsByType)) {
if (typeElements.length > 10) {
// Instanced rendering per elementi ripetuti
this.addInstancedElements(type, typeElements);
} else {
// Mesh singole per elementi unici
typeElements.forEach(elem => this.addSingleElement(elem));
}
}
// Centra la camera sul modello
this.fitCameraToModel();
this.animate();
}
private addSingleElement(element: BIMElementGeometry) {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
'position',
new THREE.Float32BufferAttribute(element.vertices.flat(), 3)
);
geometry.setAttribute(
'normal',
new THREE.Float32BufferAttribute(element.normals.flat(), 3)
);
geometry.setIndex(element.faces.flat());
// Ottimizzazione: BVH per raycast veloce
geometry.computeBoundsTree = MeshBVH.bind(null, geometry);
geometry.computeBoundsTree();
const material = this.getMaterialForType(element.ifcType);
const mesh = new THREE.Mesh(geometry, material.clone());
mesh.userData = {
globalId: element.globalId,
ifcType: element.ifcType,
name: element.name,
properties: element.properties,
};
mesh.castShadow = true;
mesh.receiveShadow = true;
this.elementMap.set(element.globalId, mesh);
this.scene.add(mesh);
}
private onElementClick(event: MouseEvent) {
const rect = this.renderer.domElement.getBoundingClientRect();
const mouse = new THREE.Vector2(
((event.clientX - rect.left) / rect.width) * 2 - 1,
-((event.clientY - rect.top) / rect.height) * 2 + 1
);
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, this.camera);
const meshes = Array.from(this.elementMap.values());
const intersects = raycaster.intersectObjects(meshes);
if (intersects.length > 0) {
const clicked = intersects[0].object as THREE.Mesh;
this.selectElement(clicked.userData['globalId']);
}
}
selectElement(globalId: string) {
// Ripristina il materiale dell'elemento precedente
if (this.selectedElement) {
const type = this.selectedElement.userData['ifcType'];
(this.selectedElement.material as THREE.MeshLambertMaterial)
.copy(this.getMaterialForType(type));
}
const mesh = this.elementMap.get(globalId);
if (mesh) {
(mesh.material as THREE.MeshLambertMaterial).copy(this.selectedMaterial);
this.selectedElement = mesh;
// Emit evento con proprietà dell'elemento
this.onElementSelected(mesh.userData);
}
}
private animate() {
requestAnimationFrame(this.animate.bind(this));
this.controls.update();
this.renderer.render(this.scene, this.camera);
}
}
Botsingsdetectie: 3D-conflictdetectie
Clash-detectie identificeert automatisch interferenties tussen BIM-elementen verschillende disciplines (bijvoorbeeld een buis die een structurele balk snijdt). Het is één van de meest kritische BIM-use cases in de ontwerpfase.
import ifcopenshell
import ifcopenshell.geom
import numpy as np
from scipy.spatial import KDTree
from typing import List, Tuple
from dataclasses import dataclass
@dataclass
class Clash:
element1_id: str
element1_type: str
element2_id: str
element2_type: str
clash_type: str # 'hard' | 'soft' | 'clearance'
distance: float
location: Tuple[float, float, float]
severity: str # 'critical' | 'major' | 'minor'
class ClashDetector:
"""
Rilevamento automatico di conflitti geometrici tra elementi BIM.
Supporta Hard Clash, Soft Clash e Clearance Clash.
"""
DISCIPLINE_PAIRS = [
('IfcBeam', 'IfcDuctSegment'),
('IfcBeam', 'IfcPipeSegment'),
('IfcColumn', 'IfcDuctSegment'),
('IfcSlab', 'IfcPipeSegment'),
('IfcWall', 'IfcDuctSegment'),
]
# Tolleranza minima per soft clash (mm convertiti in m)
CLEARANCE_TOLERANCE = {
'IfcDuctSegment': 0.10, # 100mm di spazio attorno ai condotti
'IfcPipeSegment': 0.05, # 50mm attorno ai tubi
'default': 0.025, # 25mm default
}
def __init__(self, ifc_model):
self.model = ifc_model
self.settings = ifcopenshell.geom.settings()
self.settings.set(self.settings.USE_WORLD_COORDS, True)
def detect_clashes(self, disciplines: List[str] = None) -> List[Clash]:
"""
Rileva tutti i clash tra le discipline specificate.
"""
clashes = []
pairs = disciplines or self.DISCIPLINE_PAIRS
for type_a, type_b in pairs:
elements_a = self._get_element_aabbs(type_a)
elements_b = self._get_element_aabbs(type_b)
if not elements_a or not elements_b:
continue
# Accelera la ricerca con KDTree
centers_b = np.array([e['center'] for e in elements_b])
tree = KDTree(centers_b)
for elem_a in elements_a:
# Cerca elementi vicini (raggio approssimativo)
radius = np.linalg.norm(elem_a['extents']) + max(
np.linalg.norm(e['extents']) for e in elements_b
)
nearby_indices = tree.query_ball_point(elem_a['center'], radius)
for idx in nearby_indices:
elem_b = elements_b[idx]
clash = self._check_aabb_clash(elem_a, elem_b, type_b)
if clash:
clashes.append(clash)
return clashes
def _get_element_aabbs(self, ifc_type: str) -> List[dict]:
"""Calcola AABB (Axis-Aligned Bounding Box) per ogni elemento."""
aabbs = []
for entity in self.model.by_type(ifc_type):
try:
shape = ifcopenshell.geom.create_shape(self.settings, entity)
verts = np.array(shape.geometry.verts).reshape(-1, 3)
min_pt = verts.min(axis=0)
max_pt = verts.max(axis=0)
center = (min_pt + max_pt) / 2
extents = (max_pt - min_pt) / 2
aabbs.append({
'id': entity.GlobalId,
'type': ifc_type,
'name': entity.Name or '',
'min': min_pt,
'max': max_pt,
'center': center,
'extents': extents,
})
except Exception:
pass
return aabbs
def _check_aabb_clash(self, a: dict, b: dict, type_b: str) -> Optional[Clash]:
"""Controlla se due AABB si intersecano (hard clash) o sono troppo vicine."""
clearance = self.CLEARANCE_TOLERANCE.get(type_b, self.CLEARANCE_TOLERANCE['default'])
# Hard clash: le bounding box si sovrappongono
overlap = all(
a['min'][i] <= b['max'][i] and a['max'][i] >= b['min'][i]
for i in range(3)
)
if overlap:
return Clash(
element1_id=a['id'],
element1_type=a['type'],
element2_id=b['id'],
element2_type=b['type'],
clash_type='hard',
distance=0.0,
location=tuple((a['center'] + b['center']) / 2),
severity='critical',
)
# Clearance clash: troppo vicini
distance = max(
max(a['min'][i] - b['max'][i], b['min'][i] - a['max'][i], 0)
for i in range(3)
)
if distance < clearance:
return Clash(
element1_id=a['id'],
element1_type=a['type'],
element2_id=b['id'],
element2_type=b['type'],
clash_type='clearance',
distance=round(distance * 1000, 1), # mm
location=tuple((a['center'] + b['center']) / 2),
severity='major' if distance < clearance / 2 else 'minor',
)
return None
Cloud BIM-architectuur: samenwerking tussen meerdere gebruikers
De grootste uitdaging van een cloud-BIM-systeem is realtime samenwerking op modellen die honderden MB kunnen wegen. Enterprise-oplossingen zoals Autodesk BIM 360, Trimble Connect en Bentley iTwin hanteren verschillende benaderingen, maar ze delen allemaal patronen: modelfederatie, incrementele wijzigingssets e conflictoplossing.
from dataclasses import dataclass, field
from typing import List, Dict, Optional
from datetime import datetime
import hashlib
import json
@dataclass
class BIMChangeSet:
"""Rappresenta un set di modifiche atomiche al modello BIM."""
id: str
model_id: str
author_id: str
timestamp: datetime
operations: List[dict] # Lista di operazioni CRUD sugli elementi
parent_revision: str
description: str
discipline: str # 'architectural' | 'structural' | 'mep'
class BIMCollaborationService:
"""
Gestione della collaborazione multi-disciplina su modelli BIM.
Implementa Operational Transformation per merge dei changeset.
"""
def __init__(self, storage, event_bus):
self.storage = storage
self.event_bus = event_bus
async def apply_changeset(self, changeset: BIMChangeSet) -> dict:
"""
Applica un changeset al modello con conflict detection.
"""
# Carica revisione parent
parent = await self.storage.get_revision(changeset.parent_revision)
# Verifica conflitti con changeset paralleli
concurrent_changes = await self.storage.get_concurrent_changesets(
model_id=changeset.model_id,
since=parent.timestamp,
exclude_id=changeset.id,
)
conflicts = self._detect_conflicts(changeset, concurrent_changes)
if conflicts and not self._auto_resolvable(conflicts):
return {
'status': 'conflict',
'conflicts': conflicts,
'message': 'Risoluzione manuale richiesta',
}
# Applica le operazioni
new_revision_id = await self.storage.apply_operations(
model_id=changeset.model_id,
operations=changeset.operations,
parent_revision=changeset.parent_revision,
)
# Notifica tutti i collaboratori
await self.event_bus.publish('model.updated', {
'model_id': changeset.model_id,
'revision_id': new_revision_id,
'author': changeset.author_id,
'discipline': changeset.discipline,
'change_count': len(changeset.operations),
})
return {
'status': 'success',
'revision_id': new_revision_id,
'applied_operations': len(changeset.operations),
}
def _detect_conflicts(self, changeset: BIMChangeSet,
concurrent: List[BIMChangeSet]) -> List[dict]:
"""Identifica elementi modificati concorrentemente."""
changed_ids = {op['element_id'] for op in changeset.operations}
conflicts = []
for concurrent_cs in concurrent:
concurrent_ids = {op['element_id'] for op in concurrent_cs.operations}
overlap = changed_ids.intersection(concurrent_ids)
for element_id in overlap:
conflicts.append({
'element_id': element_id,
'conflict_with': concurrent_cs.id,
'author': concurrent_cs.author_id,
'discipline': concurrent_cs.discipline,
})
return conflicts
def _auto_resolvable(self, conflicts: List[dict]) -> bool:
"""
Determina se i conflitti possono essere risolti automaticamente.
Regola: modifiche a discipline diverse sullo stesso elemento
possono essere mergate se riguardano attributi diversi.
"""
# Implementazione semplificata: conflitti tra discipline diverse
# sono auto-resolvibili se gli attributi modificati non si sovrappongono
disciplines_involved = {c['discipline'] for c in conflicts}
return len(disciplines_involved) > 1 # Discipline diverse = auto-merge
BIM-IoT-integratie voor Digital Twin
De volgende stap naar statische BIM is Digitale tweeling dynamisch: een BIM-model verrijkt met real-time IoT-gegevens van sensoren die in het gebouw zijn geïnstalleerd (temperatuur, bezetting, luchtkwaliteit, energieverbruik).
Onderdelen van een BIM Digital Twin
- BIM-modellaag: 3D IFC-geometrie met statische eigenschappen
- IoT-sensorlaag: Fysieke sensoren toegewezen aan IFC-elementen via GlobalId
- Tijdreekslaag: InfluxDB of TimescaleDB voor historische sensordata
- Analytics-laag: ML voor verbruiksvoorspelling, detectie van afwijkingen, bezettingsvoorspelling
- Visualisatielaag: 3D BIM-viewer met realtime IoT-gegevensoverlay
from influxdb_client import InfluxDBClient, Point
from influxdb_client.client.write_api import SYNCHRONOUS
from datetime import datetime
import asyncio
class BIMDigitalTwin:
"""
Integrazione BIM + IoT per Digital Twin degli edifici.
Mappa sensori fisici agli elementi IFC e persiste i dati in InfluxDB.
"""
def __init__(self, ifc_model, influx_client: InfluxDBClient,
sensor_mapping: dict):
"""
sensor_mapping: { 'sensor_id': 'ifc_global_id' }
Mappa ogni sensore fisico all'elemento IFC corrispondente.
"""
self.model = ifc_model
self.influx = influx_client
self.write_api = influx_client.write_api(write_options=SYNCHRONOUS)
self.query_api = influx_client.query_api()
self.sensor_mapping = sensor_mapping
async def ingest_sensor_reading(self, sensor_id: str,
measurement_type: str,
value: float,
timestamp: datetime):
"""
Persiste una lettura del sensore associandola all'elemento IFC.
"""
ifc_element_id = self.sensor_mapping.get(sensor_id)
if not ifc_element_id:
return
# Recupera metadati elemento IFC
element = self.model.by_guid(ifc_element_id)
storey = self._get_storey(element)
point = (
Point(measurement_type)
.tag("sensor_id", sensor_id)
.tag("ifc_element_id", ifc_element_id)
.tag("ifc_type", element.is_a())
.tag("storey", storey or "unknown")
.field("value", value)
.time(timestamp)
)
self.write_api.write(bucket="bim_sensors", record=point)
async def get_element_history(self, ifc_element_id: str,
measurement: str,
hours: int = 24) -> list:
"""Recupera la storia delle letture per un elemento IFC."""
query = f'''
from(bucket: "bim_sensors")
|> range(start: -{hours}h)
|> filter(fn: (r) => r["ifc_element_id"] == "{ifc_element_id}")
|> filter(fn: (r) => r["_measurement"] == "{measurement}")
|> aggregateWindow(every: 5m, fn: mean)
'''
tables = self.query_api.query(query)
readings = []
for table in tables:
for record in table.records:
readings.append({
'timestamp': record.get_time().isoformat(),
'value': record.get_value(),
})
return readings
async def detect_anomalies(self, storey: str,
measurement: str) -> list:
"""
Rileva anomalie nelle letture dei sensori usando z-score.
"""
query = f'''
from(bucket: "bim_sensors")
|> range(start: -7d)
|> filter(fn: (r) => r["storey"] == "{storey}")
|> filter(fn: (r) => r["_measurement"] == "{measurement}")
'''
# Implementa anomaly detection basica su z-score...
return []
Technische uitdagingen van BIM Cloud
- Grote bestanden: Complexe BIM-modellen kunnen groter zijn dan 500 MB. Gebruik streaming met HTTP Range-verzoeken en progressief laden van elementen.
- Disciplineconflicten: Architectuur, structuur en MEP wijzigen vaak dezelfde ruimtes. Implementeer vergrendeling en pushmeldingen op winkelniveau.
- Versiebeheer: BIM-modellen lenen zich niet voor tekstuele verschillen zoals broncode. Past een benadering van wijzigingensets en versiebeheer op objectniveau toe.
- Interoperabiliteit: Niet alle software exporteert IFC correct. Gebruik ifcopenshell voor validatie en normalisatie vóór het importeren.
Conclusies en volgende stappen
Moderne BIM-architectuur is een complex gedistribueerd systeem dat 3D-parsing integreert, realtime samenwerking, cloudrendering en IoT. De IFC 4.3-standaard (ISO 16739-1:2024) garandeert interoperabiliteit tussen tools, terwijl bibliotheken zoals ifcopenshell en Three.js Hiermee kunt u aangepaste viewers en pijplijnen bouwen. De transitie naar de Digital Twin, met IoT- en ML-integratie is het de natuurlijke evolutie van BIM naar echte gebouwen intelligent.
Ontdek andere artikelen in de PropTech-serie
- Artikel 03 - Smart Building IoT: sensorintegratie en edge computing
- Artikel 08 - Virtuele vastgoedrondleidingen: WebGL en 3D Web Tech
- Artikel 07 - Georuimtelijke zoek- en locatiediensten met PostGIS







