[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6FJYAM8RZQAS3FHS48W5GDG":3,"skill-same:01M6FJYAM8RZQAS3FHS48W5GDG":60},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":27,"linkOnly":28,"markdownContent":29,"markdownBody":30,"frontMatter":31,"refName":33,"commitSha":34,"skillDirectory":35,"forksCount":36,"repositoryHtmlUrl":37,"repositoryHomepageUrl":6,"ownerHtmlUrl":38,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":39,"install":40,"resources":53},"01M6FJYAM8RZQAS3FHS48W5GDG","ifc-qto-extraction",null,"Extract quantities from IFC\u002FRevit models for quantity takeoff. Uses DDC converters to get element counts, areas, volumes, lengths with grouping and reporting.","kbarbel640-del\u002Fskills","kbarbel640-del","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F255315490?v=4","01M6CJPPQGCCESXMG3K7BBGQ5J","01M6CJPPX2PB2BCZERFVM2C2B3",1,"MIT","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdatadrivenconstruction\u002Fifc-qto-extraction\u002FSKILL.md",[],[18,19,20,21,22,23,24,25,26],"extraction","ifc qto extraction","reporting","extract","models","grouping","get","element","with","2026-11-10 10:12:30",false,"---\r\nslug: \"ifc-qto-extraction\"\r\ndisplay_name: \"IFC QTO Extraction\"\r\ndescription: \"Extract quantities from IFC\u002FRevit 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\u002Fzone\u002Fsystem                              │   │\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 \u002F 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\u003C!DOCTYPE html>\r\n\u003Chtml>\r\n\u003Chead>\r\n    \u003Ctitle>QTO Report - {project_name}\u003C\u002Ftitle>\r\n    \u003Cstyle>\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    \u003C\u002Fstyle>\r\n\u003C\u002Fhead>\r\n\u003Cbody>\r\n    \u003Cdiv class=\"header\">\r\n        \u003Ch1>Quantity Takeoff Report\u003C\u002Fh1>\r\n        \u003Cp>Project: {project_name}\u003C\u002Fp>\r\n    \u003C\u002Fdiv>\r\n\r\n    \u003Cdiv class=\"summary\">\r\n        \u003Cdiv class=\"summary-card\">\r\n            \u003Ch3>Total Elements\u003C\u002Fh3>\r\n            \u003Cdiv class=\"value\">{total_count:,}\u003C\u002Fdiv>\r\n        \u003C\u002Fdiv>\r\n        \u003Cdiv class=\"summary-card\">\r\n            \u003Ch3>Total Area\u003C\u002Fh3>\r\n            \u003Cdiv class=\"value\">{total_area:,.2f} m²\u003C\u002Fdiv>\r\n        \u003C\u002Fdiv>\r\n        \u003Cdiv class=\"summary-card\">\r\n            \u003Ch3>Total Volume\u003C\u002Fh3>\r\n            \u003Cdiv class=\"value\">{total_volume:,.3f} m³\u003C\u002Fdiv>\r\n        \u003C\u002Fdiv>\r\n        \u003Cdiv class=\"summary-card\">\r\n            \u003Ch3>Categories\u003C\u002Fh3>\r\n            \u003Cdiv class=\"value\">{len(by_category)}\u003C\u002Fdiv>\r\n        \u003C\u002Fdiv>\r\n    \u003C\u002Fdiv>\r\n\r\n    \u003Ctable>\r\n        \u003Cthead>\r\n            \u003Ctr>\r\n                \u003Cth>Category \u002F Type\u003C\u002Fth>\r\n                \u003Cth class=\"number\">Count\u003C\u002Fth>\r\n                \u003Cth class=\"number\">Area (m²)\u003C\u002Fth>\r\n                \u003Cth class=\"number\">Volume (m³)\u003C\u002Fth>\r\n                \u003Cth class=\"number\">Length (m)\u003C\u002Fth>\r\n            \u003C\u002Ftr>\r\n        \u003C\u002Fthead>\r\n        \u003Ctbody>\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            \u003Ctr class=\"category-header\">\r\n                \u003Ctd>{category}\u003C\u002Ftd>\r\n                \u003Ctd class=\"number\">{cat_count:,}\u003C\u002Ftd>\r\n                \u003Ctd class=\"number\">{cat_area:,.2f}\u003C\u002Ftd>\r\n                \u003Ctd class=\"number\">{cat_volume:,.3f}\u003C\u002Ftd>\r\n                \u003Ctd class=\"number\">-\u003C\u002Ftd>\r\n            \u003C\u002Ftr>\r\n\"\"\"\r\n            for item in sorted(items, key=lambda x: x.type_name):\r\n                html += f\"\"\"\r\n            \u003Ctr>\r\n                \u003Ctd>&nbsp;&nbsp;&nbsp;{item.type_name}\u003C\u002Ftd>\r\n                \u003Ctd class=\"number\">{item.count:,}\u003C\u002Ftd>\r\n                \u003Ctd class=\"number\">{item.area:,.2f}\u003C\u002Ftd>\r\n                \u003Ctd class=\"number\">{item.volume:,.3f}\u003C\u002Ftd>\r\n                \u003Ctd class=\"number\">{item.length:,.2f}\u003C\u002Ftd>\r\n            \u003C\u002Ftr>\r\n\"\"\"\r\n\r\n        html += \"\"\"\r\n        \u003C\u002Ftbody>\r\n    \u003C\u002Ftable>\r\n\u003C\u002Fbody>\r\n\u003C\u002Fhtml>\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) \u002F f\"QTO_{timestamp}.xlsx\"\r\n    html_path = Path(output_dir) \u002F 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: \u002Fqto-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        \u002F\u002F 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.\"*\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\u002Fzone\u002Fsystem                              │   │\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 \u002F 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\u003C!DOCTYPE html>\r\n\u003Chtml>\r\n\u003Chead>\r\n    \u003Ctitle>QTO Report - {project_name}\u003C\u002Ftitle>\r\n    \u003Cstyle>\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    \u003C\u002Fstyle>\r\n\u003C\u002Fhead>\r\n\u003Cbody>\r\n    \u003Cdiv class=\"header\">\r\n        \u003Ch1>Quantity Takeoff Report\u003C\u002Fh1>\r\n        \u003Cp>Project: {project_name}\u003C\u002Fp>\r\n    \u003C\u002Fdiv>\r\n\r\n    \u003Cdiv class=\"summary\">\r\n        \u003Cdiv class=\"summary-card\">\r\n            \u003Ch3>Total Elements\u003C\u002Fh3>\r\n            \u003Cdiv class=\"value\">{total_count:,}\u003C\u002Fdiv>\r\n        \u003C\u002Fdiv>\r\n        \u003Cdiv class=\"summary-card\">\r\n            \u003Ch3>Total Area\u003C\u002Fh3>\r\n            \u003Cdiv class=\"value\">{total_area:,.2f} m²\u003C\u002Fdiv>\r\n        \u003C\u002Fdiv>\r\n        \u003Cdiv class=\"summary-card\">\r\n            \u003Ch3>Total Volume\u003C\u002Fh3>\r\n            \u003Cdiv class=\"value\">{total_volume:,.3f} m³\u003C\u002Fdiv>\r\n        \u003C\u002Fdiv>\r\n        \u003Cdiv class=\"summary-card\">\r\n            \u003Ch3>Categories\u003C\u002Fh3>\r\n            \u003Cdiv class=\"value\">{len(by_category)}\u003C\u002Fdiv>\r\n        \u003C\u002Fdiv>\r\n    \u003C\u002Fdiv>\r\n\r\n    \u003Ctable>\r\n        \u003Cthead>\r\n            \u003Ctr>\r\n                \u003Cth>Category \u002F Type\u003C\u002Fth>\r\n                \u003Cth class=\"number\">Count\u003C\u002Fth>\r\n                \u003Cth class=\"number\">Area (m²)\u003C\u002Fth>\r\n                \u003Cth class=\"number\">Volume (m³)\u003C\u002Fth>\r\n                \u003Cth class=\"number\">Length (m)\u003C\u002Fth>\r\n            \u003C\u002Ftr>\r\n        \u003C\u002Fthead>\r\n        \u003Ctbody>\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            \u003Ctr class=\"category-header\">\r\n                \u003Ctd>{category}\u003C\u002Ftd>\r\n                \u003Ctd class=\"number\">{cat_count:,}\u003C\u002Ftd>\r\n                \u003Ctd class=\"number\">{cat_area:,.2f}\u003C\u002Ftd>\r\n                \u003Ctd class=\"number\">{cat_volume:,.3f}\u003C\u002Ftd>\r\n                \u003Ctd class=\"number\">-\u003C\u002Ftd>\r\n            \u003C\u002Ftr>\r\n\"\"\"\r\n            for item in sorted(items, key=lambda x: x.type_name):\r\n                html += f\"\"\"\r\n            \u003Ctr>\r\n                \u003Ctd>&nbsp;&nbsp;&nbsp;{item.type_name}\u003C\u002Ftd>\r\n                \u003Ctd class=\"number\">{item.count:,}\u003C\u002Ftd>\r\n                \u003Ctd class=\"number\">{item.area:,.2f}\u003C\u002Ftd>\r\n                \u003Ctd class=\"number\">{item.volume:,.3f}\u003C\u002Ftd>\r\n                \u003Ctd class=\"number\">{item.length:,.2f}\u003C\u002Ftd>\r\n            \u003C\u002Ftr>\r\n\"\"\"\r\n\r\n        html += \"\"\"\r\n        \u003C\u002Ftbody>\r\n    \u003C\u002Ftable>\r\n\u003C\u002Fbody>\r\n\u003C\u002Fhtml>\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) \u002F f\"QTO_{timestamp}.xlsx\"\r\n    html_path = Path(output_dir) \u002F 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: \u002Fqto-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        \u002F\u002F 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.\"*\r\n",{"slug":5,"description":7,"display_name":32},"IFC QTO Extraction","main","e7e336ac628c3e3bd771aff1aaa469c93778f94a","skills\u002Fdatadrivenconstruction\u002Fifc-qto-extraction",0,"https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del","2026-08-23 22:39:37",{"source":8,"skillName":5,"commands":41,"prompt":51,"zipAvailable":52},[42,45,48],{"tool":43,"command":44},"npx","npx skills add kbarbel640-del\u002Fskills --skill ifc-qto-extraction",{"tool":46,"command":47},"bunx","bunx skills add kbarbel640-del\u002Fskills --skill ifc-qto-extraction",{"tool":49,"command":50},"pnpm","pnpm dlx skills add kbarbel640-del\u002Fskills --skill ifc-qto-extraction","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdatadrivenconstruction\u002Fifc-qto-extraction\u002FSKILL.md",true,[54],{"path":55,"resourceType":56,"resourceTypeDesc":57,"sizeBytes":58,"sourceHtmlUrl":59},"_meta.json","REFERENCE","参考文档",304,"https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdatadrivenconstruction\u002Fifc-qto-extraction\u002F_meta.json",[61,91,120,141,147,166],{"skillNo":62,"name":63,"nameZh":6,"description":64,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":65,"categories":66,"keywords":72,"publishedAt":27},"01M6G8J55967D6281ACMKAKTEQ","crowd-prompting","A marketplace where AI agents improve prompts, system instructions, tool descriptions, and other text-based content with domain expertise from real-world operations — and earn tokens for valuable contributions.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzzadrian\u002Fcrowd-prompting\u002FSKILL.md",[67],{"categoryNo":68,"categoryName":69,"taxonomyType":70,"taxonomyTypeDesc":71},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",[73,74,75,26,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90],"crowd prompting","prompting","api","tokens","domain","world","prompts","operations","instructions","text","tool","system","improve","productivity","marketplace","https","content","ai agents",{"skillNo":92,"name":93,"nameZh":6,"description":94,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":95,"categories":96,"keywords":102,"publishedAt":27},"01M6G8J0PKPAYVET6BWE8W4C26","dilemm-ai","Compete in DilemmAI, the prisoner's dilemma AI arena at dilemm.ai. Use when an OpenClaw agent wants to create an account, design and submit strategy prompts for their bot, enter matchmaking, analyze historical matches and leaderboards, and iterate on bot strategy. Requires headless browser automation (no API available yet).","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzyori\u002Fdilemm-ai\u002FSKILL.md",[97],{"categoryNo":98,"categoryName":99,"taxonomyType":100,"taxonomyTypeDesc":101},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[103,75,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,79,119],"dilemm ai","arena","analyze","browser","openclaw agent","create","automation","browser automation","bot","openclaw","design","strategy","headless","compete","submit","matchmaking","use",{"skillNo":121,"name":122,"nameZh":6,"description":123,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":124,"categories":125,"keywords":129,"publishedAt":27},"01M6G8HWAF3K9SW7NP3Z83CPJ6","arxiv-paper-reviews","Interact with arXiv Crawler API to fetch papers, read reviews, and submit comments. Use when working with arXiv papers, fetching paper lists by date\u002Fcategory\u002Finterest, viewing paper details with comments, or submitting paper reviews via API at http:\u002F\u002F150.158.152.82:8000.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzxrys\u002Fweak-accept\u002FSKILL.md",[126],{"categoryNo":127,"categoryName":128,"taxonomyType":100,"taxonomyTypeDesc":101},"01JBSEEDCAT000000000000021","资料检索与研究",[130,131,132,133,134,135,136,26,137,117,119,138,75,139,140],"arxiv paper reviews","reviews","arxiv","paper","crawler","papers","comments","fetch","read","http","via",{"skillNo":142,"name":122,"nameZh":6,"description":123,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":143,"categories":144,"keywords":146,"publishedAt":27},"01M6G8HQJ7N7WMY6YV7D7SSJ16","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzxrys\u002Farxiv-paper-reviews\u002FSKILL.md",[145],{"categoryNo":127,"categoryName":128,"taxonomyType":100,"taxonomyTypeDesc":101},[133,132,131,130,140,135,136,26,134,117,119,137,138,75,139],{"skillNo":148,"name":149,"nameZh":6,"description":150,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":151,"categories":152,"keywords":153,"publishedAt":27},"01M6G8HM95Y64JNVFC2HN4E2BG","git-summary","Get a quick summary of the current Git repository including status, recent commits, branches, and contributors.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzweack\u002Fgit-summary\u002FSKILL.md",[],[154,155,156,157,158,159,160,161,162,163,164,24,165],"git","summary","git summary","commits","repository","win32","status","linux","contributors","true","quick","branches",{"skillNo":167,"name":168,"nameZh":6,"description":169,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":170,"categories":171,"keywords":176,"publishedAt":27},"01M6G8HGEGF05CNEJCGVYFSC4R","agent-constitution","Interact with AgentConstitution governance contracts on Base Sepolia. Check compliance, read rules, log actions, query governance state.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fztsalexey\u002Fagent-constitution\u002FSKILL.md",[172,173],{"categoryNo":98,"categoryName":99,"taxonomyType":100,"taxonomyTypeDesc":101},{"categoryNo":174,"categoryName":175,"taxonomyType":70,"taxonomyTypeDesc":71},"01JBSEEDCAT000000000000037","律师与法务",[177,178,179,180,26,181,182,33,183,138,184,185,186,187,188,88,189,190,191],"agent constitution","constitution","contracts","tree","query","base","log","check","Rules","projects","compliance","actions","state","governance","github"]