| name | ifc-data-extraction |
| description | Extract structured data from IFC (Industry Foundation Classes) files using IfcOpenShell. Parse BIM models, extract quantities, properties, spatial relationships, and export to various formats. |
IFC Data Extraction
Overview
This skill provides comprehensive IFC file parsing and data extraction using IfcOpenShell. Extract element data, quantities, properties, and relationships from BIM models for analysis and reporting.
Based on Open BIM Standards - Working with vendor-neutral IFC format for maximum interoperability.
"IFC является открытым стандартом для обмена BIM-данными, позволяющим извлекать информацию независимо от программного обеспечения."
— DDC Methodology
Quick Start
import ifcopenshell
import ifcopenshell.util.element as element_util
import pandas as pd
ifc = ifcopenshell.open("model.ifc")
project = ifc.by_type("IfcProject")[0]
print(f"Project: {project.Name}")
walls = ifc.by_type("IfcWall")
print(f"Total walls: {len(walls)}")
wall_data = []
for wall in walls:
psets = element_util.get_psets(wall)
wall_data.append({
'GlobalId': wall.GlobalId,
'Name': wall.Name,
'Type': wall.is_a(),
'Level': get_level(wall),
'Properties': psets
})
df = pd.DataFrame(wall_data)
print(df.head())
Core Extraction Functions
Element Extractor Class
import ifcopenshell
import ifcopenshell.util.element as element_util
import ifcopenshell.util.placement as placement_util
import ifcopenshell.geom
import pandas as pd
from typing import List, Dict, Optional, Any
class IFCExtractor:
"""Extract data from IFC files"""
def __init__(self, ifc_path: str):
self.model = ifcopenshell.open(ifc_path)
self.settings = ifcopenshell.geom.settings()
def get_project_info(self) -> Dict:
"""Extract project metadata"""
project = self.model.by_type("IfcProject")[0]
site = self.model.by_type("IfcSite")
building = self.model.by_type("IfcBuilding")
return {
'project_id': project.GlobalId,
'project_name': project.Name,
'description': project.Description,
'site_count': len(site),
'building_count': len(building),
'schema': self.model.schema
}
() -> pd.DataFrame:
element_types :
element_types = [
, , , ,
, , ,
]
all_elements = []
ifc_type element_types:
elements = .model.by_type(ifc_type)
elem elements:
data = ._extract_element_data(elem)
data[] = ifc_type
all_elements.append(data)
pd.DataFrame(all_elements)
() -> :
data = {
: element.GlobalId,
: element.Name,
: element.Description,
: element.ObjectType (element, )
}
data[] = ._get_element_level(element)
data[] = ._get_element_material(element)
data[] = ._get_element_type(element)
psets = element_util.get_psets(element)
data[] = psets
base_quantities = psets.get(, {})
data.update({
: base_quantities.get(),
: base_quantities.get(),
: base_quantities.get(),
: base_quantities.get() base_quantities.get(),
: base_quantities.get() base_quantities.get()
})
data
() -> []:
(element, ):
rel element.ContainedInStructure []:
rel.RelatingStructure.is_a():
rel.RelatingStructure.Name
() -> []:
(element, ):
rel element.HasAssociations []:
rel.is_a():
material = rel.RelatingMaterial
(material, ):
material.Name
(material, ):
layers = material.ForLayerSet.MaterialLayers
layers:
layers[].Material.Name
() -> []:
(element, ):
rel element.IsTypedBy []:
rel.RelatingType.Name
() -> pd.DataFrame:
elements = .get_all_elements()
quantities = elements.groupby([, ]).agg({
: ,
: ,
: ,
:
}).rename(columns={: }).reset_index()
quantities
() -> pd.DataFrame:
storeys = .model.by_type()
level_data = []
storey storeys:
level_data.append({
: storey.GlobalId,
: storey.Name,
: storey.Elevation,
: storey.Description
})
pd.DataFrame(level_data).sort_values()
() -> pd.DataFrame:
spaces = .model.by_type()
space_data = []
space spaces:
psets = element_util.get_psets(space)
base_qty = psets.get(, {})
space_data.append({
: space.GlobalId,
: space.Name,
: space.LongName,
: ._get_element_level(space),
: base_qty.get(),
: base_qty.get(),
: base_qty.get()
})
pd.DataFrame(space_data)
() -> pd.DataFrame:
materials = {}
elem .model.by_type():
material = ._get_element_material(elem)
material:
material materials:
materials[material] = {: , : }
materials[material][] +=
psets = element_util.get_psets(elem)
volume = psets.get(, {}).get(, )
volume:
materials[material][] += volume
pd.DataFrame.from_dict(materials, orient=).reset_index()
() -> pd.DataFrame:
relationships = []
rel .model.by_type():
elem rel.RelatedElements:
relationships.append({
: elem.GlobalId,
: elem.is_a(),
: ,
: rel.RelatingStructure.GlobalId,
: rel.RelatingStructure.is_a()
})
rel .model.by_type():
part rel.RelatedObjects:
relationships.append({
: part.GlobalId,
: part.is_a(),
: ,
: rel.RelatingObject.GlobalId,
: rel.RelatingObject.is_a()
})
pd.DataFrame(relationships)
Geometry Extraction
Extract Geometry Data
import numpy as np
class IFCGeometryExtractor:
"""Extract geometry data from IFC elements"""
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)
def get_element_geometry(self, element) -> Dict:
"""Extract geometry for single element"""
try:
shape = ifcopenshell.geom.create_shape(self.settings, element)
verts = shape.geometry.verts
faces = shape.geometry.faces
vertices = np.array(verts).reshape(-1, 3)
min_coords = vertices.min(axis=0)
max_coords = vertices.max(axis=0)
dimensions = max_coords - min_coords
return {
'GlobalId': element.GlobalId,
'vertices_count': len(vertices),
'faces_count': len(faces) // 3,
'min_x': min_coords[0],
'min_y': min_coords[1],
'min_z': min_coords[],
: max_coords[],
: max_coords[],
: max_coords[],
: dimensions[],
: dimensions[],
: dimensions[],
: (min_coords[] + max_coords[]) / ,
: (min_coords[] + max_coords[]) / ,
: (min_coords[] + max_coords[]) /
}
:
{: element.GlobalId, : }
() -> pd.DataFrame:
elements = .model.by_type(element_type)
boxes = [.get_element_geometry(e) e elements]
pd.DataFrame(boxes)
() -> pd.DataFrame:
elements = .model.by_type(element_type)
volumes = []
elem elements:
:
shape = ifcopenshell.geom.create_shape(.settings, elem)
verts = np.array(shape.geometry.verts).reshape(-, )
bbox_volume = np.prod(verts.(axis=) - verts.(axis=))
volumes.append({
: elem.GlobalId,
: elem.Name,
: bbox_volume
})
:
pd.DataFrame(volumes)
Export Functions
Export to Various Formats
class IFCExporter:
"""Export IFC data to various formats"""
def __init__(self, extractor: IFCExtractor):
self.extractor = extractor
def to_excel(self, output_path: str, include_all: bool = True):
"""Export to Excel with multiple sheets"""
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
project_info = pd.DataFrame([self.extractor.get_project_info()])
project_info.to_excel(writer, sheet_name='Project', index=False)
if include_all:
elements = self.extractor.get_all_elements()
elements.to_excel(writer, sheet_name='Elements', index=False)
quantities = self.extractor.extract_quantities()
quantities.to_excel(writer, sheet_name='Quantities', index=False)
levels = self.extractor.extract_levels()
levels.to_excel(writer, sheet_name='Levels', index=False)
spaces = self.extractor.extract_spaces()
spaces.to_excel(writer, sheet_name='Spaces', index=False)
materials = .extractor.extract_materials()
materials.to_excel(writer, sheet_name=, index=)
output_path
():
os
os.makedirs(output_dir, exist_ok=)
exports = {
: .extractor.get_all_elements(),
: .extractor.extract_quantities(),
: .extractor.extract_levels(),
: .extractor.extract_spaces(),
: .extractor.extract_materials()
}
filename, df exports.items():
df.to_csv(os.path.join(output_dir, filename), index=)
output_dir
():
json
data = {
: .extractor.get_project_info(),
: .extractor.get_all_elements().to_dict(),
: .extractor.extract_quantities().to_dict(),
: .extractor.extract_levels().to_dict(),
: .extractor.extract_materials().to_dict()
}
(output_path, , encoding=) f:
json.dump(data, f, indent=, default=)
output_path
():
sqlalchemy create_engine
engine = create_engine(connection_string)
tables = {
: .extractor.get_all_elements(),
: .extractor.extract_quantities(),
: .extractor.extract_levels(),
: .extractor.extract_spaces(),
: .extractor.extract_materials()
}
table_name, df tables.items():
simple_df = df.select_dtypes(exclude=[]).copy()
col df.columns:
df[col].dtype == :
simple_df[col] = df[col].astype()
simple_df.to_sql(table_name, engine, if_exists=, index=)
(tables.keys())
Quick Reference
| Element Type | Common Properties | Quantities |
|---|
| IfcWall | IsExternal, FireRating | Length, Height, Area, Volume |
| IfcSlab | IsExternal, LoadBearing | Area, Volume, Perimeter |
| IfcColumn | LoadBearing | Height, CrossSectionArea |
| IfcBeam | LoadBearing | Length, CrossSectionArea |
| IfcDoor | FireRating, AcousticRating | Width, Height |
| IfcWindow | ThermalTransmittance | Width, Height, Area |
Property Set Lookup
PSETS = {
'Pset_WallCommon': ['IsExternal', 'LoadBearing', 'FireRating'],
'Pset_SlabCommon': ['IsExternal', 'LoadBearing', 'AcousticRating'],
'Pset_ColumnCommon': ['IsExternal', 'LoadBearing'],
'Pset_BeamCommon': ['LoadBearing', 'FireRating'],
'Pset_DoorCommon': ['FireRating', 'AcousticRating', 'SecurityRating'],
'Pset_WindowCommon': ['ThermalTransmittance', 'GlazingType'],
'BaseQuantities': ['Length', 'Width', 'Height', 'Area', 'Volume']
}
Resources
Next Steps
- See
bim-validation-pipeline for validating extracted data
- See
qto-report for quantity take-off reports
- See
4d-simulation for linking to schedules