关于 ifc-to-excel
ifc-to-excel 是一项 BIM 数据提取技能,可将 IFC 文件转换为结构化 Excel 工作簿和 Collada 几何文件。它面向需要检查、验证或报告模型数据,且无需专用 BIM 软件的团队。
主要功能
- 01将 IFC 文件转换为 Excel 工作簿
- 02将 3D 几何导出为 Collada DAE
- 03提取属性、工程量和材料
- 04包含构件边界框
- 05支持 IFC2x3、IFC4、IFC4x1 和 IFC4x3
适用场景
- 01验证提取的模型数据
- 02准备工程量清单
- 03生成材料表
- 04将 IFC 数据接入 ETL 工作流
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ datadrivenconstruction/ ddc_skills_for_ai_agents_in_construction --skill ifc-to-excel安装后在 Claude Code 中输入/ifc-to-excel确认已加载。
附带文件claw.jsoninstructions.md
详细介绍
IFC 到 Excel 转换
业务案例
问题陈述
IFC(Industry Foundation Classes)是开放的 BIM 标准,但:
- 读取 IFC 需要专用软件
- 提取属性需要编程知识
- 批量处理需要手动操作,耗时较长
- 与分析工具集成较为复杂
解决方案
IfcExporter.exe 可将 IFC 文件转换为结构化 Excel 数据库,让 BIM 数据便于用于分析、验证和报告。
业务价值
- 开放标准 - 处理任意 IFC 文件(2x3、4x、4.3)
- 无需许可证 - 无需 BIM 软件即可离线运行
- 数据提取 - 提取所有属性、工程量和材料
- 3D 几何 - 导出为 Collada DAE 格式
- 适用于工作流 - 集成到 ETL 工作流中
技术实现
CLI 语法
IfcExporter.exe <input_ifc> [options]
支持的 IFC 版本
| 版本 | 架构 | 描述 |
|---|---|---|
| IFC2x3 | MVD | 最常见的交换格式 |
| IFC4 | ADD1 | 增强的属性 |
| IFC4x1 | Alignment | 支持基础设施 |
| IFC4x3 | 最新 | 完整支持基础设施 |
输出格式
| 输出 | 描述 |
|---|---|
.xlsx | 包含元素和属性的 Excel 数据库 |
.dae | 带有匹配 ID 的 Collada 3D 几何 |
选项
| 选项 | 描述 |
|---|---|
bbox | 包含元素边界框 |
-no-xlsx | 跳过 Excel 导出 |
-no-collada | 跳过 3D 几何导出 |
示例
# Basic conversion (XLSX + DAE)
IfcExporter.exe "C:\Models\Building.ifc"
# With bounding boxes
IfcExporter.exe "C:\Models\Building.ifc" bbox
# Excel only (no 3D geometry)
IfcExporter.exe "C:\Models\Building.ifc" -no-collada
# Batch processing
for /R "C:\IFC_Models" %f in (*.ifc) do IfcExporter.exe "%f" bbox
Python 集成
import subprocess
import pandas as pd
from pathlib import Path
from typing import List, Optional, Dict, Any, Set
from dataclasses import dataclass, field
from enum import Enum
import json
class IFCVersion(Enum):
"""IFC schema versions."""
IFC2X3 = "IFC2X3"
IFC4 = "IFC4"
IFC4X1 = "IFC4X1"
IFC4X3 = "IFC4X3"
class IFCEntityType(Enum):
"""Common IFC entity types."""
IFCWALL = "IfcWall"
IFCWALLSTANDARDCASE = "IfcWallStandardCase"
IFCSLAB = "IfcSlab"
IFCCOLUMN = "IfcColumn"
IFCBEAM = "IfcBeam"
IFCDOOR = "IfcDoor"
IFCWINDOW = "IfcWindow"
IFCROOF = "IfcRoof"
IFCSTAIR = "IfcStair"
IFCRAILING = "IfcRailing"
IFCFURNISHINGELEMENT = "IfcFurnishingElement"
IFCSPACE = "IfcSpace"
IFCBUILDINGSTOREY = "IfcBuildingStorey"
IFCBUILDING = "IfcBuilding"
IFCSITE = "IfcSite"
@dataclass
class IFCElement:
"""Represents an IFC element."""
global_id: str
ifc_type: str
name: str
description: Optional[str]
object_type: Optional[str]
level: Optional[str]
# Quantities
area: Optional[float] = None
volume: Optional[float] = None
length: Optional[float] = None
height: Optional[float] = None
width: Optional[float] = None
# Bounding box (if exported)
bbox_min_x: Optional[float] = None
bbox_min_y: Optional[float] = None
bbox_min_z: Optional[float] = None
bbox_max_x: Optional[float] = None
bbox_max_y: Optional[float] = None
bbox_max_z: Optional[float] = None
# Properties
properties: Dict[str, Any] = field(default_factory=dict)
materials: List[str] = field(default_factory=list)
@dataclass
class IFCProperty:
"""Represents an IFC property."""
pset_name: str
property_name: str
value: Any
value_type: str
@dataclass
class IFCMaterial:
"""Represents an IFC material."""
name: str
category: Optional[str]
thickness: Optional[float]
layer_position: Optional[int]
class IFCExporter:
"""IFC to Excel converter using DDC IfcExporter CLI."""
def __init__(self, exporter_path: str = "IfcExporter.exe"):
self.exporter = Path(exporter_path)
if not self.exporter.exists():
raise FileNotFoundError(f"IfcExporter not found: {exporter_path}")
def convert(self, ifc_file: str,
include_bbox: bool = True,
export_xlsx: bool = True,
export_collada: bool = True) -> Path:
"""Convert IFC file to Excel."""
ifc_path = Path(ifc_file)
if not ifc_path.exists():
raise FileNotFoundError(f"IFC file not found: {ifc_file}")
cmd = [str(self.exporter), str(ifc_path)]
if include_bbox:
cmd.append("bbox")
if not export_xlsx:
cmd.append("-no-xlsx")
if not export_collada:
cmd.append("-no-collada")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(f"Export failed: {result.stderr}")
return ifc_path.with_suffix('.xlsx')
def batch_convert(self, folder: str,
include_subfolders: bool = True,
include_bbox: bool = True) -> List[Dict[str, Any]]:
"""Convert all IFC files in folder."""
folder_path = Path(folder)
pattern = "**/*.ifc" if include_subfolders else "*.ifc"
results = []
for ifc_file in folder_path.glob(pattern):
try:
output = self.convert(str(ifc_file), include_bbox)
results.append({
'input': str(ifc_file),
'output': str(output),
'status': 'success'
})
print(f"✓ Converted: {ifc_file.name}")
except Exception as e:
results.append({
'input': str(ifc_file),
'output': None,
'status': 'failed',
'error': str(e)
})
print(f"✗ Failed: {ifc_file.name} - {e}")
return results
def read_elements(self, xlsx_file: str) -> pd.DataFrame:
"""Read converted Excel as DataFrame."""
return pd.read_excel(xlsx_file, sheet_name="Elements")
def get_element_types(self, xlsx_file: str) -> pd.DataFrame:
"""Get element type summary."""
df = self.read_elements(xlsx_file)
if 'IfcType' not in df.columns:
raise ValueError("IfcType column not found")
summary = df.groupby('IfcType').agg({
'GlobalId': 'count',
'Volume': 'sum' if 'Volume' in df.columns else 'count',
'Area': 'sum' if 'Area' in df.columns else 'count'
}).reset_index()
summary.columns = ['IFC_Type', 'Count', 'Total_Volume', 'Total_Area']
return summary.sort_values('Count', ascending=False)
def get_levels(self, xlsx_file: str) -> pd.DataFrame:
"""Get building level summary."""
df = self.read_elements(xlsx_file)
level_col = None
for col in ['Level', 'BuildingStorey', 'IfcBuildingStorey']:
if col in df.columns:
level_col = col
break
if level_col is None:
return pd.DataFrame(columns=['Level', 'Element_Count'])
summary = df.groupby(level_col).agg({
'GlobalId': 'count'
}).reset_index()
summary.columns = ['Level', 'Element_Count']
return summary
def get_materials(self, xlsx_file: str) -> pd.DataFrame:
"""Get material summary."""
df = self.read_elements(xlsx_file)
if 'Material' not in df.columns:
return pd.DataFrame(columns=['Material', 'Count'])
summary = df.groupby('Material').agg({
'GlobalId': 'count'
}).reset_index()
summary.columns = ['Material', 'Element_Count']
return summary.sort_values('Element_Count', ascending=False)
def get_quantities(self, xlsx_file: str,
group_by: str = 'IfcType') -> pd.DataFrame:
"""Get quantity takeoff summary."""
df = self.read_elements(xlsx_file)
if group_by not in df.columns:
raise ValueError(f"Column {group_by} not found")
agg_dict = {'GlobalId': 'count'}
# Add numeric columns for aggregation
numeric_cols = ['Volume', 'Area', 'Length', 'Width', 'Height']
for col in numeric_cols:
if col in df.columns:
agg_dict[col] = 'sum'
summary = df.groupby(group_by).agg(agg_dict).reset_index()
return summary
def filter_by_type(self, xlsx_file: str,
ifc_types: List[str]) -> pd.DataFrame:
"""Filter elements by IFC type."""
df = self.read_elements(xlsx_file)
return df[df['IfcType'].isin(ifc_types)]
def get_properties(self, xlsx_file: str,
element_id: str) -> Dict[str, Any]:
"""Get all properties for specific element."""
df = self.read_elements(xlsx_file)
element = df[df['GlobalId'] == element_id]
if element.empty:
return {}
# Convert row to dictionary, excluding NaN values
props = element.iloc[0].dropna().to_dict()
return props
def validate_ifc_data(self, xlsx_file: str) -> Dict[str, Any]:
"""Validate IFC data quality."""
df = self.read_elements(xlsx_file)
validation = {
'total_elements': len(df),
'issues': []
}
# Check for missing GlobalIds
if 'GlobalId' in df.columns:
missing_ids = df['GlobalId'].isna().sum()
if missing_ids > 0:
validation['issues'].append(f"{missing_ids} elements missing GlobalId")
# Check for missing names
if 'Name' in df.columns:
missing_names = df['Name'].isna().sum()
if missing_names > 0:
validation['issues'].append(f"{missing_names} elements missing Name")
# Check for zero quantities
for col in ['Volume', 'Area']:
if col in df.columns:
zero_qty = (df[col] == 0).sum()
if zero_qty > 0:
validation['issues'].append(f"{zero_qty} elements with zero {col}")
# Check for duplicate GlobalIds
if 'GlobalId' in df.columns:
duplicates = df['GlobalId'].duplicated().sum()
if duplicates > 0:
validation['issues'].append(f"{duplicates} duplicate GlobalIds")
validation['is_valid'] = len(validation['issues']) == 0
return validation
class IFCQuantityTakeoff:
"""Quantity takeoff from IFC data."""
def __init__(self, exporter: IFCExporter):
self.exporter = exporter
def generate_qto(self, ifc_file: str) -> Dict[str, pd.DataFrame]:
"""Generate complete quantity takeoff."""
xlsx = self.exporter.convert(ifc_file, include_bbox=True)
df = self.exporter.read_elements(str(xlsx))
qto = {}
# Walls
walls = df[df['IfcType'].str.contains('Wall', case=False, na=False)]
if not walls.empty:
qto['Walls'] = self._summarize_elements(walls, 'Type Name')
# Slabs
slabs = df[df['IfcType'].str.contains('Slab', case=False, na=False)]
if not slabs.empty:
qto['Slabs'] = self._summarize_elements(slabs, 'Type Name')
# Columns
columns = df[df['IfcType'].str.contains('Column', case=False, na=False)]
if not columns.empty:
qto['Columns'] = self._summarize_elements(columns, 'Type Name')
# Beams
beams = df[df['IfcType'].str.contains('Beam', case=False, na=False)]
if not beams.empty:
qto['Beams'] = self._summarize_elements(beams, 'Type Name')
# Doors
doors = df[df['IfcType'].str.contains('Door', case=False, na=False)]
if not doors.empty:
qto['Doors'] = self._summarize_elements(doors, 'Type Name')
# Windows
windows = df[df['IfcType'].str.contains('Window', case=False, na=False)]
if not windows.empty:
qto['Windows'] = self._summarize_elements(windows, 'Type Name')
return qto
def _summarize_elements(self, df: pd.DataFrame,
group_col: str) -> pd.DataFrame:
"""Summarize elements by grouping column."""
if group_col not in df.columns:
group_col = 'IfcType'
agg_dict = {'GlobalId': 'count'}
for col in ['Volume', 'Area', 'Length']:
if col in df.columns:
agg_dict[col] = 'sum'
summary = df.groupby(group_col).agg(agg_dict).reset_index()
summary.rename(columns={'GlobalId': 'Count'}, inplace=True)
return summary
def export_to_excel(self, qto: Dict[str, pd.DataFrame],
output_file: str):
"""Export QTO to multi-sheet Excel."""
with pd.ExcelWriter(output_file, engine='openpyxl') as writer:
for sheet_name, df in qto.items():
df.to_excel(writer, sheet_name=sheet_name, index=False)
# Convenience functions
def convert_ifc_to_excel(ifc_file: str,
exporter_path: str = "IfcExporter.exe") -> str:
"""Quick conversion of IFC to Excel."""
exporter = IFCExporter(exporter_path)
output = exporter.convert(ifc_file)
return str(output)
def get_ifc_summary(xlsx_file: str) -> Dict[str, Any]:
"""Get summary of converted IFC data."""
df = pd.read_excel(xlsx_file, sheet_name="Elements")
return {
'total_elements': len(df),
'ifc_types': df['IfcType'].nunique() if 'IfcType' in df.columns else 0,
'levels': df['Level'].nunique() if 'Level' in df.columns else 0,
'total_volume': df['Volume'].sum() if 'Volume' in df.columns else 0,
'total_area': df['Area'].sum() if 'Area' in df.columns else 0
}
输出结构
Excel 工作表
| 工作表 | 内容 |
|---|---|
| Elements | 所有带属性的 IFC 元素 |
| Types | 元素类型摘要 |
| Levels | 建筑楼层数据 |
| Materials | 材料分配 |
| PropertySets | IFC 属性集 |
元素列
| 列 | 类型 | 描述 |
|---|---|---|
| GlobalId | string | IFC GUID |
| IfcType | string | IFC 实体类型 |
| Name | string | 元素名称 |
| Description | string | 元素描述 |
| Level | string | 建筑楼层 |
| Material | string | 主要材料 |
| Volume | float | 体积(m³) |
| Area | float | 表面积(m²) |
| Length | float | 长度(m) |
| Height | float | 高度(m) |
| Width | float | 宽度(m) |
快速入门
# Initialize exporter
exporter = IFCExporter("C:/DDC/IfcExporter.exe")
# Convert IFC to Excel
xlsx = exporter.convert("C:/Models/Building.ifc", include_bbox=True)
# Read elements
df = exporter.read_elements(str(xlsx))
print(f"Total elements: {len(df)}")
# Get element types
types = exporter.get_element_types(str(xlsx))
print(types)
# Get quantities by type
qto = exporter.get_quantities(str(xlsx), group_by='IfcType')
print(qto)
常见用例
1. 模型验证
exporter = IFCExporter()
xlsx = exporter.convert("model.ifc")
validation = exporter.validate_ifc_data(str(xlsx))
if not validation['is_valid']:
print("Issues found:")
for issue in validation['issues']:
print(f" - {issue}")
2. 工程量统计
qto_generator = IFCQuantityTakeoff(exporter)
qto = qto_generator.generate_qto("building.ifc")
for category, data in qto.items():
print(f"\n{category}:")
print(data.to_string(index=False))
3. 材料明细表
xlsx = exporter.convert("building.ifc")
materials = exporter.get_materials(str(xlsx))
print(materials)
与 DDC 工作流集成
# Full pipeline: IFC → Excel → Validation → Cost Estimate
exporter = IFCExporter("C:/DDC/IfcExporter.exe")
# 1. Convert IFC
xlsx = exporter.convert("project.ifc", include_bbox=True)
# 2. Validate data
validation = exporter.validate_ifc_data(str(xlsx))
print(f"Valid: {validation['is_valid']}")
# 3. Generate QTO
qto = IFCQuantityTakeoff(exporter)
quantities = qto.generate_qto("project.ifc")
# 4. Export for cost estimation
qto.export_to_excel(quantities, "project_qto.xlsx")
资源
- GitHub:cad2data Pipeline
- IFC 标准:buildingSMART
- DDC 图书:第 2.4 章 - CAD/BIM 数据提取
评论
支持 Markdown