Softwarová architektura BIM: 3D modelování a spolupráce pro průmysl AEC
Il Informační modelování budov (BIM) radikálně změnil svět kde architektura, inženýrství a stavebnictví (AEC) navrhuje, staví a řídí budov. V roce 2024 používá více než 66 % architektonických firem v USA nástroje BIM, zatímco globální trh se softwarem BIM byl oceněn na 7,96 miliardy dolarů v roce 2024 s projekcemi 33,64 miliard do roku 2033 (CAGR 17,37 %).
Co se ale děje v zákulisí moderního BIM softwaru? Jak a 3D model v paměti? Jak funguje spolupráce více uživatelů na sdíleném modelu? A jak cloud změnil nasazení těchto systémů? V tomto článku budeme stavět kompletní architektura systému BIM, analyzující standard IFC, formát BCF spolupráce a technické výzvy 3D vykreslování webu.
Co se naučíte
- Standard IFC 4.3 (ISO 16739-1:2024) a datové schéma BIM
- Architektura webového BIM prohlížeče s Three.js/WebGL
- Vrstva spolupráce: BIM Collaboration Format (BCF) a řízení konfliktů
- IFC parsovací kanál: od binárního souboru po renderovatelnou 3D scénu
- Automatická detekce střetu s 3D algoritmy průniku
- Cloud BIM: architektura pro více nájemců a streamování geometrie
- Integrace s IoT pro digitální dvojče budov
Standard IFC: DNA BIM
L'Industry Foundation Classes (IFC) je schéma otevřených dat, které umožňuje interoperabilitu mezi různými BIM software. Verze IFC 4.3 ADD2 je se stal standardem ISO 16739-1:2024, ratifikován v roce 2024. Je vyvíjen a udržován ze společnosti buildingSMART International.
IFC přijímá objektový model, kde každý prvek budovy (stěna, dveře, okno, nosník, přípravek) je třída se standardními vlastnostmi a vztahy předdefinované s dalšími prvky. 3D geometrie je oddělená od metadat prostřednictvím reprezentace STEP (ISO 10303).
| IFC verze | Rok | Hlavní vlastnosti | Postavení |
|---|---|---|---|
| IFC 2x3 | 2006 | První stabilní vydání, základní architektura | Legacy (stále populární) |
| IFC 4 | 2013 | Rozšířený MEP, pokročilé struktury, 4D/5D | Široce podporováno |
| IFC 4.3 | 2022/2024 | Infrastruktura civilní, železniční, geotechnická | ISO 16739-1:2024 (doporučeno) |
# 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 v Pythonu s ifcopenshell
Knihovna s otevřeným zdrojovým kódem ifcopenshell je standardní reference číst, upravovat a vytvářet soubory IFC. Podporuje IFC 2x3, IFC 4 a 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 Web Viewer s Three.js
Webový BIM prohlížeč musí zpracovávat modely s miliony polygonů při zachování 60 snímků za sekundu v prohlížeči. Klíčové techniky zahrnují frustum culling, LOD (úroveň detailu), instance vykreslování pro opakující se prvky a progresivní načítání.
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);
}
}
Clash Detection: 3D Conflict Detection
Detekce kolize automaticky identifikuje interference mezi prvky BIM různé disciplíny (např. trubka protínající konstrukční nosník). Je to jeden z nejkritičtějších případů použití BIM ve fázi návrhu.
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 Architecture: Multi-User Collaboration
Hlavní výzvou cloudového BIM systému je spolupráce v reálném čase u modelů, které mohou vážit stovky MB. Podniková řešení, jako je Autodesk BIM 360, Trimble Connect a Bentley iTwin používají různé přístupy, ale všechny sdílejí vzorce: modelová federace, sady přírůstkových změn e řešení konfliktů.
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
Integrace BIM-IoT pro digitální dvojče
Dalším krokem ke statickému BIM je Digitální dvojče dynamický: BIM model obohacený o data IoT v reálném čase ze senzorů instalovaných v budově (teplota, obsazenost, kvalita vzduchu, spotřeba energie).
Komponenty digitálního dvojčete BIM
- Vrstva BIM modelu: 3D IFC geometrie se statickými vlastnostmi
- IoT senzorová vrstva: Fyzické senzory mapované na prvky IFC přes GlobalId
- Vrstva časové řady: InfluxDB nebo TimescaleDB pro historická data senzorů
- Vrstva Analytics: ML pro predikci spotřeby, detekci anomálií, predikci obsazenosti
- Vizualizační vrstva: 3D prohlížeč BIM s překryvnými daty IoT v reálném čase
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 []
Technické výzvy BIM Cloudu
- Velké soubory: Složité modely BIM mohou přesáhnout 500 MB. Využijte streamování s požadavky HTTP Range a postupným načítáním prvků.
- Disciplinární konflikty: Architektura, struktura a MEP často upravují stejné prostory. Implementujte zamykání na úrovni obchodu a oznámení push.
- Verze: Modely BIM se nehodí k textovým rozdílům jako zdrojový kód. Přijímá sadu změn a verzování na úrovni objektů.
- Interoperabilita: Ne každý software exportuje IFC správně. Pro ověření a normalizaci před importem použijte ifcopenshell.
Závěry a další kroky
Moderní architektura BIM je komplexní distribuovaný systém, který integruje 3D analýzu, spolupráce v reálném čase, cloud rendering a IoT. Standard IFC 4.3 (ISO 16739-1:2024) zaručuje interoperabilitu mezi nástroji, zatímco knihovny jako ifcopenshell a Three.js umožňují vám vytvářet vlastní prohlížeče a kanály. Přechod na digitální dvojče, s integrací IoT a ML je to přirozený vývoj BIM směrem ke skutečným budovám inteligentní.
Prozkoumejte další články ze série PropTech
- Článek 03 – Smart Building IoT: Integrace senzorů a Edge Computing
- Článek 08 – Virtuální prohlídky nemovitostí: WebGL a 3D Web Tech
- Článek 07 – Geoprostorové vyhledávací a lokalizační služby s PostGIS







