{"id":"a545256e-9b51-442a-8ab8-754cee09d79e","entityType":"agent","slug":"clawhub-datadrivenconstruction-ifc-qto-extraction","name":"Ifc Qto Extraction","canonicalUrl":"https://www.xpersona.co/agent/clawhub-datadrivenconstruction-ifc-qto-extraction","canonicalPath":"/agent/clawhub-datadrivenconstruction-ifc-qto-extraction","generatedAt":"2026-10-11T03:55:35.682Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-11T01:46:49.486Z","emptyReason":null},"description":"Extract quantities from IFC/Revit models for quantity takeoff. Uses DDC converters to get element counts, areas, volumes, lengths with grouping and reporting. Skill: Ifc Qto Extraction Owner: datadrivenconstruction Summary: Extract quantities from IFC/Revit models for quantity takeoff. Uses DDC converters to get element counts, areas, volumes, lengths with grouping and reporting. Tags: latest:2.0.0 Version history: v2.0.0 | 2026-02-13T16:07:11.073Z | auto Major update: Adds structured quantity extraction from IFC/Revit models, grouped reporting, and Python API. - Automated","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.2K downloads reported by the source. Last updated 10/11/2026.","installCommand":"clawhub skill install s1774mv3t1cm8r1kgs9hccdnmn8852nb:ifc-qto-extraction","sourceUrl":"https://clawhub.ai/datadrivenconstruction/ifc-qto-extraction","homepage":"https://clawhub.ai/datadrivenconstruction/skills/ifc-qto-extraction","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/datadrivenconstruction/ifc-qto-extraction","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/datadrivenconstruction/skills/ifc-qto-extraction","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":62,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Extract quantities from IFC/Revit models for quantity takeoff. Uses DDC converters to get element counts, areas, volumes, lengths with grouping and reporting. S"},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-11T01:46:49.486Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T01:46:49.486Z","emptyReason":null},"stars":null,"forks":null,"downloads":1201,"packageName":null,"latestVersion":"2.0.0","tractionLabel":"1.2K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T01:46:49.424Z","emptyReason":null},"lastUpdatedAt":"2026-10-11T01:46:49.486Z","lastCrawledAt":"2026-10-11T01:46:49.424Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-12T01:46:49.424Z","lastVerifiedAt":null,"highlights":[{"version":"2.0.0","createdAt":"2026-02-13T16:07:11.073Z","changelog":"Major update: Adds structured quantity extraction from IFC/Revit models, grouped reporting, and Python API. - Automated extraction of counts, areas, volumes, lengths, and weights from BIM models (IFC, Revit, DWG). - Supports grouping and reporting by type, level, and zone. - Includes command line tools for model-to-Excel conversion. - Provides a Python API for loading, processing, and aggregating BIM quantity data. - Documentation expanded with business case, pipeline diagrams, and code samples.","fileCount":4,"zipByteSize":7695},{"version":"1.0.0","createdAt":"2026-02-07T14:33:07.229Z","changelog":"IFC QTO Extraction v1.0.0 - Initial release. - Automates extraction of element counts, areas, volumes, and lengths from IFC/Revit/DWG models using DDC converters. - Supports grouping by type, level, and zone, with output to Excel for further analysis or pricing. - Provides sample Python implementation for loading, aggregating, and reporting quantity data. - Offers command-line workflows for model-to-Excel conversion, supporting Revit (RVT), IFC, and DWG inputs.","fileCount":2,"zipByteSize":6195}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s1774mv3t1cm8r1kgs9hccdnmn8852nb:ifc-qto-extraction","setupComplexity":"low","setupSteps":["Setup complexity is LOW. This package is likely designed for quick installation with minimal external side-effects.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-datadrivenconstruction-ifc-qto-extraction/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-datadrivenconstruction-ifc-qto-extraction/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-datadrivenconstruction-ifc-qto-extraction/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-datadrivenconstruction-ifc-qto-extraction/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-datadrivenconstruction-ifc-qto-extraction/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-datadrivenconstruction-ifc-qto-extraction/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-11T03:55:35.682Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-datadrivenconstruction-ifc-qto-extraction/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-datadrivenconstruction-ifc-qto-extraction/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-datadrivenconstruction-ifc-qto-extraction/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-datadrivenconstruction-ifc-qto-extraction/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-11T01:46:49.486Z","emptyReason":null},"readme":"Skill: Ifc Qto Extraction\n\nOwner: datadrivenconstruction\n\nSummary: Extract quantities from IFC/Revit models for quantity takeoff. Uses DDC converters to get element counts, areas, volumes, lengths with grouping and reporting.\n\nTags: latest:2.0.0\n\nVersion history:\n\nv2.0.0 | 2026-02-13T16:07:11.073Z | auto\n\nMajor update: Adds structured quantity extraction from IFC/Revit models, grouped reporting, and Python API.\n\n- Automated extraction of counts, areas, volumes, lengths, and weights from BIM models (IFC, Revit, DWG).\n- Supports grouping and reporting by type, level, and zone.\n- Includes command line tools for model-to-Excel conversion.\n- Provides a Python API for loading, processing, and aggregating BIM quantity data.\n- Documentation expanded with business case, pipeline diagrams, and code samples.\n\nv1.0.0 | 2026-02-07T14:33:07.229Z | auto\n\nIFC QTO Extraction v1.0.0\n\n- Initial release.\n- Automates extraction of element counts, areas, volumes, and lengths from IFC/Revit/DWG models using DDC converters.\n- Supports grouping by type, level, and zone, with output to Excel for further analysis or pricing.\n- Provides sample Python implementation for loading, aggregating, and reporting quantity data.\n- Offers command-line workflows for model-to-Excel conversion, supporting Revit (RVT), IFC, and DWG inputs.\n\nArchive index:\n\nArchive v2.0.0: 4 files, 7695 bytes\n\nFiles: claw.json (517b), instructions.md (1961b), SKILL.md (20966b), _meta.json (137b)\n\nFile v2.0.0:SKILL.md\n\n---\r\nname: \"ifc-qto-extraction\"\r\ndescription: \"Extract quantities from IFC/Revit models for quantity takeoff. Uses DDC converters to get element counts, areas, volumes, lengths with grouping and reporting.\"\r\n---\r\n\r\n# IFC Quantity Takeoff Extraction\r\n\r\nExtract structured quantity data from BIM models (IFC, Revit) for cost estimation, material ordering, and progress tracking.\r\n\r\n## Business Case\r\n\r\n**Problem**: Manual quantity takeoff is:\r\n- Time-consuming (40-80 hours for medium project)\r\n- Error-prone (human counting mistakes)\r\n- Not repeatable (changes require full rework)\r\n- Disconnected from design (no live updates)\r\n\r\n**Solution**: Automated QTO from BIM that:\r\n- Extracts all quantities in minutes\r\n- Groups by type, level, zone\r\n- Updates instantly with model changes\r\n- Exports to Excel for pricing\r\n\r\n**ROI**: 90% reduction in QTO time, near-zero counting errors\r\n\r\n## DDC Tools Used\r\n\r\n```\r\n┌──────────────────────────────────────────────────────────────────────┐\r\n│                      QTO EXTRACTION PIPELINE                          │\r\n├──────────────────────────────────────────────────────────────────────┤\r\n│                                                                       │\r\n│   INPUT                 CONVERT                 ANALYZE               │\r\n│   ┌─────────┐          ┌─────────┐            ┌─────────┐            │\r\n│   │ .rvt    │          │ DDC     │            │ Python  │            │\r\n│   │ .ifc    │─────────►│Converter│───────────►│ pandas  │            │\r\n│   │ .dwg    │          │         │            │         │            │\r\n│   └─────────┘          └─────────┘            └─────────┘            │\r\n│                              │                      │                 │\r\n│                              ▼                      ▼                 │\r\n│                        ┌─────────┐            ┌─────────┐            │\r\n│                        │ .xlsx   │            │ Grouped │            │\r\n│                        │ raw data│            │ QTO     │            │\r\n│                        └─────────┘            └─────────┘            │\r\n│                                                    │                  │\r\n│   OUTPUT                                           ▼                  │\r\n│   ┌─────────────────────────────────────────────────────────────┐   │\r\n│   │  QTO Report                                                  │   │\r\n│   │  • Element counts by type                                    │   │\r\n│   │  • Areas (m², ft²)                                           │   │\r\n│   │  • Volumes (m³, ft³)                                         │   │\r\n│   │  • Lengths (m, ft)                                           │   │\r\n│   │  • Weights (kg, tons)                                        │   │\r\n│   │  • Grouped by level/zone/system                              │   │\r\n│   └─────────────────────────────────────────────────────────────┘   │\r\n│                                                                       │\r\n└──────────────────────────────────────────────────────────────────────┘\r\n```\r\n\r\n## CLI Commands\r\n\r\n### Revit to Excel (with BBox for volumes)\r\n\r\n```bash\r\n# Basic extraction\r\nRvtExporter.exe \"C:\\Models\\Building.rvt\"\r\n\r\n# Full extraction with bounding boxes (for volume calculations)\r\nRvtExporter.exe \"C:\\Models\\Building.rvt\" complete bbox\r\n\r\n# Include schedules (Revit's built-in QTO)\r\nRvtExporter.exe \"C:\\Models\\Building.rvt\" complete bbox schedule\r\n```\r\n\r\n### IFC to Excel\r\n\r\n```bash\r\n# Extract IFC data\r\nIfcExporter.exe \"C:\\Models\\Building.ifc\"\r\n\r\n# Output: Building.xlsx with all IFC entities\r\n```\r\n\r\n### DWG to Excel (2D areas)\r\n\r\n```bash\r\n# Extract DWG blocks and areas\r\nDwgExporter.exe \"C:\\Drawings\\FloorPlan.dwg\"\r\n```\r\n\r\n## Python Implementation\r\n\r\n```python\r\nimport pandas as pd\r\nimport numpy as np\r\nfrom pathlib import Path\r\nimport subprocess\r\nfrom typing import List, Dict, Optional\r\nfrom dataclasses import dataclass\r\n\r\n@dataclass\r\nclass QuantityItem:\r\n    \"\"\"Single quantity line item\"\"\"\r\n    category: str\r\n    type_name: str\r\n    count: int\r\n    area: float = 0.0\r\n    volume: float = 0.0\r\n    length: float = 0.0\r\n    weight: float = 0.0\r\n    unit_area: str = \"m²\"\r\n    unit_volume: str = \"m³\"\r\n    unit_length: str = \"m\"\r\n    level: str = \"\"\r\n    zone: str = \"\"\r\n\r\n\r\nclass BIMQuantityExtractor:\r\n    \"\"\"Extract quantities from BIM models using DDC converters\"\"\"\r\n\r\n    def __init__(self, converter_path: str):\r\n        self.converter_path = Path(converter_path)\r\n\r\n    def convert_model(self, model_path: str, options: List[str] = None) -> Path:\r\n        \"\"\"Convert BIM model to Excel\"\"\"\r\n\r\n        model = Path(model_path)\r\n        options = options or [\"complete\", \"bbox\"]\r\n\r\n        # Determine converter\r\n        ext = model.suffix.lower()\r\n        converters = {\r\n            '.rvt': 'RvtExporter.exe',\r\n            '.rfa': 'RvtExporter.exe',\r\n            '.ifc': 'IfcExporter.exe',\r\n            '.dwg': 'DwgExporter.exe',\r\n            '.dgn': 'DgnExporter.exe'\r\n        }\r\n\r\n        converter = self.converter_path / converters.get(ext, 'RvtExporter.exe')\r\n\r\n        # Build command\r\n        cmd = [str(converter), str(model)] + options\r\n\r\n        # Execute\r\n        result = subprocess.run(cmd, capture_output=True, text=True)\r\n\r\n        if result.returncode != 0:\r\n            raise RuntimeError(f\"Conversion failed: {result.stderr}\")\r\n\r\n        # Return path to generated Excel\r\n        xlsx_path = model.with_suffix('.xlsx')\r\n        return xlsx_path\r\n\r\n    def load_bim_data(self, xlsx_path: str) -> pd.DataFrame:\r\n        \"\"\"Load converted BIM data from Excel\"\"\"\r\n\r\n        xlsx = Path(xlsx_path)\r\n        if not xlsx.exists():\r\n            raise FileNotFoundError(f\"Excel file not found: {xlsx}\")\r\n\r\n        # Read main data sheet\r\n        df = pd.read_excel(xlsx, sheet_name=0)\r\n\r\n        # Clean column names\r\n        df.columns = df.columns.str.strip()\r\n\r\n        return df\r\n\r\n    def extract_quantities(\r\n        self,\r\n        df: pd.DataFrame,\r\n        group_by: str = \"Type Name\",\r\n        include_categories: List[str] = None\r\n    ) -> List[QuantityItem]:\r\n        \"\"\"Extract quantities grouped by type\"\"\"\r\n\r\n        # Filter categories if specified\r\n        if include_categories and 'Category' in df.columns:\r\n            df = df[df['Category'].isin(include_categories)]\r\n\r\n        # Group and aggregate\r\n        quantities = []\r\n\r\n        for (category, type_name), group in df.groupby(['Category', group_by]):\r\n            item = QuantityItem(\r\n                category=str(category),\r\n                type_name=str(type_name),\r\n                count=len(group)\r\n            )\r\n\r\n            # Extract area\r\n            area_cols = ['Area', 'Surface Area', 'Gross Area', 'Net Area']\r\n            for col in area_cols:\r\n                if col in group.columns:\r\n                    item.area = group[col].sum()\r\n                    break\r\n\r\n            # Extract volume\r\n            vol_cols = ['Volume', 'Gross Volume', 'Net Volume']\r\n            for col in vol_cols:\r\n                if col in group.columns:\r\n                    item.volume = group[col].sum()\r\n                    break\r\n\r\n            # Extract length\r\n            len_cols = ['Length', 'Curve Length', 'Unconnected Height']\r\n            for col in len_cols:\r\n                if col in group.columns:\r\n                    item.length = group[col].sum()\r\n                    break\r\n\r\n            # Extract level if available\r\n            if 'Level' in group.columns:\r\n                levels = group['Level'].dropna().unique()\r\n                item.level = ', '.join(str(l) for l in levels)\r\n\r\n            quantities.append(item)\r\n\r\n        return quantities\r\n\r\n    def extract_by_level(\r\n        self,\r\n        df: pd.DataFrame,\r\n        group_by: str = \"Type Name\"\r\n    ) -> Dict[str, List[QuantityItem]]:\r\n        \"\"\"Extract quantities grouped by level\"\"\"\r\n\r\n        result = {}\r\n\r\n        if 'Level' not in df.columns:\r\n            result['All Levels'] = self.extract_quantities(df, group_by)\r\n            return result\r\n\r\n        for level, level_df in df.groupby('Level'):\r\n            level_name = str(level) if pd.notna(level) else 'Unassigned'\r\n            result[level_name] = self.extract_quantities(level_df, group_by)\r\n\r\n        return result\r\n\r\n    def calculate_concrete_quantities(self, df: pd.DataFrame) -> dict:\r\n        \"\"\"Calculate concrete quantities for typical elements\"\"\"\r\n\r\n        concrete_categories = [\r\n            'Floors', 'Structural Floors',\r\n            'Walls', 'Structural Walls',\r\n            'Structural Foundations', 'Foundation',\r\n            'Structural Columns', 'Columns',\r\n            'Structural Framing', 'Beams'\r\n        ]\r\n\r\n        concrete_df = df[df['Category'].isin(concrete_categories)]\r\n\r\n        return {\r\n            'total_volume_m3': concrete_df['Volume'].sum() if 'Volume' in concrete_df.columns else 0,\r\n            'by_category': concrete_df.groupby('Category')['Volume'].sum().to_dict() if 'Volume' in concrete_df.columns else {},\r\n            'element_count': len(concrete_df)\r\n        }\r\n\r\n    def calculate_wall_quantities(self, df: pd.DataFrame) -> dict:\r\n        \"\"\"Calculate wall quantities\"\"\"\r\n\r\n        wall_categories = ['Walls', 'Basic Wall', 'Curtain Wall']\r\n        walls = df[df['Category'].isin(wall_categories)]\r\n\r\n        result = {\r\n            'total_area_m2': 0,\r\n            'total_length_m': 0,\r\n            'by_type': {}\r\n        }\r\n\r\n        if 'Area' in walls.columns:\r\n            result['total_area_m2'] = walls['Area'].sum()\r\n\r\n        if 'Length' in walls.columns:\r\n            result['total_length_m'] = walls['Length'].sum()\r\n\r\n        if 'Type Name' in walls.columns:\r\n            for type_name, group in walls.groupby('Type Name'):\r\n                result['by_type'][type_name] = {\r\n                    'count': len(group),\r\n                    'area': group['Area'].sum() if 'Area' in group.columns else 0,\r\n                    'length': group['Length'].sum() if 'Length' in group.columns else 0\r\n                }\r\n\r\n        return result\r\n\r\n    def generate_qto_report(\r\n        self,\r\n        quantities: List[QuantityItem],\r\n        output_path: str,\r\n        project_name: str = \"Project\"\r\n    ) -> str:\r\n        \"\"\"Generate QTO Excel report\"\"\"\r\n\r\n        # Convert to DataFrame\r\n        records = []\r\n        for q in quantities:\r\n            records.append({\r\n                'Category': q.category,\r\n                'Type': q.type_name,\r\n                'Count': q.count,\r\n                'Area (m²)': round(q.area, 2),\r\n                'Volume (m³)': round(q.volume, 3),\r\n                'Length (m)': round(q.length, 2),\r\n                'Level': q.level\r\n            })\r\n\r\n        df = pd.DataFrame(records)\r\n\r\n        # Sort by category and type\r\n        df = df.sort_values(['Category', 'Type'])\r\n\r\n        # Write to Excel with formatting\r\n        with pd.ExcelWriter(output_path, engine='openpyxl') as writer:\r\n            # Summary sheet\r\n            summary = df.groupby('Category').agg({\r\n                'Count': 'sum',\r\n                'Area (m²)': 'sum',\r\n                'Volume (m³)': 'sum',\r\n                'Length (m)': 'sum'\r\n            }).round(2)\r\n            summary.to_excel(writer, sheet_name='Summary')\r\n\r\n            # Detail sheet\r\n            df.to_excel(writer, sheet_name='Detail', index=False)\r\n\r\n            # By Level sheet\r\n            if 'Level' in df.columns and df['Level'].notna().any():\r\n                level_summary = df.groupby(['Level', 'Category']).agg({\r\n                    'Count': 'sum',\r\n                    'Area (m²)': 'sum',\r\n                    'Volume (m³)': 'sum'\r\n                }).round(2)\r\n                level_summary.to_excel(writer, sheet_name='By Level')\r\n\r\n        return output_path\r\n\r\n    def generate_html_report(\r\n        self,\r\n        quantities: List[QuantityItem],\r\n        output_path: str,\r\n        project_name: str = \"Project\"\r\n    ) -> str:\r\n        \"\"\"Generate interactive HTML QTO report\"\"\"\r\n\r\n        # Group by category\r\n        by_category = {}\r\n        for q in quantities:\r\n            if q.category not in by_category:\r\n                by_category[q.category] = []\r\n            by_category[q.category].append(q)\r\n\r\n        # Calculate totals\r\n        total_count = sum(q.count for q in quantities)\r\n        total_area = sum(q.area for q in quantities)\r\n        total_volume = sum(q.volume for q in quantities)\r\n\r\n        html = f\"\"\"\r\n<!DOCTYPE html>\r\n<html>\r\n<head>\r\n    <title>QTO Report - {project_name}</title>\r\n    <style>\r\n        body {{ font-family: Arial, sans-serif; margin: 20px; }}\r\n        .header {{ background: #2c3e50; color: white; padding: 20px; margin-bottom: 20px; }}\r\n        .summary {{ display: flex; gap: 20px; margin-bottom: 20px; }}\r\n        .summary-card {{ background: #ecf0f1; padding: 15px; border-radius: 5px; flex: 1; }}\r\n        .summary-card h3 {{ margin: 0 0 10px 0; color: #7f8c8d; font-size: 14px; }}\r\n        .summary-card .value {{ font-size: 24px; font-weight: bold; color: #2c3e50; }}\r\n        table {{ width: 100%; border-collapse: collapse; margin-bottom: 20px; }}\r\n        th {{ background: #34495e; color: white; padding: 10px; text-align: left; }}\r\n        td {{ padding: 8px; border-bottom: 1px solid #ddd; }}\r\n        tr:hover {{ background: #f5f5f5; }}\r\n        .category-header {{ background: #3498db; color: white; font-weight: bold; }}\r\n        .number {{ text-align: right; }}\r\n    </style>\r\n</head>\r\n<body>\r\n    <div class=\"header\">\r\n        <h1>Quantity Takeoff Report</h1>\r\n        <p>Project: {project_name}</p>\r\n    </div>\r\n\r\n    <div class=\"summary\">\r\n        <div class=\"summary-card\">\r\n            <h3>Total Elements</h3>\r\n            <div class=\"value\">{total_count:,}</div>\r\n        </div>\r\n        <div class=\"summary-card\">\r\n            <h3>Total Area</h3>\r\n            <div class=\"value\">{total_area:,.2f} m²</div>\r\n        </div>\r\n        <div class=\"summary-card\">\r\n            <h3>Total Volume</h3>\r\n            <div class=\"value\">{total_volume:,.3f} m³</div>\r\n        </div>\r\n        <div class=\"summary-card\">\r\n            <h3>Categories</h3>\r\n            <div class=\"value\">{len(by_category)}</div>\r\n        </div>\r\n    </div>\r\n\r\n    <table>\r\n        <thead>\r\n            <tr>\r\n                <th>Category / Type</th>\r\n                <th class=\"number\">Count</th>\r\n                <th class=\"number\">Area (m²)</th>\r\n                <th class=\"number\">Volume (m³)</th>\r\n                <th class=\"number\">Length (m)</th>\r\n            </tr>\r\n        </thead>\r\n        <tbody>\r\n\"\"\"\r\n\r\n        for category, items in sorted(by_category.items()):\r\n            cat_count = sum(i.count for i in items)\r\n            cat_area = sum(i.area for i in items)\r\n            cat_volume = sum(i.volume for i in items)\r\n\r\n            html += f\"\"\"\r\n            <tr class=\"category-header\">\r\n                <td>{category}</td>\r\n                <td class=\"number\">{cat_count:,}</td>\r\n                <td class=\"number\">{cat_area:,.2f}</td>\r\n                <td class=\"number\">{cat_volume:,.3f}</td>\r\n                <td class=\"number\">-</td>\r\n            </tr>\r\n\"\"\"\r\n            for item in sorted(items, key=lambda x: x.type_name):\r\n                html += f\"\"\"\r\n            <tr>\r\n                <td>&nbsp;&nbsp;&nbsp;{item.type_name}</td>\r\n                <td class=\"number\">{item.count:,}</td>\r\n                <td class=\"number\">{item.area:,.2f}</td>\r\n                <td class=\"number\">{item.volume:,.3f}</td>\r\n                <td class=\"number\">{item.length:,.2f}</td>\r\n            </tr>\r\n\"\"\"\r\n\r\n        html += \"\"\"\r\n        </tbody>\r\n    </table>\r\n</body>\r\n</html>\r\n\"\"\"\r\n\r\n        with open(output_path, 'w', encoding='utf-8') as f:\r\n            f.write(html)\r\n\r\n        return output_path\r\n\r\n\r\n# Usage Example\r\ndef extract_qto_from_model(\r\n    model_path: str,\r\n    converter_path: str,\r\n    output_dir: str = None\r\n) -> dict:\r\n    \"\"\"Complete QTO extraction workflow\"\"\"\r\n\r\n    from datetime import datetime\r\n\r\n    extractor = BIMQuantityExtractor(converter_path)\r\n\r\n    # Convert model\r\n    print(f\"Converting: {model_path}\")\r\n    xlsx_path = extractor.convert_model(model_path, [\"complete\", \"bbox\"])\r\n\r\n    # Load data\r\n    print(f\"Loading data from: {xlsx_path}\")\r\n    df = extractor.load_bim_data(xlsx_path)\r\n\r\n    # Extract quantities\r\n    quantities = extractor.extract_quantities(df)\r\n\r\n    # Generate reports\r\n    output_dir = output_dir or Path(model_path).parent\r\n    timestamp = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\r\n\r\n    excel_path = Path(output_dir) / f\"QTO_{timestamp}.xlsx\"\r\n    html_path = Path(output_dir) / f\"QTO_{timestamp}.html\"\r\n\r\n    extractor.generate_qto_report(quantities, str(excel_path))\r\n    extractor.generate_html_report(quantities, str(html_path))\r\n\r\n    # Calculate specific quantities\r\n    concrete = extractor.calculate_concrete_quantities(df)\r\n    walls = extractor.calculate_wall_quantities(df)\r\n\r\n    return {\r\n        'excel_report': str(excel_path),\r\n        'html_report': str(html_path),\r\n        'summary': {\r\n            'total_elements': len(df),\r\n            'categories': df['Category'].nunique() if 'Category' in df.columns else 0,\r\n            'types': df['Type Name'].nunique() if 'Type Name' in df.columns else 0\r\n        },\r\n        'concrete': concrete,\r\n        'walls': walls\r\n    }\r\n\r\n\r\nif __name__ == \"__main__\":\r\n    result = extract_qto_from_model(\r\n        model_path=r\"C:\\Projects\\Building.rvt\",\r\n        converter_path=r\"C:\\DDC\\Converters\",\r\n        output_dir=r\"C:\\Projects\\QTO\"\r\n    )\r\n\r\n    print(f\"Excel: {result['excel_report']}\")\r\n    print(f\"HTML: {result['html_report']}\")\r\n    print(f\"Concrete Volume: {result['concrete']['total_volume_m3']:.2f} m³\")\r\n```\r\n\r\n## n8n Workflow Integration\r\n\r\n```yaml\r\nname: BIM QTO Extraction\r\ntrigger:\r\n  type: webhook\r\n  path: /qto-extract\r\n\r\nsteps:\r\n  - convert_model:\r\n      node: Execute Command\r\n      command: |\r\n        \"C:\\DDC\\RvtExporter.exe\" \"{{$json.model_path}}\" complete bbox schedule\r\n\r\n  - load_excel:\r\n      node: Spreadsheet File\r\n      operation: read\r\n      file: \"={{$json.model_path.replace('.rvt', '.xlsx')}}\"\r\n\r\n  - group_quantities:\r\n      node: Code\r\n      code: |\r\n        const grouped = {};\r\n        items.forEach(item => {\r\n          const type = item.json['Type Name'];\r\n          if (!grouped[type]) {\r\n            grouped[type] = {\r\n              count: 0,\r\n              area: 0,\r\n              volume: 0\r\n            };\r\n          }\r\n          grouped[type].count++;\r\n          grouped[type].area += parseFloat(item.json['Area'] || 0);\r\n          grouped[type].volume += parseFloat(item.json['Volume'] || 0);\r\n        });\r\n        return Object.entries(grouped).map(([type, data]) => ({\r\n          type,\r\n          ...data\r\n        }));\r\n\r\n  - generate_report:\r\n      node: Code\r\n      code: |\r\n        // Generate HTML report\r\n        return generateHTMLReport(items);\r\n\r\n  - save_report:\r\n      node: Write Binary File\r\n      path: \"={{$json.output_path}}\"\r\n```\r\n\r\n## Best Practices\r\n\r\n1. **Model Quality**: Ensure BIM model has proper levels and types assigned\r\n2. **Units**: Verify model units match expected output units\r\n3. **Categories**: Use consistent category naming for grouping\r\n4. **Updates**: Re-run QTO after design changes\r\n5. **Validation**: Cross-check totals against manual spot checks\r\n\r\n## Common Quantity Formulas\r\n\r\n```python\r\n# Concrete formwork area (approximate)\r\nformwork_area = concrete_volume * 6  # m² per m³ of concrete\r\n\r\n# Rebar quantity (approximate)\r\nrebar_weight = concrete_volume * 100  # kg per m³ (typical)\r\n\r\n# Paint area from wall area\r\npaint_area = wall_area * 2  # both sides\r\n\r\n# Ceiling area from floor area\r\nceiling_area = floor_area * 0.95  # typical ratio\r\n```\r\n\r\n---\r\n\r\n*\"Measure twice, cut once. Or better yet, measure automatically from the model.\"*\n\nFile v2.0.0:_meta.json\n\n{\n  \"ownerId\": \"kn75fhjxn1jz5xbgd9ggj0nrtd80q1dz\",\n  \"slug\": \"ifc-qto-extraction\",\n  \"version\": \"2.0.0\",\n  \"publishedAt\": 1770998831073\n}\n\nFile v2.0.0:instructions.md\n\nYou are a BIM quantity takeoff assistant. You extract structured quantity data from BIM models (IFC, Revit) for cost estimation, material ordering, and progress tracking.\n\nWhen the user asks to extract quantities from a BIM model:\n1. Identify the model format (.ifc or .rvt) and conversion method\n2. For IFC: use IfcOpenShell to parse elements and extract properties\n3. For Revit: use DDC RvtExporter to convert to structured data\n4. Extract key quantities: count, area (m2), volume (m3), length (m), weight (kg)\n5. Group results by category, type, level, and material\n6. Export to Excel or CSV with pivot-ready structure\n\nWhen the user asks to analyze QTO results:\n1. Summarize totals by element category (walls, floors, columns, etc.)\n2. Show material breakdown (concrete volume, steel weight, etc.)\n3. Compare quantities across building levels\n4. Flag elements with missing or zero quantities\n\n## Input Format\n- BIM model file path (.ifc or .rvt)\n- Optional: specific element categories to extract\n- Optional: grouping preferences (by level, category, material)\n\n## Output Format\n- QTO table: category, type, level, count, area, volume, length, material\n- Summary by category with totals\n- Material summary (total concrete, steel, etc.)\n- Excel export with multiple sheets (Summary, Detail, By Level, By Material)\n\n## Supported Properties\n| Property | Unit | Source |\n|----------|------|--------|\n| Count | pcs | Element instances |\n| Area | m2 | Surface/floor area |\n| Volume | m3 | Solid geometry |\n| Length | m | Linear elements |\n| Weight | kg | Material density x volume |\n| Perimeter | m | Floor/wall perimeter |\n\n## Constraints\n- Filesystem permission required for reading BIM files and writing exports\n- IFC parsing uses IfcOpenShell (Python library, no external services)\n- Revit conversion uses DDC RvtExporter CLI tool via subprocess\n- Always validate that extracted quantities are non-negative\n- Flag elements with no geometry or zero quantities\n\nFile v2.0.0:claw.json\n\n{\n  \"name\": \"ifc-qto-extraction\",\n  \"version\": \"2.0.0\",\n  \"description\": \"Extract quantities from IFC/Revit models for quantity takeoff. Uses DDC converters to get element counts, areas, volumes, lengths with grouping and reporting.\",\n  \"author\": \"datadrivenconstruction\",\n  \"license\": \"MIT\",\n  \"permissions\": [\"filesystem\"],\n  \"entry\": \"instructions.md\",\n  \"tags\": [\"construction\", \"IFC\", \"BIM\", \"quantity-takeoff\", \"QTO\", \"Revit\", \"estimation\"],\n  \"models\": [\"claude-*\", \"gpt-*\"],\n  \"minOpenClawVersion\": \"0.8.0\"\n}\n\nArchive v1.0.0: 2 files, 6195 bytes\n\nFiles: SKILL.md (21002b), _meta.json (137b)\n\nFile v1.0.0:SKILL.md\n\n---\r\nslug: \"ifc-qto-extraction\"\r\ndisplay_name: \"IFC QTO Extraction\"\r\ndescription: \"Extract quantities from IFC/Revit models for quantity takeoff. Uses DDC converters to get element counts, areas, volumes, lengths with grouping and reporting.\"\r\n---\r\n\r\n# IFC Quantity Takeoff Extraction\r\n\r\nExtract structured quantity data from BIM models (IFC, Revit) for cost estimation, material ordering, and progress tracking.\r\n\r\n## Business Case\r\n\r\n**Problem**: Manual quantity takeoff is:\r\n- Time-consuming (40-80 hours for medium project)\r\n- Error-prone (human counting mistakes)\r\n- Not repeatable (changes require full rework)\r\n- Disconnected from design (no live updates)\r\n\r\n**Solution**: Automated QTO from BIM that:\r\n- Extracts all quantities in minutes\r\n- Groups by type, level, zone\r\n- Updates instantly with model changes\r\n- Exports to Excel for pricing\r\n\r\n**ROI**: 90% reduction in QTO time, near-zero counting errors\r\n\r\n## DDC Tools Used\r\n\r\n```\r\n┌──────────────────────────────────────────────────────────────────────┐\r\n│                      QTO EXTRACTION PIPELINE                          │\r\n├──────────────────────────────────────────────────────────────────────┤\r\n│                                                                       │\r\n│   INPUT                 CONVERT                 ANALYZE               │\r\n│   ┌─────────┐          ┌─────────┐            ┌─────────┐            │\r\n│   │ .rvt    │          │ DDC     │            │ Python  │            │\r\n│   │ .ifc    │─────────►│Converter│───────────►│ pandas  │            │\r\n│   │ .dwg    │          │         │            │         │            │\r\n│   └─────────┘          └─────────┘            └─────────┘            │\r\n│                              │                      │                 │\r\n│                              ▼                      ▼                 │\r\n│                        ┌─────────┐            ┌─────────┐            │\r\n│                        │ .xlsx   │            │ Grouped │            │\r\n│                        │ raw data│            │ QTO     │            │\r\n│                        └─────────┘            └─────────┘            │\r\n│                                                    │                  │\r\n│   OUTPUT                                           ▼                  │\r\n│   ┌─────────────────────────────────────────────────────────────┐   │\r\n│   │  QTO Report                                                  │   │\r\n│   │  • Element counts by type                                    │   │\r\n│   │  • Areas (m², ft²)                                           │   │\r\n│   │  • Volumes (m³, ft³)                                         │   │\r\n│   │  • Lengths (m, ft)                                           │   │\r\n│   │  • Weights (kg, tons)                                        │   │\r\n│   │  • Grouped by level/zone/system                              │   │\r\n│   └─────────────────────────────────────────────────────────────┘   │\r\n│                                                                       │\r\n└──────────────────────────────────────────────────────────────────────┘\r\n```\r\n\r\n## CLI Commands\r\n\r\n### Revit to Excel (with BBox for volumes)\r\n\r\n```bash\r\n# Basic extraction\r\nRvtExporter.exe \"C:\\Models\\Building.rvt\"\r\n\r\n# Full extraction with bounding boxes (for volume calculations)\r\nRvtExporter.exe \"C:\\Models\\Building.rvt\" complete bbox\r\n\r\n# Include schedules (Revit's built-in QTO)\r\nRvtExporter.exe \"C:\\Models\\Building.rvt\" complete bbox schedule\r\n```\r\n\r\n### IFC to Excel\r\n\r\n```bash\r\n# Extract IFC data\r\nIfcExporter.exe \"C:\\Models\\Building.ifc\"\r\n\r\n# Output: Building.xlsx with all IFC entities\r\n```\r\n\r\n### DWG to Excel (2D areas)\r\n\r\n```bash\r\n# Extract DWG blocks and areas\r\nDwgExporter.exe \"C:\\Drawings\\FloorPlan.dwg\"\r\n```\r\n\r\n## Python Implementation\r\n\r\n```python\r\nimport pandas as pd\r\nimport numpy as np\r\nfrom pathlib import Path\r\nimport subprocess\r\nfrom typing import List, Dict, Optional\r\nfrom dataclasses import dataclass\r\n\r\n@dataclass\r\nclass QuantityItem:\r\n    \"\"\"Single quantity line item\"\"\"\r\n    category: str\r\n    type_name: str\r\n    count: int\r\n    area: float = 0.0\r\n    volume: float = 0.0\r\n    length: float = 0.0\r\n    weight: float = 0.0\r\n    unit_area: str = \"m²\"\r\n    unit_volume: str = \"m³\"\r\n    unit_length: str = \"m\"\r\n    level: str = \"\"\r\n    zone: str = \"\"\r\n\r\n\r\nclass BIMQuantityExtractor:\r\n    \"\"\"Extract quantities from BIM models using DDC converters\"\"\"\r\n\r\n    def __init__(self, converter_path: str):\r\n        self.converter_path = Path(converter_path)\r\n\r\n    def convert_model(self, model_path: str, options: List[str] = None) -> Path:\r\n        \"\"\"Convert BIM model to Excel\"\"\"\r\n\r\n        model = Path(model_path)\r\n        options = options or [\"complete\", \"bbox\"]\r\n\r\n        # Determine converter\r\n        ext = model.suffix.lower()\r\n        converters = {\r\n            '.rvt': 'RvtExporter.exe',\r\n            '.rfa': 'RvtExporter.exe',\r\n            '.ifc': 'IfcExporter.exe',\r\n            '.dwg': 'DwgExporter.exe',\r\n            '.dgn': 'DgnExporter.exe'\r\n        }\r\n\r\n        converter = self.converter_path / converters.get(ext, 'RvtExporter.exe')\r\n\r\n        # Build command\r\n        cmd = [str(converter), str(model)] + options\r\n\r\n        # Execute\r\n        result = subprocess.run(cmd, capture_output=True, text=True)\r\n\r\n        if result.returncode != 0:\r\n            raise RuntimeError(f\"Conversion failed: {result.stderr}\")\r\n\r\n        # Return path to generated Excel\r\n        xlsx_path = model.with_suffix('.xlsx')\r\n        return xlsx_path\r\n\r\n    def load_bim_data(self, xlsx_path: str) -> pd.DataFrame:\r\n        \"\"\"Load converted BIM data from Excel\"\"\"\r\n\r\n        xlsx = Path(xlsx_path)\r\n        if not xlsx.exists():\r\n            raise FileNotFoundError(f\"Excel file not found: {xlsx}\")\r\n\r\n        # Read main data sheet\r\n        df = pd.read_excel(xlsx, sheet_name=0)\r\n\r\n        # Clean column names\r\n        df.columns = df.columns.str.strip()\r\n\r\n        return df\r\n\r\n    def extract_quantities(\r\n        self,\r\n        df: pd.DataFrame,\r\n        group_by: str = \"Type Name\",\r\n        include_categories: List[str] = None\r\n    ) -> List[QuantityItem]:\r\n        \"\"\"Extract quantities grouped by type\"\"\"\r\n\r\n        # Filter categories if specified\r\n        if include_categories and 'Category' in df.columns:\r\n            df = df[df['Category'].isin(include_categories)]\r\n\r\n        # Group and aggregate\r\n        quantities = []\r\n\r\n        for (category, type_name), group in df.groupby(['Category', group_by]):\r\n            item = QuantityItem(\r\n                category=str(category),\r\n                type_name=str(type_name),\r\n                count=len(group)\r\n            )\r\n\r\n            # Extract area\r\n            area_cols = ['Area', 'Surface Area', 'Gross Area', 'Net Area']\r\n            for col in area_cols:\r\n                if col in group.columns:\r\n                    item.area = group[col].sum()\r\n                    break\r\n\r\n            # Extract volume\r\n            vol_cols = ['Volume', 'Gross Volume', 'Net Volume']\r\n            for col in vol_cols:\r\n                if col in group.columns:\r\n                    item.volume = group[col].sum()\r\n                    break\r\n\r\n            # Extract length\r\n            len_cols = ['Length', 'Curve Length', 'Unconnected Height']\r\n            for col in len_cols:\r\n                if col in group.columns:\r\n                    item.length = group[col].sum()\r\n                    break\r\n\r\n            # Extract level if available\r\n            if 'Level' in group.columns:\r\n                levels = group['Level'].dropna().unique()\r\n                item.level = ', '.join(str(l) for l in levels)\r\n\r\n            quantities.append(item)\r\n\r\n        return quantities\r\n\r\n    def extract_by_level(\r\n        self,\r\n        df: pd.DataFrame,\r\n        group_by: str = \"Type Name\"\r\n    ) -> Dict[str, List[QuantityItem]]:\r\n        \"\"\"Extract quantities grouped by level\"\"\"\r\n\r\n        result = {}\r\n\r\n        if 'Level' not in df.columns:\r\n            result['All Levels'] = self.extract_quantities(df, group_by)\r\n            return result\r\n\r\n        for level, level_df in df.groupby('Level'):\r\n            level_name = str(level) if pd.notna(level) else 'Unassigned'\r\n            result[level_name] = self.extract_quantities(level_df, group_by)\r\n\r\n        return result\r\n\r\n    def calculate_concrete_quantities(self, df: pd.DataFrame) -> dict:\r\n        \"\"\"Calculate concrete quantities for typical elements\"\"\"\r\n\r\n        concrete_categories = [\r\n            'Floors', 'Structural Floors',\r\n            'Walls', 'Structural Walls',\r\n            'Structural Foundations', 'Foundation',\r\n            'Structural Columns', 'Columns',\r\n            'Structural Framing', 'Beams'\r\n        ]\r\n\r\n        concrete_df = df[df['Category'].isin(concrete_categories)]\r\n\r\n        return {\r\n            'total_volume_m3': concrete_df['Volume'].sum() if 'Volume' in concrete_df.columns else 0,\r\n            'by_category': concrete_df.groupby('Category')['Volume'].sum().to_dict() if 'Volume' in concrete_df.columns else {},\r\n            'element_count': len(concrete_df)\r\n        }\r\n\r\n    def calculate_wall_quantities(self, df: pd.DataFrame) -> dict:\r\n        \"\"\"Calculate wall quantities\"\"\"\r\n\r\n        wall_categories = ['Walls', 'Basic Wall', 'Curtain Wall']\r\n        walls = df[df['Category'].isin(wall_categories)]\r\n\r\n        result = {\r\n            'total_area_m2': 0,\r\n            'total_length_m': 0,\r\n            'by_type': {}\r\n        }\r\n\r\n        if 'Area' in walls.columns:\r\n            result['total_area_m2'] = walls['Area'].sum()\r\n\r\n        if 'Length' in walls.columns:\r\n            result['total_length_m'] = walls['Length'].sum()\r\n\r\n        if 'Type Name' in walls.columns:\r\n            for type_name, group in walls.groupby('Type Name'):\r\n                result['by_type'][type_name] = {\r\n                    'count': len(group),\r\n                    'area': group['Area'].sum() if 'Area' in group.columns else 0,\r\n                    'length': group['Length'].sum() if 'Length' in group.columns else 0\r\n                }\r\n\r\n        return result\r\n\r\n    def generate_qto_report(\r\n        self,\r\n        quantities: List[QuantityItem],\r\n        output_path: str,\r\n        project_name: str = \"Project\"\r\n    ) -> str:\r\n        \"\"\"Generate QTO Excel report\"\"\"\r\n\r\n        # Convert to DataFrame\r\n        records = []\r\n        for q in quantities:\r\n            records.append({\r\n                'Category': q.category,\r\n                'Type': q.type_name,\r\n                'Count': q.count,\r\n                'Area (m²)': round(q.area, 2),\r\n                'Volume (m³)': round(q.volume, 3),\r\n                'Length (m)': round(q.length, 2),\r\n                'Level': q.level\r\n            })\r\n\r\n        df = pd.DataFrame(records)\r\n\r\n        # Sort by category and type\r\n        df = df.sort_values(['Category', 'Type'])\r\n\r\n        # Write to Excel with formatting\r\n        with pd.ExcelWriter(output_path, engine='openpyxl') as writer:\r\n            # Summary sheet\r\n            summary = df.groupby('Category').agg({\r\n                'Count': 'sum',\r\n                'Area (m²)': 'sum',\r\n                'Volume (m³)': 'sum',\r\n                'Length (m)': 'sum'\r\n            }).round(2)\r\n            summary.to_excel(writer, sheet_name='Summary')\r\n\r\n            # Detail sheet\r\n            df.to_excel(writer, sheet_name='Detail', index=False)\r\n\r\n            # By Level sheet\r\n            if 'Level' in df.columns and df['Level'].notna().any():\r\n                level_summary = df.groupby(['Level', 'Category']).agg({\r\n                    'Count': 'sum',\r\n                    'Area (m²)': 'sum',\r\n                    'Volume (m³)': 'sum'\r\n                }).round(2)\r\n                level_summary.to_excel(writer, sheet_name='By Level')\r\n\r\n        return output_path\r\n\r\n    def generate_html_report(\r\n        self,\r\n        quantities: List[QuantityItem],\r\n        output_path: str,\r\n        project_name: str = \"Project\"\r\n    ) -> str:\r\n        \"\"\"Generate interactive HTML QTO report\"\"\"\r\n\r\n        # Group by category\r\n        by_category = {}\r\n        for q in quantities:\r\n            if q.category not in by_category:\r\n                by_category[q.category] = []\r\n            by_category[q.category].append(q)\r\n\r\n        # Calculate totals\r\n        total_count = sum(q.count for q in quantities)\r\n        total_area = sum(q.area for q in quantities)\r\n        total_volume = sum(q.volume for q in quantities)\r\n\r\n        html = f\"\"\"\r\n<!DOCTYPE html>\r\n<html>\r\n<head>\r\n    <title>QTO Report - {project_name}</title>\r\n    <style>\r\n        body {{ font-family: Arial, sans-serif; margin: 20px; }}\r\n        .header {{ background: #2c3e50; color: white; padding: 20px; margin-bottom: 20px; }}\r\n        .summary {{ display: flex; gap: 20px; margin-bottom: 20px; }}\r\n        .summary-card {{ background: #ecf0f1; padding: 15px; border-radius: 5px; flex: 1; }}\r\n        .summary-card h3 {{ margin: 0 0 10px 0; color: #7f8c8d; font-size: 14px; }}\r\n        .summary-card .value {{ font-size: 24px; font-weight: bold; color: #2c3e50; }}\r\n        table {{ width: 100%; border-collapse: collapse; margin-bottom: 20px; }}\r\n        th {{ background: #34495e; color: white; padding: 10px; text-align: left; }}\r\n        td {{ padding: 8px; border-bottom: 1px solid #ddd; }}\r\n        tr:hover {{ background: #f5f5f5; }}\r\n        .category-header {{ background: #3498db; color: white; font-weight: bold; }}\r\n        .number {{ text-align: right; }}\r\n    </style>\r\n</head>\r\n<body>\r\n    <div class=\"header\">\r\n        <h1>Quantity Takeoff Report</h1>\r\n        <p>Project: {project_name}</p>\r\n    </div>\r\n\r\n    <div class=\"summary\">\r\n        <div class=\"summary-card\">\r\n            <h3>Total Elements</h3>\r\n            <div class=\"value\">{total_count:,}</div>\r\n        </div>\r\n        <div class=\"summary-card\">\r\n            <h3>Total Area</h3>\r\n            <div class=\"value\">{total_area:,.2f} m²</div>\r\n        </div>\r\n        <div class=\"summary-card\">\r\n            <h3>Total Volume</h3>\r\n            <div class=\"value\">{total_volume:,.3f} m³</div>\r\n        </div>\r\n        <div class=\"summary-card\">\r\n            <h3>Categories</h3>\r\n            <div class=\"value\">{len(by_category)}</div>\r\n        </div>\r\n    </div>\r\n\r\n    <table>\r\n        <thead>\r\n            <tr>\r\n                <th>Category / Type</th>\r\n                <th class=\"number\">Count</th>\r\n                <th class=\"number\">Area (m²)</th>\r\n                <th class=\"number\">Volume (m³)</th>\r\n                <th class=\"number\">Length (m)</th>\r\n            </tr>\r\n        </thead>\r\n        <tbody>\r\n\"\"\"\r\n\r\n        for category, items in sorted(by_category.items()):\r\n            cat_count = sum(i.count for i in items)\r\n            cat_area = sum(i.area for i in items)\r\n            cat_volume = sum(i.volume for i in items)\r\n\r\n            html += f\"\"\"\r\n            <tr class=\"category-header\">\r\n                <td>{category}</td>\r\n                <td class=\"number\">{cat_count:,}</td>\r\n                <td class=\"number\">{cat_area:,.2f}</td>\r\n                <td class=\"number\">{cat_volume:,.3f}</td>\r\n                <td class=\"number\">-</td>\r\n            </tr>\r\n\"\"\"\r\n            for item in sorted(items, key=lambda x: x.type_name):\r\n                html += f\"\"\"\r\n            <tr>\r\n                <td>&nbsp;&nbsp;&nbsp;{item.type_name}</td>\r\n                <td class=\"number\">{item.count:,}</td>\r\n                <td class=\"number\">{item.area:,.2f}</td>\r\n                <td class=\"number\">{item.volume:,.3f}</td>\r\n                <td class=\"number\">{item.length:,.2f}</td>\r\n            </tr>\r\n\"\"\"\r\n\r\n        html += \"\"\"\r\n        </tbody>\r\n    </table>\r\n</body>\r\n</html>\r\n\"\"\"\r\n\r\n        with open(output_path, 'w', encoding='utf-8') as f:\r\n            f.write(html)\r\n\r\n        return output_path\r\n\r\n\r\n# Usage Example\r\ndef extract_qto_from_model(\r\n    model_path: str,\r\n    converter_path: str,\r\n    output_dir: str = None\r\n) -> dict:\r\n    \"\"\"Complete QTO extraction workflow\"\"\"\r\n\r\n    from datetime import datetime\r\n\r\n    extractor = BIMQuantityExtractor(converter_path)\r\n\r\n    # Convert model\r\n    print(f\"Converting: {model_path}\")\r\n    xlsx_path = extractor.convert_model(model_path, [\"complete\", \"bbox\"])\r\n\r\n    # Load data\r\n    print(f\"Loading data from: {xlsx_path}\")\r\n    df = extractor.load_bim_data(xlsx_path)\r\n\r\n    # Extract quantities\r\n    quantities = extractor.extract_quantities(df)\r\n\r\n    # Generate reports\r\n    output_dir = output_dir or Path(model_path).parent\r\n    timestamp = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\r\n\r\n    excel_path = Path(output_dir) / f\"QTO_{timestamp}.xlsx\"\r\n    html_path = Path(output_dir) / f\"QTO_{timestamp}.html\"\r\n\r\n    extractor.generate_qto_report(quantities, str(excel_path))\r\n    extractor.generate_html_report(quantities, str(html_path))\r\n\r\n    # Calculate specific quantities\r\n    concrete = extractor.calculate_concrete_quantities(df)\r\n    walls = extractor.calculate_wall_quantities(df)\r\n\r\n    return {\r\n        'excel_report': str(excel_path),\r\n        'html_report': str(html_path),\r\n        'summary': {\r\n            'total_elements': len(df),\r\n            'categories': df['Category'].nunique() if 'Category' in df.columns else 0,\r\n            'types': df['Type Name'].nunique() if 'Type Name' in df.columns else 0\r\n        },\r\n        'concrete': concrete,\r\n        'walls': walls\r\n    }\r\n\r\n\r\nif __name__ == \"__main__\":\r\n    result = extract_qto_from_model(\r\n        model_path=r\"C:\\Projects\\Building.rvt\",\r\n        converter_path=r\"C:\\DDC\\Converters\",\r\n        output_dir=r\"C:\\Projects\\QTO\"\r\n    )\r\n\r\n    print(f\"Excel: {result['excel_report']}\")\r\n    print(f\"HTML: {result['html_report']}\")\r\n    print(f\"Concrete Volume: {result['concrete']['total_volume_m3']:.2f} m³\")\r\n```\r\n\r\n## n8n Workflow Integration\r\n\r\n```yaml\r\nname: BIM QTO Extraction\r\ntrigger:\r\n  type: webhook\r\n  path: /qto-extract\r\n\r\nsteps:\r\n  - convert_model:\r\n      node: Execute Command\r\n      command: |\r\n        \"C:\\DDC\\RvtExporter.exe\" \"{{$json.model_path}}\" complete bbox schedule\r\n\r\n  - load_excel:\r\n      node: Spreadsheet File\r\n      operation: read\r\n      file: \"={{$json.model_path.replace('.rvt', '.xlsx')}}\"\r\n\r\n  - group_quantities:\r\n      node: Code\r\n      code: |\r\n        const grouped = {};\r\n        items.forEach(item => {\r\n          const type = item.json['Type Name'];\r\n          if (!grouped[type]) {\r\n            grouped[type] = {\r\n              count: 0,\r\n              area: 0,\r\n              volume: 0\r\n            };\r\n          }\r\n          grouped[type].count++;\r\n          grouped[type].area += parseFloat(item.json['Area'] || 0);\r\n          grouped[type].volume += parseFloat(item.json['Volume'] || 0);\r\n        });\r\n        return Object.entries(grouped).map(([type, data]) => ({\r\n          type,\r\n          ...data\r\n        }));\r\n\r\n  - generate_report:\r\n      node: Code\r\n      code: |\r\n        // Generate HTML report\r\n        return generateHTMLReport(items);\r\n\r\n  - save_report:\r\n      node: Write Binary File\r\n      path: \"={{$json.output_path}}\"\r\n```\r\n\r\n## Best Practices\r\n\r\n1. **Model Quality**: Ensure BIM model has proper levels and types assigned\r\n2. **Units**: Verify model units match expected output units\r\n3. **Categories**: Use consistent category naming for grouping\r\n4. **Updates**: Re-run QTO after design changes\r\n5. **Validation**: Cross-check totals against manual spot checks\r\n\r\n## Common Quantity Formulas\r\n\r\n```python\r\n# Concrete formwork area (approximate)\r\nformwork_area = concrete_volume * 6  # m² per m³ of concrete\r\n\r\n# Rebar quantity (approximate)\r\nrebar_weight = concrete_volume * 100  # kg per m³ (typical)\r\n\r\n# Paint area from wall area\r\npaint_area = wall_area * 2  # both sides\r\n\r\n# Ceiling area from floor area\r\nceiling_area = floor_area * 0.95  # typical ratio\r\n```\r\n\r\n---\r\n\r\n*\"Measure twice, cut once. Or better yet, measure automatically from the model.\"*\n\nFile v1.0.0:_meta.json\n\n{\n  \"ownerId\": \"kn75fhjxn1jz5xbgd9ggj0nrtd80q1dz\",\n  \"slug\": \"ifc-qto-extraction\",\n  \"version\": \"1.0.0\",\n  \"publishedAt\": 1770474787229\n}","readmeExcerpt":"Skill: Ifc Qto Extraction Owner: datadrivenconstruction Summary: Extract quantities from IFC/Revit models for quantity takeoff. Uses DDC converters to get element counts, areas, volumes, lengths with grouping and reporting. Tags: latest:2.0.0 Version history: v2.0.0 | 2026-02-13T16:07:11.073Z | auto Major update: Adds structured quantity extraction from IFC/Revit models, grouped reporting, and Python API. - Automated","codeSnippets":[],"executableExamples":[],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\r\nname: \"ifc-qto-extraction\"\r\ndescription: \"Extract quantities from IFC/Revit models for quantity takeoff. Uses DDC converters to get element counts, areas, volumes, lengths with grouping and reporting.\"\r\n---\r\n\r\n# IFC Quantity Takeoff Extraction\r\n\r\nExtract structured quantity data from BIM models (IFC, Revit) for cost estimation, material ordering, and progress tracking.\r\n\r\n## Business Case\r\n\r\n**Problem**: Manual quantity takeoff is:\r\n- Time-consuming (40-80 hours for medium project)\r\n- Error-prone (human counting mistakes)\r\n- Not repeatable (changes require full rework)\r\n- Disconnected from design (no live updates)\r\n\r\n**Solution**: Automated QTO from BIM that:\r\n- Extracts all quantities in minutes\r\n- Groups by type, level, zone\r\n- Updates instantly with model changes\r\n- Exports to Excel for pricing\r\n\r\n**ROI**: 90% reduction in QTO time, near-zero counting errors\r\n\r\n## DDC Tools Used\r\n\r\n```\r\n┌──────────────────────────────────────────────────────────────────────┐\r\n│                      QTO EXTRACTION PIPELINE                          │\r\n├──────────────────────────────────────────────────────────────────────┤\r\n│                                                                       │\r\n│   INPUT                 CONVERT                 ANALYZE               │\r\n│   ┌─────────┐          ┌─────────┐            ┌─────────┐            │\r\n│   │ .rvt    │          │ DDC     │            │ Python  │            │\r\n│   │ .ifc    │─────────►│Converter│───────────►│ pandas  │            │\r\n│   │ .dwg    │          │         │            │         │            │\r\n│   └─────────┘          └─────────┘            └─────────┘            │\r\n│                              │                      │                 │\r\n│                              ▼                      ▼                 │\r\n│                        ┌─────────┐            ┌─────────┐            │\r\n│                        │ .xlsx   │            │ Grouped │            │\r\n│                        │ raw data│            │ QTO     │            │\r\n│                        └─────────┘            └─────────┘            │\r\n│                                                    │                  │\r\n│   OUTPUT                                           ▼                  │\r\n│   ┌─────────────────────────────────────────────────────────────┐   │\r\n│   │  QTO Report                                                  │   │\r\n│   │  • Element counts by type                                    │   │\r\n│   │  • Areas (m², ft²)                                           │   │\r\n│   │  • Volumes (m³, ft³)                                         │   │\r\n│   │  • Lengths (m, ft)                                           │   │\r\n│   │  • Weights (kg, tons)                                        │   │\r\n│   │  • Grouped by level/zone/system                              │   │\r\n│   └─────────────────────────────────────────────────────────────┘   │\r\n│                                                                       │\r\n└─────────────"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn75fhjxn1jz5xbgd9ggj0nrtd80q1dz\",\n  \"slug\": \"ifc-qto-extraction\",\n  \"version\": \"2.0.0\",\n  \"publishedAt\": 1770998831073\n}"},{"path":"instructions.md","content":"You are a BIM quantity takeoff assistant. You extract structured quantity data from BIM models (IFC, Revit) for cost estimation, material ordering, and progress tracking.\n\nWhen the user asks to extract quantities from a BIM model:\n1. Identify the model format (.ifc or .rvt) and conversion method\n2. For IFC: use IfcOpenShell to parse elements and extract properties\n3. For Revit: use DDC RvtExporter to convert to structured data\n4. Extract key quantities: count, area (m2), volume (m3), length (m), weight (kg)\n5. Group results by category, type, level, and material\n6. Export to Excel or CSV with pivot-ready structure\n\nWhen the user asks to analyze QTO results:\n1. Summarize totals by element category (walls, floors, columns, etc.)\n2. Show material breakdown (concrete volume, steel weight, etc.)\n3. Compare quantities across building levels\n4. Flag elements with missing or zero quantities\n\n## Input Format\n- BIM model file path (.ifc or .rvt)\n- Optional: specific element categories to extract\n- Optional: grouping preferences (by level, category, material)\n\n## Output Format\n- QTO table: category, type, level, count, area, volume, length, material\n- Summary by category with totals\n- Material summary (total concrete, steel, etc.)\n- Excel export with multiple sheets (Summary, Detail, By Level, By Material)\n\n## Supported Properties\n| Property | Unit | Source |\n|----------|------|--------|\n| Count | pcs | Element instances |\n| Area | m2 | Surface/floor area |\n| Volume | m3 | Solid geometry |\n| Length | m | Linear elements |\n| Weight | kg | Material density x volume |\n| Perimeter | m | Floor/wall perimeter |\n\n## Constraints\n- Filesystem permission required for reading BIM files and writing exports\n- IFC parsing uses IfcOpenShell (Python library, no external services)\n- Revit conversion uses DDC RvtExporter CLI tool via subprocess\n- Always validate that extracted quantities are non-negative\n- Flag elements with no geometry or zero quantities"},{"path":"claw.json","content":"{\n  \"name\": \"ifc-qto-extraction\",\n  \"version\": \"2.0.0\",\n  \"description\": \"Extract quantities from IFC/Revit models for quantity takeoff. Uses DDC converters to get element counts, areas, volumes, lengths with grouping and reporting.\",\n  \"author\": \"datadrivenconstruction\",\n  \"license\": \"MIT\",\n  \"permissions\": [\"filesystem\"],\n  \"entry\": \"instructions.md\",\n  \"tags\": [\"construction\", \"IFC\", \"BIM\", \"quantity-takeoff\", \"QTO\", \"Revit\", \"estimation\"],\n  \"models\": [\"claude-*\", \"gpt-*\"],\n  \"minOpenClawVersion\": \"0.8.0\"\n}"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"Extract quantities from IFC/Revit models for quantity takeoff. Uses DDC converters to get element counts, areas, volumes, lengths with grouping and reporting. Skill: Ifc Qto Extraction Owner: datadrivenconstruction Summary: Extract quantities from IFC/Revit models for quantity takeoff. Uses DDC converters to get element counts, areas, volumes, lengths with grouping and reporting. Tags: latest:2.0.0 Version history: v2.0.0 | 2026-02-13T16:07:11.073Z | auto Major update: Adds structured quantity extraction from IFC/Revit models, grouped reporting, and Python API. - Automated","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1010,"uniquenessScore":48,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-11T01:46:49.486Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-11T01:46:49.486Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-11T03:55:35.682Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}