Ifc Data Extraction
/install ifc-data-extraction
\r \r
IFC Data Extraction\r
\r
Overview\r
\r 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.\r \r Based on Open BIM Standards - Working with vendor-neutral IFC format for maximum interoperability.\r \r
"IFC является открытым стандартом для обмена BIM-данными, позволяющим извлекать информацию независимо от программного обеспечения."\r — DDC Methodology\r \r
Quick Start\r
\r
import ifcopenshell\r
import ifcopenshell.util.element as element_util\r
import pandas as pd\r
\r
# Open IFC file\r
ifc = ifcopenshell.open("model.ifc")\r
\r
# Get project info\r
project = ifc.by_type("IfcProject")[0]\r
print(f"Project: {project.Name}")\r
\r
# Extract all walls\r
walls = ifc.by_type("IfcWall")\r
print(f"Total walls: {len(walls)}")\r
\r
# Get wall data\r
wall_data = []\r
for wall in walls:\r
psets = element_util.get_psets(wall)\r
wall_data.append({\r
'GlobalId': wall.GlobalId,\r
'Name': wall.Name,\r
'Type': wall.is_a(),\r
'Level': get_level(wall),\r
'Properties': psets\r
})\r
\r
df = pd.DataFrame(wall_data)\r
print(df.head())\r
```\r
\r
## Core Extraction Functions\r
\r
### Element Extractor Class\r
\r
```python\r
import ifcopenshell\r
import ifcopenshell.util.element as element_util\r
import ifcopenshell.util.placement as placement_util\r
import ifcopenshell.geom\r
import pandas as pd\r
from typing import List, Dict, Optional, Any\r
\r
class IFCExtractor:\r
"""Extract data from IFC files"""\r
\r
def __init__(self, ifc_path: str):\r
self.model = ifcopenshell.open(ifc_path)\r
self.settings = ifcopenshell.geom.settings()\r
\r
def get_project_info(self) -> Dict:\r
"""Extract project metadata"""\r
project = self.model.by_type("IfcProject")[0]\r
site = self.model.by_type("IfcSite")\r
building = self.model.by_type("IfcBuilding")\r
\r
return {\r
'project_id': project.GlobalId,\r
'project_name': project.Name,\r
'description': project.Description,\r
'site_count': len(site),\r
'building_count': len(building),\r
'schema': self.model.schema\r
}\r
\r
def get_all_elements(self, element_types: List[str] = None) -> pd.DataFrame:\r
"""Extract all elements of specified types"""\r
if element_types is None:\r
element_types = [\r
'IfcWall', 'IfcSlab', 'IfcColumn', 'IfcBeam',\r
'IfcDoor', 'IfcWindow', 'IfcStair', 'IfcRoof'\r
]\r
\r
all_elements = []\r
\r
for ifc_type in element_types:\r
elements = self.model.by_type(ifc_type)\r
\r
for elem in elements:\r
data = self._extract_element_data(elem)\r
data['IFC_Type'] = ifc_type\r
all_elements.append(data)\r
\r
return pd.DataFrame(all_elements)\r
\r
def _extract_element_data(self, element) -> Dict:\r
"""Extract data from single element"""\r
# Basic info\r
data = {\r
'GlobalId': element.GlobalId,\r
'Name': element.Name,\r
'Description': element.Description,\r
'ObjectType': element.ObjectType if hasattr(element, 'ObjectType') else None\r
}\r
\r
# Get level/storey\r
data['Level'] = self._get_element_level(element)\r
\r
# Get material\r
data['Material'] = self._get_element_material(element)\r
\r
# Get type\r
data['TypeName'] = self._get_element_type(element)\r
\r
# Get all property sets\r
psets = element_util.get_psets(element)\r
data['PropertySets'] = psets\r
\r
# Extract common quantities\r
base_quantities = psets.get('BaseQuantities', {})\r
data.update({\r
'Length': base_quantities.get('Length'),\r
'Width': base_quantities.get('Width'),\r
'Height': base_quantities.get('Height'),\r
'Area': base_quantities.get('NetSideArea') or base_quantities.get('GrossArea'),\r
'Volume': base_quantities.get('NetVolume') or base_quantities.get('GrossVolume')\r
})\r
\r
return data\r
\r
def _get_element_level(self, element) -> Optional[str]:\r
"""Get the building storey for an element"""\r
if hasattr(element, 'ContainedInStructure'):\r
for rel in element.ContainedInStructure or []:\r
if rel.RelatingStructure.is_a('IfcBuildingStorey'):\r
return rel.RelatingStructure.Name\r
return None\r
\r
def _get_element_material(self, element) -> Optional[str]:\r
"""Get material name for element"""\r
if hasattr(element, 'HasAssociations'):\r
for rel in element.HasAssociations or []:\r
if rel.is_a('IfcRelAssociatesMaterial'):\r
material = rel.RelatingMaterial\r
if hasattr(material, 'Name'):\r
return material.Name\r
elif hasattr(material, 'ForLayerSet'):\r
layers = material.ForLayerSet.MaterialLayers\r
if layers:\r
return layers[0].Material.Name\r
return None\r
\r
def _get_element_type(self, element) -> Optional[str]:\r
"""Get element type name"""\r
if hasattr(element, 'IsTypedBy'):\r
for rel in element.IsTypedBy or []:\r
return rel.RelatingType.Name\r
return None\r
\r
def extract_quantities(self) -> pd.DataFrame:\r
"""Extract quantities for all elements"""\r
elements = self.get_all_elements()\r
\r
# Group by category and level\r
quantities = elements.groupby(['IFC_Type', 'Level']).agg({\r
'GlobalId': 'count',\r
'Volume': 'sum',\r
'Area': 'sum',\r
'Length': 'sum'\r
}).rename(columns={'GlobalId': 'Count'}).reset_index()\r
\r
return quantities\r
\r
def extract_levels(self) -> pd.DataFrame:\r
"""Extract building levels/storeys"""\r
storeys = self.model.by_type("IfcBuildingStorey")\r
\r
level_data = []\r
for storey in storeys:\r
level_data.append({\r
'GlobalId': storey.GlobalId,\r
'Name': storey.Name,\r
'Elevation': storey.Elevation,\r
'Description': storey.Description\r
})\r
\r
return pd.DataFrame(level_data).sort_values('Elevation')\r
\r
def extract_spaces(self) -> pd.DataFrame:\r
"""Extract spaces/rooms"""\r
spaces = self.model.by_type("IfcSpace")\r
\r
space_data = []\r
for space in spaces:\r
psets = element_util.get_psets(space)\r
base_qty = psets.get('BaseQuantities', {})\r
\r
space_data.append({\r
'GlobalId': space.GlobalId,\r
'Name': space.Name,\r
'LongName': space.LongName,\r
'Level': self._get_element_level(space),\r
'Area': base_qty.get('NetFloorArea'),\r
'Volume': base_qty.get('NetVolume'),\r
'Height': base_qty.get('Height')\r
})\r
\r
return pd.DataFrame(space_data)\r
\r
def extract_materials(self) -> pd.DataFrame:\r
"""Extract material summary"""\r
materials = {}\r
\r
for elem in self.model.by_type("IfcProduct"):\r
material = self._get_element_material(elem)\r
if material:\r
if material not in materials:\r
materials[material] = {'count': 0, 'volume': 0}\r
\r
materials[material]['count'] += 1\r
\r
psets = element_util.get_psets(elem)\r
volume = psets.get('BaseQuantities', {}).get('NetVolume', 0)\r
if volume:\r
materials[material]['volume'] += volume\r
\r
return pd.DataFrame.from_dict(materials, orient='index').reset_index()\r
\r
def extract_relationships(self) -> pd.DataFrame:\r
"""Extract element relationships"""\r
relationships = []\r
\r
# Spatial containment\r
for rel in self.model.by_type("IfcRelContainedInSpatialStructure"):\r
for elem in rel.RelatedElements:\r
relationships.append({\r
'Element': elem.GlobalId,\r
'Element_Type': elem.is_a(),\r
'Relationship': 'ContainedIn',\r
'Related_To': rel.RelatingStructure.GlobalId,\r
'Related_Type': rel.RelatingStructure.is_a()\r
})\r
\r
# Aggregation\r
for rel in self.model.by_type("IfcRelAggregates"):\r
for part in rel.RelatedObjects:\r
relationships.append({\r
'Element': part.GlobalId,\r
'Element_Type': part.is_a(),\r
'Relationship': 'PartOf',\r
'Related_To': rel.RelatingObject.GlobalId,\r
'Related_Type': rel.RelatingObject.is_a()\r
})\r
\r
return pd.DataFrame(relationships)\r
```\r
\r
## Geometry Extraction\r
\r
### Extract Geometry Data\r
\r
```python\r
import numpy as np\r
\r
class IFCGeometryExtractor:\r
"""Extract geometry data from IFC elements"""\r
\r
def __init__(self, ifc_path: str):\r
self.model = ifcopenshell.open(ifc_path)\r
self.settings = ifcopenshell.geom.settings()\r
self.settings.set(self.settings.USE_WORLD_COORDS, True)\r
\r
def get_element_geometry(self, element) -> Dict:\r
"""Extract geometry for single element"""\r
try:\r
shape = ifcopenshell.geom.create_shape(self.settings, element)\r
\r
verts = shape.geometry.verts\r
faces = shape.geometry.faces\r
\r
# Calculate bounding box\r
vertices = np.array(verts).reshape(-1, 3)\r
min_coords = vertices.min(axis=0)\r
max_coords = vertices.max(axis=0)\r
dimensions = max_coords - min_coords\r
\r
return {\r
'GlobalId': element.GlobalId,\r
'vertices_count': len(vertices),\r
'faces_count': len(faces) // 3,\r
'min_x': min_coords[0],\r
'min_y': min_coords[1],\r
'min_z': min_coords[2],\r
'max_x': max_coords[0],\r
'max_y': max_coords[1],\r
'max_z': max_coords[2],\r
'length': dimensions[0],\r
'width': dimensions[1],\r
'height': dimensions[2],\r
'center_x': (min_coords[0] + max_coords[0]) / 2,\r
'center_y': (min_coords[1] + max_coords[1]) / 2,\r
'center_z': (min_coords[2] + max_coords[2]) / 2\r
}\r
except:\r
return {'GlobalId': element.GlobalId, 'error': 'Geometry extraction failed'}\r
\r
def get_bounding_boxes(self, element_type: str) -> pd.DataFrame:\r
"""Get bounding boxes for all elements of type"""\r
elements = self.model.by_type(element_type)\r
boxes = [self.get_element_geometry(e) for e in elements]\r
return pd.DataFrame(boxes)\r
\r
def calculate_volumes(self, element_type: str) -> pd.DataFrame:\r
"""Calculate volumes using geometry"""\r
elements = self.model.by_type(element_type)\r
volumes = []\r
\r
for elem in elements:\r
try:\r
shape = ifcopenshell.geom.create_shape(self.settings, elem)\r
# Calculate volume from mesh (simplified)\r
verts = np.array(shape.geometry.verts).reshape(-1, 3)\r
bbox_volume = np.prod(verts.max(axis=0) - verts.min(axis=0))\r
\r
volumes.append({\r
'GlobalId': elem.GlobalId,\r
'Name': elem.Name,\r
'BBox_Volume': bbox_volume\r
})\r
except:\r
pass\r
\r
return pd.DataFrame(volumes)\r
```\r
\r
## Export Functions\r
\r
### Export to Various Formats\r
\r
```python\r
class IFCExporter:\r
"""Export IFC data to various formats"""\r
\r
def __init__(self, extractor: IFCExtractor):\r
self.extractor = extractor\r
\r
def to_excel(self, output_path: str, include_all: bool = True):\r
"""Export to Excel with multiple sheets"""\r
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:\r
# Project info\r
project_info = pd.DataFrame([self.extractor.get_project_info()])\r
project_info.to_excel(writer, sheet_name='Project', index=False)\r
\r
# All elements\r
if include_all:\r
elements = self.extractor.get_all_elements()\r
elements.to_excel(writer, sheet_name='Elements', index=False)\r
\r
# Quantities\r
quantities = self.extractor.extract_quantities()\r
quantities.to_excel(writer, sheet_name='Quantities', index=False)\r
\r
# Levels\r
levels = self.extractor.extract_levels()\r
levels.to_excel(writer, sheet_name='Levels', index=False)\r
\r
# Spaces\r
spaces = self.extractor.extract_spaces()\r
spaces.to_excel(writer, sheet_name='Spaces', index=False)\r
\r
# Materials\r
materials = self.extractor.extract_materials()\r
materials.to_excel(writer, sheet_name='Materials', index=False)\r
\r
return output_path\r
\r
def to_csv(self, output_dir: str):\r
"""Export to multiple CSV files"""\r
import os\r
os.makedirs(output_dir, exist_ok=True)\r
\r
exports = {\r
'elements.csv': self.extractor.get_all_elements(),\r
'quantities.csv': self.extractor.extract_quantities(),\r
'levels.csv': self.extractor.extract_levels(),\r
'spaces.csv': self.extractor.extract_spaces(),\r
'materials.csv': self.extractor.extract_materials()\r
}\r
\r
for filename, df in exports.items():\r
df.to_csv(os.path.join(output_dir, filename), index=False)\r
\r
return output_dir\r
\r
def to_json(self, output_path: str):\r
"""Export to JSON"""\r
import json\r
\r
data = {\r
'project': self.extractor.get_project_info(),\r
'elements': self.extractor.get_all_elements().to_dict('records'),\r
'quantities': self.extractor.extract_quantities().to_dict('records'),\r
'levels': self.extractor.extract_levels().to_dict('records'),\r
'materials': self.extractor.extract_materials().to_dict('records')\r
}\r
\r
with open(output_path, 'w', encoding='utf-8') as f:\r
json.dump(data, f, indent=2, default=str)\r
\r
return output_path\r
\r
def to_database(self, connection_string: str, table_prefix: str = 'ifc_'):\r
"""Export to SQL database"""\r
from sqlalchemy import create_engine\r
\r
engine = create_engine(connection_string)\r
\r
tables = {\r
f'{table_prefix}elements': self.extractor.get_all_elements(),\r
f'{table_prefix}quantities': self.extractor.extract_quantities(),\r
f'{table_prefix}levels': self.extractor.extract_levels(),\r
f'{table_prefix}spaces': self.extractor.extract_spaces(),\r
f'{table_prefix}materials': self.extractor.extract_materials()\r
}\r
\r
for table_name, df in tables.items():\r
# Remove complex columns for database storage\r
simple_df = df.select_dtypes(exclude=['object']).copy()\r
for col in df.columns:\r
if df[col].dtype == 'object':\r
simple_df[col] = df[col].astype(str)\r
\r
simple_df.to_sql(table_name, engine, if_exists='replace', index=False)\r
\r
return list(tables.keys())\r
```\r
\r
## Quick Reference\r
\r
| Element Type | Common Properties | Quantities |\r
|-------------|-------------------|------------|\r
| IfcWall | IsExternal, FireRating | Length, Height, Area, Volume |\r
| IfcSlab | IsExternal, LoadBearing | Area, Volume, Perimeter |\r
| IfcColumn | LoadBearing | Height, CrossSectionArea |\r
| IfcBeam | LoadBearing | Length, CrossSectionArea |\r
| IfcDoor | FireRating, AcousticRating | Width, Height |\r
| IfcWindow | ThermalTransmittance | Width, Height, Area |\r
\r
## Property Set Lookup\r
\r
```python\r
# Common IFC Property Sets\r
PSETS = {\r
'Pset_WallCommon': ['IsExternal', 'LoadBearing', 'FireRating'],\r
'Pset_SlabCommon': ['IsExternal', 'LoadBearing', 'AcousticRating'],\r
'Pset_ColumnCommon': ['IsExternal', 'LoadBearing'],\r
'Pset_BeamCommon': ['LoadBearing', 'FireRating'],\r
'Pset_DoorCommon': ['FireRating', 'AcousticRating', 'SecurityRating'],\r
'Pset_WindowCommon': ['ThermalTransmittance', 'GlazingType'],\r
'BaseQuantities': ['Length', 'Width', 'Height', 'Area', 'Volume']\r
}\r
```\r
\r
## Resources\r
\r
- **IfcOpenShell**: https://ifcopenshell.org\r
- **IFC Standard**: https://www.buildingsmart.org/standards/bsi-standards/industry-foundation-classes/\r
- **DDC Website**: https://datadrivenconstruction.io\r
\r
## Next Steps\r
\r
- See `bim-validation-pipeline` for validating extracted data\r
- See `qto-report` for quantity take-off reports\r
- See `4d-simulation` for linking to schedules\r
- Make sure OpenClaw is installed (local or Docker)
- Run the install command in chat:
/install ifc-data-extraction - After installation, invoke the skill by name or use
/ifc-data-extraction - Provide required inputs per the skill's parameter spec and get structured output
What is Ifc Data Extraction?
Extract structured data from IFC (Industry Foundation Classes) files using IfcOpenShell. Parse BIM models, extract quantities, properties, spatial relationships, and export to various formats. It is an AI Agent Skill for Claude Code / OpenClaw, with 1263 downloads so far.
How do I install Ifc Data Extraction?
Run "/install ifc-data-extraction" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.
Is Ifc Data Extraction free?
Yes, Ifc Data Extraction is completely free (open-source). You can download, install and use it at no cost.
Which platforms does Ifc Data Extraction support?
Ifc Data Extraction is cross-platform and runs anywhere OpenClaw / Claude Code is available (cross-platform).
Who created Ifc Data Extraction?
It is built and maintained by datadrivenconstruction (@datadrivenconstruction); the current version is v2.0.0.