CAD To Data
Overview
Based on DDC methodology (Chapter 2.4), this skill converts CAD and BIM files to structured data, extracting element properties, quantities, and relationships from Revit, IFC, DWG, and DGN files.
Book Reference: "Преобразование данных в структурированную форму" / "Data Transformation to Structured Form"
Quick Start
from dataclasses import dataclass, field
from enum import Enum
from typing import List, Dict, Optional, Any, Tuple, Generator
from datetime import datetime
import json
class CADFormat(Enum):
"""Supported CAD/BIM formats"""
IFC = "ifc"
RVT = "rvt"
DWG = "dwg"
DXF = "dxf"
DGN = "dgn"
NWD = "nwd"
STEP = "step"
class ElementCategory(Enum):
"""BIM element categories"""
WALL = "wall"
FLOOR = "floor"
ROOF = "roof"
CEILING = "ceiling"
DOOR = "door"
WINDOW = "window"
COLUMN = "column"
BEAM = "beam"
STAIR = "stair"
RAMP = "ramp"
FURNITURE = "furniture"
EQUIPMENT = "equipment"
PIPE = "pipe"
DUCT = "duct"
CABLE_TRAY = "cable_tray"
SPACE = "space"
GENERIC = "generic"
@dataclass
class Point3D:
"""3D point"""
x: float
y: float
z: float
@dataclass
class BoundingBox3D:
"""3D bounding box"""
min_point: Point3D
max_point: Point3D
@property
def width(self) -> float:
return abs(self.max_point.x - self.min_point.x)
@property
def depth(self) -> float:
return abs(self.max_point.y - self.min_point.y)
@property
def height(self) -> float:
return abs(self.max_point.z - self.min_point.z)
@property
def volume(self) -> float:
return self.width * self.depth * self.height
@dataclass
class MaterialInfo:
"""Material information"""
name: str
category: str
color: Optional[str] = None
area: float = 0.0
volume: float = 0.0
properties: Dict[str, Any] = field(default_factory=dict)
@dataclass
class CADElement:
"""Extracted CAD/BIM element"""
id: str
guid: str
name: str
category: ElementCategory
type_name: str
level: Optional[str] = None
bounding_box: Optional[BoundingBox3D] = None
properties: Dict[str, Any] = field(default_factory=dict)
quantities: Dict[str, float] = field(default_factory=dict)
materials: List[MaterialInfo] = field(default_factory=list)
relationships: Dict[str, List[str]] = field(default_factory=dict)
@dataclass
class CADLayer:
"""CAD layer information"""
name: str
color: Optional[str] = None
line_type: Optional[str] = None
visible: bool = True
element_count: int = 0
@dataclass
class CADExtractionResult:
"""Result of CAD extraction"""
file_path: str
file_format: CADFormat
elements: List[CADElement]
layers: List[CADLayer]
levels: List[str]
total_elements: int
categories: Dict[str, int]
extraction_time: float
metadata: Dict[str, Any] = field(default_factory=dict)
class IFCExtractor:
"""Extract data from IFC files"""
def __init__(self):
self.schema_version = "IFC4"
self.element_mapping = self._build_element_mapping()
def _build_element_mapping(self) -> Dict[str, ElementCategory]:
"""Map IFC types to categories"""
return {
"IfcWall": ElementCategory.WALL,
"IfcWallStandardCase": ElementCategory.WALL,
"IfcSlab": ElementCategory.FLOOR,
"IfcRoof": ElementCategory.ROOF,
"IfcCeiling": ElementCategory.CEILING,
"IfcDoor": ElementCategory.DOOR,
"IfcWindow": ElementCategory.WINDOW,
"IfcColumn": ElementCategory.COLUMN,
"IfcBeam": ElementCategory.BEAM,
"IfcStair": ElementCategory.STAIR,
"IfcRamp": ElementCategory.RAMP,
"IfcFurnishingElement": ElementCategory.FURNITURE,
"IfcPipeSegment": ElementCategory.PIPE,
"IfcDuctSegment": ElementCategory.DUCT,
"IfcCableCarrierSegment": ElementCategory.CABLE_TRAY,
"IfcSpace": ElementCategory.SPACE,
}
def extract(
self,
file_path: str,
categories: Optional[List[ElementCategory]] = None
) -> CADExtractionResult:
"""
Extract data from IFC file.
Args:
file_path: Path to IFC file
categories: Optional filter for categories
Returns:
Extraction result
"""
start_time = datetime.now()
# In production, use ifcopenshell:
# import ifcopenshell
# ifc_file = ifcopenshell.open(file_path)
# Simulated extraction
elements = self._simulate_ifc_elements()
# Filter by category if specified
if categories:
elements = [e for e in elements if e.category in categories]
# Build category counts
category_counts = {}
for element in elements:
cat = element.category.value
category_counts[cat] = category_counts.get(cat, 0) + 1
# Extract levels
levels = list(set(e.level for e in elements if e.level))
extraction_time = (datetime.now() - start_time).total_seconds()
return CADExtractionResult(
file_path=file_path,
file_format=CADFormat.IFC,
elements=elements,
layers=[], # IFC doesn't use layers in traditional sense
levels=levels,
total_elements=len(elements),
categories=category_counts,
extraction_time=extraction_time,
metadata={
"schema": self.schema_version,
"project_name": "Sample Project"
}
)
def _simulate_ifc_elements(self) -> List[CADElement]:
"""Simulate IFC element extraction"""
elements = []
# Sample walls
for i in range(10):
elements.append(CADElement(
id=f"wall_{i}",
guid=f"1234567890ABCDEF{i:04d}",
name=f"Basic Wall {i}",
category=ElementCategory.WALL,
type_name="Basic Wall:200mm Concrete",
level="Level 1",
bounding_box=BoundingBox3D(
min_point=Point3D(i * 5, 0, 0),
max_point=Point3D(i * 5 + 5, 0.2, 3)
),
properties={
"IsExternal": True,
"FireRating": "1 HR",
"LoadBearing": True
},
quantities={
"Length": 5.0,
"Height": 3.0,
"Width": 0.2,
"Area": 15.0,
"Volume": 3.0
},
materials=[
MaterialInfo(
name="Concrete",
category="Concrete",
area=15.0,
volume=3.0
)
]
))
# Sample doors
for i in range(5):
elements.append(CADElement(
id=f"door_{i}",
guid=f"DOOR0000000000{i:04d}",
name=f"Single Door {i}",
category=ElementCategory.DOOR,
type_name="Single Flush:900x2100",
level="Level 1",
properties={
"FireRating": "None",
"IsExternal": False
},
quantities={
"Width": 0.9,
"Height": 2.1,
"Area": 1.89
},
relationships={
"host_wall": [f"wall_{i}"]
}
))
# Sample spaces
for i in range(3):
elements.append(CADElement(
id=f"space_{i}",
guid=f"SPACE000000000{i:04d}",
name=f"Room {i+101}",