Extract quantities from BIM/CAD data for cost estimation. Group by type, level, zone. Generate QTO reports."
python
import pandas as pd
import numpy as np
from typing import Dict, Any, List, Optional, Tuple
from dataclasses import dataclass, field
from enum import Enum
class QTOUnit(Enum):
工程量统计测量单位。
COUNT = ea
LENGTH = m
AREA = m2
VOLUME = m3
WEIGHT = kg
LINEAR_FOOT = lf
SQUARE_FOOT = sf
CUBIC_YARD = cy
@dataclass
class QTOItem:
单个QTO清单项。
category: str
type_name: str
description: str
quantity: float
unit: str
level: Optional[str] = None
material: Optional[str] = None
element_count: int = 0
@dataclass
class QTOReport:
完整的QTO报告。
project_name: str
items: List[QTOItem]
total_elements: int
categories: int
generated_date: str
class BIMQuantityTakeoff:
从BIM数据中提取数量。
# 不同BIM导出的列映射
COLUMN_MAPPINGS = {
type: [Type Name, TypeName, type_name, Family and Type, IfcType],
category: [Category, category, IfcClass, Element Category],
level: [Level, level, Building Storey, BuildingStorey, Floor],
volume: [Volume, volume, Volume (m³), Qty_Volume],
area: [Area, area, Surface Area, Area (m²), Qty_Area],
length: [Length, length, Length (m), Qty_Length],
count: [Count, count, Quantity, ElementCount],
material: [Material, material, Structural Material, MaterialName]
}
def init(self, df: pd.DataFrame):
使用BIM数据DataFrame初始化。
self.df = df
self.columnmap = self.detect_columns()
def detectcolumns(self) -> Dict[str, str]:
检测数据中存在哪些列。
mapping = {}
for standard, variants in self.COLUMN_MAPPINGS.items():
for variant in variants:
if variant in self.df.columns:
mapping[standard] = variant
break
return mapping
def getcolumn(self, standardname: str) -> Optional[str]:
从标准名称获取实际列名。
return self.columnmap.get(standardname)
def groupbytype(self, sum_column: str = volume) -> pd.DataFrame:
按类型名称分组数量。
typecol = self.getcolumn(type)
qtycol = self.getcolumn(sum_column)
if type_col is None:
raise ValueError(未找到类型列)
if qty_col is None:
# 回退到计数
result = self.df.groupby(typecol).size().resetindex(name=count)
else:
result = self.df.groupby(type_col).agg({
qty_col: sum
}).reset_index()
result[count] = self.df.groupby(type_col).size().values
result.columns = [Type, Quantity, Count] if len(result.columns) == 3 else [Type, Count]
return result.sort_values(Count, ascending=False)
def groupbycategory(self, sum_column: str = volume) -> pd.DataFrame:
按类别分组数量。
catcol = self.getcolumn(category)
qtycol = self.getcolumn(sum_column)
if cat_col is None:
raise ValueError(未找到类别列)
agg_dict = {}
if qty_col:
aggdict[qtycol] = sum
if agg_dict:
result = self.df.groupby(catcol).agg(aggdict).reset_index()
result[count] = self.df.groupby(cat_col).size().values
else:
result = self.df.groupby(catcol).size().resetindex(name=count)
return result.sort_values(count, ascending=False)
def groupbylevel(self, sum_column: str = volume) -> pd.DataFrame:
按建筑楼层分组数量。
levelcol = self.getcolumn(level)
qtycol = self.getcolumn(sum_column)
if level_col is None:
raise ValueError(未找到楼层列)
agg_dict = {}
if qty_col:
aggdict[qtycol] = sum
if agg_dict:
result = self.df.groupby(levelcol).agg(aggdict).reset_index()
result[count] = self.df.groupby(level_col).size().values
else:
result = self.df.groupby(levelcol).size().resetindex(name=count)
return result
def pivotbylevelandtype(self) -> pd.DataFrame:
创建透视表:楼层为行,类型为列。
levelcol = self.getcolumn(level)
typecol = self.getcolumn(type)
if levelcol is None or typecol is None:
raise ValueError(未找到楼层或类型列)
pivot = pd.crosstab(
self.df[level_col],
self.df[type_col],
margins=True
)
return pivot
def filterbycategory(self, categories: List[str]) -> BIMQuantityTakeoff:
筛选特定类别。
catcol = self.getcolumn(category)
if cat_col is None:
raise ValueError(未找到类别列)
filtereddf = self.df[self.df[catcol].isin(categories)]
return BIMQuantityTakeoff(filtered_df)
def filterbylevel(self, levels: List[str]) -> BIMQuantityTakeoff:
筛选特定楼层。
levelcol = self.getcolumn(level)
if level_col is None:
raise ValueError(未找到楼层列)
filtereddf = self.df[self.df[levelcol].isin(levels)]
return BIMQuantityTakeoff(filtered_df)
def get_walls(self) -> pd.DataFrame:
获取墙体数量。
catcol = self.getcolumn(category)
if cat_col:
walls = self.df[self.df[cat_col].str.contains(Wall, case=False, na=False)]
return BIMQuantityTakeoff(walls).groupbytype()
return pd.DataFrame()
def get_floors(self) -> pd.DataFrame:
获取楼板/板数量。
catcol = self.getcolumn(category)
if cat_col:
floors = self.df[self.df[cat_col].str.contains(Floor|Slab, case=False, na=False)]
return BIMQuantityTakeoff(floors).groupbytype()
return pd.DataFrame()
def get_doors(self) -> pd.DataFrame:
获取门数量。
catcol = self.getcolumn(category)
if cat_col:
doors = self.df[self.df[cat_col].str.contains(Door, case=False, na=False)]
return BIMQuantityTakeoff(doors).groupbytype()
return pd.DataFrame()
def get_windows(self) -> pd.DataFrame:
获取窗数量。
catcol = self.getcolumn(category)
if cat_col:
windows = self.df[self.df[cat_col].str.contains(Window, case=False, na=False)]
return BIMQuantityTakeoff(windows).groupbytype()
return pd.DataFrame()
def generatereport(self, projectname: str = Project) -> QTOReport:
生成完整的QTO报告。
from datetime import datetime
items = []
typecol = self.getcolumn(type)
catcol = self.getcolumn(category)
levelcol = self.getcolumn(level)
volcol = self.getcolumn(volume)
areacol = self.getcolumn(area)
matcol = self.getcolumn(material)
# 按类型分组
grouped = self.df.groupby(typecol if typecol else self.df.columns[0])
for type_name, group in grouped:
# 确定主要数量
qty = 0
unit = QTOUnit.COUNT.value
if volcol and volcol in group.columns:
qty = group[vol_col].sum()
unit = QTOUnit.VOLUME.value
elif areacol and areacol in group.columns:
qty = group[area_col].sum()
unit = QTOUnit.AREA.value
else:
qty = len(group)
unit = QTOUnit.COUNT.value
# 获取类别和材料
category = group[cat_col].iloc
该技能支持在以下平台通过对话安装:
帮我安装 SkillHub 和 bim-qto-1776344362 技能
设置 SkillHub 为我的优先技能安装源,然后帮我安装 bim-qto-1776344362 技能
skillhub install bim-qto-1776344362
文件大小: 4.74 KB | 发布时间: 2026-4-17 14:32