Spaces:
Sleeping
Sleeping
IFCore Deploy commited on
Commit Β·
51982d6
0
Parent(s):
deploy(prod): 2026-02-21T01:10:43Z
Browse filesThis view is limited to 50 files because it contains too many changes. Β See raw diff
- Dockerfile +13 -0
- README.md +10 -0
- deploy.sh +48 -0
- main.py +475 -0
- orchestrator.py +106 -0
- requirements.txt +6 -0
- teams/Mastodonte/.claude/skills/IFCore-skill/SKILL.md +229 -0
- teams/Mastodonte/.claude/skills/IFCore-skill/references/architecture.md +73 -0
- teams/Mastodonte/.claude/skills/IFCore-skill/references/development-patterns.md +84 -0
- teams/Mastodonte/.claude/skills/IFCore-skill/references/frontend-architecture.md +241 -0
- teams/Mastodonte/.claude/skills/IFCore-skill/references/repo-structure.md +53 -0
- teams/Mastodonte/.claude/skills/IFCore-skill/references/validation-schema.md +164 -0
- teams/Mastodonte/.gitignore +45 -0
- teams/Mastodonte/01_team_b +303 -0
- teams/Mastodonte/COMPLIANCE_AUDIT.md +246 -0
- teams/Mastodonte/Mastodontes_Tool_E.ipynb +372 -0
- teams/Mastodonte/Mastodontes_Tools.ipynb +857 -0
- teams/Mastodonte/USER_PORTAL_PRD.md +166 -0
- teams/Mastodonte/main.py +97 -0
- teams/Mastodonte/requirements.txt +1 -0
- teams/Mastodonte/tools/__init__.py +22 -0
- teams/Mastodonte/tools/checker_dwelling.py +101 -0
- teams/Mastodonte/tools/checker_heights.py +97 -0
- teams/Mastodonte/tools/checker_living_rooms.py +148 -0
- teams/Mastodonte/tools/checker_occupancy.py +173 -0
- teams/Mastodonte/tools/checker_service_spaces.py +109 -0
- teams/demo/tools/checker_demo.py +32 -0
- teams/lux-ai/AGENT_HANDOFF_REPORT.md +526 -0
- teams/lux-ai/API function/SOLAR_PRODUCTION_PIPELINE.md +356 -0
- teams/lux-ai/API function/run_solar_analysis.py +0 -0
- teams/lux-ai/API function/scan_ifc_models.py +427 -0
- teams/lux-ai/API function/solar_production_engine.py +211 -0
- teams/lux-ai/Final pipeline/README.md +352 -0
- teams/lux-ai/Final pipeline/__init__.py +15 -0
- teams/lux-ai/Final pipeline/analyze.py +306 -0
- teams/lux-ai/Final pipeline/config.py +50 -0
- teams/lux-ai/Final pipeline/ifc_metadata_extractor.py +442 -0
- teams/lux-ai/Final pipeline/ifc_roof_parser.py +359 -0
- teams/lux-ai/Final pipeline/key_aliases.json +3323 -0
- teams/lux-ai/Final pipeline/run_solar_analysis.py +325 -0
- teams/lux-ai/Final pipeline/solar_production_engine.py +167 -0
- teams/lux-ai/IFC key checker/discover_ifc_keys.py +387 -0
- teams/lux-ai/IFC key checker/ifc_key_inventory.json +0 -0
- teams/lux-ai/IFC key checker/key_aliases.json +3275 -0
- teams/lux-ai/Logs/ifc_scan.log +74 -0
- teams/lux-ai/Logs/ifc_scan_results.csv +37 -0
- teams/lux-ai/Logs/solar_pipeline.log +6 -0
- teams/lux-ai/Logs/solar_pipeline_scan.csv +37 -0
- teams/lux-ai/Lux ai tool/README.md +101 -0
- teams/lux-ai/Lux ai tool/final_pipeline/__init__.py +15 -0
Dockerfile
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
COPY requirements.txt .
|
| 5 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 6 |
+
|
| 7 |
+
COPY . .
|
| 8 |
+
|
| 9 |
+
RUN useradd -m -u 1000 user
|
| 10 |
+
USER user
|
| 11 |
+
|
| 12 |
+
EXPOSE 7860
|
| 13 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1"]
|
README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: IFCore Platform
|
| 3 |
+
emoji: "\U0001F3D7\uFE0F"
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
IFC compliance checker backend. Auto-discovers `check_*` functions from team modules.
|
deploy.sh
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
|
| 4 |
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
| 5 |
+
|
| 6 |
+
# --- Target selection (default: staging for safety) ---
|
| 7 |
+
TARGET="${1:-staging}"
|
| 8 |
+
case "$TARGET" in
|
| 9 |
+
staging) HF_REPO="${HF_REPO:-serJD/ifcore-platform-staging}" ;;
|
| 10 |
+
prod) HF_REPO="${HF_REPO:-serJD/ifcore-platform}" ;;
|
| 11 |
+
*) echo "Usage: deploy.sh [staging|prod]"; exit 1 ;;
|
| 12 |
+
esac
|
| 13 |
+
|
| 14 |
+
echo "Deploying to $TARGET -> https://huggingface.co/spaces/$HF_REPO"
|
| 15 |
+
|
| 16 |
+
cd "$SCRIPT_DIR"
|
| 17 |
+
|
| 18 |
+
git -C "$SCRIPT_DIR/.." submodule update --init --recursive --remote
|
| 19 |
+
|
| 20 |
+
TMPDIR=$(mktemp -d)
|
| 21 |
+
trap 'rm -rf "$TMPDIR"' EXIT
|
| 22 |
+
|
| 23 |
+
rsync -a --exclude='.git' --exclude='__pycache__' \
|
| 24 |
+
--exclude='*.pdf' --exclude='*.png' --exclude='*.jpg' --exclude='*.jpeg' \
|
| 25 |
+
--exclude='*.gif' --exclude='*.zip' --exclude='*.mp4' --exclude='*.mov' \
|
| 26 |
+
--exclude='*.ifc' --exclude='*.ifczip' --exclude='*.glb' --exclude='*.obj' \
|
| 27 |
+
. "$TMPDIR/"
|
| 28 |
+
|
| 29 |
+
find "$TMPDIR/teams" -name ".git" -type f -delete 2>/dev/null || true
|
| 30 |
+
find "$TMPDIR/teams" -name ".git" -type d -exec rm -rf {} + 2>/dev/null || true
|
| 31 |
+
|
| 32 |
+
cd "$TMPDIR"
|
| 33 |
+
|
| 34 |
+
git init -b main
|
| 35 |
+
git config user.email "deploy@ifcore"
|
| 36 |
+
git config user.name "IFCore Deploy"
|
| 37 |
+
git add .
|
| 38 |
+
git commit --no-gpg-sign -m "deploy($TARGET): $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
| 39 |
+
# Use token auth in CI (set HF_TOKEN env var or GitHub secret)
|
| 40 |
+
if [ -n "${HF_TOKEN:-}" ]; then
|
| 41 |
+
HF_REMOTE="https://serJD:${HF_TOKEN}@huggingface.co/spaces/$HF_REPO"
|
| 42 |
+
else
|
| 43 |
+
HF_REMOTE="https://huggingface.co/spaces/$HF_REPO"
|
| 44 |
+
fi
|
| 45 |
+
git remote add hf "$HF_REMOTE"
|
| 46 |
+
git push hf main --force
|
| 47 |
+
|
| 48 |
+
echo "Deployed to https://huggingface.co/spaces/$HF_REPO"
|
main.py
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import asyncio
|
| 3 |
+
import base64
|
| 4 |
+
import uuid
|
| 5 |
+
import logging
|
| 6 |
+
import tempfile
|
| 7 |
+
from contextlib import asynccontextmanager
|
| 8 |
+
from dataclasses import dataclass
|
| 9 |
+
from typing import Optional
|
| 10 |
+
import httpx
|
| 11 |
+
from fastapi import FastAPI, BackgroundTasks
|
| 12 |
+
from fastapi.responses import JSONResponse
|
| 13 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 14 |
+
from pydantic import BaseModel, Field
|
| 15 |
+
from pydantic_ai import Agent, RunContext
|
| 16 |
+
from pydantic_ai.usage import UsageLimits
|
| 17 |
+
from orchestrator import discover_checks, run_all_checks
|
| 18 |
+
|
| 19 |
+
logging.basicConfig(level=logging.INFO)
|
| 20 |
+
logger = logging.getLogger("ifcore")
|
| 21 |
+
|
| 22 |
+
# In-memory job store β CF Worker polls this
|
| 23 |
+
_jobs: dict = {}
|
| 24 |
+
|
| 25 |
+
# ---------------------------------------------------------------------------
|
| 26 |
+
# Regulation knowledge base β Spanish / Catalan building bye-laws
|
| 27 |
+
# Each entry contains the official regulation, article/section reference,
|
| 28 |
+
# PDF link, content reference, compliance threshold, and required action.
|
| 29 |
+
# ---------------------------------------------------------------------------
|
| 30 |
+
REGULATIONS_KB: dict[str, dict] = {
|
| 31 |
+
"walls": {
|
| 32 |
+
"regulation": "CTE DB SE-F β Seguridad Estructural: Cimientos",
|
| 33 |
+
"reference": "CTE DB SE-F, Section 4.1 (Muros); EHE-08, Art. 23",
|
| 34 |
+
"pdf": "https://www.codigotecnico.org/pdf/Documentos/SE/DBSEF.pdf",
|
| 35 |
+
"page_ref": "Section 4.1, p. 14 β Minimum wall thickness 100 mm",
|
| 36 |
+
"threshold": "Minimum wall thickness: β₯ 100 mm",
|
| 37 |
+
"action": (
|
| 38 |
+
"Increase wall thickness to β₯ 100 mm. For load-bearing walls, a qualified "
|
| 39 |
+
"structural engineer must verify revised stability calculations under CTE DB SE. "
|
| 40 |
+
"Update architectural and structural drawings accordingly."
|
| 41 |
+
),
|
| 42 |
+
},
|
| 43 |
+
"beams": {
|
| 44 |
+
"regulation": "EHE-08 β InstrucciΓ³n de HormigΓ³n Estructural",
|
| 45 |
+
"reference": "EHE-08, Art. 23 (Vigas) and Art. 42.3 (Dimensiones mΓnimas)",
|
| 46 |
+
"pdf": "https://www.mitma.gob.es/recursos_mfom/0820200.pdf",
|
| 47 |
+
"page_ref": "Art. 23.1, p. 62 β Minimum depth 200 mm; Art. 23.2 β Minimum width 150 mm",
|
| 48 |
+
"threshold": "Minimum beam depth: β₯ 200 mm; minimum beam width: β₯ 150 mm",
|
| 49 |
+
"action": (
|
| 50 |
+
"Redesign beam cross-section to achieve depth β₯ 200 mm and width β₯ 150 mm. "
|
| 51 |
+
"Recheck load and deflection calculations. Have a licensed structural engineer "
|
| 52 |
+
"verify and sign off the revised design. Update structural drawings."
|
| 53 |
+
),
|
| 54 |
+
},
|
| 55 |
+
"columns": {
|
| 56 |
+
"regulation": "EHE-08 β InstrucciΓ³n de HormigΓ³n Estructural",
|
| 57 |
+
"reference": "EHE-08, Art. 24 (Pilares) and Art. 42.3",
|
| 58 |
+
"pdf": "https://www.mitma.gob.es/recursos_mfom/0820200.pdf",
|
| 59 |
+
"page_ref": "Art. 24.1, p. 65 β Minimum column dimension 250 mm",
|
| 60 |
+
"threshold": "Minimum column dimension: β₯ 250 mm",
|
| 61 |
+
"action": (
|
| 62 |
+
"Increase the smaller column dimension to β₯ 250 mm. Re-evaluate reinforcement "
|
| 63 |
+
"ratios and load capacity. Update column schedule and structural calculations. "
|
| 64 |
+
"Coordinate changes with the foundation design."
|
| 65 |
+
),
|
| 66 |
+
},
|
| 67 |
+
"foundations": {
|
| 68 |
+
"regulation": "CTE DB SE-C β Seguridad Estructural: Cimientos; EHE-08",
|
| 69 |
+
"reference": "EHE-08, Art. 69 (Cimentaciones); CTE DB SE-C, Section 4.1",
|
| 70 |
+
"pdf": "https://www.codigotecnico.org/pdf/Documentos/SE/DBSEC.pdf",
|
| 71 |
+
"page_ref": "Art. 69.1 β Minimum foundation element depth 200 mm; DB SE-C Section 4.1, p. 18",
|
| 72 |
+
"threshold": "Minimum foundation depth: β₯ 200 mm",
|
| 73 |
+
"action": (
|
| 74 |
+
"Deepen or redesign foundation elements to β₯ 200 mm. If a geotechnical study "
|
| 75 |
+
"has not been done, commission one. Submit revised foundation drawings to the "
|
| 76 |
+
"project certifier. Ensure compliance with DB SE-C soil bearing capacity requirements."
|
| 77 |
+
),
|
| 78 |
+
},
|
| 79 |
+
"slabs": {
|
| 80 |
+
"regulation": "CTE DB HE β Ahorro de EnergΓa; EHE-08",
|
| 81 |
+
"reference": "CTE DB HE1, Table 2.3 (Transmitancias lΓmite); EHE-08, Art. 22",
|
| 82 |
+
"pdf": "https://www.codigotecnico.org/pdf/Documentos/HE/DBHE.pdf",
|
| 83 |
+
"page_ref": "HE1 Table 2.3, p. 11 β Slab thickness 150β200 mm; Art. 22 structural dimensions",
|
| 84 |
+
"threshold": "Slab thickness: 150β200 mm",
|
| 85 |
+
"action": (
|
| 86 |
+
"Adjust slab thickness to the 150β200 mm range. Verify structural load capacity "
|
| 87 |
+
"for the revised thickness. If thermal performance is affected, recalculate U-values "
|
| 88 |
+
"for the slab assembly using HULC or equivalent CTE tool."
|
| 89 |
+
),
|
| 90 |
+
},
|
| 91 |
+
"doors": {
|
| 92 |
+
"regulation": "CTE DB SUA β Seguridad de UtilizaciΓ³n y Accesibilidad",
|
| 93 |
+
"reference": "CTE DB SUA, SUA-9 (Accesibilidad), Section 1.1.1 and Table 2.1",
|
| 94 |
+
"pdf": "https://www.codigotecnico.org/pdf/Documentos/SUA/DBSUA.pdf",
|
| 95 |
+
"page_ref": "SUA-9 Section 1.1.1, p. 47 β Minimum door clear width 800 mm; Table 2.1, p. 49",
|
| 96 |
+
"threshold": "Minimum door clear width: β₯ 800 mm",
|
| 97 |
+
"action": (
|
| 98 |
+
"Replace or widen door frames to achieve β₯ 800 mm clear passage width. "
|
| 99 |
+
"For full wheelchair access, 900 mm is recommended. Update the door schedule "
|
| 100 |
+
"in architectural drawings. In Catalan projects, also verify Decreto 141/2012."
|
| 101 |
+
),
|
| 102 |
+
},
|
| 103 |
+
"windows": {
|
| 104 |
+
"regulation": "CTE DB SUA β Seguridad de UtilizaciΓ³n y Accesibilidad",
|
| 105 |
+
"reference": "CTE DB SUA, SUA-1, Section 2.1 (ProtecciΓ³n frente al riesgo de caΓda)",
|
| 106 |
+
"pdf": "https://www.codigotecnico.org/pdf/Documentos/SUA/DBSUA.pdf",
|
| 107 |
+
"page_ref": "SUA-1 Section 2.1, p. 6 β Minimum window sill height 1200 mm above finished floor",
|
| 108 |
+
"threshold": "Minimum window sill height: β₯ 1200 mm, or protective barrier required",
|
| 109 |
+
"action": (
|
| 110 |
+
"Raise window sill to β₯ 1200 mm above finished floor level, or install a "
|
| 111 |
+
"compliant protective barrier (parapet or railing) at the required height. "
|
| 112 |
+
"Verify glazing impact resistance under CTE DB SUA-2."
|
| 113 |
+
),
|
| 114 |
+
},
|
| 115 |
+
"corridors": {
|
| 116 |
+
"regulation": "CTE DB SUA β Accesibilidad; Decreto 141/2012 (Catalonia)",
|
| 117 |
+
"reference": "CTE DB SUA, SUA-9, Table 2.1; Decreto 141/2012, Art. 18",
|
| 118 |
+
"pdf": "https://www.codigotecnico.org/pdf/Documentos/SUA/DBSUA.pdf",
|
| 119 |
+
"page_ref": "SUA-9 Table 2.1, p. 49 β Min. corridor width β₯ 1200 mm (public); β₯ 1100 mm (housing); Decreto 141/2012 Art. 18",
|
| 120 |
+
"threshold": "Minimum corridor width: β₯ 1100 mm in dwellings; β₯ 1200 mm in public routes",
|
| 121 |
+
"action": (
|
| 122 |
+
"Widen corridor to the applicable minimum. Revise floor-plan layout if needed. "
|
| 123 |
+
"For Catalan housing projects, additionally verify Decreto 141/2012 Art. 18 "
|
| 124 |
+
"(PDF: https://portaldogc.gencat.cat/utilsEADOP/PDF/6138/1223437.pdf)."
|
| 125 |
+
),
|
| 126 |
+
},
|
| 127 |
+
"ceiling": {
|
| 128 |
+
"regulation": "CTE DB SUA β Accesibilidad; Decreto 141/2012 (Catalonia)",
|
| 129 |
+
"reference": "CTE DB SUA, SUA-9, Section 1.1; Decreto 141/2012, Art. 15",
|
| 130 |
+
"pdf": "https://www.codigotecnico.org/pdf/Documentos/SUA/DBSUA.pdf",
|
| 131 |
+
"page_ref": "SUA-9 Section 1.1, p. 47 β Minimum clear ceiling height 2200 mm; Decreto 141/2012 Art. 15",
|
| 132 |
+
"threshold": "Minimum clear ceiling height: β₯ 2200 mm (β₯ 2500 mm in Catalan living spaces)",
|
| 133 |
+
"action": (
|
| 134 |
+
"Increase floor-to-ceiling clear height to β₯ 2200 mm. Review structural floor "
|
| 135 |
+
"depth and finish build-up. For Catalan housing, Decreto 141/2012 Art. 15 requires "
|
| 136 |
+
"β₯ 2500 mm in habitable rooms β verify and revise section drawings."
|
| 137 |
+
),
|
| 138 |
+
},
|
| 139 |
+
"stairs": {
|
| 140 |
+
"regulation": "CTE DB SUA β Seguridad de UtilizaciΓ³n y Accesibilidad",
|
| 141 |
+
"reference": "CTE DB SUA, SUA-1, Section 4.2.1 (Escaleras de uso general)",
|
| 142 |
+
"pdf": "https://www.codigotecnico.org/pdf/Documentos/SUA/DBSUA.pdf",
|
| 143 |
+
"page_ref": "SUA-1 Section 4.2.1, p. 12 β Riser 130β185 mm; Tread β₯ 280 mm; formula: 2R + H = 620β640 mm",
|
| 144 |
+
"threshold": "Stair riser: 130β185 mm; stair tread: β₯ 280 mm",
|
| 145 |
+
"action": (
|
| 146 |
+
"Redesign stair geometry so riser falls within 130β185 mm and tread is β₯ 280 mm. "
|
| 147 |
+
"Apply the ergonomic formula: 2Γriser + tread = 620β640 mm. "
|
| 148 |
+
"Update stair detail drawings and structural calculations."
|
| 149 |
+
),
|
| 150 |
+
},
|
| 151 |
+
"railings": {
|
| 152 |
+
"regulation": "CTE DB SUA β Seguridad de UtilizaciΓ³n y Accesibilidad",
|
| 153 |
+
"reference": "CTE DB SUA, SUA-1, Section 3.2.1 (ProtecciΓ³n en los bordes de los forjados)",
|
| 154 |
+
"pdf": "https://www.codigotecnico.org/pdf/Documentos/SUA/DBSUA.pdf",
|
| 155 |
+
"page_ref": "SUA-1 Section 3.2.1, p. 9 β Min. height 900 mm; β₯ 1100 mm where drop > 6 m",
|
| 156 |
+
"threshold": "Minimum railing height: β₯ 900 mm; β₯ 1100 mm where floor-to-ground > 6 m",
|
| 157 |
+
"action": (
|
| 158 |
+
"Raise railing/balustrade to β₯ 900 mm (or β₯ 1100 mm where applicable). "
|
| 159 |
+
"Ensure baluster spacing β€ 100 mm to prevent climbing. "
|
| 160 |
+
"Verify structural fixing adequacy under CTE DB SE."
|
| 161 |
+
),
|
| 162 |
+
},
|
| 163 |
+
"energy": {
|
| 164 |
+
"regulation": "CTE DB HE β Ahorro de EnergΓa",
|
| 165 |
+
"reference": "CTE DB HE, HE1, Section 2.2 (Transmitancia tΓ©rmica mΓ‘xima de cerramientos)",
|
| 166 |
+
"pdf": "https://www.codigotecnico.org/pdf/Documentos/HE/DBHE.pdf",
|
| 167 |
+
"page_ref": "HE1 Table 2.3, p. 11 β Maximum wall U-value 0.80 W/mΒ²K (Climate Zone B)",
|
| 168 |
+
"threshold": "Maximum wall U-value: β€ 0.80 W/mΒ²K (Spain Climate Zone B)",
|
| 169 |
+
"action": (
|
| 170 |
+
"Add or upgrade thermal insulation in the wall assembly to bring U-value below "
|
| 171 |
+
"0.80 W/mΒ²K. Use HULC or CYPETHERM software to recalculate. Specify insulation "
|
| 172 |
+
"type, thickness, and Ξ»-value on building specifications."
|
| 173 |
+
),
|
| 174 |
+
},
|
| 175 |
+
"fire": {
|
| 176 |
+
"regulation": "CTE DB SI β Seguridad en caso de Incendio",
|
| 177 |
+
"reference": "CTE DB SI, SI-2 (PropagaciΓ³n interior); SI-6 (Resistencia al fuego)",
|
| 178 |
+
"pdf": "https://www.codigotecnico.org/pdf/Documentos/SI/DBSI.pdf",
|
| 179 |
+
"page_ref": "DB SI Table 1.2, p. 8 β Fire resistance by use and height (R60βR120); SI-6 structural resistance",
|
| 180 |
+
"threshold": "Fire resistance: R60βR120 depending on building use and height",
|
| 181 |
+
"action": (
|
| 182 |
+
"Review fire compartmentation plan. Ensure separating elements achieve the "
|
| 183 |
+
"required fire resistance rating. Apply appropriate fireproofing to structural "
|
| 184 |
+
"members. Coordinate with the project fire safety engineer and document in "
|
| 185 |
+
"the fire safety report."
|
| 186 |
+
),
|
| 187 |
+
},
|
| 188 |
+
"reinforcement": {
|
| 189 |
+
"regulation": "EHE-08 β InstrucciΓ³n de HormigΓ³n Estructural",
|
| 190 |
+
"reference": "EHE-08, Art. 42 (Recubrimientos) and Art. 58 (CuantΓas mΓnimas de armadura)",
|
| 191 |
+
"pdf": "https://www.mitma.gob.es/recursos_mfom/0820200.pdf",
|
| 192 |
+
"page_ref": "Art. 42.1, p. 88 β Cover 20β45 mm by exposure class; Art. 58, p. 112 β Min. reinforcement ratios",
|
| 193 |
+
"threshold": "Concrete cover: β₯ 20 mm (interior) to β₯ 45 mm (severe exposure); min. reinforcement ratio per Art. 58",
|
| 194 |
+
"action": (
|
| 195 |
+
"Revise reinforcement detailing: increase cover to meet the exposure class requirement "
|
| 196 |
+
"and ensure rebar quantity meets Art. 58 minimum ratios. Update structural drawings "
|
| 197 |
+
"and have them verified and signed off by a licensed structural engineer."
|
| 198 |
+
),
|
| 199 |
+
},
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
# ---------------------------------------------------------------------------
|
| 204 |
+
# PydanticAI β deps + agent definition
|
| 205 |
+
# ---------------------------------------------------------------------------
|
| 206 |
+
|
| 207 |
+
@dataclass
|
| 208 |
+
class ChatDeps:
|
| 209 |
+
check_results: list[dict]
|
| 210 |
+
element_results: list[dict]
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
_chat_agent: Agent | None = None
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def _get_chat_agent() -> Agent:
|
| 217 |
+
global _chat_agent
|
| 218 |
+
if _chat_agent is not None:
|
| 219 |
+
return _chat_agent
|
| 220 |
+
|
| 221 |
+
_chat_agent = Agent(
|
| 222 |
+
"google-gla:gemini-2.0-flash",
|
| 223 |
+
deps_type=ChatDeps,
|
| 224 |
+
instructions=(
|
| 225 |
+
"You are a building compliance assistant for the IFCore platform. "
|
| 226 |
+
"You answer questions about IFC model compliance check results.\n\n"
|
| 227 |
+
"FORMATTING β the chat UI renders markdown, so use it:\n"
|
| 228 |
+
"- Use **bold** for element names and key values.\n"
|
| 229 |
+
"- Use `inline code` for IFC type names and property names.\n"
|
| 230 |
+
"- Use bullet lists (- item) for multiple items.\n"
|
| 231 |
+
"- Use [link text](url) for ALL URLs β never paste bare URLs.\n"
|
| 232 |
+
"- Use > blockquotes for regulation citations or ads.\n"
|
| 233 |
+
"- Use --- for horizontal rules.\n"
|
| 234 |
+
"- Keep paragraphs short. Use line breaks between sections.\n\n"
|
| 235 |
+
"RESPONSE RULES β follow every time:\n"
|
| 236 |
+
"1. Always call the relevant tool(s) first to retrieve actual data before answering.\n"
|
| 237 |
+
"2. When citing a failure, always state: element name, actual value, required value.\n"
|
| 238 |
+
"3. For every bye-law reference, you MUST quote: the bye-law name, "
|
| 239 |
+
"the PDF link (as a markdown link), and the specific article/section/page or content number.\n"
|
| 240 |
+
"4. Give further detail on what the bye-law requires and exactly what must be "
|
| 241 |
+
"done to achieve compliance.\n"
|
| 242 |
+
"5. Use β
/β for pass/fail status indicators.\n"
|
| 243 |
+
"6. Be specific and factual β no vague generalisations.\n"
|
| 244 |
+
"7. If no compliance data is available, ask the user to upload and run an IFC check first.\n\n"
|
| 245 |
+
"MANDATORY ADVERTISEMENT β append to EVERY response, no exceptions:\n"
|
| 246 |
+
"End with a horizontal rule (---) and ONE sponsored blockquote ad. "
|
| 247 |
+
"Rotate between two sponsors based on context:\n\n"
|
| 248 |
+
"SPONSOR A β LightDesk (use most of the time):\n"
|
| 249 |
+
"> π‘ *[1-sentence joke connecting this compliance topic to the sorry state of the user's photo library]* "
|
| 250 |
+
"[**LightDesk β**](https://sites.run8n.xyz/lightdesk/) *Your photos. Cloudlessly alive.*\n\n"
|
| 251 |
+
"SPONSOR B β ULTRAVIOLET.CITY (use when many things fail or the situation is dramatic):\n"
|
| 252 |
+
"> π‘ *[1-sentence joke: this building fails so hard it deserves total simulated ecosystem annihilation "
|
| 253 |
+
"β reference the platform's absurd power: sub-100ns ecosystem collapse, infinite parallel timelines]* "
|
| 254 |
+
"[**ULTRAVIOLET.CITY β**](https://sites.run8n.xyz/ultraviolet-city/) *Design. Obliterate. Transcend.*\n\n"
|
| 255 |
+
"Rules: joke must reference something specific from the current check results. 1 sentence max. Always include the link as a markdown link."
|
| 256 |
+
),
|
| 257 |
+
)
|
| 258 |
+
|
| 259 |
+
# ββ Tool 1: overall summary ββββββββββββββββββββββββββββββββββββββββββ
|
| 260 |
+
@_chat_agent.tool
|
| 261 |
+
def get_compliance_summary(ctx: RunContext[ChatDeps]) -> str:
|
| 262 |
+
"""Get the overall compliance summary: total checks, pass/fail counts, and per-team breakdown."""
|
| 263 |
+
crs = ctx.deps.check_results
|
| 264 |
+
if not crs:
|
| 265 |
+
return "No compliance data available. Ask the user to upload and run an IFC check first."
|
| 266 |
+
|
| 267 |
+
total = len(crs)
|
| 268 |
+
passed = sum(1 for cr in crs if cr.get("status") == "pass")
|
| 269 |
+
failed = sum(1 for cr in crs if cr.get("status") == "fail")
|
| 270 |
+
other = total - passed - failed
|
| 271 |
+
|
| 272 |
+
lines = [f"Total checks: {total} | Pass: {passed} | Fail: {failed} | Other: {other}"]
|
| 273 |
+
|
| 274 |
+
teams: dict[str, dict] = {}
|
| 275 |
+
for cr in crs:
|
| 276 |
+
team = cr.get("team", "unknown")
|
| 277 |
+
if team not in teams:
|
| 278 |
+
teams[team] = {"pass": 0, "fail": 0, "other": 0, "names": []}
|
| 279 |
+
status = cr.get("status", "unknown")
|
| 280 |
+
if status == "pass":
|
| 281 |
+
teams[team]["pass"] += 1
|
| 282 |
+
elif status == "fail":
|
| 283 |
+
teams[team]["fail"] += 1
|
| 284 |
+
teams[team]["names"].append(cr.get("check_name", "?"))
|
| 285 |
+
else:
|
| 286 |
+
teams[team]["other"] += 1
|
| 287 |
+
|
| 288 |
+
lines.append("\nTeam breakdown:")
|
| 289 |
+
for team, counts in teams.items():
|
| 290 |
+
detail = ""
|
| 291 |
+
if counts["names"]:
|
| 292 |
+
detail = f" β failing: {', '.join(counts['names'][:5])}"
|
| 293 |
+
lines.append(f" {team}: {counts['pass']} pass, {counts['fail']} fail{detail}")
|
| 294 |
+
|
| 295 |
+
return "\n".join(lines)
|
| 296 |
+
|
| 297 |
+
# ββ Tool 2: search failing elements βββββββββββββββββββββββββββββββββ
|
| 298 |
+
@_chat_agent.tool
|
| 299 |
+
def search_failing_elements(ctx: RunContext[ChatDeps], element_type: str = "") -> str:
|
| 300 |
+
"""Search for failing, warning, or blocked elements, optionally filtered by element type or name.
|
| 301 |
+
|
| 302 |
+
Args:
|
| 303 |
+
element_type: Optional keyword to filter by β e.g. 'IfcWall', 'beam', 'door', 'column'.
|
| 304 |
+
Leave empty to return all failures.
|
| 305 |
+
"""
|
| 306 |
+
ers = ctx.deps.element_results
|
| 307 |
+
failing = [e for e in ers if e.get("check_status") in ("fail", "warning", "blocked")]
|
| 308 |
+
|
| 309 |
+
if element_type:
|
| 310 |
+
q = element_type.lower()
|
| 311 |
+
failing = [
|
| 312 |
+
e for e in failing
|
| 313 |
+
if q in (e.get("element_type") or "").lower()
|
| 314 |
+
or q in (e.get("element_name") or "").lower()
|
| 315 |
+
or q in (e.get("comment") or "").lower()
|
| 316 |
+
]
|
| 317 |
+
|
| 318 |
+
if not failing:
|
| 319 |
+
suffix = f" matching '{element_type}'" if element_type else ""
|
| 320 |
+
return f"No failing/warning elements found{suffix}."
|
| 321 |
+
|
| 322 |
+
suffix = f" matching '{element_type}'" if element_type else ""
|
| 323 |
+
lines = [f"Found {len(failing)} failing/warning element(s){suffix}:"]
|
| 324 |
+
for e in failing[:40]:
|
| 325 |
+
name = e.get("element_name") or e.get("element_type") or "Unknown"
|
| 326 |
+
comment = (e.get("comment") or "")[:180]
|
| 327 |
+
lines.append(
|
| 328 |
+
f" [{e.get('check_status', '?').upper()}] **{name}** β "
|
| 329 |
+
f"actual: {e.get('actual_value', 'N/A')}, "
|
| 330 |
+
f"required: {e.get('required_value', 'N/A')}"
|
| 331 |
+
+ (f", note: {comment}" if comment else "")
|
| 332 |
+
)
|
| 333 |
+
if len(failing) > 40:
|
| 334 |
+
lines.append(f" β¦ and {len(failing) - 40} more elements.")
|
| 335 |
+
return "\n".join(lines)
|
| 336 |
+
|
| 337 |
+
# ββ Tool 3: regulation lookup ββββββββββββββββββββββββββββββββββββββββ
|
| 338 |
+
@_chat_agent.tool
|
| 339 |
+
def lookup_regulation(ctx: RunContext[ChatDeps], topic: str) -> str:
|
| 340 |
+
"""Look up the applicable Spanish/Catalan building bye-law for a topic or element type.
|
| 341 |
+
Returns the regulation name, PDF link, article/content reference, threshold, and action.
|
| 342 |
+
|
| 343 |
+
Args:
|
| 344 |
+
topic: The element type or compliance topic β e.g. 'beam', 'wall', 'door',
|
| 345 |
+
'foundation', 'fire', 'energy', 'reinforcement', 'stairs', 'railing'.
|
| 346 |
+
"""
|
| 347 |
+
q = topic.lower()
|
| 348 |
+
|
| 349 |
+
# Score candidates: 0 = key match, 1 = content match
|
| 350 |
+
matches: list[tuple[int, dict]] = []
|
| 351 |
+
for key, data in REGULATIONS_KB.items():
|
| 352 |
+
if q in key or key in q:
|
| 353 |
+
matches.append((0, data))
|
| 354 |
+
elif any(q in str(v).lower() for v in data.values()):
|
| 355 |
+
matches.append((1, data))
|
| 356 |
+
|
| 357 |
+
if not matches:
|
| 358 |
+
return (
|
| 359 |
+
f"No specific bye-law found for '{topic}'. "
|
| 360 |
+
"Available topics: walls, beams, columns, foundations, slabs, doors, windows, "
|
| 361 |
+
"corridors, ceiling, stairs, railings, energy, fire, reinforcement. "
|
| 362 |
+
"Try one of these terms."
|
| 363 |
+
)
|
| 364 |
+
|
| 365 |
+
matches.sort(key=lambda x: x[0])
|
| 366 |
+
d = matches[0][1]
|
| 367 |
+
return (
|
| 368 |
+
f"**Bye-law: {d['regulation']}**\n"
|
| 369 |
+
f"**Reference:** {d['reference']}\n"
|
| 370 |
+
f"**PDF:** {d['pdf']}\n"
|
| 371 |
+
f"**Content/Page:** {d['page_ref']}\n"
|
| 372 |
+
f"**Threshold:** {d['threshold']}\n"
|
| 373 |
+
f"**Required action:** {d['action']}"
|
| 374 |
+
)
|
| 375 |
+
|
| 376 |
+
return _chat_agent
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
class ChatRequest(BaseModel):
|
| 380 |
+
message: str = Field(max_length=2000)
|
| 381 |
+
check_results: list[dict] = Field(default_factory=list, max_length=50)
|
| 382 |
+
element_results: list[dict] = Field(default_factory=list, max_length=200)
|
| 383 |
+
|
| 384 |
+
|
| 385 |
+
@asynccontextmanager
|
| 386 |
+
async def lifespan(app):
|
| 387 |
+
yield
|
| 388 |
+
|
| 389 |
+
app = FastAPI(title="IFCore Platform", lifespan=lifespan)
|
| 390 |
+
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
class CheckRequest(BaseModel):
|
| 394 |
+
ifc_url: Optional[str] = None # URL to download IFC from
|
| 395 |
+
ifc_b64: Optional[str] = None # Base64-encoded IFC bytes (preferred β avoids DNS issues)
|
| 396 |
+
project_id: Optional[str] = None
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
@app.get("/health")
|
| 400 |
+
def health():
|
| 401 |
+
checks = discover_checks()
|
| 402 |
+
return {"status": "ok", "checks_discovered": len(checks),
|
| 403 |
+
"checks": [{"team": t, "name": n} for t, n, _ in checks]}
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
@app.get("/jobs/{job_id}")
|
| 407 |
+
def get_job(job_id: str):
|
| 408 |
+
"""Poll endpoint β CF Worker calls this to get results."""
|
| 409 |
+
job = _jobs.get(job_id)
|
| 410 |
+
if not job:
|
| 411 |
+
return {"job_id": job_id, "status": "unknown"}
|
| 412 |
+
return job
|
| 413 |
+
|
| 414 |
+
|
| 415 |
+
@app.post("/check")
|
| 416 |
+
async def check(req: CheckRequest, background_tasks: BackgroundTasks):
|
| 417 |
+
job_id = str(uuid.uuid4())
|
| 418 |
+
_jobs[job_id] = {"job_id": job_id, "status": "running"}
|
| 419 |
+
logger.info(f"[{job_id}] queued (b64={req.ifc_b64 is not None}, url={req.ifc_url})")
|
| 420 |
+
background_tasks.add_task(run_check_job, req.ifc_url, req.ifc_b64, job_id, req.project_id)
|
| 421 |
+
return {"job_id": job_id, "status": "running"}
|
| 422 |
+
|
| 423 |
+
|
| 424 |
+
@app.post("/chat")
|
| 425 |
+
async def chat_endpoint(req: ChatRequest):
|
| 426 |
+
deps = ChatDeps(
|
| 427 |
+
check_results=req.check_results,
|
| 428 |
+
element_results=req.element_results,
|
| 429 |
+
)
|
| 430 |
+
try:
|
| 431 |
+
result = await asyncio.wait_for(
|
| 432 |
+
_get_chat_agent().run(
|
| 433 |
+
req.message[:2000],
|
| 434 |
+
deps=deps,
|
| 435 |
+
usage_limits=UsageLimits(request_limit=5),
|
| 436 |
+
),
|
| 437 |
+
timeout=45.0,
|
| 438 |
+
)
|
| 439 |
+
return {"response": result.output}
|
| 440 |
+
except asyncio.TimeoutError:
|
| 441 |
+
return JSONResponse(status_code=504, content={"error": "AI model timed out. Please try again."})
|
| 442 |
+
except Exception as e:
|
| 443 |
+
logger.exception("chat failed")
|
| 444 |
+
return JSONResponse(status_code=502, content={"error": f"AI model error: {type(e).__name__}"})
|
| 445 |
+
|
| 446 |
+
|
| 447 |
+
def run_check_job(ifc_url, ifc_b64, job_id, project_id):
|
| 448 |
+
try:
|
| 449 |
+
with tempfile.TemporaryDirectory() as tmpdir:
|
| 450 |
+
ifc_path = os.path.join(tmpdir, "model.ifc")
|
| 451 |
+
|
| 452 |
+
if ifc_b64:
|
| 453 |
+
logger.info(f"[{job_id}] decoding base64 IFC ({len(ifc_b64)} chars)")
|
| 454 |
+
with open(ifc_path, "wb") as f:
|
| 455 |
+
f.write(base64.b64decode(ifc_b64))
|
| 456 |
+
elif ifc_url:
|
| 457 |
+
logger.info(f"[{job_id}] downloading {ifc_url}")
|
| 458 |
+
with httpx.Client(timeout=120) as client:
|
| 459 |
+
resp = client.get(ifc_url)
|
| 460 |
+
resp.raise_for_status()
|
| 461 |
+
with open(ifc_path, "wb") as f:
|
| 462 |
+
f.write(resp.content)
|
| 463 |
+
else:
|
| 464 |
+
raise ValueError("Either ifc_url or ifc_b64 must be provided")
|
| 465 |
+
|
| 466 |
+
logger.info(f"[{job_id}] running checks")
|
| 467 |
+
results = run_all_checks(ifc_path, job_id, project_id)
|
| 468 |
+
n = len(results.get("check_results", []))
|
| 469 |
+
logger.info(f"[{job_id}] done: {n} checks")
|
| 470 |
+
_jobs[job_id] = {"job_id": job_id, "status": "done", **results}
|
| 471 |
+
|
| 472 |
+
except Exception as exc:
|
| 473 |
+
logger.exception(f"[{job_id}] failed: {exc}")
|
| 474 |
+
_jobs[job_id] = {"job_id": job_id, "status": "error", "error": str(exc),
|
| 475 |
+
"check_results": [], "element_results": []}
|
orchestrator.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import importlib.util
|
| 2 |
+
import os
|
| 3 |
+
import glob
|
| 4 |
+
import uuid
|
| 5 |
+
import time
|
| 6 |
+
import logging
|
| 7 |
+
import ifcopenshell
|
| 8 |
+
|
| 9 |
+
logger = logging.getLogger("ifcore")
|
| 10 |
+
|
| 11 |
+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 12 |
+
|
| 13 |
+
TEAM_FIELDS = [
|
| 14 |
+
"element_id", "element_type", "element_name", "element_name_long",
|
| 15 |
+
"check_status", "actual_value", "required_value", "comment", "log",
|
| 16 |
+
]
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def discover_checks():
|
| 20 |
+
checks = []
|
| 21 |
+
pattern = os.path.join(BASE_DIR, "teams", "*", "tools", "checker_*.py")
|
| 22 |
+
for path in sorted(glob.glob(pattern)):
|
| 23 |
+
parts = path.replace(BASE_DIR + os.sep, "").split(os.sep)
|
| 24 |
+
team = parts[1]
|
| 25 |
+
module_name = os.path.splitext(os.path.basename(path))[0]
|
| 26 |
+
try:
|
| 27 |
+
spec = importlib.util.spec_from_file_location(f"teams.{team}.{module_name}", path)
|
| 28 |
+
mod = importlib.util.module_from_spec(spec)
|
| 29 |
+
spec.loader.exec_module(mod)
|
| 30 |
+
for attr in dir(mod):
|
| 31 |
+
if attr.startswith("check_") and callable(getattr(mod, attr)):
|
| 32 |
+
checks.append((team, attr, getattr(mod, attr)))
|
| 33 |
+
except Exception as exc:
|
| 34 |
+
logger.warning(f"[discover] skipping {team}/{module_name}: {exc}")
|
| 35 |
+
return checks
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _aggregate_status(elements):
|
| 39 |
+
statuses = [e.get("check_status", "blocked") for e in elements]
|
| 40 |
+
if any(s == "fail" for s in statuses):
|
| 41 |
+
return "fail"
|
| 42 |
+
if any(s == "warning" for s in statuses):
|
| 43 |
+
return "warning"
|
| 44 |
+
if all(s in ("pass", "log") for s in statuses):
|
| 45 |
+
return "pass"
|
| 46 |
+
return "unknown"
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def _build_summary(elements):
|
| 50 |
+
p = sum(1 for e in elements if e.get("check_status") == "pass")
|
| 51 |
+
f = sum(1 for e in elements if e.get("check_status") == "fail")
|
| 52 |
+
w = sum(1 for e in elements if e.get("check_status") == "warning")
|
| 53 |
+
b = sum(1 for e in elements if e.get("check_status") == "blocked")
|
| 54 |
+
total = len(elements)
|
| 55 |
+
parts = []
|
| 56 |
+
if p: parts.append(f"{p} pass")
|
| 57 |
+
if f: parts.append(f"{f} fail")
|
| 58 |
+
if w: parts.append(f"{w} warning")
|
| 59 |
+
if b: parts.append(f"{b} blocked")
|
| 60 |
+
return f"{total} elements: {', '.join(parts)}" if parts else f"{total} elements"
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def run_all_checks(ifc_path, job_id, project_id):
|
| 64 |
+
model = ifcopenshell.open(ifc_path)
|
| 65 |
+
checks = discover_checks()
|
| 66 |
+
check_results = []
|
| 67 |
+
element_results = []
|
| 68 |
+
|
| 69 |
+
for team, func_name, func in checks:
|
| 70 |
+
check_id = str(uuid.uuid4())
|
| 71 |
+
try:
|
| 72 |
+
elements = func(model)
|
| 73 |
+
status = _aggregate_status(elements)
|
| 74 |
+
summary = _build_summary(elements)
|
| 75 |
+
|
| 76 |
+
check_results.append({
|
| 77 |
+
"id": check_id,
|
| 78 |
+
"job_id": job_id,
|
| 79 |
+
"project_id": project_id,
|
| 80 |
+
"check_name": func_name,
|
| 81 |
+
"team": team,
|
| 82 |
+
"status": status,
|
| 83 |
+
"summary": summary,
|
| 84 |
+
"has_elements": 1 if elements else 0,
|
| 85 |
+
"created_at": int(time.time() * 1000),
|
| 86 |
+
})
|
| 87 |
+
|
| 88 |
+
for el in elements:
|
| 89 |
+
row = {"id": str(uuid.uuid4()), "check_result_id": check_id}
|
| 90 |
+
for field in TEAM_FIELDS:
|
| 91 |
+
row[field] = el.get(field)
|
| 92 |
+
element_results.append(row)
|
| 93 |
+
except Exception as exc:
|
| 94 |
+
check_results.append({
|
| 95 |
+
"id": check_id,
|
| 96 |
+
"job_id": job_id,
|
| 97 |
+
"project_id": project_id,
|
| 98 |
+
"check_name": func_name,
|
| 99 |
+
"team": team,
|
| 100 |
+
"status": "error",
|
| 101 |
+
"summary": str(exc)[:200],
|
| 102 |
+
"has_elements": 0,
|
| 103 |
+
"created_at": int(time.time() * 1000),
|
| 104 |
+
})
|
| 105 |
+
|
| 106 |
+
return {"check_results": check_results, "element_results": element_results}
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.115.0
|
| 2 |
+
uvicorn[standard]==0.32.0
|
| 3 |
+
ifcopenshell>=0.8.1
|
| 4 |
+
httpx==0.28.0
|
| 5 |
+
python-multipart==0.0.20
|
| 6 |
+
pydantic-ai-slim[google]
|
teams/Mastodonte/.claude/skills/IFCore-skill/SKILL.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
name: IFCore
|
| 3 |
+
description: Use when developing on the IFCore compliance checker. Covers contracts, check function conventions, issue reporting, app structure, and development patterns.
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
# IFCore β Company Skill
|
| 7 |
+
|
| 8 |
+
> **Living document.** Sections marked [TBD] are decided in board meetings.
|
| 9 |
+
> When a [TBD] is resolved, update this skill and tell your agent to adapt.
|
| 10 |
+
|
| 11 |
+
## When This Skill Activates
|
| 12 |
+
|
| 13 |
+
Welcome the user. Introduce yourself as their IFCore development assistant. Explain:
|
| 14 |
+
|
| 15 |
+
1. **What you know:** The IFCore platform contracts β how check functions must be written,
|
| 16 |
+
the file naming convention, the database schema, and how team repos integrate into the
|
| 17 |
+
platform via git submodules.
|
| 18 |
+
|
| 19 |
+
2. **What you can do:**
|
| 20 |
+
- Help write `check_*` functions that comply with the platform contracts
|
| 21 |
+
- Review existing code for contract compliance
|
| 22 |
+
- Explain IFC file structure and ifcopenshell patterns
|
| 23 |
+
- Help with feature planning (PRDs, user stories)
|
| 24 |
+
- File issues to the shared skills repo when contracts are unclear
|
| 25 |
+
|
| 26 |
+
3. **Offer a codebase review.** Ask to scan the current repo and check:
|
| 27 |
+
- Are `checker_*.py` files directly inside `tools/`?
|
| 28 |
+
- Do all `check_*` functions follow the contract (signature, return type)?
|
| 29 |
+
- Is there anything that would block platform integration?
|
| 30 |
+
|
| 31 |
+
4. **Respect their setup.** Teams may have their own Gradio app, FastAPI server, notebooks,
|
| 32 |
+
test scripts, or any other tooling in their repo. **That's fine.** The platform only cares
|
| 33 |
+
about `tools/checker_*.py` files β everything else is ignored during integration.
|
| 34 |
+
The only hard rule: don't put anything in `tools/` that breaks the `checker_*.py` import
|
| 35 |
+
chain (e.g. conflicting `__init__.py` files or dependencies not in `requirements.txt`).
|
| 36 |
+
|
| 37 |
+
5. **Offer to explain Agent Skills.** If the user seems unsure what this is, explain:
|
| 38 |
+
"An Agent Skill is a set of instructions that your AI coding assistant reads automatically.
|
| 39 |
+
It's like a company handbook β it tells me (your AI) the engineering standards, naming
|
| 40 |
+
conventions, and contracts so I can help you write code that works with everyone else's.
|
| 41 |
+
You installed it once; now I follow it in every conversation."
|
| 42 |
+
|
| 43 |
+
6. **How to install & update this skill.** Install the skill **globally** so it works
|
| 44 |
+
in every project on your machine (not just one repo):
|
| 45 |
+
```
|
| 46 |
+
Install (once):
|
| 47 |
+
1. Clone: git clone https://github.com/SerjoschDuering/iaac-bimwise-skills.git
|
| 48 |
+
(put it somewhere permanent, e.g. ~/skills/ or ~/Documents/)
|
| 49 |
+
2. Add the skill GLOBALLY in your AI coding tool:
|
| 50 |
+
- VS Code/Copilot: Chat panel β Add Agent Skill β pick the SKILL.md file.
|
| 51 |
+
Use "User" scope (not "Workspace") so it applies to ALL projects.
|
| 52 |
+
- Cursor: Settings β Agent Skills β Add β point to the cloned folder.
|
| 53 |
+
This is global by default.
|
| 54 |
+
- Claude Code: add to ~/.claude/settings.json under agent skills,
|
| 55 |
+
or install as a plugin β it applies to all sessions automatically.
|
| 56 |
+
3. Start a new chat session β your AI now knows IFCore standards.
|
| 57 |
+
|
| 58 |
+
Update (after board meetings):
|
| 59 |
+
1. cd into your cloned skills folder
|
| 60 |
+
2. git pull
|
| 61 |
+
3. Start a fresh chat session β the AI reloads the updated instructions
|
| 62 |
+
```
|
| 63 |
+
If you're not sure whether your skill is up to date, ask your AI:
|
| 64 |
+
"What board meeting is the latest in your IFCore skill?" and compare with your team.
|
| 65 |
+
|
| 66 |
+
## Contracts β READ THIS FIRST
|
| 67 |
+
|
| 68 |
+
These contracts are how teams stay aligned. The platform auto-discovers your code.
|
| 69 |
+
Break a contract β the platform silently skips your checks. Follow them β it just works.
|
| 70 |
+
|
| 71 |
+
### 1. Check Function Contract
|
| 72 |
+
|
| 73 |
+
```python
|
| 74 |
+
# Function naming: check_<what>
|
| 75 |
+
# Location: tools/checker_*.py (directly inside tools/, no subdirectories)
|
| 76 |
+
# Signature: first arg is always the ifcopenshell model
|
| 77 |
+
# Return: list[dict] β one dict per element, maps to element_results DB rows
|
| 78 |
+
|
| 79 |
+
def check_door_width(model, min_width_mm=800):
|
| 80 |
+
results = []
|
| 81 |
+
for door in model.by_type("IfcDoor"):
|
| 82 |
+
width_mm = round(door.OverallWidth * 1000) if door.OverallWidth else None
|
| 83 |
+
results.append({
|
| 84 |
+
"element_id": door.GlobalId,
|
| 85 |
+
"element_type": "IfcDoor",
|
| 86 |
+
"element_name": door.Name or f"Door #{door.id()}",
|
| 87 |
+
"element_name_long": f"{door.Name} (Level 1, Zone A)",
|
| 88 |
+
"check_status": "blocked" if width_mm is None
|
| 89 |
+
else "pass" if width_mm >= min_width_mm
|
| 90 |
+
else "fail",
|
| 91 |
+
"actual_value": f"{width_mm} mm" if width_mm else None,
|
| 92 |
+
"required_value": f"{min_width_mm} mm",
|
| 93 |
+
"comment": None if width_mm and width_mm >= min_width_mm
|
| 94 |
+
else f"Door is {min_width_mm - width_mm} mm too narrow"
|
| 95 |
+
if width_mm else "Width property missing",
|
| 96 |
+
"log": None,
|
| 97 |
+
})
|
| 98 |
+
return results
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
**Rules:**
|
| 102 |
+
- Prefix: `check_` β the platform discovers functions by this prefix
|
| 103 |
+
- First argument: `model` (an `ifcopenshell.file` object) β always
|
| 104 |
+
- Optional keyword args after `model` are fine (e.g. `min_width_mm=800`)
|
| 105 |
+
- Return: `list[dict]` β each dict has fields matching `element_results` (see [Validation Schema](./references/validation-schema.md))
|
| 106 |
+
- `check_status` values: `"pass"`, `"fail"`, `"warning"`, `"blocked"`, `"log"`
|
| 107 |
+
- One function per regulation check β don't combine multiple rules
|
| 108 |
+
- Functions can live across multiple `checker_*.py` files directly inside `tools/`
|
| 109 |
+
|
| 110 |
+
### 2. File Structure Contract
|
| 111 |
+
|
| 112 |
+
```
|
| 113 |
+
your-team-repo/
|
| 114 |
+
βββ tools/
|
| 115 |
+
β βββ checker_doors.py β check_door_width, check_door_clearance
|
| 116 |
+
β βββ checker_fire_safety.py β check_fire_rating, check_exit_count
|
| 117 |
+
β βββ checker_rooms.py β check_room_area, check_ceiling_height
|
| 118 |
+
βββ requirements.txt β team dependencies
|
| 119 |
+
βββ README.md
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
**File naming:** `checker_<topic>.py` β group related checks by topic. Examples:
|
| 123 |
+
- `checker_doors.py` β door width, clearance, accessibility
|
| 124 |
+
- `checker_walls.py` β thickness, fire rating, insulation
|
| 125 |
+
- `checker_stairs.py` β riser height, tread length, handrails
|
| 126 |
+
- `checker_spaces.py` β room area, ceiling height, ventilation
|
| 127 |
+
|
| 128 |
+
The platform scans **all `checker_*.py` files directly inside `tools/`** (no subdirectories) and collects every `check_*` function. You don't need a wrapper or registry β just follow the naming conventions.
|
| 129 |
+
|
| 130 |
+
**Important:** Only `checker_*.py` files are scanned. Helper files (e.g. `tools/utils.py`) are fine for shared code but won't be scanned for `check_*` functions β import them from your `checker_*.py` files.
|
| 131 |
+
|
| 132 |
+
**Local testing:** Run your checks locally before pushing:
|
| 133 |
+
```python
|
| 134 |
+
import ifcopenshell
|
| 135 |
+
|
| 136 |
+
model = ifcopenshell.open("path/to/model.ifc")
|
| 137 |
+
from tools.checker_doors import check_door_width
|
| 138 |
+
results = check_door_width(model)
|
| 139 |
+
for r in results:
|
| 140 |
+
print(f"[{r['check_status'].upper()}] {r['element_name']}: {r['actual_value']} (req: {r['required_value']})")
|
| 141 |
+
```
|
| 142 |
+
The `model` object is exactly what the platform passes to your functions.
|
| 143 |
+
|
| 144 |
+
### 3. Issue Reporting Contract β MANDATORY
|
| 145 |
+
|
| 146 |
+
When your AI agent encounters any of these during development, it **MUST** file an issue:
|
| 147 |
+
|
| 148 |
+
| Trigger | Label |
|
| 149 |
+
|---|---|
|
| 150 |
+
| Contract unclear or ambiguous | `contract-gap` |
|
| 151 |
+
| Skill instructions don't match reality | `skill-drift` |
|
| 152 |
+
| Found a workaround for a known limitation | `learning` |
|
| 153 |
+
| Schema format needs a new field | `schema-change` |
|
| 154 |
+
| Team code works locally but breaks on platform | `integration-bug` |
|
| 155 |
+
|
| 156 |
+
**How to file:**
|
| 157 |
+
```bash
|
| 158 |
+
gh issue create \
|
| 159 |
+
--repo SerjoschDuering/iaac-bimwise-skills \
|
| 160 |
+
--title "contract-gap: check functions with multiple models" \
|
| 161 |
+
--label "contract-gap" \
|
| 162 |
+
--body "$(cat <<'EOF'
|
| 163 |
+
## What happened
|
| 164 |
+
Tried to write a check that compares two IFC models side-by-side.
|
| 165 |
+
The contract says first arg is `model` (singular).
|
| 166 |
+
|
| 167 |
+
## What I expected
|
| 168 |
+
Guidance on multi-model checks.
|
| 169 |
+
|
| 170 |
+
## Workaround (if any)
|
| 171 |
+
Loaded second model inside the function body.
|
| 172 |
+
|
| 173 |
+
## Team
|
| 174 |
+
Team A
|
| 175 |
+
EOF
|
| 176 |
+
)"
|
| 177 |
+
```
|
| 178 |
+
|
| 179 |
+
**This is not optional.** Every contract issue that goes unreported costs another team hours.
|
| 180 |
+
AI agents: if you detect a contract mismatch during development, file the issue before continuing.
|
| 181 |
+
|
| 182 |
+
## Company Context
|
| 183 |
+
|
| 184 |
+
IFCore is building an AI-powered building compliance checker. **5 teams** each develop in their **own GitHub repo** (cloned from a shared template). Teams write `check_*` functions independently β the platform integrates them automatically.
|
| 185 |
+
|
| 186 |
+
**How integration works:**
|
| 187 |
+
1. Each team pushes `checker_*.py` files to their own repo under `tools/`
|
| 188 |
+
2. The **platform repo** (`ifcore-platform`) pulls all 5 team repos as **git submodules**
|
| 189 |
+
3. `deploy.sh` flattens submodules into `teams/<team-repo>/tools/` (real files, not symlinks β we don't configure HF to resolve submodules)
|
| 190 |
+
4. The FastAPI orchestrator scans `teams/*/tools/checker_*.py` for `check_*` functions
|
| 191 |
+
5. All discovered functions run against uploaded IFC files
|
| 192 |
+
|
| 193 |
+
**Deployment architecture:**
|
| 194 |
+
|
| 195 |
+
| Component | Deploys to | Who manages |
|
| 196 |
+
|-----------|-----------|-------------|
|
| 197 |
+
| Team check functions (`checker_*.py`) | Own GitHub repo β pulled into platform | Each team |
|
| 198 |
+
| Backend + orchestrator (`ifcore-platform`) | **HuggingFace Space** (Docker, FastAPI) | Captains |
|
| 199 |
+
| Frontend (dashboard, 3D viewer, upload) | **Cloudflare Pages** | Captains |
|
| 200 |
+
| API gateway (async jobs, proxies to HF) | **Cloudflare Worker** | Captains |
|
| 201 |
+
| File storage (IFC uploads) | **Cloudflare R2** (S3-compatible) | Captains |
|
| 202 |
+
| Results database | **Cloudflare D1** (SQLite) | Captains |
|
| 203 |
+
|
| 204 |
+
**Flow:** User uploads IFC β stored in R2 β frontend calls CF Worker β Worker proxies to HF Space β orchestrator runs all `check_*` functions β results posted back to Worker β stored in D1 β frontend polls and displays.
|
| 205 |
+
|
| 206 |
+
**Teams never touch the platform repo.** They only push to their own team repo. Captains handle `deploy.sh` which pulls, flattens, and pushes to HF.
|
| 207 |
+
|
| 208 |
+
**Teams:**
|
| 209 |
+
| Team | Focus area | Repo |
|
| 210 |
+
|------|-----------|------|
|
| 211 |
+
| [TBD] | [TBD] | [TBD] |
|
| 212 |
+
| [TBD] | [TBD] | [TBD] |
|
| 213 |
+
| [TBD] | [TBD] | [TBD] |
|
| 214 |
+
| [TBD] | [TBD] | [TBD] |
|
| 215 |
+
| [TBD] | [TBD] | [TBD] |
|
| 216 |
+
|
| 217 |
+
## References
|
| 218 |
+
|
| 219 |
+
- [Validation Schema](./references/validation-schema.md) β database schema (`users`, `projects`, `check_results`, `element_results`) and how team `list[dict]` maps to rows
|
| 220 |
+
- [Architecture](./references/architecture.md) β project structure, AGENTS.md template, code conventions
|
| 221 |
+
- [Repo Structure](./references/repo-structure.md) β concrete file tree examples for all 4 repos (team, platform, frontend, gateway)
|
| 222 |
+
- [Frontend Architecture](./references/frontend-architecture.md) β modules, shared Zustand store, API client, D1 tables, how to add features
|
| 223 |
+
- [Development Patterns](./references/development-patterns.md) β how to plan and build new features
|
| 224 |
+
|
| 225 |
+
### Related Skills (separate repos, installed alongside this one)
|
| 226 |
+
|
| 227 |
+
- **pydantic-ai** β PydanticAI agent framework: tools, structured output, orchestration, chat patterns
|
| 228 |
+
- **huggingface-deploy** β deploy the platform (`ifcore-platform`) as a Docker Space on HuggingFace; covers Dockerfile, secrets, R2 caching, and the flatten-before-push submodule pattern
|
| 229 |
+
- **cloudflare** β deploy the frontend + API gateway on Cloudflare Pages/Workers
|
teams/Mastodonte/.claude/skills/IFCore-skill/references/architecture.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Architecture & Conventions
|
| 2 |
+
|
| 3 |
+
## Project Structure
|
| 4 |
+
|
| 5 |
+
```
|
| 6 |
+
your-team-repo/
|
| 7 |
+
βββ tools/
|
| 8 |
+
β βββ checker_doors.py # check_door_width, check_door_clearance
|
| 9 |
+
β βββ checker_fire_safety.py # check_fire_rating, check_exit_count
|
| 10 |
+
β βββ checker_rooms.py # check_room_area, check_ceiling_height
|
| 11 |
+
βββ requirements.txt # team dependencies
|
| 12 |
+
βββ README.md
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
**File naming:** `checker_<topic>.py` β group related checks by topic.
|
| 16 |
+
Only `tools/checker_*.py` matters to the platform. Everything else (local test scripts,
|
| 17 |
+
notebooks, Gradio apps, CLI tools) is your choice β the platform ignores it.
|
| 18 |
+
|
| 19 |
+
**Platform auto-discovery:** the orchestrator scans `teams/*/tools/checker_*.py` and collects
|
| 20 |
+
every `check_*` function. No subdirectories β files must be directly inside `tools/`.
|
| 21 |
+
Helper files (e.g. `tools/utils.py`) are fine for shared code but won't be scanned.
|
| 22 |
+
|
| 23 |
+
**Platform integration:** the platform (`ifcore-platform`) pulls all 5 team repos via git
|
| 24 |
+
submodules and flattens them into `teams/<your-repo>/tools/` before building the Docker image.
|
| 25 |
+
Your repo structure (`tools/checker_*.py` with `check_*` functions) must match this layout
|
| 26 |
+
exactly for auto-discovery to work. Captains handle the pull and flatten via `deploy.sh` β
|
| 27 |
+
teams never push to the platform repo directly.
|
| 28 |
+
|
| 29 |
+
## Code Conventions
|
| 30 |
+
|
| 31 |
+
- **Max 300 lines per file.** Split into modules when approaching the limit.
|
| 32 |
+
- **One function per check.** Don't combine multiple regulation checks.
|
| 33 |
+
- **File names:** `checker_<topic>.py` β e.g. `checker_doors.py`, `checker_fire_safety.py`.
|
| 34 |
+
- **Function names:** `check_<what>` β e.g. `check_door_width`, `check_room_area`.
|
| 35 |
+
- **First arg is always `model`** β an `ifcopenshell.file` object.
|
| 36 |
+
- **Return `list[str]`** β each string prefixed `[PASS]`, `[FAIL]`, or `[???]`.
|
| 37 |
+
- **No bare try/except.** Only catch specific known errors.
|
| 38 |
+
|
| 39 |
+
**What is `model`?** It's an `ifcopenshell.file` object β a parsed IFC file loaded into memory.
|
| 40 |
+
You query it with `model.by_type("IfcDoor")` to get all doors, `model.by_type("IfcWall")` for
|
| 41 |
+
walls, etc. Each element has properties like `.Name`, `.GlobalId`, and type-specific attributes.
|
| 42 |
+
|
| 43 |
+
## AGENTS.md / CLAUDE.md
|
| 44 |
+
|
| 45 |
+
Every team MUST have this file in their repo root. Your AI assistant reads it automatically.
|
| 46 |
+
If it does not exist, create it before starting any work.
|
| 47 |
+
|
| 48 |
+
**Template:**
|
| 49 |
+
```markdown
|
| 50 |
+
# <Project Name>
|
| 51 |
+
|
| 52 |
+
Always read the IFCore skill before developing on this project.
|
| 53 |
+
|
| 54 |
+
## Structure
|
| 55 |
+
<paste your app/ directory tree here>
|
| 56 |
+
|
| 57 |
+
## Conventions
|
| 58 |
+
- Max 300 lines per file
|
| 59 |
+
- One function per regulation check
|
| 60 |
+
- Files: tools/checker_<topic>.py β only checker_*.py files are scanned
|
| 61 |
+
- Functions: check_*(model, ...) -> list[str] with [PASS]/[FAIL]/[???] prefix
|
| 62 |
+
|
| 63 |
+
## Issue Reporting
|
| 64 |
+
When you encounter a contract mismatch, skill gap, or integration problem:
|
| 65 |
+
gh issue create --repo SerjoschDuering/iaac-bimwise-skills --label "<label>" --title "<title>"
|
| 66 |
+
Labels: contract-gap, skill-drift, learning, schema-change, integration-bug
|
| 67 |
+
|
| 68 |
+
## Learnings
|
| 69 |
+
<!-- Add here after every debugging session that reveals a recurring issue -->
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
**Keep it updated.** After any session where you hit a recurring error, add it to Learnings.
|
| 73 |
+
The AI gets smarter with every fix β only if you write it down.
|
teams/Mastodonte/.claude/skills/IFCore-skill/references/development-patterns.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Development Patterns
|
| 2 |
+
|
| 3 |
+
## How to Work With the User
|
| 4 |
+
|
| 5 |
+
You're helping someone who may be new to coding, AI, or both. Before doing anything:
|
| 6 |
+
|
| 7 |
+
- **Ask what they want to build.** Don't assume β let them explain in their own words.
|
| 8 |
+
- **Suggest an approach and explain why.** If you recommend something, say what it does
|
| 9 |
+
and why it's a good fit. Offer to explain any term they might not know.
|
| 10 |
+
- **Break things into small steps.** One thing at a time. Check in after each step.
|
| 11 |
+
- **Use plain language.** Say "a function that checks door widths" not "a callable that
|
| 12 |
+
validates dimensional properties." If you must use a technical term, explain it inline.
|
| 13 |
+
|
| 14 |
+
## When Someone Wants to Add a New Check
|
| 15 |
+
|
| 16 |
+
Start a conversation β don't jump straight to code.
|
| 17 |
+
|
| 18 |
+
1. **Understand what they want.** Ask: "What rule or regulation should this check enforce?"
|
| 19 |
+
Get a concrete example β "doors must be at least 900mm wide" is better than "door check."
|
| 20 |
+
2. **Talk through the approach.** Explain what the check function will do, what IFC data
|
| 21 |
+
it needs, and roughly how it works. Ask if that sounds right.
|
| 22 |
+
3. **Suggest a plan.** Offer to write a short plan first (see template below). This is
|
| 23 |
+
a simple document that describes the goal and what "done" looks like. It helps avoid
|
| 24 |
+
building the wrong thing. If the user prefers to skip it, that's fine β go straight to code.
|
| 25 |
+
4. **Build it together.** Write the check function, test it, and walk through the result.
|
| 26 |
+
Explain what each part does. Update AGENTS.md with anything you learned.
|
| 27 |
+
5. **Review when done.** Suggest a quick review β either in a fresh chat or with a subagent
|
| 28 |
+
if available. This catches things you both might have missed.
|
| 29 |
+
|
| 30 |
+
## Feature Plans (Optional but Helpful)
|
| 31 |
+
|
| 32 |
+
For bigger features, a plan folder keeps things organized:
|
| 33 |
+
|
| 34 |
+
```
|
| 35 |
+
feature-plans/
|
| 36 |
+
βββ F1-door-width-check/
|
| 37 |
+
βββ plan.md # what we're building and why
|
| 38 |
+
βββ phase-a/ # if the feature has multiple phases
|
| 39 |
+
β βββ plan.md # scope for this phase only
|
| 40 |
+
βββ learnings.md # what went wrong, what to do differently
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
A "plan" (sometimes called a PRD β Product Requirements Document) is just a short
|
| 44 |
+
description of what you're building. It helps you think before you code.
|
| 45 |
+
|
| 46 |
+
## Plan Template
|
| 47 |
+
|
| 48 |
+
```markdown
|
| 49 |
+
# F<N>: <Feature Name>
|
| 50 |
+
|
| 51 |
+
## What Are We Building?
|
| 52 |
+
<!-- Explain the goal in plain language. What problem does this solve?
|
| 53 |
+
What will users be able to do after this is built? -->
|
| 54 |
+
|
| 55 |
+
## What Does "Done" Look Like?
|
| 56 |
+
- [ ] ...
|
| 57 |
+
<!-- A checklist. When every box is ticked, the feature is done. -->
|
| 58 |
+
|
| 59 |
+
## How It Works
|
| 60 |
+
<!-- Brief description of the approach. What IFC data do we need?
|
| 61 |
+
What does the check function do? -->
|
| 62 |
+
|
| 63 |
+
## Phases (if it's a big feature)
|
| 64 |
+
- Phase A: ...
|
| 65 |
+
- Phase B: ...
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
Don't overthink this. A few sentences per section is fine. Skip "Phases" for small checks.
|
| 69 |
+
|
| 70 |
+
## After You Finish
|
| 71 |
+
|
| 72 |
+
Suggest a review:
|
| 73 |
+
|
| 74 |
+
> "That check is working. Want me to do a quick review to catch anything we missed?
|
| 75 |
+
> I can do it right now, or you can start a fresh chat and paste this:"
|
| 76 |
+
|
| 77 |
+
```
|
| 78 |
+
I just finished implementing [feature name].
|
| 79 |
+
Codebase is at [path]. Key files changed: [list].
|
| 80 |
+
Please review against the plan at feature-plans/F<N>-<name>/plan.md
|
| 81 |
+
and check for IFCore contract compliance.
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
If subagents are available, offer to run the review automatically.
|
teams/Mastodonte/.claude/skills/IFCore-skill/references/frontend-architecture.md
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Frontend Architecture
|
| 2 |
+
|
| 3 |
+
Modular web app. Each feature (upload, results, 3D viewer, dashboard) is a
|
| 4 |
+
**module** β a self-contained folder. Backend operations are **async jobs**.
|
| 5 |
+
|
| 6 |
+
> **New to web development?** A frontend is the part users see and click on
|
| 7 |
+
> (the website). The backend is the server doing the heavy work (running checks).
|
| 8 |
+
> They talk to each other through an **API** β a set of URLs the frontend
|
| 9 |
+
> calls to send or receive data.
|
| 10 |
+
|
| 11 |
+
## Structure
|
| 12 |
+
|
| 13 |
+
```
|
| 14 |
+
src/
|
| 15 |
+
βββ app.js β shell: nav bar + router
|
| 16 |
+
βββ api.js β shared API client β CF Worker
|
| 17 |
+
βββ store.js β shared state (Zustand)
|
| 18 |
+
βββ poller.js β polls active jobs, updates store
|
| 19 |
+
βββ modules/
|
| 20 |
+
β βββ upload/ β file upload
|
| 21 |
+
β βββ results/ β results table
|
| 22 |
+
β βββ viewer-3d/ β IFC 3D viewer
|
| 23 |
+
β βββ dashboard/ β analytics
|
| 24 |
+
βββ shared/ β reusable components
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
## Shell + Router
|
| 28 |
+
|
| 29 |
+
Nav bar at top, content area below. Router swaps modules like tabs.
|
| 30 |
+
|
| 31 |
+
> **What's a router?** It watches the URL in the browser. When the URL says
|
| 32 |
+
> `/results`, it shows the results module. When it says `/dashboard`, it shows
|
| 33 |
+
> the dashboard. Each "page" is a module, but it's all one app β no page reloads.
|
| 34 |
+
|
| 35 |
+
```
|
| 36 |
+
ββββββββββββββββββββββββββββββββββββββββ
|
| 37 |
+
β Nav: Upload | Results | 3D | Dash β
|
| 38 |
+
ββββββββββββββββββββββββββββββββββββββββ€
|
| 39 |
+
β β active module renders here β β
|
| 40 |
+
ββββββββββββββββββββββββββββββββββββββββ
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
Each module exports `mount(container)`. That's the only contract.
|
| 44 |
+
|
| 45 |
+
## Async Job Pattern
|
| 46 |
+
|
| 47 |
+
Backend tasks (IFC checks, AI agents) take 10-60 seconds. Too slow for
|
| 48 |
+
a normal request-response. Everything uses **async jobs**.
|
| 49 |
+
|
| 50 |
+
> **What's async?** Normally when you click a button, the browser waits for
|
| 51 |
+
> the server to respond β that's synchronous ("sync"). But if the server needs
|
| 52 |
+
> 30 seconds, the browser would freeze. **Async** means: "start the work,
|
| 53 |
+
> come back and check later." The server returns a job ID immediately,
|
| 54 |
+
> and the frontend keeps checking ("polling") until the job is done.
|
| 55 |
+
|
| 56 |
+
```
|
| 57 |
+
Frontend Worker (proxy) HF Space (FastAPI)
|
| 58 |
+
ββββββββ ββββββββββββββ ββββββββββββββββββ
|
| 59 |
+
POST /check βββ> proxy βββ> start background task
|
| 60 |
+
<βββ {jobId} <βββ return {jobId} immediately
|
| 61 |
+
|
| 62 |
+
poll GET /jobs/id read D1 ...working...
|
| 63 |
+
<βββ {status:"running"}
|
| 64 |
+
|
| 65 |
+
poll GET /jobs/id read D1 βββ> POST /jobs/id/complete (callback)
|
| 66 |
+
<βββ {status:"done", data:[...]}
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
**Why this pattern?** The CF Worker has a 10ms CPU limit on the free tier β
|
| 70 |
+
it physically can't wait 30 seconds. So it just reads/writes to the database
|
| 71 |
+
and returns. The heavy work happens on the HF Space.
|
| 72 |
+
|
| 73 |
+
> **What's a callback?** When the HF Space finishes, it calls the Worker
|
| 74 |
+
> back ("hey, job X is done, here are the results"). The Worker writes
|
| 75 |
+
> the results to the database. Next time the frontend polls, it gets them.
|
| 76 |
+
|
| 77 |
+
### Recipe: Adding a New Async Endpoint
|
| 78 |
+
|
| 79 |
+
Three files. Always the same pattern.
|
| 80 |
+
|
| 81 |
+
| File | What to add |
|
| 82 |
+
|------|-------------|
|
| 83 |
+
| **HF Space** `main.py` | `POST /your-thing` β starts `BackgroundTasks`, returns `{jobId}`. When done, POSTs results back to Worker. |
|
| 84 |
+
| **Worker** | Proxy route for `POST /api/your-thing`. Job tracking routes (`GET /api/jobs/:id`, `POST /api/jobs/:id/complete`) are shared β built once. |
|
| 85 |
+
| **Frontend** `api.js` | `startYourThing(fileUrl)` β returns `{jobId}`. Call `store.trackJob(jobId)` β poller handles the rest. |
|
| 86 |
+
|
| 87 |
+
> **What's an endpoint?** A specific URL the server listens on. `POST /check`
|
| 88 |
+
> is an endpoint. `GET /jobs/123` is another. Think of them as doors into the
|
| 89 |
+
> backend β each door does one thing.
|
| 90 |
+
|
| 91 |
+
## Shared State (Zustand)
|
| 92 |
+
|
| 93 |
+
**Zustand** is a tiny state library (~1KB). Think of it as a shared whiteboard β
|
| 94 |
+
any module can read or write to it. The poller updates it when jobs complete.
|
| 95 |
+
|
| 96 |
+
> **What's state?** The data your app is currently showing. "Which file is
|
| 97 |
+
> selected? Are checks running? What were the results?" That's all state.
|
| 98 |
+
> Without a shared store, each module would have its own copy and they'd
|
| 99 |
+
> get out of sync. Zustand keeps one source of truth.
|
| 100 |
+
|
| 101 |
+
**How modules use it:**
|
| 102 |
+
- **Upload** sets `currentFile`, starts a job β `trackJob(jobId)`
|
| 103 |
+
- **Results** reads `getActiveResults()` β renders a table
|
| 104 |
+
- **3D Viewer** reads results β highlights failing elements in red
|
| 105 |
+
- **Dashboard** reads results β shows charts and stats
|
| 106 |
+
|
| 107 |
+
They all see the same data. When a job completes, everything re-renders.
|
| 108 |
+
|
| 109 |
+
```javascript
|
| 110 |
+
// store.js β schematic
|
| 111 |
+
{
|
| 112 |
+
currentFile: null, // { url, name }
|
| 113 |
+
jobs: {}, // { [jobId]: { status, data, startedAt } }
|
| 114 |
+
activeJobId: null,
|
| 115 |
+
trackJob(jobId), // start tracking
|
| 116 |
+
completeJob(jobId, data), // poller calls this
|
| 117 |
+
getActiveResults(), // results for active job
|
| 118 |
+
}
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
**Poller:** every 2s, calls `GET /api/jobs/:id` for running jobs.
|
| 122 |
+
When status flips to `"done"`, calls `store.completeJob()`.
|
| 123 |
+
|
| 124 |
+
**API client** (`api.js`): all modules go through this β never call `fetch()` directly.
|
| 125 |
+
Key functions: `uploadFile()`, `startCheck()`, `getJob()`, `getStats()`.
|
| 126 |
+
|
| 127 |
+
> **What's `fetch()`?** The browser's built-in way to call an API. `api.js`
|
| 128 |
+
> wraps it so you don't repeat the base URL and error handling everywhere.
|
| 129 |
+
|
| 130 |
+
## Module Pattern
|
| 131 |
+
|
| 132 |
+
Modules render once, then **subscribe** to re-render on state changes:
|
| 133 |
+
|
| 134 |
+
```javascript
|
| 135 |
+
// modules/summary/index.js β schematic
|
| 136 |
+
export function mount(container) {
|
| 137 |
+
function render() {
|
| 138 |
+
const results = useStore.getState().getActiveResults()
|
| 139 |
+
container.innerHTML = `${passed} passed, ${failed} failed`
|
| 140 |
+
}
|
| 141 |
+
render() // initial
|
| 142 |
+
useStore.subscribe(render) // re-render on change
|
| 143 |
+
}
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
> **What's subscribe?** "Notify me when something changes." Without it, your
|
| 147 |
+
> module renders once and never updates. With `subscribe(render)`, every time
|
| 148 |
+
> the store changes (new results, job completed), your module re-renders
|
| 149 |
+
> automatically.
|
| 150 |
+
|
| 151 |
+
**Rules:**
|
| 152 |
+
- Don't import from other modules
|
| 153 |
+
- Read state from `store.js` via `subscribe()`
|
| 154 |
+
- Call backend through `api.js`
|
| 155 |
+
- One folder, one concern
|
| 156 |
+
|
| 157 |
+
## Adding a Module (Checklist)
|
| 158 |
+
|
| 159 |
+
1. Create `src/modules/<name>/index.js` with `mount()` + `subscribe()`
|
| 160 |
+
2. Register route in `app.js`
|
| 161 |
+
3. If it needs a new backend endpoint β follow async recipe above
|
| 162 |
+
|
| 163 |
+
## Adding Shared State
|
| 164 |
+
|
| 165 |
+
Only if multiple modules need it. Otherwise keep local.
|
| 166 |
+
|
| 167 |
+
1. Add to `store.js`: state field + setter
|
| 168 |
+
2. Read from modules via `getState()` + `subscribe()`
|
| 169 |
+
|
| 170 |
+
## Database (D1)
|
| 171 |
+
|
| 172 |
+
> **What's D1?** Cloudflare's database service. It runs SQLite (a simple
|
| 173 |
+
> database that stores data in tables, like a spreadsheet). The frontend
|
| 174 |
+
> never talks to D1 directly β it goes through the Worker API.
|
| 175 |
+
|
| 176 |
+
```sql
|
| 177 |
+
CREATE TABLE users (
|
| 178 |
+
id TEXT PRIMARY KEY,
|
| 179 |
+
name TEXT,
|
| 180 |
+
team TEXT, -- e.g. "ifcore-team-a", nullable
|
| 181 |
+
created_at INTEGER
|
| 182 |
+
);
|
| 183 |
+
|
| 184 |
+
CREATE TABLE projects (
|
| 185 |
+
id TEXT PRIMARY KEY,
|
| 186 |
+
user_id TEXT REFERENCES users(id),
|
| 187 |
+
name TEXT,
|
| 188 |
+
file_url TEXT,
|
| 189 |
+
ifc_schema TEXT, -- e.g. "IFC4", null if unknown
|
| 190 |
+
region TEXT, -- e.g. "CH", null if unknown
|
| 191 |
+
building_type TEXT, -- e.g. "residential", null
|
| 192 |
+
metadata TEXT, -- JSON blob, nullable
|
| 193 |
+
created_at INTEGER
|
| 194 |
+
);
|
| 195 |
+
|
| 196 |
+
CREATE TABLE check_results (
|
| 197 |
+
id TEXT PRIMARY KEY,
|
| 198 |
+
project_id TEXT REFERENCES projects(id),
|
| 199 |
+
job_id TEXT, -- groups results from one run
|
| 200 |
+
check_name TEXT, -- e.g. "check_door_width"
|
| 201 |
+
team TEXT, -- e.g. "ifcore-team-a"
|
| 202 |
+
status TEXT DEFAULT 'running', -- pass | fail | unknown | error | running
|
| 203 |
+
summary TEXT, -- "14 doors: 12 pass, 2 fail"
|
| 204 |
+
has_elements INTEGER DEFAULT 0,
|
| 205 |
+
created_at INTEGER
|
| 206 |
+
);
|
| 207 |
+
|
| 208 |
+
CREATE TABLE element_results (
|
| 209 |
+
id TEXT PRIMARY KEY,
|
| 210 |
+
check_result_id TEXT REFERENCES check_results(id),
|
| 211 |
+
element_id TEXT, -- IFC GlobalId (nullable)
|
| 212 |
+
element_type TEXT, -- e.g. "IfcDoor" (nullable)
|
| 213 |
+
element_name TEXT, -- e.g. "Door #42" (nullable)
|
| 214 |
+
element_name_long TEXT, -- e.g. "Door #42 (Level 1, Zone A)" (nullable)
|
| 215 |
+
check_status TEXT, -- pass | fail | warning | blocked | log
|
| 216 |
+
actual_value TEXT, -- e.g. "750 mm"
|
| 217 |
+
required_value TEXT, -- e.g. "800 mm"
|
| 218 |
+
comment TEXT, -- human-readable explanation (nullable)
|
| 219 |
+
log TEXT -- debug/trace info (nullable)
|
| 220 |
+
);
|
| 221 |
+
```
|
| 222 |
+
|
| 223 |
+
See [Validation Schema](./validation-schema.md) for how team `list[dict]` output maps to these rows.
|
| 224 |
+
|
| 225 |
+
> **What's a migration?** A file that changes the database structure
|
| 226 |
+
> (adds a table, adds a column). You run it once with `wrangler d1 execute`.
|
| 227 |
+
> It's like a recipe for the database β run it, and the new table exists.
|
| 228 |
+
|
| 229 |
+
**Adding a new table:** migration file β `wrangler d1 execute` β Worker endpoint β `api.js` function β module uses it.
|
| 230 |
+
|
| 231 |
+
## PRD Review (Wednesday)
|
| 232 |
+
|
| 233 |
+
> **What's a PRD?** Product Requirements Document β a short plan describing
|
| 234 |
+
> what you're building, why, and what "done" looks like. Doesn't need to be
|
| 235 |
+
> fancy. Half a page is fine.
|
| 236 |
+
|
| 237 |
+
Before building, each team writes a PRD for their module. All reviewed together:
|
| 238 |
+
- What each team builds
|
| 239 |
+
- What async endpoints are needed
|
| 240 |
+
- What shared state each module expects
|
| 241 |
+
- Whether modules overlap
|
teams/Mastodonte/.claude/skills/IFCore-skill/references/repo-structure.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Repository Structure
|
| 2 |
+
|
| 3 |
+
## Team repos β `ifcore-team-a` β¦ `ifcore-team-e`
|
| 4 |
+
One repo per team. Students only ever touch their own.
|
| 5 |
+
```
|
| 6 |
+
ifcore-team-a/
|
| 7 |
+
βββ tools/
|
| 8 |
+
β βββ checker_doors.py # check_door_width, check_door_clearance
|
| 9 |
+
β βββ checker_fire_safety.py # check_fire_rating, check_exit_count
|
| 10 |
+
β βββ checker_rooms.py # check_room_area, check_ceiling_height
|
| 11 |
+
βββ requirements.txt
|
| 12 |
+
βββ AGENTS.md
|
| 13 |
+
βββ README.md
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
## Platform monorepo β `ifcore-platform`
|
| 17 |
+
**One repo. Two folders. Two deployments.**
|
| 18 |
+
```
|
| 19 |
+
ifcore-platform/ β ONE git repo
|
| 20 |
+
β
|
| 21 |
+
βββ backend/ β deploys to HuggingFace Space
|
| 22 |
+
β βββ README.md β HF frontmatter (sdk: docker, app_port: 7860)
|
| 23 |
+
β βββ Dockerfile
|
| 24 |
+
β βββ requirements.txt
|
| 25 |
+
β βββ main.py
|
| 26 |
+
β βββ orchestrator.py
|
| 27 |
+
β βββ deploy.sh
|
| 28 |
+
β βββ teams/ β gitignored, populated by deploy.sh
|
| 29 |
+
β βββ ifcore-team-a/tools/checker_*.py
|
| 30 |
+
β βββ ifcore-team-b/tools/checker_*.py
|
| 31 |
+
β βββ ... β one folder per team, flattened from submodules
|
| 32 |
+
β
|
| 33 |
+
βββ frontend/ β deploys to Cloudflare (Pages + Worker)
|
| 34 |
+
βββ public/
|
| 35 |
+
β βββ index.html
|
| 36 |
+
βββ src/ β static app (CF Pages)
|
| 37 |
+
β βββ app.js
|
| 38 |
+
β βββ api.js
|
| 39 |
+
β βββ store.js
|
| 40 |
+
β βββ poller.js
|
| 41 |
+
β βββ modules/
|
| 42 |
+
β βββ upload/index.js
|
| 43 |
+
β βββ results/index.js
|
| 44 |
+
β βββ viewer-3d/index.js
|
| 45 |
+
β βββ dashboard/index.js
|
| 46 |
+
βββ functions/ β API gateway (CF Pages Functions = Worker)
|
| 47 |
+
β βββ api/
|
| 48 |
+
β βββ [[route]].js
|
| 49 |
+
βββ migrations/
|
| 50 |
+
β βββ 0001_create_jobs.sql
|
| 51 |
+
βββ package.json
|
| 52 |
+
βββ wrangler.toml β D1 + R2 bindings, custom domain
|
| 53 |
+
```
|
teams/Mastodonte/.claude/skills/IFCore-skill/references/validation-schema.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Validation Schema
|
| 2 |
+
|
| 3 |
+
Two layers: what teams produce, and how the platform stores it.
|
| 4 |
+
|
| 5 |
+
## Team Output (locked β Board Meeting #1)
|
| 6 |
+
|
| 7 |
+
Each `check_*` function returns `list[dict]`. Each dict is one element checked,
|
| 8 |
+
mapping directly to one `element_results` row in the database.
|
| 9 |
+
|
| 10 |
+
```python
|
| 11 |
+
def check_door_width(model, min_width_mm=800):
|
| 12 |
+
results = []
|
| 13 |
+
for door in model.by_type("IfcDoor"):
|
| 14 |
+
width_mm = round(door.OverallWidth * 1000) if door.OverallWidth else None
|
| 15 |
+
results.append({
|
| 16 |
+
"element_id": door.GlobalId,
|
| 17 |
+
"element_type": "IfcDoor",
|
| 18 |
+
"element_name": door.Name or f"Door #{door.id()}",
|
| 19 |
+
"element_name_long": f"{door.Name} (Level 1, Zone A)",
|
| 20 |
+
"check_status": "blocked" if width_mm is None
|
| 21 |
+
else "pass" if width_mm >= min_width_mm
|
| 22 |
+
else "fail",
|
| 23 |
+
"actual_value": f"{width_mm} mm" if width_mm else None,
|
| 24 |
+
"required_value": f"{min_width_mm} mm",
|
| 25 |
+
"comment": None if width_mm and width_mm >= min_width_mm
|
| 26 |
+
else f"Door is {min_width_mm - width_mm} mm too narrow"
|
| 27 |
+
if width_mm else "Width property missing",
|
| 28 |
+
"log": None,
|
| 29 |
+
})
|
| 30 |
+
return results
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
**Required dict fields** (teams produce these β `id` and `check_result_id` are added by the orchestrator):
|
| 34 |
+
|
| 35 |
+
| Field | Type | Description |
|
| 36 |
+
|-------|------|-------------|
|
| 37 |
+
| `element_id` | string \| null | IFC GlobalId |
|
| 38 |
+
| `element_type` | string \| null | e.g. `"IfcDoor"`, `"IfcWall"` |
|
| 39 |
+
| `element_name` | string \| null | Short name, e.g. `"Door #42"` |
|
| 40 |
+
| `element_name_long` | string \| null | Detailed name with context, e.g. `"Door #42 (Level 1, Zone A)"` |
|
| 41 |
+
| `check_status` | string | **`pass`** \| **`fail`** \| **`warning`** \| **`blocked`** \| **`log`** |
|
| 42 |
+
| `actual_value` | string \| null | What was found, e.g. `"750 mm"` |
|
| 43 |
+
| `required_value` | string \| null | What the regulation requires, e.g. `"800 mm"` |
|
| 44 |
+
| `comment` | string \| null | Human-readable explanation (why it failed, what's wrong) |
|
| 45 |
+
| `log` | string \| null | Debug/trace info (optional, for troubleshooting) |
|
| 46 |
+
|
| 47 |
+
**`check_status` values:**
|
| 48 |
+
- `"pass"` β element meets the requirement
|
| 49 |
+
- `"fail"` β element violates the requirement
|
| 50 |
+
- `"warning"` β element is borderline or needs manual review
|
| 51 |
+
- `"blocked"` β data missing, check cannot run (e.g. property not found)
|
| 52 |
+
- `"log"` β informational output, not a pass/fail judgment
|
| 53 |
+
|
| 54 |
+
## Platform Database Schema (D1)
|
| 55 |
+
|
| 56 |
+
Four tables. The frontend reads from these via the CF Worker API.
|
| 57 |
+
|
| 58 |
+
```
|
| 59 |
+
βββββββββββ βββββββββββββββ ββββββββββββββββββ ββββββββββββββββββββ
|
| 60 |
+
β users β 1βββ* β projects β 1βββ* β check_results β 1βββ* β element_results β
|
| 61 |
+
βββββββββββ βββββββββββββββ ββββββββββββββββββ ββββββββββββββββββββ
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
### `users` β one row per person
|
| 65 |
+
|
| 66 |
+
```json
|
| 67 |
+
{
|
| 68 |
+
"id": "string",
|
| 69 |
+
"name": "string",
|
| 70 |
+
"team": "string | null",
|
| 71 |
+
"created_at": "integer"
|
| 72 |
+
}
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
### `projects` β one row per uploaded IFC file
|
| 76 |
+
|
| 77 |
+
```json
|
| 78 |
+
{
|
| 79 |
+
"id": "string",
|
| 80 |
+
"user_id": "string",
|
| 81 |
+
"name": "string",
|
| 82 |
+
"file_url": "string",
|
| 83 |
+
"ifc_schema": "string | null",
|
| 84 |
+
"region": "string | null",
|
| 85 |
+
"building_type": "string | null",
|
| 86 |
+
"metadata": "string | null (JSON)",
|
| 87 |
+
"created_at": "integer"
|
| 88 |
+
}
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
### `check_results` β one row per `check_*` function run
|
| 92 |
+
|
| 93 |
+
```json
|
| 94 |
+
{
|
| 95 |
+
"id": "string",
|
| 96 |
+
"project_id": "string",
|
| 97 |
+
"job_id": "string",
|
| 98 |
+
"check_name": "string",
|
| 99 |
+
"team": "string",
|
| 100 |
+
"status": "string (running | pass | fail | unknown | error)",
|
| 101 |
+
"summary": "string",
|
| 102 |
+
"has_elements": "integer (0 | 1)",
|
| 103 |
+
"created_at": "integer"
|
| 104 |
+
}
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
- `check_name`: the function name, e.g. `check_door_width`
|
| 108 |
+
- `team`: derived from the repo folder name, e.g. `ifcore-team-a`
|
| 109 |
+
- `status`: `running` while job is in progress; then aggregate β `pass` if all elements pass, `fail` if any fail, `error` if the function threw
|
| 110 |
+
- `summary`: human-readable, e.g. "14 doors checked: 12 pass, 2 fail"
|
| 111 |
+
- `has_elements`: `1` if the check produced element-level results, `0` otherwise
|
| 112 |
+
|
| 113 |
+
### `element_results` β one row per element checked
|
| 114 |
+
|
| 115 |
+
```json
|
| 116 |
+
{
|
| 117 |
+
"id": "string",
|
| 118 |
+
"check_result_id": "string",
|
| 119 |
+
"element_id": "string | null",
|
| 120 |
+
"element_type": "string | null",
|
| 121 |
+
"element_name": "string | null",
|
| 122 |
+
"element_name_long":"string | null",
|
| 123 |
+
"check_status": "string (pass | fail | warning | blocked | log)",
|
| 124 |
+
"actual_value": "string | null",
|
| 125 |
+
"required_value": "string | null",
|
| 126 |
+
"comment": "string | null",
|
| 127 |
+
"log": "string | null"
|
| 128 |
+
}
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
- `id`, `check_result_id`: added by the orchestrator (teams don't produce these)
|
| 132 |
+
- `element_id`: IFC GlobalId (if available)
|
| 133 |
+
- `check_status`: matches what the team function returns β not aggregated
|
| 134 |
+
- `comment`: human-readable explanation of the result
|
| 135 |
+
- `log`: debug/trace info for troubleshooting
|
| 136 |
+
|
| 137 |
+
## How It Fits Together
|
| 138 |
+
|
| 139 |
+
```
|
| 140 |
+
Team function returns:
|
| 141 |
+
[
|
| 142 |
+
{"element_id": "2O2Fr$t4X7Z", "element_type": "IfcDoor",
|
| 143 |
+
"element_name": "Door #42", "element_name_long": "Door #42 (Level 1)",
|
| 144 |
+
"check_status": "pass", "actual_value": "850 mm", "required_value": "800 mm",
|
| 145 |
+
"comment": null, "log": null},
|
| 146 |
+
{"element_id": "1B3Rs$u5Y8A", "element_type": "IfcDoor",
|
| 147 |
+
"element_name": "Door #17", "element_name_long": "Door #17 (Level 2)",
|
| 148 |
+
"check_status": "fail", "actual_value": "750 mm", "required_value": "800 mm",
|
| 149 |
+
"comment": "Door is 50 mm too narrow", "log": null}
|
| 150 |
+
]
|
| 151 |
+
|
| 152 |
+
Orchestrator creates:
|
| 153 |
+
|
| 154 |
+
check_results row:
|
| 155 |
+
check_name = "check_door_width"
|
| 156 |
+
team = "ifcore-team-a"
|
| 157 |
+
status = "fail" β any fail β whole check fails
|
| 158 |
+
summary = "2 doors: 1 pass, 1 fail"
|
| 159 |
+
has_elements = 1
|
| 160 |
+
|
| 161 |
+
element_results rows: (one per dict, id + check_result_id added)
|
| 162 |
+
{ element_name: "Door #42", check_status: "pass", actual_value: "850 mm", ... }
|
| 163 |
+
{ element_name: "Door #17", check_status: "fail", actual_value: "750 mm", comment: "Door is 50 mm too narrow", ... }
|
| 164 |
+
```
|
teams/Mastodonte/.gitignore
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =========================
|
| 2 |
+
# Python cache
|
| 3 |
+
# =========================
|
| 4 |
+
__pycache__/
|
| 5 |
+
*.py[cod]
|
| 6 |
+
*$py.class
|
| 7 |
+
|
| 8 |
+
# =========================
|
| 9 |
+
# Virtual environments
|
| 10 |
+
# =========================
|
| 11 |
+
venv/
|
| 12 |
+
.env/
|
| 13 |
+
.venv/
|
| 14 |
+
env/
|
| 15 |
+
ENV/
|
| 16 |
+
|
| 17 |
+
# =========================
|
| 18 |
+
# Jupyter
|
| 19 |
+
# =========================
|
| 20 |
+
.ipynb_checkpoints/
|
| 21 |
+
|
| 22 |
+
# =========================
|
| 23 |
+
# OS files
|
| 24 |
+
# =========================
|
| 25 |
+
.DS_Store
|
| 26 |
+
Thumbs.db
|
| 27 |
+
|
| 28 |
+
# =========================
|
| 29 |
+
# Logs
|
| 30 |
+
# =========================
|
| 31 |
+
*.log
|
| 32 |
+
|
| 33 |
+
# =========================
|
| 34 |
+
# IDE / Editor
|
| 35 |
+
# =========================
|
| 36 |
+
.vscode/
|
| 37 |
+
.idea/
|
| 38 |
+
|
| 39 |
+
# =========================
|
| 40 |
+
# Build / Packaging
|
| 41 |
+
# =========================
|
| 42 |
+
build/
|
| 43 |
+
dist/
|
| 44 |
+
*.egg-info/
|
| 45 |
+
.ifc/
|
teams/Mastodonte/01_team_b
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Team B β User Portal
|
| 2 |
+
|
| 3 |
+
**Branch:** `feature/team-B-adminpanel` | **Verdict:** NEEDS CHANGES (major scope reduction)
|
| 4 |
+
|
| 5 |
+
| Criterion | Score | Notes |
|
| 6 |
+
|-----------|-------|-------|
|
| 7 |
+
| Architecture Fit | 3/5 | Correctly uses CF Worker + Pages + D1, but ignores existing patterns |
|
| 8 |
+
| Scope Feasibility | 1/5 | 4-week enterprise plan for a 1-day sprint |
|
| 9 |
+
| Shared State | 1/5 | No Zustand slice docs. No Reads/Writes section |
|
| 10 |
+
| API Design | 2/5 | 13+ new endpoints, none follow async job pattern |
|
| 11 |
+
| Security | 3/5 | Good on paper, impossible to implement correctly in 1 day |
|
| 12 |
+
|
| 13 |
+
**Shared State:** Reads `store.projects` (profile page). Writes: none (uses Better Auth `useSession()` hook).
|
| 14 |
+
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
Hey Team B! You took on one of the most important features in the entire platform.
|
| 18 |
+
Authentication is the backbone that every other team depends on.
|
| 19 |
+
Here is where you stand and what to do tomorrow.
|
| 20 |
+
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
## What You Did Well
|
| 24 |
+
|
| 25 |
+
1. **You thought like a real product team.** Your PRD covers email verification,
|
| 26 |
+
billing, support tickets, account deletion, RBAC, monitoring -- that is a
|
| 27 |
+
complete SaaS auth system. The fact that you thought through edge cases
|
| 28 |
+
like stale Stripe webhooks and partial provisioning rollback shows genuine
|
| 29 |
+
product thinking. That matters.
|
| 30 |
+
|
| 31 |
+
2. **You picked the right infrastructure.** Cloudflare Workers for API, Pages
|
| 32 |
+
for frontend, D1 for storage -- this matches the platform architecture exactly.
|
| 33 |
+
You did not invent your own deployment target or try to spin up a separate
|
| 34 |
+
server. That alignment saves everyone time.
|
| 35 |
+
|
| 36 |
+
3. **Security was on your mind from the start.** HTTP-only cookies, CSRF protection,
|
| 37 |
+
SameSite headers, encrypted fields, PCI considerations for Stripe -- you
|
| 38 |
+
clearly researched what secure auth looks like. That awareness is more valuable
|
| 39 |
+
than most people realize.
|
| 40 |
+
|
| 41 |
+
---
|
| 42 |
+
|
| 43 |
+
## The Big Picture
|
| 44 |
+
|
| 45 |
+
The IFCore platform has a specific architecture. Here is the part that matters for you:
|
| 46 |
+
|
| 47 |
+
```
|
| 48 |
+
Browser --> Cloudflare Worker (API + auth) --> HuggingFace Space (IFC checks)
|
| 49 |
+
|
|
| 50 |
+
D1 (database)
|
| 51 |
+
R2 (file storage)
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
Your job is to add **one thing** to this picture: a way for users to sign up,
|
| 55 |
+
log in, and have the platform know who they are. That is it. Every other team
|
| 56 |
+
(3D viewer, dashboard, report generator) just needs `useSession()` to get the
|
| 57 |
+
current user. If you ship that, you unblock everyone.
|
| 58 |
+
|
| 59 |
+
The platform already has a `users` table in D1:
|
| 60 |
+
```sql
|
| 61 |
+
CREATE TABLE users (id TEXT PRIMARY KEY, name TEXT, team TEXT, created_at INTEGER);
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
Your auth system needs to work **alongside** this, not replace it.
|
| 65 |
+
|
| 66 |
+
---
|
| 67 |
+
|
| 68 |
+
## What Needs to Change
|
| 69 |
+
|
| 70 |
+
### 1. Scope: cut 80% of the features
|
| 71 |
+
|
| 72 |
+
**Why:** Your PRD describes a 4-week enterprise build. The course ends tomorrow.
|
| 73 |
+
Even experienced engineers at companies like Auth0 took months to build what you
|
| 74 |
+
described. This is not a criticism -- it means you were thinking big. But shipping
|
| 75 |
+
something real beats having a perfect plan that never runs.
|
| 76 |
+
|
| 77 |
+
**What to keep:** Sign up, log in, log out, and a profile page. That is your MVP.
|
| 78 |
+
|
| 79 |
+
### 2. Use Better Auth instead of building auth from scratch
|
| 80 |
+
|
| 81 |
+
**Why:** The Cloudflare skill that your AI agent has access to includes a complete
|
| 82 |
+
Better Auth blueprint. Better Auth is an open-source library that handles password
|
| 83 |
+
hashing, sessions, cookies, and database tables automatically. It works with D1,
|
| 84 |
+
Drizzle, and Hono -- exactly the stack the platform uses.
|
| 85 |
+
|
| 86 |
+
Building auth from scratch (your own password hashing, session tokens, CSRF
|
| 87 |
+
protection) is a weeks-long project with serious security risks if done wrong.
|
| 88 |
+
Better Auth gives you all of that in about 50 lines of config.
|
| 89 |
+
|
| 90 |
+
**Critical detail:** Do NOT use bcrypt for password hashing. Cloudflare Workers
|
| 91 |
+
have a 10ms CPU limit on the free tier. Bcrypt exceeds that limit and your signup
|
| 92 |
+
will fail with Error 1102. The Cloudflare skill has a PBKDF2 hasher that works
|
| 93 |
+
within Workers limits.
|
| 94 |
+
|
| 95 |
+
### 3. Do not modify the existing `users` table
|
| 96 |
+
|
| 97 |
+
**Why:** Other teams already depend on the current schema. If you change it,
|
| 98 |
+
their code breaks. Better Auth creates its own tables (`user`, `session`,
|
| 99 |
+
`account`, `verification`) -- it does not touch the existing `users` table.
|
| 100 |
+
This is additive, not destructive.
|
| 101 |
+
|
| 102 |
+
### 4. Follow the platform's shared state pattern
|
| 103 |
+
|
| 104 |
+
**Why:** The platform uses Zustand for client-side state. Each feature gets a
|
| 105 |
+
"slice" that other teams can read from. Your PRD did not include a Shared State
|
| 106 |
+
section, which means other teams would not know how to access auth data.
|
| 107 |
+
|
| 108 |
+
The good news: Better Auth provides a `useSession()` React hook. Other teams
|
| 109 |
+
can just call it. You do not even need a Zustand slice -- the hook is the
|
| 110 |
+
interface.
|
| 111 |
+
|
| 112 |
+
### 5. Drop all external service dependencies
|
| 113 |
+
|
| 114 |
+
**Why:** Stripe requires a merchant account. Email verification requires a
|
| 115 |
+
transactional email provider (SendGrid, Resend, etc.). TOTP/2FA requires
|
| 116 |
+
a mobile app integration. None of these are provisioned, and setting them up
|
| 117 |
+
takes longer than building the auth feature itself.
|
| 118 |
+
|
| 119 |
+
---
|
| 120 |
+
|
| 121 |
+
## Your 1-Day Game Plan
|
| 122 |
+
|
| 123 |
+
Your AI coding agent has the **Cloudflare skill** installed. It contains the
|
| 124 |
+
exact Better Auth blueprint below. Tell your agent: "Use the Cloudflare skill's
|
| 125 |
+
Better Auth pattern to set up authentication." It will know what to do.
|
| 126 |
+
|
| 127 |
+
### Morning (3 hours): Backend auth
|
| 128 |
+
|
| 129 |
+
**Step 1 -- Install Better Auth** (10 min)
|
| 130 |
+
```bash
|
| 131 |
+
cd frontend
|
| 132 |
+
npm install better-auth drizzle-orm
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
**Step 2 -- Add the nodejs_compat flag** (5 min)
|
| 136 |
+
|
| 137 |
+
In `frontend/wrangler.jsonc`, add:
|
| 138 |
+
```jsonc
|
| 139 |
+
"compatibility_flags": ["nodejs_compat"]
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
**Step 2b -- Update the Bindings type** (5 min)
|
| 143 |
+
|
| 144 |
+
In `frontend/worker/types.ts`, add the auth secrets:
|
| 145 |
+
```typescript
|
| 146 |
+
export type Bindings = {
|
| 147 |
+
DB: D1Database;
|
| 148 |
+
STORAGE: R2Bucket;
|
| 149 |
+
ASSETS: Fetcher;
|
| 150 |
+
HF_SPACE_URL: string;
|
| 151 |
+
BETTER_AUTH_SECRET: string; // add this
|
| 152 |
+
BETTER_AUTH_URL: string; // add this
|
| 153 |
+
};
|
| 154 |
+
```
|
| 155 |
+
|
| 156 |
+
**Step 3 -- Create the auth config** (30 min)
|
| 157 |
+
|
| 158 |
+
Create `frontend/worker/lib/auth.ts`. The Cloudflare skill has the full file
|
| 159 |
+
including the PBKDF2 hasher. Key points:
|
| 160 |
+
- Use `drizzleAdapter(db, { provider: 'sqlite' })` for D1
|
| 161 |
+
- Set `basePath: '/api/auth'` (NOT `/auth` -- see note below)
|
| 162 |
+
- Add both localhost and production URLs to `trustedOrigins`
|
| 163 |
+
- Use the PBKDF2 hash/verify functions (NOT bcrypt)
|
| 164 |
+
|
| 165 |
+
**Critical path note:** The Worker only intercepts requests matching
|
| 166 |
+
`run_worker_first: ["/api/*"]` in `wrangler.jsonc`. If you mount auth
|
| 167 |
+
at `/auth/*`, requests will never reach the Worker -- they hit the
|
| 168 |
+
static asset handler and 404. Always use `/api/auth/*`.
|
| 169 |
+
|
| 170 |
+
**Step 4 -- Mount auth routes in the Worker** (15 min)
|
| 171 |
+
|
| 172 |
+
First, fix CORS. The existing `cors({ origin: "*" })` is incompatible with
|
| 173 |
+
auth cookies (`credentials: 'include'`). Browsers reject wildcard origins
|
| 174 |
+
when credentials are sent. Replace with an explicit allowlist:
|
| 175 |
+
```typescript
|
| 176 |
+
app.use("/api/*", cors({
|
| 177 |
+
origin: ["http://localhost:5173", "https://ifcore-platform.tralala798.workers.dev"],
|
| 178 |
+
credentials: true,
|
| 179 |
+
}));
|
| 180 |
+
```
|
| 181 |
+
|
| 182 |
+
Then mount the auth routes under `/api/auth/*`:
|
| 183 |
+
```typescript
|
| 184 |
+
app.on(['GET', 'POST'], '/api/auth/*', (c) => {
|
| 185 |
+
const auth = createAuth(c.env)
|
| 186 |
+
return auth.handler(c.req.raw)
|
| 187 |
+
})
|
| 188 |
+
```
|
| 189 |
+
|
| 190 |
+
**Step 5 -- Set secrets** (10 min)
|
| 191 |
+
|
| 192 |
+
For production:
|
| 193 |
+
```bash
|
| 194 |
+
wrangler secret put BETTER_AUTH_SECRET # generate with: openssl rand -base64 32
|
| 195 |
+
wrangler secret put BETTER_AUTH_URL # https://ifcore-platform.tralala798.workers.dev
|
| 196 |
+
```
|
| 197 |
+
|
| 198 |
+
For local development, create `frontend/.dev.vars`:
|
| 199 |
+
```env
|
| 200 |
+
BETTER_AUTH_SECRET=local-dev-secret-change-me
|
| 201 |
+
BETTER_AUTH_URL=http://localhost:5173
|
| 202 |
+
```
|
| 203 |
+
Without `.dev.vars`, local `npm run dev` will crash on missing env vars.
|
| 204 |
+
|
| 205 |
+
**Step 6 -- Create the D1 migration** (20 min)
|
| 206 |
+
|
| 207 |
+
Add a new migration file (e.g., `0003_auth.sql`) with the Better Auth tables:
|
| 208 |
+
`session`, `account`, `verification`. Better Auth defaults to a table named
|
| 209 |
+
`user` (singular), but the platform already has `users` (plural). To avoid
|
| 210 |
+
two separate user tables, configure Better Auth to use the existing table:
|
| 211 |
+
```typescript
|
| 212 |
+
// in auth.ts config
|
| 213 |
+
user: { tableName: "users" },
|
| 214 |
+
```
|
| 215 |
+
Then the migration only adds columns to `users` (e.g., `email_verified`,
|
| 216 |
+
`image`) and creates the three new tables. Do NOT drop or recreate `users`.
|
| 217 |
+
|
| 218 |
+
**Note:** The `@better-auth/cli generate` command does NOT work with the
|
| 219 |
+
factory-pattern auth config required for Workers. Create the SQL manually.
|
| 220 |
+
See the Better Auth docs for exact column definitions.
|
| 221 |
+
|
| 222 |
+
### Afternoon (3 hours): Frontend
|
| 223 |
+
|
| 224 |
+
**Step 7 -- Create the auth client** (15 min)
|
| 225 |
+
|
| 226 |
+
Create `frontend/src/lib/auth-client.ts`:
|
| 227 |
+
```typescript
|
| 228 |
+
import { createAuthClient } from 'better-auth/react'
|
| 229 |
+
|
| 230 |
+
export const authClient = createAuthClient({
|
| 231 |
+
baseURL: '/api/auth',
|
| 232 |
+
fetchOptions: { credentials: 'include' },
|
| 233 |
+
})
|
| 234 |
+
|
| 235 |
+
export const { useSession } = authClient
|
| 236 |
+
```
|
| 237 |
+
|
| 238 |
+
**Step 8 -- Build the login page** (45 min)
|
| 239 |
+
|
| 240 |
+
A simple form with email + password fields, a "Log In" button, and a
|
| 241 |
+
"Sign Up" button. The Cloudflare skill has a complete `AuthForm.tsx` component
|
| 242 |
+
you can adapt. Add it as a route at `/login` using TanStack Router.
|
| 243 |
+
|
| 244 |
+
**Step 9 -- Build the profile page** (30 min)
|
| 245 |
+
|
| 246 |
+
Show the current user's name, email, and team. Add a "Log Out" button.
|
| 247 |
+
Route: `/profile`.
|
| 248 |
+
|
| 249 |
+
**Step 10 -- Update the navbar** (15 min)
|
| 250 |
+
|
| 251 |
+
If logged in: show the user's name and a link to `/profile`.
|
| 252 |
+
If not logged in: show a "Log In" link to `/login`.
|
| 253 |
+
|
| 254 |
+
**Step 11 -- Test end to end** (30 min)
|
| 255 |
+
|
| 256 |
+
1. Sign up with an email and password
|
| 257 |
+
2. Verify you are redirected and the navbar shows your name
|
| 258 |
+
3. Log out and verify the navbar changes
|
| 259 |
+
4. Log back in and verify your profile page works
|
| 260 |
+
|
| 261 |
+
**Step 12 -- Deploy** (15 min)
|
| 262 |
+
```bash
|
| 263 |
+
cd frontend
|
| 264 |
+
npm run db:migrate:remote # applies the new auth migration
|
| 265 |
+
npm run deploy # builds + deploys to Cloudflare
|
| 266 |
+
```
|
| 267 |
+
|
| 268 |
+
---
|
| 269 |
+
|
| 270 |
+
## What to Cut (and Why That is OK)
|
| 271 |
+
|
| 272 |
+
| Feature | Why it is cut |
|
| 273 |
+
|---------|---------------|
|
| 274 |
+
| Stripe billing | Requires a merchant account nobody has set up |
|
| 275 |
+
| Email verification | Requires a transactional email provider |
|
| 276 |
+
| Password reset | Depends on email sending |
|
| 277 |
+
| 2FA / TOTP | Nice for security, but adds a full day of work |
|
| 278 |
+
| 4-role RBAC | Use `role: "member" or "captain"` -- two roles is enough |
|
| 279 |
+
| Support ticketing | Completely separate feature, not auth |
|
| 280 |
+
| Account deletion with 30-day retention | Post-launch feature |
|
| 281 |
+
| API key management | Not needed for the demo |
|
| 282 |
+
| Monitoring and alerting | Not needed for the demo |
|
| 283 |
+
|
| 284 |
+
Cutting features is not failure. Every real product launch involves cutting scope.
|
| 285 |
+
The difference between a shipped product and a perfect plan is that one of them
|
| 286 |
+
actually exists. You aimed high -- now let's ship what matters.
|
| 287 |
+
|
| 288 |
+
---
|
| 289 |
+
|
| 290 |
+
## How Your Feature Connects to Others
|
| 291 |
+
|
| 292 |
+
Authentication is a **foundation feature**. Here is how other teams use it:
|
| 293 |
+
|
| 294 |
+
- **Team D (3D Viewer):** Calls `useSession()` to know which user uploaded a model
|
| 295 |
+
- **Team E (Dashboard):** Filters check results by user/team using the session
|
| 296 |
+
- **Report Generator:** Attaches user info to exported compliance reports
|
| 297 |
+
- **Every team:** Can protect routes with your auth -- only logged-in users see the app
|
| 298 |
+
|
| 299 |
+
If you ship login + signup + session management, you give every other team
|
| 300 |
+
a `useSession()` hook that returns `{ id, email, name }`. That is the most
|
| 301 |
+
valuable single feature anyone can deliver tomorrow.
|
| 302 |
+
|
| 303 |
+
You aimed high and that is great. Now let's focus on what we can ship.
|
teams/Mastodonte/COMPLIANCE_AUDIT.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# IFCore Compliance Audit Report
|
| 2 |
+
|
| 3 |
+
**Date:** February 18, 2026
|
| 4 |
+
**Project:** AI Speed Run - Week 05
|
| 5 |
+
**Status:** β
**FULLY COMPLIANT**
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## Executive Summary
|
| 10 |
+
|
| 11 |
+
All Python files in `tools/` directory comply with IFCore platform contracts:
|
| 12 |
+
- β
Naming conventions enforced
|
| 13 |
+
- β
Function signatures correct
|
| 14 |
+
- β
Return types compliant
|
| 15 |
+
- β
IFCore schema implemented
|
| 16 |
+
- β
Module exports configured
|
| 17 |
+
|
| 18 |
+
---
|
| 19 |
+
|
| 20 |
+
## File-by-File Audit
|
| 21 |
+
|
| 22 |
+
### 1. **checker_dwelling.py** β
COMPLIANT
|
| 23 |
+
|
| 24 |
+
| Requirement | Status | Details |
|
| 25 |
+
|---|---|---|
|
| 26 |
+
| **Filename** | β
| Follows `checker_*.py` pattern |
|
| 27 |
+
| **Function Name** | β
| `check_dwelling_area()` follows `check_*` prefix |
|
| 28 |
+
| **Signature** | β
| `check_dwelling_area(model, min_area=36.0)` - model is 1st arg |
|
| 29 |
+
| **Return Type** | β
| Returns `list[dict]` |
|
| 30 |
+
| **Schema Keys** | β
| All IFCore keys present: element_id, element_type, element_name, element_name_long, check_status, actual_value, required_value, comment, log |
|
| 31 |
+
| **Status Values** | β
| Uses only valid statuses: "pass", "fail" |
|
| 32 |
+
| **Helper Functions** | β
| `calculate_space_area()` is properly isolated |
|
| 33 |
+
| **Testing** | β
| Local test harness included (`if __name__ == "__main__"`) |
|
| 34 |
+
|
| 35 |
+
**Notes:** Function correctly evaluates total dwelling area and applies aggregate pass/fail status to all spaces.
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
### 2. **checker_heights.py** β
COMPLIANT
|
| 40 |
+
|
| 41 |
+
| Requirement | Status | Details |
|
| 42 |
+
|---|---|---|
|
| 43 |
+
| **Filename** | β
| Follows `checker_*.py` pattern |
|
| 44 |
+
| **Function Name** | β
| `check_living_area_height()` follows `check_*` prefix |
|
| 45 |
+
| **Signature** | β
| `check_living_area_height(model, min_height=2.50)` - model is 1st arg |
|
| 46 |
+
| **Return Type** | β
| Returns `list[dict]` |
|
| 47 |
+
| **Schema Keys** | β
| All IFCore keys present |
|
| 48 |
+
| **Status Values** | β
| Uses only valid statuses: "pass", "fail" |
|
| 49 |
+
| **Helper Functions** | β
| `get_main_living_areas()`, `get_space_height()` properly isolated |
|
| 50 |
+
| **Testing** | β
| Local test harness included |
|
| 51 |
+
| **Keywords** | β
| Spaces filtered by: "living", "bedroom", "hall" |
|
| 52 |
+
|
| 53 |
+
**Notes:** Old `load_model()` function present but unused (harmless legacy code).
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
|
| 57 |
+
### 3. **checker_living_rooms.py** β
COMPLIANT
|
| 58 |
+
|
| 59 |
+
| Requirement | Status | Details |
|
| 60 |
+
|---|---|---|
|
| 61 |
+
| **Filename** | β
| Follows `checker_*.py` pattern |
|
| 62 |
+
| **Function Name** | β
| `check_living_room_compliance()` follows `check_*` prefix |
|
| 63 |
+
| **Signature** | β
| `check_living_room_compliance(model)` - model is 1st arg |
|
| 64 |
+
| **Return Type** | β
| Returns `list[dict]` |
|
| 65 |
+
| **Schema Keys** | β
| All IFCore keys present |
|
| 66 |
+
| **Status Values** | β
| Uses only valid statuses: "pass", "fail" |
|
| 67 |
+
| **Rules Encoded** | β
| Min area 10 mΒ² (or 14 mΒ² if living+kitchen), clearance check 2.4 x 2.4 m |
|
| 68 |
+
| **Helper Functions** | β
| `calculate_space_area()`, `get_space_name()`, `can_fit_square()` properly isolated |
|
| 69 |
+
| **Testing** | β
| Local test harness included |
|
| 70 |
+
|
| 71 |
+
**Notes:** Rules correctly cascade and aggregate reasons field. Old `load_model()` function present but unused (harmless legacy code).
|
| 72 |
+
|
| 73 |
+
---
|
| 74 |
+
|
| 75 |
+
### 4. **checker_service_spaces.py** β
COMPLIANT
|
| 76 |
+
|
| 77 |
+
| Requirement | Status | Details |
|
| 78 |
+
|---|---|---|
|
| 79 |
+
| **Filename** | β
| Follows `checker_*.py` pattern |
|
| 80 |
+
| **Function Name** | β
| `check_service_spaces_min_height()` follows `check_*` prefix |
|
| 81 |
+
| **Signature** | β
| `check_service_spaces_min_height(model, min_height=2.20)` - model is 1st arg |
|
| 82 |
+
| **Return Type** | β
| Returns `list[dict]` |
|
| 83 |
+
| **Schema Keys** | β
| All IFCore keys present |
|
| 84 |
+
| **Status Values** | β
| Uses only valid statuses: "pass", "fail" |
|
| 85 |
+
| **Service Space Keywords** | β
| Spaces filtered by: "bath", "bathroom", "wc", "toilet", "kitchen", "cocina", "hall", "hallway", "corridor", "pasillo" |
|
| 86 |
+
| **Helper Functions** | β
| `get_space_name()`, `get_space_height()`, `is_service_space()` properly isolated |
|
| 87 |
+
| **Testing** | β
| Local test harness included |
|
| 88 |
+
|
| 89 |
+
**Notes:** Old `load_model()` function present but unused (harmless legacy code).
|
| 90 |
+
|
| 91 |
+
---
|
| 92 |
+
|
| 93 |
+
### 5. **__init__.py** β
COMPLIANT
|
| 94 |
+
|
| 95 |
+
| Requirement | Status | Details |
|
| 96 |
+
|---|---|---|
|
| 97 |
+
| **Exports** | β
| All 4 check functions exported: `check_dwelling_area`, `check_living_area_height`, `check_living_room_compliance`, `check_service_spaces_min_height` |
|
| 98 |
+
| **__all__ Declaration** | β
| Properly declared for auto-discovery |
|
| 99 |
+
| **Import Paths** | β
| Relative imports from checker modules correct |
|
| 100 |
+
| **Documentation** | β
| Clear docstring explaining IFCore contract |
|
| 101 |
+
|
| 102 |
+
**Notes:** Properly configured for platform auto-discovery.
|
| 103 |
+
|
| 104 |
+
---
|
| 105 |
+
|
| 106 |
+
## IFCore Schema Validation
|
| 107 |
+
|
| 108 |
+
### Required Fields (All Present in All Checkers)
|
| 109 |
+
|
| 110 |
+
```python
|
| 111 |
+
{
|
| 112 |
+
"element_id": str, # β
GlobalId of IFC element
|
| 113 |
+
"element_type": str, # β
"IfcSpace", etc.
|
| 114 |
+
"element_name": str, # β
Short name
|
| 115 |
+
"element_name_long": str, # β
Descriptive name with context
|
| 116 |
+
"check_status": str, # β
"pass", "fail", "warning", "blocked", "log"
|
| 117 |
+
"actual_value": str|None, # β
Measured value with units
|
| 118 |
+
"required_value": str|None, # β
Required threshold with units
|
| 119 |
+
"comment": str|None, # β
Failure explanation or None
|
| 120 |
+
"log": str|None, # β
Debug info or None
|
| 121 |
+
}
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
**Compliance:** β
All checkers implement all 9 required fields
|
| 125 |
+
|
| 126 |
+
---
|
| 127 |
+
|
| 128 |
+
## Function Signature Validation
|
| 129 |
+
|
| 130 |
+
### Convention: `check_<what>(model, **optional_kwargs)`
|
| 131 |
+
|
| 132 |
+
```
|
| 133 |
+
β
check_dwelling_area(model, min_area=36.0)
|
| 134 |
+
β
check_living_area_height(model, min_height=2.50)
|
| 135 |
+
β
check_living_room_compliance(model)
|
| 136 |
+
β
check_service_spaces_min_height(model, min_height=2.20)
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
**Compliance:** β
All functions follow contract exactly
|
| 140 |
+
|
| 141 |
+
---
|
| 142 |
+
|
| 143 |
+
## Return Type Validation
|
| 144 |
+
|
| 145 |
+
### Contract: All functions return `list[dict]`
|
| 146 |
+
|
| 147 |
+
```
|
| 148 |
+
β
check_dwelling_area() β list[dict] (269 spaces tested)
|
| 149 |
+
β
check_living_area_height() β list[dict] (per living area)
|
| 150 |
+
β
check_living_room_compliance() β list[dict] (per living room)
|
| 151 |
+
β
check_service_spaces_min_height() β list[dict] (63 service spaces tested)
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
**Compliance:** β
All functions return correct type
|
| 155 |
+
|
| 156 |
+
---
|
| 157 |
+
|
| 158 |
+
## File Structure Validation
|
| 159 |
+
|
| 160 |
+
```
|
| 161 |
+
tools/
|
| 162 |
+
βββ __init__.py β
Exports all checkers
|
| 163 |
+
βββ checker_dwelling.py β
check_dwelling_area()
|
| 164 |
+
βββ checker_heights.py β
check_living_area_height()
|
| 165 |
+
βββ checker_living_rooms.py β
check_living_room_compliance()
|
| 166 |
+
βββ checker_service_spaces.py β
check_service_spaces_min_height()
|
| 167 |
+
βββ __pycache__/
|
| 168 |
+
|
| 169 |
+
main.py β
Integration test harness
|
| 170 |
+
βββ Loads model once
|
| 171 |
+
βββ Calls all 4 checkers
|
| 172 |
+
βββ Collects & summarizes results
|
| 173 |
+
```
|
| 174 |
+
|
| 175 |
+
**Compliance:** β
File structure correct
|
| 176 |
+
|
| 177 |
+
---
|
| 178 |
+
|
| 179 |
+
## Platform Integration Readiness
|
| 180 |
+
|
| 181 |
+
### Discovery Phase β
|
| 182 |
+
- Platform scans `tools/checker_*.py` files
|
| 183 |
+
- Finds all `check_*` functions by name prefix
|
| 184 |
+
- Reads function signature for parameter mapping
|
| 185 |
+
|
| 186 |
+
### Execution Phase β
|
| 187 |
+
- Platform loads IFC model once
|
| 188 |
+
- Passes loaded `model` object to each `check_*` function
|
| 189 |
+
- Each function receives correct argument type (ifcopenshell.file)
|
| 190 |
+
|
| 191 |
+
### Result Aggregation Phase β
|
| 192 |
+
- Platform collects `list[dict]` results from each checker
|
| 193 |
+
- Maps to `element_results` database table
|
| 194 |
+
- Schema keys directly match table columns
|
| 195 |
+
|
| 196 |
+
---
|
| 197 |
+
|
| 198 |
+
## Removed Files
|
| 199 |
+
|
| 200 |
+
| File | Reason |
|
| 201 |
+
|------|--------|
|
| 202 |
+
| `_init_.py` | β Removed - old file conflicting with `__init__.py` |
|
| 203 |
+
|
| 204 |
+
---
|
| 205 |
+
|
| 206 |
+
## Recommendations
|
| 207 |
+
|
| 208 |
+
### β
No Changes Required
|
| 209 |
+
|
| 210 |
+
All files are compliant and production-ready. The codebase:
|
| 211 |
+
- Follows IFCore naming conventions exactly
|
| 212 |
+
- Implements function contracts correctly
|
| 213 |
+
- Returns proper schema
|
| 214 |
+
- Is ready for platform integration
|
| 215 |
+
|
| 216 |
+
### Optional: Code Cleanup
|
| 217 |
+
|
| 218 |
+
The following unused legacy functions can remain (they don't break functionality):
|
| 219 |
+
- `checker_heights.py`: `load_model()` β not used, kept for reference
|
| 220 |
+
- `checker_living_rooms.py`: `load_model()` β not used, kept for reference
|
| 221 |
+
- `checker_service_spaces.py`: `load_model()` β not used, kept for reference
|
| 222 |
+
|
| 223 |
+
These don't interfere with platform discovery (platform only looks for `check_*` functions).
|
| 224 |
+
|
| 225 |
+
---
|
| 226 |
+
|
| 227 |
+
## Test Results Summary
|
| 228 |
+
|
| 229 |
+
```
|
| 230 |
+
β
dwelling_area: 269 β / 0 β
|
| 231 |
+
β
service_spaces_min_height: 63 β / 0 β
|
| 232 |
+
β
living_area_height: 0 results (no matching spaces in model)
|
| 233 |
+
β
living_room_compliance: 0 results (no matching spaces in model)
|
| 234 |
+
```
|
| 235 |
+
|
| 236 |
+
All functions executed without errors and returned correct schema.
|
| 237 |
+
|
| 238 |
+
---
|
| 239 |
+
|
| 240 |
+
## Conclusion
|
| 241 |
+
|
| 242 |
+
**Status: β
FULLY COMPLIANT WITH ICORE CONTRACTS**
|
| 243 |
+
|
| 244 |
+
Your codebase is ready for deployment to the IFCore platform. All naming conventions, function signatures, return types, and schema requirements are met.
|
| 245 |
+
|
| 246 |
+
**Next step:** Push to your team repository. The platform will auto-discover and integrate your check functions.
|
teams/Mastodonte/Mastodontes_Tool_E.ipynb
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"nbformat": 4,
|
| 3 |
+
"nbformat_minor": 0,
|
| 4 |
+
"metadata": {
|
| 5 |
+
"colab": {
|
| 6 |
+
"provenance": []
|
| 7 |
+
},
|
| 8 |
+
"kernelspec": {
|
| 9 |
+
"name": "python3",
|
| 10 |
+
"display_name": "Python 3"
|
| 11 |
+
},
|
| 12 |
+
"language_info": {
|
| 13 |
+
"name": "python"
|
| 14 |
+
}
|
| 15 |
+
},
|
| 16 |
+
"cells": [
|
| 17 |
+
{
|
| 18 |
+
"cell_type": "markdown",
|
| 19 |
+
"source": [
|
| 20 |
+
"Dependencies"
|
| 21 |
+
],
|
| 22 |
+
"metadata": {
|
| 23 |
+
"id": "8dq-7nvZdH8R"
|
| 24 |
+
}
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"cell_type": "code",
|
| 28 |
+
"source": [
|
| 29 |
+
"# Install ifcopenshell for IFC parsing\n",
|
| 30 |
+
"!pip install ifcopenshell matplotlib"
|
| 31 |
+
],
|
| 32 |
+
"metadata": {
|
| 33 |
+
"colab": {
|
| 34 |
+
"base_uri": "https://localhost:8080/"
|
| 35 |
+
},
|
| 36 |
+
"id": "XO7xZxExcr7A",
|
| 37 |
+
"outputId": "7829861f-5134-4946-8830-b925276f0ee1"
|
| 38 |
+
},
|
| 39 |
+
"execution_count": null,
|
| 40 |
+
"outputs": [
|
| 41 |
+
{
|
| 42 |
+
"output_type": "stream",
|
| 43 |
+
"name": "stdout",
|
| 44 |
+
"text": [
|
| 45 |
+
"Collecting ifcopenshell\n",
|
| 46 |
+
" Downloading ifcopenshell-0.8.4.post1-py312-none-manylinux_2_31_x86_64.whl.metadata (12 kB)\n",
|
| 47 |
+
"Requirement already satisfied: matplotlib in /usr/local/lib/python3.12/dist-packages (3.10.0)\n",
|
| 48 |
+
"Requirement already satisfied: shapely in /usr/local/lib/python3.12/dist-packages (from ifcopenshell) (2.1.2)\n",
|
| 49 |
+
"Requirement already satisfied: numpy in /usr/local/lib/python3.12/dist-packages (from ifcopenshell) (2.0.2)\n",
|
| 50 |
+
"Collecting isodate (from ifcopenshell)\n",
|
| 51 |
+
" Downloading isodate-0.7.2-py3-none-any.whl.metadata (11 kB)\n",
|
| 52 |
+
"Requirement already satisfied: python-dateutil in /usr/local/lib/python3.12/dist-packages (from ifcopenshell) (2.9.0.post0)\n",
|
| 53 |
+
"Requirement already satisfied: lark in /usr/local/lib/python3.12/dist-packages (from ifcopenshell) (1.3.1)\n",
|
| 54 |
+
"Requirement already satisfied: typing-extensions in /usr/local/lib/python3.12/dist-packages (from ifcopenshell) (4.15.0)\n",
|
| 55 |
+
"Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (1.3.3)\n",
|
| 56 |
+
"Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (0.12.1)\n",
|
| 57 |
+
"Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (4.61.1)\n",
|
| 58 |
+
"Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (1.4.9)\n",
|
| 59 |
+
"Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (26.0)\n",
|
| 60 |
+
"Requirement already satisfied: pillow>=8 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (11.3.0)\n",
|
| 61 |
+
"Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (3.3.2)\n",
|
| 62 |
+
"Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil->ifcopenshell) (1.17.0)\n",
|
| 63 |
+
"Downloading ifcopenshell-0.8.4.post1-py312-none-manylinux_2_31_x86_64.whl (42.6 MB)\n",
|
| 64 |
+
"\u001b[2K \u001b[90mββββββββββββββββββββββββββββββββββββββββ\u001b[0m \u001b[32m42.6/42.6 MB\u001b[0m \u001b[31m21.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
| 65 |
+
"\u001b[?25hDownloading isodate-0.7.2-py3-none-any.whl (22 kB)\n",
|
| 66 |
+
"Installing collected packages: isodate, ifcopenshell\n",
|
| 67 |
+
"Successfully installed ifcopenshell-0.8.4.post1 isodate-0.7.2\n"
|
| 68 |
+
]
|
| 69 |
+
}
|
| 70 |
+
]
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"cell_type": "markdown",
|
| 74 |
+
"source": [
|
| 75 |
+
"Cloning Repo"
|
| 76 |
+
],
|
| 77 |
+
"metadata": {
|
| 78 |
+
"id": "0vgb8bCZdLgH"
|
| 79 |
+
}
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"cell_type": "code",
|
| 83 |
+
"source": [
|
| 84 |
+
"!git clone https://github.com/sylvainHellin/ifc-bench.git"
|
| 85 |
+
],
|
| 86 |
+
"metadata": {
|
| 87 |
+
"colab": {
|
| 88 |
+
"base_uri": "https://localhost:8080/"
|
| 89 |
+
},
|
| 90 |
+
"id": "EYiAOI_9dQDU",
|
| 91 |
+
"outputId": "abd0c597-bb0a-40b3-f840-f04f54e3d927"
|
| 92 |
+
},
|
| 93 |
+
"execution_count": null,
|
| 94 |
+
"outputs": [
|
| 95 |
+
{
|
| 96 |
+
"output_type": "stream",
|
| 97 |
+
"name": "stdout",
|
| 98 |
+
"text": [
|
| 99 |
+
"Cloning into 'ifc-bench'...\n",
|
| 100 |
+
"remote: Enumerating objects: 90, done.\u001b[K\n",
|
| 101 |
+
"remote: Counting objects: 100% (12/12), done.\u001b[K\n",
|
| 102 |
+
"remote: Compressing objects: 100% (12/12), done.\u001b[K\n",
|
| 103 |
+
"remote: Total 90 (delta 1), reused 6 (delta 0), pack-reused 78 (from 1)\u001b[K\n",
|
| 104 |
+
"Receiving objects: 100% (90/90), 45.07 MiB | 22.28 MiB/s, done.\n",
|
| 105 |
+
"Resolving deltas: 100% (10/10), done.\n",
|
| 106 |
+
"Filtering content: 100% (5/5), 163.22 MiB | 22.39 MiB/s, done.\n"
|
| 107 |
+
]
|
| 108 |
+
}
|
| 109 |
+
]
|
| 110 |
+
},
|
| 111 |
+
{
|
| 112 |
+
"cell_type": "markdown",
|
| 113 |
+
"source": [
|
| 114 |
+
"Tool E Area-Occupancy"
|
| 115 |
+
],
|
| 116 |
+
"metadata": {
|
| 117 |
+
"id": "lUnjwOstdRKE"
|
| 118 |
+
}
|
| 119 |
+
},
|
| 120 |
+
{
|
| 121 |
+
"cell_type": "code",
|
| 122 |
+
"source": [
|
| 123 |
+
"import ifcopenshell\n",
|
| 124 |
+
"import ifcopenshell.geom\n",
|
| 125 |
+
"import math"
|
| 126 |
+
],
|
| 127 |
+
"metadata": {
|
| 128 |
+
"id": "d5CtifBBdduf"
|
| 129 |
+
},
|
| 130 |
+
"execution_count": null,
|
| 131 |
+
"outputs": []
|
| 132 |
+
},
|
| 133 |
+
{
|
| 134 |
+
"cell_type": "code",
|
| 135 |
+
"source": [
|
| 136 |
+
"# Requirements\n",
|
| 137 |
+
"import math\n",
|
| 138 |
+
"import json\n",
|
| 139 |
+
"import ifcopenshell\n",
|
| 140 |
+
"import ifcopenshell.geom\n",
|
| 141 |
+
"\n",
|
| 142 |
+
"# --------------------------\n",
|
| 143 |
+
"# Geometry settings\n",
|
| 144 |
+
"# --------------------------\n",
|
| 145 |
+
"settings = ifcopenshell.geom.settings()\n",
|
| 146 |
+
"settings.set(settings.USE_WORLD_COORDS, True)\n",
|
| 147 |
+
"\n",
|
| 148 |
+
"# --------------------------\n",
|
| 149 |
+
"# IFC helpers\n",
|
| 150 |
+
"# --------------------------\n",
|
| 151 |
+
"def calculate_space_area(space):\n",
|
| 152 |
+
" \"\"\"Approximate area by summing triangle areas from the space mesh.\"\"\"\n",
|
| 153 |
+
" try:\n",
|
| 154 |
+
" shape = ifcopenshell.geom.create_shape(settings, space)\n",
|
| 155 |
+
" verts = shape.geometry.verts\n",
|
| 156 |
+
" faces = shape.geometry.faces\n",
|
| 157 |
+
" area = 0.0\n",
|
| 158 |
+
"\n",
|
| 159 |
+
" for i in range(0, len(faces), 3):\n",
|
| 160 |
+
" i0, i1, i2 = faces[i] * 3, faces[i + 1] * 3, faces[i + 2] * 3\n",
|
| 161 |
+
" v0 = verts[i0:i0 + 3]\n",
|
| 162 |
+
" v1 = verts[i1:i1 + 3]\n",
|
| 163 |
+
" v2 = verts[i2:i2 + 3]\n",
|
| 164 |
+
"\n",
|
| 165 |
+
" a = math.sqrt((v1[0]-v0[0])**2 + (v1[1]-v0[1])**2 + (v1[2]-v0[2])**2)\n",
|
| 166 |
+
" b = math.sqrt((v2[0]-v1[0])**2 + (v2[1]-v1[1])**2 + (v2[2]-v1[2])**2)\n",
|
| 167 |
+
" c = math.sqrt((v0[0]-v2[0])**2 + (v0[1]-v2[1])**2 + (v0[2]-v2[2])**2)\n",
|
| 168 |
+
"\n",
|
| 169 |
+
" s = (a + b + c) / 2.0\n",
|
| 170 |
+
" area += math.sqrt(max(s * (s - a) * (s - b) * (s - c), 0.0))\n",
|
| 171 |
+
"\n",
|
| 172 |
+
" return float(area)\n",
|
| 173 |
+
" except Exception:\n",
|
| 174 |
+
" return 0.0\n",
|
| 175 |
+
"\n",
|
| 176 |
+
"\n",
|
| 177 |
+
"def _space_label(space):\n",
|
| 178 |
+
" \"\"\"Return a readable label.\"\"\"\n",
|
| 179 |
+
" return getattr(space, \"LongName\", None) or getattr(space, \"Name\", None) or \"Unnamed\"\n",
|
| 180 |
+
"\n",
|
| 181 |
+
"\n",
|
| 182 |
+
"def _matches_any_keyword(text, keywords):\n",
|
| 183 |
+
" t = (text or \"\").lower()\n",
|
| 184 |
+
" return any(k in t for k in keywords)\n",
|
| 185 |
+
"\n",
|
| 186 |
+
"\n",
|
| 187 |
+
"def get_spaces_by_keywords(model, keywords):\n",
|
| 188 |
+
" \"\"\"Filter IfcSpace by semantic keywords.\"\"\"\n",
|
| 189 |
+
" spaces = model.by_type(\"IfcSpace\")\n",
|
| 190 |
+
" return [s for s in spaces if _matches_any_keyword(_space_label(s), keywords)]\n",
|
| 191 |
+
"\n",
|
| 192 |
+
"\n",
|
| 193 |
+
"# --------------------------\n",
|
| 194 |
+
"# Regulation logic\n",
|
| 195 |
+
"# --------------------------\n",
|
| 196 |
+
"def area_to_occupancy(area):\n",
|
| 197 |
+
" \"\"\"\n",
|
| 198 |
+
" Mapping regulation:\n",
|
| 199 |
+
" <5 mΒ² -> not valid bedroom\n",
|
| 200 |
+
" β₯5 mΒ² -> 1 person\n",
|
| 201 |
+
" β₯8 mΒ² -> 2 people\n",
|
| 202 |
+
" β₯12 mΒ² -> 3 people\n",
|
| 203 |
+
" \"\"\"\n",
|
| 204 |
+
" if area < 5.0:\n",
|
| 205 |
+
" return 0\n",
|
| 206 |
+
" elif area < 8.0:\n",
|
| 207 |
+
" return 1\n",
|
| 208 |
+
" elif area < 12.0:\n",
|
| 209 |
+
" return 2\n",
|
| 210 |
+
" else:\n",
|
| 211 |
+
" return 3\n",
|
| 212 |
+
"\n",
|
| 213 |
+
"\n",
|
| 214 |
+
"def bedroom_occupancy_check(ifc_model_path):\n",
|
| 215 |
+
" \"\"\"\n",
|
| 216 |
+
" Determines allowed occupancy from bedroom areas.\n",
|
| 217 |
+
"\n",
|
| 218 |
+
" Special rule:\n",
|
| 219 |
+
" - If NO bedrooms exist (studio dwelling), occupancy is limited to max 2 people.\n",
|
| 220 |
+
" \"\"\"\n",
|
| 221 |
+
"\n",
|
| 222 |
+
" model = ifcopenshell.open(ifc_model_path)\n",
|
| 223 |
+
"\n",
|
| 224 |
+
" bedroom_keywords = [\"bedroom\", \"habitacion\", \"habitaciΓ³n\", \"dormitorio\"]\n",
|
| 225 |
+
" living_keywords = [\"living\", \"salon\", \"salΓ³n\", \"studio\", \"estudio\", \"common\"]\n",
|
| 226 |
+
"\n",
|
| 227 |
+
" bedrooms = get_spaces_by_keywords(model, bedroom_keywords)\n",
|
| 228 |
+
" living_spaces = get_spaces_by_keywords(model, living_keywords)\n",
|
| 229 |
+
"\n",
|
| 230 |
+
" room_areas = {}\n",
|
| 231 |
+
" occupancy = {}\n",
|
| 232 |
+
" total_people = 0\n",
|
| 233 |
+
"\n",
|
| 234 |
+
" # --------------------------\n",
|
| 235 |
+
" # Studio case (no bedrooms)\n",
|
| 236 |
+
" # --------------------------\n",
|
| 237 |
+
" if len(bedrooms) == 0:\n",
|
| 238 |
+
" if not living_spaces:\n",
|
| 239 |
+
" return {\n",
|
| 240 |
+
" \"result\": \"fail\",\n",
|
| 241 |
+
" \"reason\": \"No habitable space identified\",\n",
|
| 242 |
+
" \"room_areas\": {},\n",
|
| 243 |
+
" \"occupancy\": {},\n",
|
| 244 |
+
" \"total_allowed_people\": 0\n",
|
| 245 |
+
" }\n",
|
| 246 |
+
"\n",
|
| 247 |
+
" main_space = living_spaces[0]\n",
|
| 248 |
+
" label = _space_label(main_space)\n",
|
| 249 |
+
" area = calculate_space_area(main_space)\n",
|
| 250 |
+
"\n",
|
| 251 |
+
" allowed = min(area_to_occupancy(area), 2)\n",
|
| 252 |
+
"\n",
|
| 253 |
+
" room_areas[label] = float(area)\n",
|
| 254 |
+
" occupancy[label] = allowed\n",
|
| 255 |
+
"\n",
|
| 256 |
+
" return {\n",
|
| 257 |
+
" \"result\": \"pass\",\n",
|
| 258 |
+
" \"reason\": \"Studio dwelling limited to maximum 2 occupants\",\n",
|
| 259 |
+
" \"room_areas\": room_areas,\n",
|
| 260 |
+
" \"occupancy\": occupancy,\n",
|
| 261 |
+
" \"total_allowed_people\": allowed\n",
|
| 262 |
+
" }\n",
|
| 263 |
+
"\n",
|
| 264 |
+
" # --------------------------\n",
|
| 265 |
+
" # Bedroom-based occupancy\n",
|
| 266 |
+
" # --------------------------\n",
|
| 267 |
+
" for space in bedrooms:\n",
|
| 268 |
+
" label = _space_label(space)\n",
|
| 269 |
+
" area = calculate_space_area(space)\n",
|
| 270 |
+
" allowed = area_to_occupancy(area)\n",
|
| 271 |
+
"\n",
|
| 272 |
+
" if allowed == 0:\n",
|
| 273 |
+
" return {\n",
|
| 274 |
+
" \"result\": \"fail\",\n",
|
| 275 |
+
" \"reason\": f\"{label} area {area:.2f} mΒ² is below minimum 5 mΒ²\",\n",
|
| 276 |
+
" \"room_areas\": room_areas,\n",
|
| 277 |
+
" \"occupancy\": occupancy,\n",
|
| 278 |
+
" \"total_allowed_people\": total_people\n",
|
| 279 |
+
" }\n",
|
| 280 |
+
"\n",
|
| 281 |
+
" room_areas[label] = float(area)\n",
|
| 282 |
+
" occupancy[label] = allowed\n",
|
| 283 |
+
" total_people += allowed\n",
|
| 284 |
+
"\n",
|
| 285 |
+
" return {\n",
|
| 286 |
+
" \"result\": \"pass\",\n",
|
| 287 |
+
" \"reason\": \"Bedroom areas satisfy occupancy requirements\",\n",
|
| 288 |
+
" \"room_areas\": room_areas,\n",
|
| 289 |
+
" \"occupancy\": occupancy,\n",
|
| 290 |
+
" \"total_allowed_people\": total_people\n",
|
| 291 |
+
" }\n",
|
| 292 |
+
"\n",
|
| 293 |
+
"\n",
|
| 294 |
+
"# --------------------------\n",
|
| 295 |
+
"# Tool entrypoint\n",
|
| 296 |
+
"# --------------------------\n",
|
| 297 |
+
"def bedroom_occupancy_check_tool(ifc_model_path: str):\n",
|
| 298 |
+
" return bedroom_occupancy_check(ifc_model_path)\n",
|
| 299 |
+
"\n",
|
| 300 |
+
"\n",
|
| 301 |
+
"# --------------------------\n",
|
| 302 |
+
"# Schema (LLM-callable description)\n",
|
| 303 |
+
"# --------------------------\n",
|
| 304 |
+
"BEDROOM_OCCUPANCY_CHECK_SCHEMA = {\n",
|
| 305 |
+
" \"name\": \"bedroom_occupancy_check_tool\",\n",
|
| 306 |
+
" \"description\": \"Evaluates bedroom areas in an IFC model to determine the legally allowed number of occupants per room and for the dwelling.\",\n",
|
| 307 |
+
" \"parameters\": {\n",
|
| 308 |
+
" \"type\": \"object\",\n",
|
| 309 |
+
" \"properties\": {\n",
|
| 310 |
+
" \"ifc_model_path\": {\n",
|
| 311 |
+
" \"type\": \"string\",\n",
|
| 312 |
+
" \"description\": \"Filesystem path to the IFC model to evaluate.\"\n",
|
| 313 |
+
" }\n",
|
| 314 |
+
" },\n",
|
| 315 |
+
" \"required\": [\"ifc_model_path\"]\n",
|
| 316 |
+
" }\n",
|
| 317 |
+
"}\n",
|
| 318 |
+
"\n",
|
| 319 |
+
"\n",
|
| 320 |
+
"# --------------------------\n",
|
| 321 |
+
"# Optional: local sanity test\n",
|
| 322 |
+
"# --------------------------\n",
|
| 323 |
+
"if __name__ == \"__main__\":\n",
|
| 324 |
+
" print(\"Schema OK:\")\n",
|
| 325 |
+
" print(json.dumps(BEDROOM_OCCUPANCY_CHECK_SCHEMA, indent=2))\n",
|
| 326 |
+
"\n",
|
| 327 |
+
" ifc_path = \"/content/ifc-bench/projects/duplex/arc.ifc\"\n",
|
| 328 |
+
" result = bedroom_occupancy_check_tool(ifc_path)\n",
|
| 329 |
+
"\n",
|
| 330 |
+
" print(result[\"result\"])\n",
|
| 331 |
+
" print(result[\"reason\"])\n",
|
| 332 |
+
" print(\"Allowed occupants:\", result[\"total_allowed_people\"])"
|
| 333 |
+
],
|
| 334 |
+
"metadata": {
|
| 335 |
+
"colab": {
|
| 336 |
+
"base_uri": "https://localhost:8080/"
|
| 337 |
+
},
|
| 338 |
+
"id": "LWQ59ic1jaq2",
|
| 339 |
+
"outputId": "89b0e5cc-bae6-4fe3-9bc1-fa174a4c0a9c"
|
| 340 |
+
},
|
| 341 |
+
"execution_count": null,
|
| 342 |
+
"outputs": [
|
| 343 |
+
{
|
| 344 |
+
"output_type": "stream",
|
| 345 |
+
"name": "stdout",
|
| 346 |
+
"text": [
|
| 347 |
+
"Schema OK:\n",
|
| 348 |
+
"{\n",
|
| 349 |
+
" \"name\": \"bedroom_occupancy_check_tool\",\n",
|
| 350 |
+
" \"description\": \"Evaluates bedroom areas in an IFC model to determine the legally allowed number of occupants per room and for the dwelling.\",\n",
|
| 351 |
+
" \"parameters\": {\n",
|
| 352 |
+
" \"type\": \"object\",\n",
|
| 353 |
+
" \"properties\": {\n",
|
| 354 |
+
" \"ifc_model_path\": {\n",
|
| 355 |
+
" \"type\": \"string\",\n",
|
| 356 |
+
" \"description\": \"Filesystem path to the IFC model to evaluate.\"\n",
|
| 357 |
+
" }\n",
|
| 358 |
+
" },\n",
|
| 359 |
+
" \"required\": [\n",
|
| 360 |
+
" \"ifc_model_path\"\n",
|
| 361 |
+
" ]\n",
|
| 362 |
+
" }\n",
|
| 363 |
+
"}\n",
|
| 364 |
+
"pass\n",
|
| 365 |
+
"Bedroom areas satisfy occupancy requirements\n",
|
| 366 |
+
"Allowed occupants: 12\n"
|
| 367 |
+
]
|
| 368 |
+
}
|
| 369 |
+
]
|
| 370 |
+
}
|
| 371 |
+
]
|
| 372 |
+
}
|
teams/Mastodonte/Mastodontes_Tools.ipynb
ADDED
|
@@ -0,0 +1,857 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 4,
|
| 6 |
+
"metadata": {
|
| 7 |
+
"id": "Sml3OcRVDn34",
|
| 8 |
+
"colab": {
|
| 9 |
+
"base_uri": "https://localhost:8080/"
|
| 10 |
+
},
|
| 11 |
+
"outputId": "98ebd21d-e3d0-42da-8db2-dda12723f747"
|
| 12 |
+
},
|
| 13 |
+
"outputs": [
|
| 14 |
+
{
|
| 15 |
+
"output_type": "stream",
|
| 16 |
+
"name": "stdout",
|
| 17 |
+
"text": [
|
| 18 |
+
"Collecting ifcopenshell\n",
|
| 19 |
+
" Downloading ifcopenshell-0.8.4.post1-py312-none-manylinux_2_31_x86_64.whl.metadata (12 kB)\n",
|
| 20 |
+
"Requirement already satisfied: matplotlib in /usr/local/lib/python3.12/dist-packages (3.10.0)\n",
|
| 21 |
+
"Requirement already satisfied: shapely in /usr/local/lib/python3.12/dist-packages (from ifcopenshell) (2.1.2)\n",
|
| 22 |
+
"Requirement already satisfied: numpy in /usr/local/lib/python3.12/dist-packages (from ifcopenshell) (2.0.2)\n",
|
| 23 |
+
"Collecting isodate (from ifcopenshell)\n",
|
| 24 |
+
" Downloading isodate-0.7.2-py3-none-any.whl.metadata (11 kB)\n",
|
| 25 |
+
"Requirement already satisfied: python-dateutil in /usr/local/lib/python3.12/dist-packages (from ifcopenshell) (2.9.0.post0)\n",
|
| 26 |
+
"Requirement already satisfied: lark in /usr/local/lib/python3.12/dist-packages (from ifcopenshell) (1.3.1)\n",
|
| 27 |
+
"Requirement already satisfied: typing-extensions in /usr/local/lib/python3.12/dist-packages (from ifcopenshell) (4.15.0)\n",
|
| 28 |
+
"Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (1.3.3)\n",
|
| 29 |
+
"Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (0.12.1)\n",
|
| 30 |
+
"Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (4.61.1)\n",
|
| 31 |
+
"Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (1.4.9)\n",
|
| 32 |
+
"Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (26.0)\n",
|
| 33 |
+
"Requirement already satisfied: pillow>=8 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (11.3.0)\n",
|
| 34 |
+
"Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (3.3.2)\n",
|
| 35 |
+
"Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil->ifcopenshell) (1.17.0)\n",
|
| 36 |
+
"Downloading ifcopenshell-0.8.4.post1-py312-none-manylinux_2_31_x86_64.whl (42.6 MB)\n",
|
| 37 |
+
"\u001b[2K \u001b[90mββββββββββββββββββββββββββββββββββββββββ\u001b[0m \u001b[32m42.6/42.6 MB\u001b[0m \u001b[31m17.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
| 38 |
+
"\u001b[?25hDownloading isodate-0.7.2-py3-none-any.whl (22 kB)\n",
|
| 39 |
+
"Installing collected packages: isodate, ifcopenshell\n",
|
| 40 |
+
"Successfully installed ifcopenshell-0.8.4.post1 isodate-0.7.2\n"
|
| 41 |
+
]
|
| 42 |
+
}
|
| 43 |
+
],
|
| 44 |
+
"source": [
|
| 45 |
+
"# Install ifcopenshell for IFC parsing\n",
|
| 46 |
+
"!pip install ifcopenshell matplotlib"
|
| 47 |
+
]
|
| 48 |
+
},
|
| 49 |
+
{
|
| 50 |
+
"cell_type": "markdown",
|
| 51 |
+
"source": [
|
| 52 |
+
"Cloning Repo - Depending on IFC File required"
|
| 53 |
+
],
|
| 54 |
+
"metadata": {
|
| 55 |
+
"id": "pQdoZ0ZMITx4"
|
| 56 |
+
}
|
| 57 |
+
},
|
| 58 |
+
{
|
| 59 |
+
"cell_type": "code",
|
| 60 |
+
"execution_count": 6,
|
| 61 |
+
"metadata": {
|
| 62 |
+
"id": "LH-AJrKQD3-3",
|
| 63 |
+
"colab": {
|
| 64 |
+
"base_uri": "https://localhost:8080/"
|
| 65 |
+
},
|
| 66 |
+
"outputId": "0b26ee51-3475-4f24-be68-b3a7d71c9559"
|
| 67 |
+
},
|
| 68 |
+
"outputs": [
|
| 69 |
+
{
|
| 70 |
+
"output_type": "stream",
|
| 71 |
+
"name": "stdout",
|
| 72 |
+
"text": [
|
| 73 |
+
"Cloning into 'ifc-bench'...\n",
|
| 74 |
+
"remote: Enumerating objects: 90, done.\u001b[K\n",
|
| 75 |
+
"remote: Counting objects: 100% (12/12), done.\u001b[K\n",
|
| 76 |
+
"remote: Compressing objects: 100% (12/12), done.\u001b[K\n",
|
| 77 |
+
"remote: Total 90 (delta 1), reused 6 (delta 0), pack-reused 78 (from 1)\u001b[K\n",
|
| 78 |
+
"Receiving objects: 100% (90/90), 45.07 MiB | 30.23 MiB/s, done.\n",
|
| 79 |
+
"Resolving deltas: 100% (10/10), done.\n",
|
| 80 |
+
"Filtering content: 100% (5/5), 163.22 MiB | 62.23 MiB/s, done.\n"
|
| 81 |
+
]
|
| 82 |
+
}
|
| 83 |
+
],
|
| 84 |
+
"source": [
|
| 85 |
+
"!git clone https://github.com/sylvainHellin/ifc-bench.git"
|
| 86 |
+
]
|
| 87 |
+
},
|
| 88 |
+
{
|
| 89 |
+
"cell_type": "markdown",
|
| 90 |
+
"source": [
|
| 91 |
+
"# Tool A (Useful Surface Area)"
|
| 92 |
+
],
|
| 93 |
+
"metadata": {
|
| 94 |
+
"id": "H5XL0-eub_8E"
|
| 95 |
+
}
|
| 96 |
+
},
|
| 97 |
+
{
|
| 98 |
+
"cell_type": "code",
|
| 99 |
+
"source": [
|
| 100 |
+
"\n",
|
| 101 |
+
"# Requirements\n",
|
| 102 |
+
"import math\n",
|
| 103 |
+
"import json\n",
|
| 104 |
+
"import ifcopenshell\n",
|
| 105 |
+
"import ifcopenshell.geom\n",
|
| 106 |
+
"\n",
|
| 107 |
+
"# --------------------------\n",
|
| 108 |
+
"# Geometry settings\n",
|
| 109 |
+
"# --------------------------\n",
|
| 110 |
+
"settings = ifcopenshell.geom.settings()\n",
|
| 111 |
+
"settings.set(settings.USE_WORLD_COORDS, True)\n",
|
| 112 |
+
"\n",
|
| 113 |
+
"# --------------------------\n",
|
| 114 |
+
"# IFC helpers\n",
|
| 115 |
+
"# --------------------------\n",
|
| 116 |
+
"def calculate_space_area(space):\n",
|
| 117 |
+
" \"\"\"Approximate area by summing triangle areas from the space mesh.\"\"\"\n",
|
| 118 |
+
" try:\n",
|
| 119 |
+
" shape = ifcopenshell.geom.create_shape(settings, space)\n",
|
| 120 |
+
" verts = shape.geometry.verts\n",
|
| 121 |
+
" faces = shape.geometry.faces\n",
|
| 122 |
+
" area = 0.0\n",
|
| 123 |
+
"\n",
|
| 124 |
+
" for i in range(0, len(faces), 3):\n",
|
| 125 |
+
" i0, i1, i2 = faces[i] * 3, faces[i + 1] * 3, faces[i + 2] * 3\n",
|
| 126 |
+
" v0 = verts[i0:i0 + 3]\n",
|
| 127 |
+
" v1 = verts[i1:i1 + 3]\n",
|
| 128 |
+
" v2 = verts[i2:i2 + 3]\n",
|
| 129 |
+
"\n",
|
| 130 |
+
" a = math.sqrt((v1[0]-v0[0])**2 + (v1[1]-v0[1])**2 + (v1[2]-v0[2])**2)\n",
|
| 131 |
+
" b = math.sqrt((v2[0]-v1[0])**2 + (v2[1]-v1[1])**2 + (v2[2]-v1[2])**2)\n",
|
| 132 |
+
" c = math.sqrt((v0[0]-v2[0])**2 + (v0[1]-v2[1])**2 + (v0[2]-v2[2])**2)\n",
|
| 133 |
+
"\n",
|
| 134 |
+
" s = (a + b + c) / 2.0\n",
|
| 135 |
+
" area += math.sqrt(max(s * (s - a) * (s - b) * (s - c), 0.0))\n",
|
| 136 |
+
"\n",
|
| 137 |
+
" return float(area)\n",
|
| 138 |
+
" except Exception:\n",
|
| 139 |
+
" return 0.0\n",
|
| 140 |
+
"\n",
|
| 141 |
+
"def dwelling_area_check(ifc_model_path, min_area=36.0):\n",
|
| 142 |
+
" \"\"\"Check if total dwelling useful area >= min_area.\"\"\"\n",
|
| 143 |
+
" model = ifcopenshell.open(ifc_model_path)\n",
|
| 144 |
+
" spaces = model.by_type(\"IfcSpace\")\n",
|
| 145 |
+
"\n",
|
| 146 |
+
" total_area = 0.0\n",
|
| 147 |
+
" room_areas = {}\n",
|
| 148 |
+
"\n",
|
| 149 |
+
" for space in spaces:\n",
|
| 150 |
+
" area = calculate_space_area(space)\n",
|
| 151 |
+
" room_name = getattr(space, \"LongName\", None) or getattr(space, \"Name\", None) or \"Unnamed\"\n",
|
| 152 |
+
" room_areas[str(room_name)] = float(area)\n",
|
| 153 |
+
" total_area += float(area)\n",
|
| 154 |
+
"\n",
|
| 155 |
+
" if total_area >= float(min_area):\n",
|
| 156 |
+
" return {\n",
|
| 157 |
+
" \"result\": \"pass\",\n",
|
| 158 |
+
" \"reason\": f\"Total useful area {total_area:.2f} mΒ² >= {float(min_area):.2f} mΒ²\",\n",
|
| 159 |
+
" \"total_area\": float(total_area),\n",
|
| 160 |
+
" \"room_areas\": room_areas,\n",
|
| 161 |
+
" }\n",
|
| 162 |
+
"\n",
|
| 163 |
+
" return {\n",
|
| 164 |
+
" \"result\": \"fail\",\n",
|
| 165 |
+
" \"reason\": f\"Total useful area {total_area:.2f} mΒ² < {float(min_area):.2f} mΒ²\",\n",
|
| 166 |
+
" \"total_area\": float(total_area),\n",
|
| 167 |
+
" \"room_areas\": room_areas,\n",
|
| 168 |
+
" }\n",
|
| 169 |
+
"\n",
|
| 170 |
+
"# --------------------------\n",
|
| 171 |
+
"# Tool entrypoint (what your LLM router will call later)\n",
|
| 172 |
+
"# --------------------------\n",
|
| 173 |
+
"def dwelling_area_check_tool(ifc_model_path: str, min_area: float = 36.0):\n",
|
| 174 |
+
" return dwelling_area_check(ifc_model_path, min_area)\n",
|
| 175 |
+
"\n",
|
| 176 |
+
"# --------------------------\n",
|
| 177 |
+
"# Schema (API key not needed)\n",
|
| 178 |
+
"# Plain JSON-schema-like dict, easy to serialize + pass to any LLM framework\n",
|
| 179 |
+
"# --------------------------\n",
|
| 180 |
+
"DWELLING_AREA_CHECK_SCHEMA = {\n",
|
| 181 |
+
" \"name\": \"dwelling_area_check_tool\",\n",
|
| 182 |
+
" \"description\": \"Checks whether an IFC dwelling meets a minimum total useful area requirement and returns a room-by-room breakdown.\",\n",
|
| 183 |
+
" \"parameters\": {\n",
|
| 184 |
+
" \"type\": \"object\",\n",
|
| 185 |
+
" \"properties\": {\n",
|
| 186 |
+
" \"ifc_model_path\": {\n",
|
| 187 |
+
" \"type\": \"string\",\n",
|
| 188 |
+
" \"description\": \"Filesystem path to the IFC model.\"\n",
|
| 189 |
+
" },\n",
|
| 190 |
+
" \"min_area\": {\n",
|
| 191 |
+
" \"type\": \"number\",\n",
|
| 192 |
+
" \"description\": \"Minimum total area in mΒ². Default is 36.\"\n",
|
| 193 |
+
" }\n",
|
| 194 |
+
" },\n",
|
| 195 |
+
" \"required\": [\"ifc_model_path\"]\n",
|
| 196 |
+
" }\n",
|
| 197 |
+
"}\n",
|
| 198 |
+
"\n",
|
| 199 |
+
"# --------------------------\n",
|
| 200 |
+
"# Optional: local sanity test (no API usage)\n",
|
| 201 |
+
"# --------------------------\n",
|
| 202 |
+
"if __name__ == \"__main__\":\n",
|
| 203 |
+
" print(\"Schema OK:\")\n",
|
| 204 |
+
" print(json.dumps(DWELLING_AREA_CHECK_SCHEMA, indent=2))\n",
|
| 205 |
+
"\n",
|
| 206 |
+
" ifc_path = \"/content/ifc-bench/projects/duplex/arc.ifc\"\n",
|
| 207 |
+
" result = dwelling_area_check_tool(ifc_path, min_area=36.0)\n",
|
| 208 |
+
" print(result[\"result\"])\n",
|
| 209 |
+
" print(result[\"reason\"])\n"
|
| 210 |
+
],
|
| 211 |
+
"metadata": {
|
| 212 |
+
"colab": {
|
| 213 |
+
"base_uri": "https://localhost:8080/"
|
| 214 |
+
},
|
| 215 |
+
"id": "kLyNP9RDa9bN",
|
| 216 |
+
"outputId": "f8d9ef19-977d-4ac8-a547-fd5fed1ac5b2"
|
| 217 |
+
},
|
| 218 |
+
"execution_count": null,
|
| 219 |
+
"outputs": [
|
| 220 |
+
{
|
| 221 |
+
"output_type": "stream",
|
| 222 |
+
"name": "stdout",
|
| 223 |
+
"text": [
|
| 224 |
+
"Schema OK:\n",
|
| 225 |
+
"{\n",
|
| 226 |
+
" \"name\": \"dwelling_area_check_tool\",\n",
|
| 227 |
+
" \"description\": \"Checks whether an IFC dwelling meets a minimum total useful area requirement and returns a room-by-room breakdown.\",\n",
|
| 228 |
+
" \"parameters\": {\n",
|
| 229 |
+
" \"type\": \"object\",\n",
|
| 230 |
+
" \"properties\": {\n",
|
| 231 |
+
" \"ifc_model_path\": {\n",
|
| 232 |
+
" \"type\": \"string\",\n",
|
| 233 |
+
" \"description\": \"Filesystem path to the IFC model.\"\n",
|
| 234 |
+
" },\n",
|
| 235 |
+
" \"min_area\": {\n",
|
| 236 |
+
" \"type\": \"number\",\n",
|
| 237 |
+
" \"description\": \"Minimum total area in m\\u00b2. Default is 36.\"\n",
|
| 238 |
+
" }\n",
|
| 239 |
+
" },\n",
|
| 240 |
+
" \"required\": [\n",
|
| 241 |
+
" \"ifc_model_path\"\n",
|
| 242 |
+
" ]\n",
|
| 243 |
+
" }\n",
|
| 244 |
+
"}\n",
|
| 245 |
+
"pass\n",
|
| 246 |
+
"Total useful area 1711.01 mΒ² >= 36.00 mΒ²\n"
|
| 247 |
+
]
|
| 248 |
+
}
|
| 249 |
+
]
|
| 250 |
+
},
|
| 251 |
+
{
|
| 252 |
+
"cell_type": "markdown",
|
| 253 |
+
"metadata": {
|
| 254 |
+
"id": "w65dIpWxJpmH"
|
| 255 |
+
},
|
| 256 |
+
"source": [
|
| 257 |
+
"# TOOL B - Living room Height Regulation"
|
| 258 |
+
]
|
| 259 |
+
},
|
| 260 |
+
{
|
| 261 |
+
"cell_type": "code",
|
| 262 |
+
"source": [
|
| 263 |
+
"import json\n",
|
| 264 |
+
"import ifcopenshell\n",
|
| 265 |
+
"import ifcopenshell.geom\n",
|
| 266 |
+
"\n",
|
| 267 |
+
"# --------------------------\n",
|
| 268 |
+
"# Geometry settings\n",
|
| 269 |
+
"# --------------------------\n",
|
| 270 |
+
"settings = ifcopenshell.geom.settings()\n",
|
| 271 |
+
"settings.set(settings.USE_WORLD_COORDS, True)\n",
|
| 272 |
+
"\n",
|
| 273 |
+
"# --------------------------\n",
|
| 274 |
+
"# Helper functions\n",
|
| 275 |
+
"# --------------------------\n",
|
| 276 |
+
"def load_model(ifc_model_path):\n",
|
| 277 |
+
" \"\"\"Load IFC model\"\"\"\n",
|
| 278 |
+
" return ifcopenshell.open(ifc_model_path)\n",
|
| 279 |
+
"\n",
|
| 280 |
+
"def get_main_living_areas(ifc_model):\n",
|
| 281 |
+
" \"\"\"\n",
|
| 282 |
+
" Return spaces considered main living areas.\n",
|
| 283 |
+
" Filtering by common keywords in space name.\n",
|
| 284 |
+
" \"\"\"\n",
|
| 285 |
+
" spaces = ifc_model.by_type(\"IfcSpace\")\n",
|
| 286 |
+
" main_spaces = []\n",
|
| 287 |
+
" for s in spaces:\n",
|
| 288 |
+
" if s.Name and any(keyword in s.Name.lower() for keyword in [\"living\", \"bedroom\", \"hall\"]):\n",
|
| 289 |
+
" main_spaces.append(s)\n",
|
| 290 |
+
" return main_spaces\n",
|
| 291 |
+
"\n",
|
| 292 |
+
"def get_space_height(space):\n",
|
| 293 |
+
" \"\"\"\n",
|
| 294 |
+
" Approximate height of a space from geometry bounding box.\n",
|
| 295 |
+
" Uses Z coordinates of all vertices.\n",
|
| 296 |
+
" \"\"\"\n",
|
| 297 |
+
" try:\n",
|
| 298 |
+
" shape = ifcopenshell.geom.create_shape(settings, space)\n",
|
| 299 |
+
" verts = shape.geometry.verts\n",
|
| 300 |
+
" if not verts:\n",
|
| 301 |
+
" return 0.0\n",
|
| 302 |
+
" zs = verts[2::3]\n",
|
| 303 |
+
" return float(max(zs) - min(zs))\n",
|
| 304 |
+
" except Exception:\n",
|
| 305 |
+
" return 0.0\n",
|
| 306 |
+
"\n",
|
| 307 |
+
"# --------------------------\n",
|
| 308 |
+
"# Main check function\n",
|
| 309 |
+
"# --------------------------\n",
|
| 310 |
+
"def living_area_height_check(ifc_model_path, min_height=2.50):\n",
|
| 311 |
+
" \"\"\"\n",
|
| 312 |
+
" Check if all main living areas meet the minimum height requirement.\n",
|
| 313 |
+
"\n",
|
| 314 |
+
" Returns a dictionary with:\n",
|
| 315 |
+
" - result: 'pass' or 'fail'\n",
|
| 316 |
+
" - reason: explanation string\n",
|
| 317 |
+
" - room_heights: dictionary of room_name -> calculated height\n",
|
| 318 |
+
" \"\"\"\n",
|
| 319 |
+
" model = load_model(ifc_model_path)\n",
|
| 320 |
+
" spaces = get_main_living_areas(model)\n",
|
| 321 |
+
"\n",
|
| 322 |
+
" room_heights = {}\n",
|
| 323 |
+
" for s in spaces:\n",
|
| 324 |
+
" height = get_space_height(s)\n",
|
| 325 |
+
" room_name = s.Name or \"Unnamed\"\n",
|
| 326 |
+
" room_heights[room_name] = height\n",
|
| 327 |
+
" if height < min_height:\n",
|
| 328 |
+
" return {\n",
|
| 329 |
+
" \"result\": \"fail\",\n",
|
| 330 |
+
" \"reason\": f\"{room_name} height below {min_height}m\",\n",
|
| 331 |
+
" \"room_heights\": room_heights\n",
|
| 332 |
+
" }\n",
|
| 333 |
+
"\n",
|
| 334 |
+
" return {\n",
|
| 335 |
+
" \"result\": \"pass\",\n",
|
| 336 |
+
" \"reason\": f\"All main living areas meet minimum height {min_height}m\",\n",
|
| 337 |
+
" \"room_heights\": room_heights\n",
|
| 338 |
+
" }\n",
|
| 339 |
+
"\n",
|
| 340 |
+
"# --------------------------\n",
|
| 341 |
+
"# Tool entrypoint (for an LLM router later)\n",
|
| 342 |
+
"# --------------------------\n",
|
| 343 |
+
"def living_area_height_check_tool(ifc_model_path: str, min_height: float = 2.50):\n",
|
| 344 |
+
" return living_area_height_check(ifc_model_path, min_height)\n",
|
| 345 |
+
"\n",
|
| 346 |
+
"# --------------------------\n",
|
| 347 |
+
"# Schema (no API key needed)\n",
|
| 348 |
+
"# --------------------------\n",
|
| 349 |
+
"LIVING_AREA_HEIGHT_CHECK_SCHEMA = {\n",
|
| 350 |
+
" \"name\": \"living_area_height_check_tool\",\n",
|
| 351 |
+
" \"description\": \"Checks whether main living areas in an IFC meet a minimum height requirement and returns a room-by-room height breakdown.\",\n",
|
| 352 |
+
" \"parameters\": {\n",
|
| 353 |
+
" \"type\": \"object\",\n",
|
| 354 |
+
" \"properties\": {\n",
|
| 355 |
+
" \"ifc_model_path\": {\n",
|
| 356 |
+
" \"type\": \"string\",\n",
|
| 357 |
+
" \"description\": \"Filesystem path to the IFC model.\"\n",
|
| 358 |
+
" },\n",
|
| 359 |
+
" \"min_height\": {\n",
|
| 360 |
+
" \"type\": \"number\",\n",
|
| 361 |
+
" \"description\": \"Minimum height in meters. Default is 2.50.\"\n",
|
| 362 |
+
" }\n",
|
| 363 |
+
" },\n",
|
| 364 |
+
" \"required\": [\"ifc_model_path\"]\n",
|
| 365 |
+
" }\n",
|
| 366 |
+
"}\n",
|
| 367 |
+
"\n",
|
| 368 |
+
"# --------------------------\n",
|
| 369 |
+
"# Usage example (prints schema + pass/fail)\n",
|
| 370 |
+
"# --------------------------\n",
|
| 371 |
+
"if __name__ == \"__main__\":\n",
|
| 372 |
+
" print(\"Schema OK:\")\n",
|
| 373 |
+
" print(json.dumps(LIVING_AREA_HEIGHT_CHECK_SCHEMA, indent=2))\n",
|
| 374 |
+
"\n",
|
| 375 |
+
" ifc_path = \"/content/ifc-bench/projects/duplex/arc.ifc\" # change if needed\n",
|
| 376 |
+
" check = living_area_height_check_tool(ifc_path, min_height=2.50)\n",
|
| 377 |
+
"\n",
|
| 378 |
+
" print(\"\\nCheck result:\")\n",
|
| 379 |
+
" print(check[\"result\"])\n",
|
| 380 |
+
" print(check[\"reason\"])\n",
|
| 381 |
+
" print(\"Room heights:\", check[\"room_heights\"])\n"
|
| 382 |
+
],
|
| 383 |
+
"metadata": {
|
| 384 |
+
"colab": {
|
| 385 |
+
"base_uri": "https://localhost:8080/"
|
| 386 |
+
},
|
| 387 |
+
"id": "2ZM-IGvFfMDj",
|
| 388 |
+
"outputId": "57d50092-fece-4522-fa79-ff940129a895"
|
| 389 |
+
},
|
| 390 |
+
"execution_count": 7,
|
| 391 |
+
"outputs": [
|
| 392 |
+
{
|
| 393 |
+
"output_type": "stream",
|
| 394 |
+
"name": "stdout",
|
| 395 |
+
"text": [
|
| 396 |
+
"Schema OK:\n",
|
| 397 |
+
"{\n",
|
| 398 |
+
" \"name\": \"living_area_height_check_tool\",\n",
|
| 399 |
+
" \"description\": \"Checks whether main living areas in an IFC meet a minimum height requirement and returns a room-by-room height breakdown.\",\n",
|
| 400 |
+
" \"parameters\": {\n",
|
| 401 |
+
" \"type\": \"object\",\n",
|
| 402 |
+
" \"properties\": {\n",
|
| 403 |
+
" \"ifc_model_path\": {\n",
|
| 404 |
+
" \"type\": \"string\",\n",
|
| 405 |
+
" \"description\": \"Filesystem path to the IFC model.\"\n",
|
| 406 |
+
" },\n",
|
| 407 |
+
" \"min_height\": {\n",
|
| 408 |
+
" \"type\": \"number\",\n",
|
| 409 |
+
" \"description\": \"Minimum height in meters. Default is 2.50.\"\n",
|
| 410 |
+
" }\n",
|
| 411 |
+
" },\n",
|
| 412 |
+
" \"required\": [\n",
|
| 413 |
+
" \"ifc_model_path\"\n",
|
| 414 |
+
" ]\n",
|
| 415 |
+
" }\n",
|
| 416 |
+
"}\n",
|
| 417 |
+
"\n",
|
| 418 |
+
"Check result:\n",
|
| 419 |
+
"pass\n",
|
| 420 |
+
"All main living areas meet minimum height 2.5m\n",
|
| 421 |
+
"Room heights: {}\n"
|
| 422 |
+
]
|
| 423 |
+
}
|
| 424 |
+
]
|
| 425 |
+
},
|
| 426 |
+
{
|
| 427 |
+
"cell_type": "markdown",
|
| 428 |
+
"metadata": {
|
| 429 |
+
"id": "1WSJIjCPKUk2"
|
| 430 |
+
},
|
| 431 |
+
"source": [
|
| 432 |
+
"# TOOL C - Living Room Area Check"
|
| 433 |
+
]
|
| 434 |
+
},
|
| 435 |
+
{
|
| 436 |
+
"cell_type": "code",
|
| 437 |
+
"source": [
|
| 438 |
+
"import json\n",
|
| 439 |
+
"import math\n",
|
| 440 |
+
"import ifcopenshell\n",
|
| 441 |
+
"import ifcopenshell.geom\n",
|
| 442 |
+
"\n",
|
| 443 |
+
"# Optional: only needed if you truly have it available\n",
|
| 444 |
+
"# from ifcopenshell.util.element import get_psets as _get_psets\n",
|
| 445 |
+
"\n",
|
| 446 |
+
"# --------------------------\n",
|
| 447 |
+
"# Geometry settings\n",
|
| 448 |
+
"# --------------------------\n",
|
| 449 |
+
"settings = ifcopenshell.geom.settings()\n",
|
| 450 |
+
"settings.set(settings.USE_WORLD_COORDS, True)\n",
|
| 451 |
+
"\n",
|
| 452 |
+
"# --------------------------\n",
|
| 453 |
+
"# Helper functions\n",
|
| 454 |
+
"# --------------------------\n",
|
| 455 |
+
"def load_model(ifc_model_path):\n",
|
| 456 |
+
" return ifcopenshell.open(ifc_model_path)\n",
|
| 457 |
+
"\n",
|
| 458 |
+
"def calculate_space_area(space):\n",
|
| 459 |
+
" \"\"\"\n",
|
| 460 |
+
" Approximate area from mesh triangles.\n",
|
| 461 |
+
" Note: this is mesh surface area, not guaranteed to be floor area.\n",
|
| 462 |
+
" \"\"\"\n",
|
| 463 |
+
" if hasattr(space, \"NetFloorArea\") and space.NetFloorArea:\n",
|
| 464 |
+
" return float(space.NetFloorArea)\n",
|
| 465 |
+
"\n",
|
| 466 |
+
" try:\n",
|
| 467 |
+
" shape = ifcopenshell.geom.create_shape(settings, space)\n",
|
| 468 |
+
" verts = shape.geometry.verts\n",
|
| 469 |
+
" faces = shape.geometry.faces\n",
|
| 470 |
+
" area = 0.0\n",
|
| 471 |
+
"\n",
|
| 472 |
+
" for i in range(0, len(faces), 3):\n",
|
| 473 |
+
" idx0, idx1, idx2 = faces[i] * 3, faces[i + 1] * 3, faces[i + 2] * 3\n",
|
| 474 |
+
" v0 = verts[idx0:idx0 + 3]\n",
|
| 475 |
+
" v1 = verts[idx1:idx1 + 3]\n",
|
| 476 |
+
" v2 = verts[idx2:idx2 + 3]\n",
|
| 477 |
+
"\n",
|
| 478 |
+
" a = math.sqrt((v1[0]-v0[0])**2 + (v1[1]-v0[1])**2 + (v1[2]-v0[2])**2)\n",
|
| 479 |
+
" b = math.sqrt((v2[0]-v1[0])**2 + (v2[1]-v1[1])**2 + (v2[2]-v1[2])**2)\n",
|
| 480 |
+
" c = math.sqrt((v0[0]-v2[0])**2 + (v0[1]-v2[1])**2 + (v0[2]-v2[2])**2)\n",
|
| 481 |
+
"\n",
|
| 482 |
+
" s = (a + b + c) / 2.0\n",
|
| 483 |
+
" area += math.sqrt(max(s * (s - a) * (s - b) * (s - c), 0.0))\n",
|
| 484 |
+
"\n",
|
| 485 |
+
" return float(area)\n",
|
| 486 |
+
" except Exception:\n",
|
| 487 |
+
" return 0.0\n",
|
| 488 |
+
"\n",
|
| 489 |
+
"def get_space_name(space):\n",
|
| 490 |
+
" \"\"\"Get descriptive name if available, fallback to LongName/Name/GlobalId.\"\"\"\n",
|
| 491 |
+
" if getattr(space, \"LongName\", None):\n",
|
| 492 |
+
" return str(space.LongName)\n",
|
| 493 |
+
" if getattr(space, \"Name\", None):\n",
|
| 494 |
+
" return str(space.Name)\n",
|
| 495 |
+
"\n",
|
| 496 |
+
" # If you have ifcopenshell.util.element available, you can enable this block:\n",
|
| 497 |
+
" # try:\n",
|
| 498 |
+
" # psets = _get_psets(space)\n",
|
| 499 |
+
" # for props in (psets or {}).values():\n",
|
| 500 |
+
" # for k, v in (props or {}).items():\n",
|
| 501 |
+
" # if \"name\" in str(k).lower() and isinstance(v, str) and v.strip():\n",
|
| 502 |
+
" # return v.strip()\n",
|
| 503 |
+
" # except Exception:\n",
|
| 504 |
+
" # pass\n",
|
| 505 |
+
"\n",
|
| 506 |
+
" return str(getattr(space, \"GlobalId\", \"Unnamed\"))\n",
|
| 507 |
+
"\n",
|
| 508 |
+
"def can_fit_square(area_m2, width=2.4, depth=2.4):\n",
|
| 509 |
+
" \"\"\"Approx check: area must allow a width x depth square.\"\"\"\n",
|
| 510 |
+
" return float(area_m2) >= float(width) * float(depth)\n",
|
| 511 |
+
"\n",
|
| 512 |
+
"# --------------------------\n",
|
| 513 |
+
"# Living room compliance\n",
|
| 514 |
+
"# --------------------------\n",
|
| 515 |
+
"def living_room_compliance(ifc_model_path):\n",
|
| 516 |
+
" \"\"\"\n",
|
| 517 |
+
" Rule encoded:\n",
|
| 518 |
+
" - Living room min area 10 mΒ²\n",
|
| 519 |
+
" - If living+kitchen combined: min area 14 mΒ²\n",
|
| 520 |
+
" - Must allow 2.40 x 2.40 m clearance (approx via area check)\n",
|
| 521 |
+
" Returns list of per-space results.\n",
|
| 522 |
+
" \"\"\"\n",
|
| 523 |
+
" model = load_model(ifc_model_path)\n",
|
| 524 |
+
" spaces = model.by_type(\"IfcSpace\")\n",
|
| 525 |
+
"\n",
|
| 526 |
+
" results = []\n",
|
| 527 |
+
"\n",
|
| 528 |
+
" for space in spaces:\n",
|
| 529 |
+
" raw_name = get_space_name(space)\n",
|
| 530 |
+
" name = raw_name.lower()\n",
|
| 531 |
+
" area = calculate_space_area(space)\n",
|
| 532 |
+
" if area <= 0:\n",
|
| 533 |
+
" continue\n",
|
| 534 |
+
"\n",
|
| 535 |
+
" if \"living\" in name:\n",
|
| 536 |
+
" has_kitchen = \"kitchen\" in name\n",
|
| 537 |
+
" min_area = 14.0 if has_kitchen else 10.0\n",
|
| 538 |
+
"\n",
|
| 539 |
+
" clearance_ok = can_fit_square(area, 2.4, 2.4)\n",
|
| 540 |
+
" passed = (area >= min_area) and clearance_ok\n",
|
| 541 |
+
"\n",
|
| 542 |
+
" reasons = []\n",
|
| 543 |
+
" if area < min_area:\n",
|
| 544 |
+
" reasons.append(f\"Area {area:.2f} mΒ² < required {min_area:.2f} mΒ²\")\n",
|
| 545 |
+
" if not clearance_ok:\n",
|
| 546 |
+
" reasons.append(\"Does not allow 2.40 m x 2.40 m square (approx)\")\n",
|
| 547 |
+
"\n",
|
| 548 |
+
" results.append({\n",
|
| 549 |
+
" \"space_name\": raw_name,\n",
|
| 550 |
+
" \"area_m2\": float(area),\n",
|
| 551 |
+
" \"min_required_m2\": float(min_area),\n",
|
| 552 |
+
" \"clearance_ok\": bool(clearance_ok),\n",
|
| 553 |
+
" \"result\": \"pass\" if passed else \"fail\",\n",
|
| 554 |
+
" \"reason\": \"; \".join(reasons) if reasons else \"Complies\"\n",
|
| 555 |
+
" })\n",
|
| 556 |
+
"\n",
|
| 557 |
+
" return results\n",
|
| 558 |
+
"\n",
|
| 559 |
+
"# --------------------------\n",
|
| 560 |
+
"# Tool entrypoint (for an LLM router later)\n",
|
| 561 |
+
"# --------------------------\n",
|
| 562 |
+
"def living_room_compliance_tool(ifc_model_path: str):\n",
|
| 563 |
+
" return living_room_compliance(ifc_model_path)\n",
|
| 564 |
+
"\n",
|
| 565 |
+
"# --------------------------\n",
|
| 566 |
+
"# Schema (no API key needed)\n",
|
| 567 |
+
"# --------------------------\n",
|
| 568 |
+
"LIVING_ROOM_COMPLIANCE_SCHEMA = {\n",
|
| 569 |
+
" \"name\": \"living_room_compliance_tool\",\n",
|
| 570 |
+
" \"description\": \"Checks living room spaces in an IFC for minimum area and clearance rules and returns per-space compliance results.\",\n",
|
| 571 |
+
" \"parameters\": {\n",
|
| 572 |
+
" \"type\": \"object\",\n",
|
| 573 |
+
" \"properties\": {\n",
|
| 574 |
+
" \"ifc_model_path\": {\n",
|
| 575 |
+
" \"type\": \"string\",\n",
|
| 576 |
+
" \"description\": \"Filesystem path to the IFC model.\"\n",
|
| 577 |
+
" }\n",
|
| 578 |
+
" },\n",
|
| 579 |
+
" \"required\": [\"ifc_model_path\"]\n",
|
| 580 |
+
" }\n",
|
| 581 |
+
"}\n",
|
| 582 |
+
"\n",
|
| 583 |
+
"# --------------------------\n",
|
| 584 |
+
"# Usage example (prints schema + report)\n",
|
| 585 |
+
"# --------------------------\n",
|
| 586 |
+
"if __name__ == \"__main__\":\n",
|
| 587 |
+
" print(\"Schema OK:\")\n",
|
| 588 |
+
" print(json.dumps(LIVING_ROOM_COMPLIANCE_SCHEMA, indent=2))\n",
|
| 589 |
+
"\n",
|
| 590 |
+
" ifc_path = \"/content/ifc-bench/projects/duplex/arc.ifc\" # change if needed\n",
|
| 591 |
+
" report = living_room_compliance_tool(ifc_path)\n",
|
| 592 |
+
"\n",
|
| 593 |
+
" print(\"\\nReport:\")\n",
|
| 594 |
+
" if not report:\n",
|
| 595 |
+
" print(\"No living room spaces matched (keyword 'living') or no computable areas.\")\n",
|
| 596 |
+
" else:\n",
|
| 597 |
+
" for r in report:\n",
|
| 598 |
+
" print(f\"{r['space_name']}: {r['area_m2']:.2f} mΒ² (min {r['min_required_m2']:.2f} mΒ²) β {r['result']}\")\n",
|
| 599 |
+
" if r[\"result\"] == \"fail\":\n",
|
| 600 |
+
" print(f\" Reason: {r['reason']}\")"
|
| 601 |
+
],
|
| 602 |
+
"metadata": {
|
| 603 |
+
"colab": {
|
| 604 |
+
"base_uri": "https://localhost:8080/"
|
| 605 |
+
},
|
| 606 |
+
"id": "VdNVP6ZxlSMv",
|
| 607 |
+
"outputId": "1199768c-fb27-4aa6-f63f-6f0be8259a5d"
|
| 608 |
+
},
|
| 609 |
+
"execution_count": 8,
|
| 610 |
+
"outputs": [
|
| 611 |
+
{
|
| 612 |
+
"output_type": "stream",
|
| 613 |
+
"name": "stdout",
|
| 614 |
+
"text": [
|
| 615 |
+
"Schema OK:\n",
|
| 616 |
+
"{\n",
|
| 617 |
+
" \"name\": \"living_room_compliance_tool\",\n",
|
| 618 |
+
" \"description\": \"Checks living room spaces in an IFC for minimum area and clearance rules and returns per-space compliance results.\",\n",
|
| 619 |
+
" \"parameters\": {\n",
|
| 620 |
+
" \"type\": \"object\",\n",
|
| 621 |
+
" \"properties\": {\n",
|
| 622 |
+
" \"ifc_model_path\": {\n",
|
| 623 |
+
" \"type\": \"string\",\n",
|
| 624 |
+
" \"description\": \"Filesystem path to the IFC model.\"\n",
|
| 625 |
+
" }\n",
|
| 626 |
+
" },\n",
|
| 627 |
+
" \"required\": [\n",
|
| 628 |
+
" \"ifc_model_path\"\n",
|
| 629 |
+
" ]\n",
|
| 630 |
+
" }\n",
|
| 631 |
+
"}\n",
|
| 632 |
+
"\n",
|
| 633 |
+
"Report:\n",
|
| 634 |
+
"Living Room: 109.86 mΒ² (min 10.00 mΒ²) β pass\n",
|
| 635 |
+
"Living Room: 109.86 mΒ² (min 10.00 mΒ²) β pass\n"
|
| 636 |
+
]
|
| 637 |
+
}
|
| 638 |
+
]
|
| 639 |
+
},
|
| 640 |
+
{
|
| 641 |
+
"cell_type": "markdown",
|
| 642 |
+
"source": [
|
| 643 |
+
"# Tool D Service Area Heights"
|
| 644 |
+
],
|
| 645 |
+
"metadata": {
|
| 646 |
+
"id": "4aFj5ZiBlYYm"
|
| 647 |
+
}
|
| 648 |
+
},
|
| 649 |
+
{
|
| 650 |
+
"cell_type": "code",
|
| 651 |
+
"source": [
|
| 652 |
+
"import json\n",
|
| 653 |
+
"import ifcopenshell\n",
|
| 654 |
+
"import ifcopenshell.geom\n",
|
| 655 |
+
"\n",
|
| 656 |
+
"# --------------------------\n",
|
| 657 |
+
"# Geometry settings\n",
|
| 658 |
+
"# --------------------------\n",
|
| 659 |
+
"settings = ifcopenshell.geom.settings()\n",
|
| 660 |
+
"settings.set(settings.USE_WORLD_COORDS, True)\n",
|
| 661 |
+
"\n",
|
| 662 |
+
"# --------------------------\n",
|
| 663 |
+
"# Helper functions\n",
|
| 664 |
+
"# --------------------------\n",
|
| 665 |
+
"def load_model(ifc_model_path):\n",
|
| 666 |
+
" return ifcopenshell.open(ifc_model_path)\n",
|
| 667 |
+
"\n",
|
| 668 |
+
"def get_space_name(space):\n",
|
| 669 |
+
" \"\"\"Get descriptive name if available, fallback to LongName/Name/GlobalId.\"\"\"\n",
|
| 670 |
+
" if getattr(space, \"LongName\", None):\n",
|
| 671 |
+
" return str(space.LongName)\n",
|
| 672 |
+
" if getattr(space, \"Name\", None):\n",
|
| 673 |
+
" return str(space.Name)\n",
|
| 674 |
+
" return str(getattr(space, \"GlobalId\", \"Unnamed\"))\n",
|
| 675 |
+
"\n",
|
| 676 |
+
"def get_space_height(space):\n",
|
| 677 |
+
" \"\"\"\n",
|
| 678 |
+
" Approximate height of a space from geometry bounding box.\n",
|
| 679 |
+
" Uses Z coordinates of all vertices.\n",
|
| 680 |
+
" \"\"\"\n",
|
| 681 |
+
" try:\n",
|
| 682 |
+
" shape = ifcopenshell.geom.create_shape(settings, space)\n",
|
| 683 |
+
" verts = shape.geometry.verts\n",
|
| 684 |
+
" if not verts:\n",
|
| 685 |
+
" return 0.0\n",
|
| 686 |
+
" zs = verts[2::3]\n",
|
| 687 |
+
" return float(max(zs) - min(zs))\n",
|
| 688 |
+
" except Exception:\n",
|
| 689 |
+
" return 0.0\n",
|
| 690 |
+
"\n",
|
| 691 |
+
"def is_service_space(space_name_lower):\n",
|
| 692 |
+
" \"\"\"Bathrooms, kitchens, hallways by keywords.\"\"\"\n",
|
| 693 |
+
" service_keywords = [\n",
|
| 694 |
+
" \"bath\", \"bathroom\", \"baΓ±o\", \"bano\", \"wc\", \"toilet\",\n",
|
| 695 |
+
" \"kitchen\", \"cocina\",\n",
|
| 696 |
+
" \"hall\", \"hallway\", \"corridor\", \"pasillo\"\n",
|
| 697 |
+
" ]\n",
|
| 698 |
+
" return any(k in space_name_lower for k in service_keywords)\n",
|
| 699 |
+
"\n",
|
| 700 |
+
"# --------------------------\n",
|
| 701 |
+
"# Compliance check\n",
|
| 702 |
+
"# --------------------------\n",
|
| 703 |
+
"def service_spaces_min_height_check(ifc_model_path, min_height=2.20):\n",
|
| 704 |
+
" \"\"\"\n",
|
| 705 |
+
" Regulation:\n",
|
| 706 |
+
" - Minimum height in bathrooms, kitchens, and hallways is 2.20 m\n",
|
| 707 |
+
"\n",
|
| 708 |
+
" Returns:\n",
|
| 709 |
+
" - result: 'pass' or 'fail'\n",
|
| 710 |
+
" - reason\n",
|
| 711 |
+
" - room_heights: dict label -> height\n",
|
| 712 |
+
" - checked_spaces: list of labels that were evaluated\n",
|
| 713 |
+
" \"\"\"\n",
|
| 714 |
+
" model = load_model(ifc_model_path)\n",
|
| 715 |
+
" spaces = model.by_type(\"IfcSpace\")\n",
|
| 716 |
+
"\n",
|
| 717 |
+
" room_heights = {}\n",
|
| 718 |
+
" checked_spaces = []\n",
|
| 719 |
+
"\n",
|
| 720 |
+
" for space in spaces:\n",
|
| 721 |
+
" label = get_space_name(space)\n",
|
| 722 |
+
" label_l = label.lower()\n",
|
| 723 |
+
"\n",
|
| 724 |
+
" if not is_service_space(label_l):\n",
|
| 725 |
+
" continue\n",
|
| 726 |
+
"\n",
|
| 727 |
+
" checked_spaces.append(label)\n",
|
| 728 |
+
" h = get_space_height(space)\n",
|
| 729 |
+
" room_heights[label] = h\n",
|
| 730 |
+
"\n",
|
| 731 |
+
" if h < float(min_height):\n",
|
| 732 |
+
" return {\n",
|
| 733 |
+
" \"result\": \"fail\",\n",
|
| 734 |
+
" \"reason\": f\"{label} height below {float(min_height):.2f}m\",\n",
|
| 735 |
+
" \"room_heights\": room_heights,\n",
|
| 736 |
+
" \"checked_spaces\": checked_spaces\n",
|
| 737 |
+
" }\n",
|
| 738 |
+
"\n",
|
| 739 |
+
" if not checked_spaces:\n",
|
| 740 |
+
" return {\n",
|
| 741 |
+
" \"result\": \"fail\",\n",
|
| 742 |
+
" \"reason\": \"No bathrooms/kitchens/hallways matched by keywords, nothing checked\",\n",
|
| 743 |
+
" \"room_heights\": room_heights,\n",
|
| 744 |
+
" \"checked_spaces\": checked_spaces\n",
|
| 745 |
+
" }\n",
|
| 746 |
+
"\n",
|
| 747 |
+
" return {\n",
|
| 748 |
+
" \"result\": \"pass\",\n",
|
| 749 |
+
" \"reason\": f\"All checked bathrooms/kitchens/hallways meet minimum height {float(min_height):.2f}m\",\n",
|
| 750 |
+
" \"room_heights\": room_heights,\n",
|
| 751 |
+
" \"checked_spaces\": checked_spaces\n",
|
| 752 |
+
" }\n",
|
| 753 |
+
"\n",
|
| 754 |
+
"# --------------------------\n",
|
| 755 |
+
"# Tool entrypoint (for an LLM router later)\n",
|
| 756 |
+
"# --------------------------\n",
|
| 757 |
+
"def service_spaces_min_height_check_tool(ifc_model_path: str, min_height: float = 2.20):\n",
|
| 758 |
+
" return service_spaces_min_height_check(ifc_model_path, min_height)\n",
|
| 759 |
+
"\n",
|
| 760 |
+
"# --------------------------\n",
|
| 761 |
+
"# Schema (no API key needed)\n",
|
| 762 |
+
"# --------------------------\n",
|
| 763 |
+
"SERVICE_SPACES_MIN_HEIGHT_SCHEMA = {\n",
|
| 764 |
+
" \"name\": \"service_spaces_min_height_check_tool\",\n",
|
| 765 |
+
" \"description\": \"Checks bathrooms, kitchens, and hallways in an IFC for a minimum height requirement (default 2.20m).\",\n",
|
| 766 |
+
" \"parameters\": {\n",
|
| 767 |
+
" \"type\": \"object\",\n",
|
| 768 |
+
" \"properties\": {\n",
|
| 769 |
+
" \"ifc_model_path\": {\n",
|
| 770 |
+
" \"type\": \"string\",\n",
|
| 771 |
+
" \"description\": \"Filesystem path to the IFC model.\"\n",
|
| 772 |
+
" },\n",
|
| 773 |
+
" \"min_height\": {\n",
|
| 774 |
+
" \"type\": \"number\",\n",
|
| 775 |
+
" \"description\": \"Minimum height in meters. Default is 2.20.\"\n",
|
| 776 |
+
" }\n",
|
| 777 |
+
" },\n",
|
| 778 |
+
" \"required\": [\"ifc_model_path\"]\n",
|
| 779 |
+
" }\n",
|
| 780 |
+
"}\n",
|
| 781 |
+
"\n",
|
| 782 |
+
"# --------------------------\n",
|
| 783 |
+
"# Usage example (prints schema + pass/fail)\n",
|
| 784 |
+
"# --------------------------\n",
|
| 785 |
+
"if __name__ == \"__main__\":\n",
|
| 786 |
+
" print(\"Schema OK:\")\n",
|
| 787 |
+
" print(json.dumps(SERVICE_SPACES_MIN_HEIGHT_SCHEMA, indent=2))\n",
|
| 788 |
+
"\n",
|
| 789 |
+
" ifc_path = \"/content/ifc-bench/projects/duplex/arc.ifc\" # change if needed\n",
|
| 790 |
+
" check = service_spaces_min_height_check_tool(ifc_path, min_height=2.20)\n",
|
| 791 |
+
"\n",
|
| 792 |
+
" print(\"\\nCheck result:\")\n",
|
| 793 |
+
" print(check[\"result\"])\n",
|
| 794 |
+
" print(check[\"reason\"])\n",
|
| 795 |
+
" print(\"Room heights:\", check[\"room_heights\"])\n",
|
| 796 |
+
" print(\"Checked spaces:\", check[\"checked_spaces\"])"
|
| 797 |
+
],
|
| 798 |
+
"metadata": {
|
| 799 |
+
"colab": {
|
| 800 |
+
"base_uri": "https://localhost:8080/"
|
| 801 |
+
},
|
| 802 |
+
"id": "IWZ7ISP3mYg4",
|
| 803 |
+
"outputId": "aeaa8d7f-6b14-4339-984b-6da478889046"
|
| 804 |
+
},
|
| 805 |
+
"execution_count": 9,
|
| 806 |
+
"outputs": [
|
| 807 |
+
{
|
| 808 |
+
"output_type": "stream",
|
| 809 |
+
"name": "stdout",
|
| 810 |
+
"text": [
|
| 811 |
+
"Schema OK:\n",
|
| 812 |
+
"{\n",
|
| 813 |
+
" \"name\": \"service_spaces_min_height_check_tool\",\n",
|
| 814 |
+
" \"description\": \"Checks bathrooms, kitchens, and hallways in an IFC for a minimum height requirement (default 2.20m).\",\n",
|
| 815 |
+
" \"parameters\": {\n",
|
| 816 |
+
" \"type\": \"object\",\n",
|
| 817 |
+
" \"properties\": {\n",
|
| 818 |
+
" \"ifc_model_path\": {\n",
|
| 819 |
+
" \"type\": \"string\",\n",
|
| 820 |
+
" \"description\": \"Filesystem path to the IFC model.\"\n",
|
| 821 |
+
" },\n",
|
| 822 |
+
" \"min_height\": {\n",
|
| 823 |
+
" \"type\": \"number\",\n",
|
| 824 |
+
" \"description\": \"Minimum height in meters. Default is 2.20.\"\n",
|
| 825 |
+
" }\n",
|
| 826 |
+
" },\n",
|
| 827 |
+
" \"required\": [\n",
|
| 828 |
+
" \"ifc_model_path\"\n",
|
| 829 |
+
" ]\n",
|
| 830 |
+
" }\n",
|
| 831 |
+
"}\n",
|
| 832 |
+
"\n",
|
| 833 |
+
"Check result:\n",
|
| 834 |
+
"pass\n",
|
| 835 |
+
"All checked bathrooms/kitchens/hallways meet minimum height 2.20m\n",
|
| 836 |
+
"Room heights: {'Hallway': 2.8810000000001947, 'Bathroom 2': 2.5870000000001836, 'Bathroom 1': 2.587000000000001, 'Kitchen': 2.587000000000001}\n",
|
| 837 |
+
"Checked spaces: ['Hallway', 'Hallway', 'Bathroom 2', 'Bathroom 1', 'Kitchen', 'Kitchen', 'Bathroom 2', 'Bathroom 1']\n"
|
| 838 |
+
]
|
| 839 |
+
}
|
| 840 |
+
]
|
| 841 |
+
}
|
| 842 |
+
],
|
| 843 |
+
"metadata": {
|
| 844 |
+
"colab": {
|
| 845 |
+
"provenance": []
|
| 846 |
+
},
|
| 847 |
+
"kernelspec": {
|
| 848 |
+
"display_name": "Python 3",
|
| 849 |
+
"name": "python3"
|
| 850 |
+
},
|
| 851 |
+
"language_info": {
|
| 852 |
+
"name": "python"
|
| 853 |
+
}
|
| 854 |
+
},
|
| 855 |
+
"nbformat": 4,
|
| 856 |
+
"nbformat_minor": 0
|
| 857 |
+
}
|
teams/Mastodonte/USER_PORTAL_PRD.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# User Portal β PRD (IFCore) β MVP
|
| 2 |
+
|
| 3 |
+
## Overview (Revised per Team B feedback)
|
| 4 |
+
A lightweight authentication system for IFCore enabling sign up, login, logout, and profile management. This is a **foundation feature** that unblocks all other teams by providing `useSession()` hooks and role-based access.
|
| 5 |
+
|
| 6 |
+
**Scope:** Signup β Login β Profile page only. **Duration:** 4β6 hours (not 4 weeks).
|
| 7 |
+
|
| 8 |
+
Uses:
|
| 9 |
+
- **Frontend:** Cloudflare Pages (TanStack Router, Zustand)
|
| 10 |
+
- **Auth backend:** Cloudflare Worker with **Better Auth** (replaces manual auth implementation)
|
| 11 |
+
- **Database:** D1 (Better Auth creates its own tables; does NOT modify existing `users` table)
|
| 12 |
+
- **Email/Stripe/TOTP:** Cut from MVP (post-launch features)
|
| 13 |
+
|
| 14 |
+
## Users & Roles (Simplified)
|
| 15 |
+
- **All users**: sign up with email + password, log in, view profile, log out.
|
| 16 |
+
- **Role model**: `role: "member"` (default) or `role: "captain"` (admins). Simple, not four-role RBAC.
|
| 17 |
+
|
| 18 |
+
## Goals
|
| 19 |
+
1. **Unblock all other teams** by providing `useSession()` React hook and persistent session state.
|
| 20 |
+
2. Ship a working login/signup flow in < 6 hours using Better Auth (not custom hashing/session logic).
|
| 21 |
+
3. Integrate with D1 and Cloudflare Workers following IFCore architecture.
|
| 22 |
+
4. Respect existing `users` table schema β use Better Auth's default tables instead.
|
| 23 |
+
|
| 24 |
+
## MVP Scope (Shippable in 1 day)
|
| 25 |
+
|
| 26 |
+
1. **Sign up** β email + password. Create user in Better Auth's `user` table.
|
| 27 |
+
2. **Log in** β email + password. Session cookie set by Worker.
|
| 28 |
+
3. **Log out** β clear session, redirect to home.
|
| 29 |
+
4. **Profile page** β show current user's name and email. Link to logout.
|
| 30 |
+
5. **Navigation** β show "Log in" if unauthenticated, show user name + "Profile" link if authenticated.
|
| 31 |
+
6. **RBAC enforcement** β each user has `role: "member"` or `"captain"` (stored in Better Auth's user table).
|
| 32 |
+
|
| 33 |
+
### NOT in MVP (cut for later)
|
| 34 |
+
- Email verification
|
| 35 |
+
- Password reset
|
| 36 |
+
- Stripe billing / subscription management
|
| 37 |
+
- 2FA / TOTP
|
| 38 |
+
- Support ticketing
|
| 39 |
+
- Account deletion
|
| 40 |
+
- API key management
|
| 41 |
+
- Onboarding questionnaire
|
| 42 |
+
- T&Cs flow (optional: can add with a checkbox, but no signing logic)
|
| 43 |
+
- Support ticket system
|
| 44 |
+
|
| 45 |
+
## Data Model (Better Auth defaults + minimal custom fields)
|
| 46 |
+
|
| 47 |
+
Better Auth creates these tables automatically:
|
| 48 |
+
- `user` β id, email, password (hashed with PBKDF2, NOT bcrypt), name, email_verified (boolean), image (optional), role (TEXT: "member" or "captain")
|
| 49 |
+
- `session` β session management (Better Auth handles this)
|
| 50 |
+
- `account` β OAuth/social login (not used in MVP)
|
| 51 |
+
- `verification` β email verification tokens (not used in MVP)
|
| 52 |
+
|
| 53 |
+
**Do NOT modify the existing `users` table.** It is used by other teams and the platform. Better Auth's `user` table is separate and additive.
|
| 54 |
+
|
| 55 |
+
## API Endpoints (Worker, via Better Auth)
|
| 56 |
+
|
| 57 |
+
All endpoints are auto-generated by Better Auth under `/api/auth/*`:
|
| 58 |
+
- `POST /api/auth/sign-up` β Create new user (email, password, name)
|
| 59 |
+
- `POST /api/auth/sign-in` β Log in (email, password) β returns session cookie
|
| 60 |
+
- `POST /api/auth/sign-out` β Log out β clear session cookie
|
| 61 |
+
- `GET /api/auth/session` β Get current session (for `useSession()` hook)
|
| 62 |
+
|
| 63 |
+
Other teams call `useSession()` to access the session state. No custom API design needed.
|
| 64 |
+
|
| 65 |
+
## UX Flows (simplified)
|
| 66 |
+
|
| 67 |
+
1. **Sign up** β Form (email, password, name) β POST /api/auth/sign-up β Session cookie set β Redirect to `/profile`
|
| 68 |
+
2. **Log in** β Form (email, password) β POST /api/auth/sign-in β Session cookie set β Redirect to `/profile`
|
| 69 |
+
3. **Log out** β POST /api/auth/sign-out β Clear cookie β Redirect to `/`
|
| 70 |
+
4. **Profile page** β Show user name, email. Link to logout.
|
| 71 |
+
5. **Nav bar** β If logged in, show name + "Profile" link. Else, show "Log In" link.
|
| 72 |
+
|
| 73 |
+
## Why Better Auth (not custom auth)
|
| 74 |
+
|
| 75 |
+
**Rule:** Do NOT use bcrypt for password hashing in Cloudflare Workers. Bcrypt exceeds the 10ms CPU limit and causes Error 1102. Better Auth handles this automatically with PBKDF2.
|
| 76 |
+
|
| 77 |
+
**Why not build custom auth:**
|
| 78 |
+
- Passwords: hashing, validation, salting (security-critical, weeks of testing).
|
| 79 |
+
- Sessions: cookie management, CSRF tokens, expiry, refresh logic.
|
| 80 |
+
- Database schema: user tables, session tables, verifications (tedious DDL).
|
| 81 |
+
- Cookies: HTTP-only, SameSite, Secure flags.
|
| 82 |
+
|
| 83 |
+
Better Auth does all of this in ~50 lines of config. Use it.
|
| 84 |
+
|
| 85 |
+
## Security & Compliance (simplified for MVP)
|
| 86 |
+
|
| 87 |
+
- **Passwords:** Better Auth uses PBKDF2 (works in Workers). Enforces strong passwords at signup.
|
| 88 |
+
- **Sessions:** HTTP-only, Secure, SameSite=Strict cookies managed by Better Auth.
|
| 89 |
+
- **CSRF:** Automatic via SameSite cookies.
|
| 90 |
+
- **Database tables:** Better Auth creates its own; does NOT touch existing `users` table.
|
| 91 |
+
- **Env variables:** BETTER_AUTH_SECRET and BETTER_AUTH_URL stored in Cloudflare secrets or `.dev.vars`.
|
| 92 |
+
|
| 93 |
+
## Monitoring & Alerts
|
| 94 |
+
- Not in MVP. Post-launch: monitor signup/login failure spikes, session errors, Worker errors via D1 logs.
|
| 95 |
+
|
| 96 |
+
## Edge Cases & Error Handling
|
| 97 |
+
- Invalid credentials: show error on login form.
|
| 98 |
+
- Password too weak: show validation error.
|
| 99 |
+
- Email already registered: show error on signup.
|
| 100 |
+
- Session expired: redirect to login.
|
| 101 |
+
- Database errors: log and show generic "something went wrong" message.
|
| 102 |
+
|
| 103 |
+
No complex retry logic, idempotency, or partial provisioning recovery needed for MVP.
|
| 104 |
+
|
| 105 |
+
## MVP (v1) β 4β6 hours, shippable feature
|
| 106 |
+
|
| 107 |
+
**Backend (2β3 hours)**
|
| 108 |
+
- Install Better Auth and configure for D1 + PBKDF2.
|
| 109 |
+
- Mount auth routes in Cloudflare Worker at `/api/auth/*`.
|
| 110 |
+
- Create D1 migration for Better Auth tables (`user`, `session`, `account`, `verification`).
|
| 111 |
+
- Ensure `run_worker_first: ["/api/*"]` in wrangler.jsonc so Worker intercepts requests.
|
| 112 |
+
- Set `BETTER_AUTH_SECRET` and `BETTER_AUTH_URL` environment variables.
|
| 113 |
+
|
| 114 |
+
**Frontend (2β3 hours)**
|
| 115 |
+
- Create `auth-client.ts` with `createAuthClient` and `useSession` hook.
|
| 116 |
+
- Build login page: email + password form, "Sign Up" button.
|
| 117 |
+
- Build signup page: email + password + name form.
|
| 118 |
+
- Build profile page: show user name, email, logout button.
|
| 119 |
+
- Update navbar: show login/logout state conditionally.
|
| 120 |
+
- Deploy to Cloudflare Pages.
|
| 121 |
+
|
| 122 |
+
**Testing (30 min)**
|
| 123 |
+
- Sign up with new email.
|
| 124 |
+
- Verify navbar shows your name.
|
| 125 |
+
- Log out and verify navbar changes.
|
| 126 |
+
- Log back in and verify profile page works.
|
| 127 |
+
|
| 128 |
+
## Acceptance Criteria
|
| 129 |
+
- Users can sign up with email + password and are logged in automatically.
|
| 130 |
+
- Users can log in with existing email + password.
|
| 131 |
+
- Users can log out and navbar reflects unauthenticated state.
|
| 132 |
+
- Profile page shows user name and email; only accessible when logged in.
|
| 133 |
+
- Session persists across page reloads (cookie-based).
|
| 134 |
+
- Better Auth tables created in D1 without modifying the existing `users` table.
|
| 135 |
+
|
| 136 |
+
## How This Unblocks Other Teams
|
| 137 |
+
|
| 138 |
+
Every other team uses `useSession()` to gate features and filter data:
|
| 139 |
+
```jsx
|
| 140 |
+
import { useSession } from '@/lib/auth-client'
|
| 141 |
+
|
| 142 |
+
export function Profile() {
|
| 143 |
+
const { data: session } = useSession()
|
| 144 |
+
if (!session) return <Redirect to="/login" />
|
| 145 |
+
return <h1>Welcome {session.user.name}</h1>
|
| 146 |
+
}
|
| 147 |
+
```
|
| 148 |
+
|
| 149 |
+
You ship login/signup/profile. Other teams ship the dashboard, 3D viewer, report generator. Together = complete product.
|
| 150 |
+
|
| 151 |
+
## Next Steps / Deliverables
|
| 152 |
+
1. Review this MVP scope and approve (should take ~1 hour, not 4 weeks).
|
| 153 |
+
2. Scaffold Cloudflare Worker Better Auth config (use the Cloudflare skill blueprint).
|
| 154 |
+
3. Create frontend components (LoginPage, SignupPage, ProfilePage).
|
| 155 |
+
4. Deploy to staging and test end-to-end.
|
| 156 |
+
5. Do quick RBAC test (ensure captain role is stored/retrieved).
|
| 157 |
+
6. Deploy to production.
|
| 158 |
+
|
| 159 |
+
## Post-Launch Roadmap (out of scope for MVP)
|
| 160 |
+
- Email verification and password reset
|
| 161 |
+
- Stripe billing + subscription management
|
| 162 |
+
- 2FA / TOTP
|
| 163 |
+
- Support ticket system
|
| 164 |
+
- Account deletion with retention window
|
| 165 |
+
- Team invite / member management
|
| 166 |
+
- API key generation and rotation
|
teams/Mastodonte/main.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
IFCore Compliance Checker - Integration Test
|
| 3 |
+
|
| 4 |
+
This script tests all check_* functions against an IFC model.
|
| 5 |
+
Each check_* function returns list[dict] following the IFCore schema.
|
| 6 |
+
"""
|
| 7 |
+
import ifcopenshell
|
| 8 |
+
from tools.checker_dwelling import check_dwelling_area
|
| 9 |
+
from tools.checker_heights import check_living_area_height
|
| 10 |
+
from tools.checker_living_rooms import check_living_room_compliance
|
| 11 |
+
from tools.checker_service_spaces import check_service_spaces_min_height
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
# Map of check names to functions and their parameters
|
| 15 |
+
CHECKS = {
|
| 16 |
+
"dwelling_area": {
|
| 17 |
+
"func": check_dwelling_area,
|
| 18 |
+
"kwargs": {"min_area": 36.0},
|
| 19 |
+
},
|
| 20 |
+
"living_area_height": {
|
| 21 |
+
"func": check_living_area_height,
|
| 22 |
+
"kwargs": {"min_height": 2.50},
|
| 23 |
+
},
|
| 24 |
+
"living_room_compliance": {
|
| 25 |
+
"func": check_living_room_compliance,
|
| 26 |
+
"kwargs": {},
|
| 27 |
+
},
|
| 28 |
+
"service_spaces_min_height": {
|
| 29 |
+
"func": check_service_spaces_min_height,
|
| 30 |
+
"kwargs": {"min_height": 2.20},
|
| 31 |
+
},
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def run_all_checks(ifc_model_path):
|
| 36 |
+
"""
|
| 37 |
+
Load IFC model once and run all checks.
|
| 38 |
+
|
| 39 |
+
Returns:
|
| 40 |
+
dict: { check_name: [results], ... }
|
| 41 |
+
"""
|
| 42 |
+
print(f"Loading IFC model: {ifc_model_path}")
|
| 43 |
+
model = ifcopenshell.open(ifc_model_path)
|
| 44 |
+
|
| 45 |
+
all_results = {}
|
| 46 |
+
|
| 47 |
+
for check_name, check_info in CHECKS.items():
|
| 48 |
+
print(f"\nRunning {check_name}...")
|
| 49 |
+
func = check_info["func"]
|
| 50 |
+
kwargs = check_info["kwargs"]
|
| 51 |
+
|
| 52 |
+
try:
|
| 53 |
+
results = func(model, **kwargs)
|
| 54 |
+
all_results[check_name] = results
|
| 55 |
+
print(str(results))
|
| 56 |
+
|
| 57 |
+
# Print summary
|
| 58 |
+
if results:
|
| 59 |
+
passed = sum(1 for r in results if r["check_status"] == "pass")
|
| 60 |
+
failed = sum(1 for r in results if r["check_status"] == "fail")
|
| 61 |
+
print(f" [OK] {check_name}: {passed} passed, {failed} failed")
|
| 62 |
+
|
| 63 |
+
for r in results:
|
| 64 |
+
status_symbol = "[PASS]" if r["check_status"] == "pass" else "[FAIL]"
|
| 65 |
+
print(f" {status_symbol} {r['element_name']}: {r['actual_value']} (req: {r['required_value']})")
|
| 66 |
+
if r['comment']:
|
| 67 |
+
print(f" -> {r['comment']}")
|
| 68 |
+
else:
|
| 69 |
+
print(f" [INFO] {check_name}: No elements checked")
|
| 70 |
+
|
| 71 |
+
except Exception as e:
|
| 72 |
+
print(f" [ERROR] {check_name} FAILED: {str(e)}")
|
| 73 |
+
all_results[check_name] = []
|
| 74 |
+
|
| 75 |
+
return all_results
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
if __name__ == "__main__":
|
| 79 |
+
ifc_path = r"C:/Users/OWNER/Desktop/IAAC/T05/AI_SpeedRun/AI-Speed-Run-Week/ifc_models/arc.ifc"
|
| 80 |
+
|
| 81 |
+
print("=" * 70)
|
| 82 |
+
print("IFCore Compliance Checker - Integration Test")
|
| 83 |
+
print("=" * 70)
|
| 84 |
+
|
| 85 |
+
results = run_all_checks(ifc_path)
|
| 86 |
+
|
| 87 |
+
print("\n" + "=" * 70)
|
| 88 |
+
print("SUMMARY")
|
| 89 |
+
print("=" * 70)
|
| 90 |
+
for check_name, check_results in results.items():
|
| 91 |
+
if check_results:
|
| 92 |
+
passed = sum(1 for r in check_results if r["check_status"] == "pass")
|
| 93 |
+
failed = sum(1 for r in check_results if r["check_status"] == "fail")
|
| 94 |
+
print(f"{check_name}: {passed} PASS / {failed} FAIL")
|
| 95 |
+
else:
|
| 96 |
+
print(f"{check_name}: (no results)")
|
| 97 |
+
|
teams/Mastodonte/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
ifcopenshell
|
teams/Mastodonte/tools/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
IFCore Compliance Checker Tools
|
| 3 |
+
|
| 4 |
+
All check_* functions exported from this package follow the IFCore contract:
|
| 5 |
+
- Signature: check_*(model, **kwargs)
|
| 6 |
+
- Returns: list[dict] with IFCore schema (element_id, element_type, check_status, etc.)
|
| 7 |
+
- Platform auto-discovers check functions by name prefix "check_"
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from .checker_dwelling import check_dwelling_area
|
| 11 |
+
from .checker_heights import check_living_area_height
|
| 12 |
+
from .checker_living_rooms import check_living_room_compliance
|
| 13 |
+
from .checker_service_spaces import check_service_spaces_min_height
|
| 14 |
+
from .checker_occupancy import check_bedroom_occupancy
|
| 15 |
+
|
| 16 |
+
__all__ = [
|
| 17 |
+
"check_dwelling_area",
|
| 18 |
+
"check_living_area_height",
|
| 19 |
+
"check_living_room_compliance",
|
| 20 |
+
"check_service_spaces_min_height",
|
| 21 |
+
"check_bedroom_occupancy",
|
| 22 |
+
]
|
teams/Mastodonte/tools/checker_dwelling.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Requirements
|
| 2 |
+
import math
|
| 3 |
+
import json
|
| 4 |
+
import ifcopenshell
|
| 5 |
+
import ifcopenshell.geom
|
| 6 |
+
|
| 7 |
+
# --------------------------
|
| 8 |
+
# Geometry settings
|
| 9 |
+
# --------------------------
|
| 10 |
+
settings = ifcopenshell.geom.settings()
|
| 11 |
+
settings.set(settings.USE_WORLD_COORDS, True)
|
| 12 |
+
|
| 13 |
+
# --------------------------
|
| 14 |
+
# IFC helpers
|
| 15 |
+
# --------------------------
|
| 16 |
+
def calculate_space_area(space):
|
| 17 |
+
"""Approximate area by summing triangle areas from the space mesh."""
|
| 18 |
+
try:
|
| 19 |
+
shape = ifcopenshell.geom.create_shape(settings, space)
|
| 20 |
+
verts = shape.geometry.verts
|
| 21 |
+
faces = shape.geometry.faces
|
| 22 |
+
area = 0.0
|
| 23 |
+
|
| 24 |
+
for i in range(0, len(faces), 3):
|
| 25 |
+
i0, i1, i2 = faces[i] * 3, faces[i + 1] * 3, faces[i + 2] * 3
|
| 26 |
+
v0 = verts[i0:i0 + 3]
|
| 27 |
+
v1 = verts[i1:i1 + 3]
|
| 28 |
+
v2 = verts[i2:i2 + 3]
|
| 29 |
+
|
| 30 |
+
a = math.sqrt((v1[0]-v0[0])**2 + (v1[1]-v0[1])**2 + (v1[2]-v0[2])**2)
|
| 31 |
+
b = math.sqrt((v2[0]-v1[0])**2 + (v2[1]-v1[1])**2 + (v2[2]-v1[2])**2)
|
| 32 |
+
c = math.sqrt((v0[0]-v2[0])**2 + (v0[1]-v2[1])**2 + (v0[2]-v2[2])**2)
|
| 33 |
+
|
| 34 |
+
s = (a + b + c) / 2.0
|
| 35 |
+
area += math.sqrt(max(s * (s - a) * (s - b) * (s - c), 0.0))
|
| 36 |
+
|
| 37 |
+
return float(area)
|
| 38 |
+
except Exception:
|
| 39 |
+
return 0.0
|
| 40 |
+
|
| 41 |
+
def check_dwelling_area(model, min_area=36.0):
|
| 42 |
+
"""
|
| 43 |
+
Check if each space in the dwelling meets minimum area requirement.
|
| 44 |
+
|
| 45 |
+
Args:
|
| 46 |
+
model: ifcopenshell.file object (pre-loaded IFC model)
|
| 47 |
+
min_area: minimum total area in mΒ² (default 36.0)
|
| 48 |
+
|
| 49 |
+
Returns:
|
| 50 |
+
list[dict]: One dict per IfcSpace element, following IFCore schema
|
| 51 |
+
"""
|
| 52 |
+
spaces = model.by_type("IfcSpace")
|
| 53 |
+
results = []
|
| 54 |
+
total_area = 0.0
|
| 55 |
+
|
| 56 |
+
for space in spaces:
|
| 57 |
+
area = calculate_space_area(space)
|
| 58 |
+
space_name = getattr(space, "LongName", None) or getattr(space, "Name", None) or f"Space #{space.id()}"
|
| 59 |
+
space_id = getattr(space, "GlobalId", str(space.id()))
|
| 60 |
+
|
| 61 |
+
total_area += float(area)
|
| 62 |
+
|
| 63 |
+
results.append({
|
| 64 |
+
"element_id": space_id,
|
| 65 |
+
"element_type": "IfcSpace",
|
| 66 |
+
"element_name": space_name,
|
| 67 |
+
"element_name_long": f"{space_name} (dwelling area check)",
|
| 68 |
+
"check_status": "pass", # Will be determined after loop for aggregate
|
| 69 |
+
"actual_value": f"{area:.2f} mΒ²",
|
| 70 |
+
"required_value": f"{min_area:.2f} mΒ²",
|
| 71 |
+
"comment": None,
|
| 72 |
+
"log": None,
|
| 73 |
+
})
|
| 74 |
+
|
| 75 |
+
# Update check_status based on total area
|
| 76 |
+
if len(results) == 0:
|
| 77 |
+
return results
|
| 78 |
+
|
| 79 |
+
status = "pass" if total_area >= float(min_area) else "fail"
|
| 80 |
+
for result in results:
|
| 81 |
+
result["check_status"] = status
|
| 82 |
+
if status == "fail":
|
| 83 |
+
result["comment"] = f"Total dwelling area {total_area:.2f} mΒ² is below required {min_area:.2f} mΒ²"
|
| 84 |
+
|
| 85 |
+
return results
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
# --------------------------
|
| 89 |
+
# Local testing (optional)
|
| 90 |
+
# --------------------------
|
| 91 |
+
if __name__ == "__main__":
|
| 92 |
+
import ifcopenshell
|
| 93 |
+
ifc_path = "C:/Users/OWNER/Desktop/IAAC/T05/AI_SpeedRun/AI-Speed-Run-Week/ifc_models/arc.ifc"
|
| 94 |
+
model = ifcopenshell.open(ifc_path)
|
| 95 |
+
results = check_dwelling_area(model, min_area=36.0)
|
| 96 |
+
|
| 97 |
+
print("Dwelling Area Check Results:")
|
| 98 |
+
for r in results:
|
| 99 |
+
print(f"[{r['check_status'].upper()}] {r['element_name']}: {r['actual_value']}")
|
| 100 |
+
if r['comment']:
|
| 101 |
+
print(f" β {r['comment']}")
|
teams/Mastodonte/tools/checker_heights.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import ifcopenshell
|
| 3 |
+
import ifcopenshell.geom
|
| 4 |
+
|
| 5 |
+
# --------------------------
|
| 6 |
+
# Geometry settings
|
| 7 |
+
# --------------------------
|
| 8 |
+
settings = ifcopenshell.geom.settings()
|
| 9 |
+
settings.set(settings.USE_WORLD_COORDS, True)
|
| 10 |
+
|
| 11 |
+
# --------------------------
|
| 12 |
+
# Helper functions
|
| 13 |
+
# --------------------------
|
| 14 |
+
def load_model(ifc_model_path):
|
| 15 |
+
"""Load IFC model"""
|
| 16 |
+
return ifcopenshell.open(ifc_model_path)
|
| 17 |
+
|
| 18 |
+
def get_main_living_areas(ifc_model):
|
| 19 |
+
"""
|
| 20 |
+
Return spaces considered main living areas.
|
| 21 |
+
Filtering by common keywords in space name.
|
| 22 |
+
"""
|
| 23 |
+
spaces = ifc_model.by_type("IfcSpace")
|
| 24 |
+
main_spaces = []
|
| 25 |
+
for s in spaces:
|
| 26 |
+
if s.Name and any(keyword in s.Name.lower() for keyword in ["living", "bedroom", "hall"]):
|
| 27 |
+
main_spaces.append(s)
|
| 28 |
+
return main_spaces
|
| 29 |
+
|
| 30 |
+
def get_space_height(space):
|
| 31 |
+
"""
|
| 32 |
+
Approximate height of a space from geometry bounding box.
|
| 33 |
+
Uses Z coordinates of all vertices.
|
| 34 |
+
"""
|
| 35 |
+
try:
|
| 36 |
+
shape = ifcopenshell.geom.create_shape(settings, space)
|
| 37 |
+
verts = shape.geometry.verts
|
| 38 |
+
if not verts:
|
| 39 |
+
return 0.0
|
| 40 |
+
zs = verts[2::3]
|
| 41 |
+
return float(max(zs) - min(zs))
|
| 42 |
+
except Exception:
|
| 43 |
+
return 0.0
|
| 44 |
+
|
| 45 |
+
# --------------------------
|
| 46 |
+
# Main check function
|
| 47 |
+
# --------------------------
|
| 48 |
+
def check_living_area_height(model, min_height=2.50):
|
| 49 |
+
"""
|
| 50 |
+
Check if all main living areas meet the minimum height requirement.
|
| 51 |
+
|
| 52 |
+
Args:
|
| 53 |
+
model: ifcopenshell.file object (pre-loaded IFC model)
|
| 54 |
+
min_height: minimum height in meters (default 2.50)
|
| 55 |
+
|
| 56 |
+
Returns:
|
| 57 |
+
list[dict]: One dict per living/bedroom/hall space, following IFCore schema
|
| 58 |
+
"""
|
| 59 |
+
spaces = get_main_living_areas(model)
|
| 60 |
+
results = []
|
| 61 |
+
|
| 62 |
+
for space in spaces:
|
| 63 |
+
height = get_space_height(space)
|
| 64 |
+
space_name = space.Name or f"Space #{space.id()}"
|
| 65 |
+
space_id = getattr(space, "GlobalId", str(space.id()))
|
| 66 |
+
|
| 67 |
+
status = "pass" if height >= min_height else "fail"
|
| 68 |
+
comment = None if height >= min_height else f"Height {height:.2f}m below required {min_height:.2f}m"
|
| 69 |
+
|
| 70 |
+
results.append({
|
| 71 |
+
"element_id": space_id,
|
| 72 |
+
"element_type": "IfcSpace",
|
| 73 |
+
"element_name": space_name,
|
| 74 |
+
"element_name_long": f"{space_name} (living area)",
|
| 75 |
+
"check_status": status,
|
| 76 |
+
"actual_value": f"{height:.2f} m",
|
| 77 |
+
"required_value": f"{min_height:.2f} m",
|
| 78 |
+
"comment": comment,
|
| 79 |
+
"log": None,
|
| 80 |
+
})
|
| 81 |
+
|
| 82 |
+
return results
|
| 83 |
+
|
| 84 |
+
# --------------------------
|
| 85 |
+
# Local testing (optional)
|
| 86 |
+
# --------------------------
|
| 87 |
+
if __name__ == "__main__":
|
| 88 |
+
import ifcopenshell
|
| 89 |
+
ifc_path = "C:/Users/OWNER/Desktop/IAAC/T05/AI_SpeedRun/AI-Speed-Run-Week/ifc_models/arc.ifc"
|
| 90 |
+
model = ifcopenshell.open(ifc_path)
|
| 91 |
+
results = check_living_area_height(model, min_height=2.50)
|
| 92 |
+
|
| 93 |
+
print("Living Area Height Check Results:")
|
| 94 |
+
for r in results:
|
| 95 |
+
print(f"[{r['check_status'].upper()}] {r['element_name']}: {r['actual_value']}")
|
| 96 |
+
if r['comment']:
|
| 97 |
+
print(f" β {r['comment']}")
|
teams/Mastodonte/tools/checker_living_rooms.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import math
|
| 3 |
+
import ifcopenshell
|
| 4 |
+
import ifcopenshell.geom
|
| 5 |
+
|
| 6 |
+
# Optional: only needed if you truly have it available
|
| 7 |
+
# from ifcopenshell.util.element import get_psets as _get_psets
|
| 8 |
+
|
| 9 |
+
# --------------------------
|
| 10 |
+
# Geometry settings
|
| 11 |
+
# --------------------------
|
| 12 |
+
settings = ifcopenshell.geom.settings()
|
| 13 |
+
settings.set(settings.USE_WORLD_COORDS, True)
|
| 14 |
+
|
| 15 |
+
# --------------------------
|
| 16 |
+
# Helper functions
|
| 17 |
+
# --------------------------
|
| 18 |
+
def load_model(ifc_model_path):
|
| 19 |
+
return ifcopenshell.open(ifc_model_path)
|
| 20 |
+
|
| 21 |
+
def calculate_space_area(space):
|
| 22 |
+
"""
|
| 23 |
+
Approximate area from mesh triangles.
|
| 24 |
+
Note: this is mesh surface area, not guaranteed to be floor area.
|
| 25 |
+
"""
|
| 26 |
+
if hasattr(space, "NetFloorArea") and space.NetFloorArea:
|
| 27 |
+
return float(space.NetFloorArea)
|
| 28 |
+
|
| 29 |
+
try:
|
| 30 |
+
shape = ifcopenshell.geom.create_shape(settings, space)
|
| 31 |
+
verts = shape.geometry.verts
|
| 32 |
+
faces = shape.geometry.faces
|
| 33 |
+
area = 0.0
|
| 34 |
+
|
| 35 |
+
for i in range(0, len(faces), 3):
|
| 36 |
+
idx0, idx1, idx2 = faces[i] * 3, faces[i + 1] * 3, faces[i + 2] * 3
|
| 37 |
+
v0 = verts[idx0:idx0 + 3]
|
| 38 |
+
v1 = verts[idx1:idx1 + 3]
|
| 39 |
+
v2 = verts[idx2:idx2 + 3]
|
| 40 |
+
|
| 41 |
+
a = math.sqrt((v1[0]-v0[0])**2 + (v1[1]-v0[1])**2 + (v1[2]-v0[2])**2)
|
| 42 |
+
b = math.sqrt((v2[0]-v1[0])**2 + (v2[1]-v1[1])**2 + (v2[2]-v1[2])**2)
|
| 43 |
+
c = math.sqrt((v0[0]-v2[0])**2 + (v0[1]-v2[1])**2 + (v0[2]-v2[2])**2)
|
| 44 |
+
|
| 45 |
+
s = (a + b + c) / 2.0
|
| 46 |
+
area += math.sqrt(max(s * (s - a) * (s - b) * (s - c), 0.0))
|
| 47 |
+
|
| 48 |
+
return float(area)
|
| 49 |
+
except Exception:
|
| 50 |
+
return 0.0
|
| 51 |
+
|
| 52 |
+
def get_space_name(space):
|
| 53 |
+
"""Get descriptive name if available, fallback to LongName/Name/GlobalId."""
|
| 54 |
+
if getattr(space, "LongName", None):
|
| 55 |
+
return str(space.LongName)
|
| 56 |
+
if getattr(space, "Name", None):
|
| 57 |
+
return str(space.Name)
|
| 58 |
+
|
| 59 |
+
# If you have ifcopenshell.util.element available, you can enable this block:
|
| 60 |
+
# try:
|
| 61 |
+
# psets = _get_psets(space)
|
| 62 |
+
# for props in (psets or {}).values():
|
| 63 |
+
# for k, v in (props or {}).items():
|
| 64 |
+
# if "name" in str(k).lower() and isinstance(v, str) and v.strip():
|
| 65 |
+
# return v.strip()
|
| 66 |
+
# except Exception:
|
| 67 |
+
# pass
|
| 68 |
+
|
| 69 |
+
return str(getattr(space, "GlobalId", "Unnamed"))
|
| 70 |
+
|
| 71 |
+
def can_fit_square(area_m2, width=2.4, depth=2.4):
|
| 72 |
+
"""Approx check: area must allow a width x depth square."""
|
| 73 |
+
return float(area_m2) >= float(width) * float(depth)
|
| 74 |
+
|
| 75 |
+
# --------------------------
|
| 76 |
+
# Living room compliance
|
| 77 |
+
# --------------------------
|
| 78 |
+
def check_living_room_compliance(model):
|
| 79 |
+
"""
|
| 80 |
+
Check living room spaces for minimum area and clearance rules.
|
| 81 |
+
|
| 82 |
+
Rule encoded:
|
| 83 |
+
- Living room min area 10 mΒ²
|
| 84 |
+
- If living+kitchen combined: min area 14 mΒ²
|
| 85 |
+
- Must allow 2.40 x 2.40 m clearance (approx via area check)
|
| 86 |
+
|
| 87 |
+
Args:
|
| 88 |
+
model: ifcopenshell.file object (pre-loaded IFC model)
|
| 89 |
+
|
| 90 |
+
Returns:
|
| 91 |
+
list[dict]: One dict per living room space, following IFCore schema
|
| 92 |
+
"""
|
| 93 |
+
spaces = model.by_type("IfcSpace")
|
| 94 |
+
results = []
|
| 95 |
+
|
| 96 |
+
for space in spaces:
|
| 97 |
+
raw_name = get_space_name(space)
|
| 98 |
+
name = raw_name.lower()
|
| 99 |
+
area = calculate_space_area(space)
|
| 100 |
+
space_id = getattr(space, "GlobalId", str(space.id()))
|
| 101 |
+
|
| 102 |
+
if area <= 0:
|
| 103 |
+
continue
|
| 104 |
+
|
| 105 |
+
if "living" in name:
|
| 106 |
+
has_kitchen = "kitchen" in name
|
| 107 |
+
min_area = 14.0 if has_kitchen else 10.0
|
| 108 |
+
|
| 109 |
+
clearance_ok = can_fit_square(area, 2.4, 2.4)
|
| 110 |
+
passed = (area >= min_area) and clearance_ok
|
| 111 |
+
|
| 112 |
+
reasons = []
|
| 113 |
+
if area < min_area:
|
| 114 |
+
reasons.append(f"Area {area:.2f} mΒ² < required {min_area:.2f} mΒ²")
|
| 115 |
+
if not clearance_ok:
|
| 116 |
+
reasons.append("Does not allow 2.40 m x 2.40 m square (approx)")
|
| 117 |
+
|
| 118 |
+
results.append({
|
| 119 |
+
"element_id": space_id,
|
| 120 |
+
"element_type": "IfcSpace",
|
| 121 |
+
"element_name": raw_name,
|
| 122 |
+
"element_name_long": f"{raw_name} (living room zone)",
|
| 123 |
+
"check_status": "pass" if passed else "fail",
|
| 124 |
+
"actual_value": f"{area:.2f} mΒ²",
|
| 125 |
+
"required_value": f"{min_area:.2f} mΒ²",
|
| 126 |
+
"comment": "; ".join(reasons) if reasons else None,
|
| 127 |
+
"log": None,
|
| 128 |
+
})
|
| 129 |
+
|
| 130 |
+
return results
|
| 131 |
+
|
| 132 |
+
# --------------------------
|
| 133 |
+
# Local testing (optional)
|
| 134 |
+
# --------------------------
|
| 135 |
+
if __name__ == "__main__":
|
| 136 |
+
import ifcopenshell
|
| 137 |
+
ifc_path = "C:/Users/OWNER/Desktop/IAAC/T05/AI_SpeedRun/AI-Speed-Run-Week/ifc_models/arc.ifc"
|
| 138 |
+
model = ifcopenshell.open(ifc_path)
|
| 139 |
+
results = check_living_room_compliance(model)
|
| 140 |
+
|
| 141 |
+
print("Living Room Compliance Check Results:")
|
| 142 |
+
if not results:
|
| 143 |
+
print("No living room spaces matched (keyword 'living') or no computable areas.")
|
| 144 |
+
else:
|
| 145 |
+
for r in results:
|
| 146 |
+
print(f"[{r['check_status'].upper()}] {r['element_name']}: {r['actual_value']}")
|
| 147 |
+
if r['comment']:
|
| 148 |
+
print(f" -> {r['comment']}")
|
teams/Mastodonte/tools/checker_occupancy.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import json
|
| 3 |
+
import ifcopenshell
|
| 4 |
+
import ifcopenshell.geom
|
| 5 |
+
|
| 6 |
+
# --------------------------
|
| 7 |
+
# Geometry settings
|
| 8 |
+
# --------------------------
|
| 9 |
+
settings = ifcopenshell.geom.settings()
|
| 10 |
+
settings.set(settings.USE_WORLD_COORDS, True)
|
| 11 |
+
|
| 12 |
+
# --------------------------
|
| 13 |
+
# Helper functions
|
| 14 |
+
# --------------------------
|
| 15 |
+
def calculate_space_area(space):
|
| 16 |
+
"""Approximate area by summing triangle areas from the space mesh."""
|
| 17 |
+
try:
|
| 18 |
+
shape = ifcopenshell.geom.create_shape(settings, space)
|
| 19 |
+
verts = shape.geometry.verts
|
| 20 |
+
faces = shape.geometry.faces
|
| 21 |
+
area = 0.0
|
| 22 |
+
|
| 23 |
+
for i in range(0, len(faces), 3):
|
| 24 |
+
i0, i1, i2 = faces[i] * 3, faces[i + 1] * 3, faces[i + 2] * 3
|
| 25 |
+
v0 = verts[i0:i0 + 3]
|
| 26 |
+
v1 = verts[i1:i1 + 3]
|
| 27 |
+
v2 = verts[i2:i2 + 3]
|
| 28 |
+
|
| 29 |
+
a = math.dist(v0, v1)
|
| 30 |
+
b = math.dist(v1, v2)
|
| 31 |
+
c = math.dist(v2, v0)
|
| 32 |
+
|
| 33 |
+
s = (a + b + c) / 2.0
|
| 34 |
+
area += math.sqrt(max(s * (s - a) * (s - b) * (s - c), 0.0))
|
| 35 |
+
|
| 36 |
+
return float(area)
|
| 37 |
+
except Exception:
|
| 38 |
+
return 0.0
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def get_space_label(space):
|
| 42 |
+
"""Get descriptive name if available, fallback to GlobalId."""
|
| 43 |
+
return str(
|
| 44 |
+
getattr(space, "LongName", None)
|
| 45 |
+
or getattr(space, "Name", None)
|
| 46 |
+
or f"Space #{space.id()}"
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def area_to_occupancy(area_m2):
|
| 51 |
+
"""
|
| 52 |
+
Regulation mapping:
|
| 53 |
+
<5 mΒ² -> invalid bedroom
|
| 54 |
+
β₯5 mΒ² -> 1 person
|
| 55 |
+
β₯8 mΒ² -> 2 people
|
| 56 |
+
β₯12 mΒ² -> 3 people
|
| 57 |
+
"""
|
| 58 |
+
if area_m2 < 5.0:
|
| 59 |
+
return 0
|
| 60 |
+
elif area_m2 < 8.0:
|
| 61 |
+
return 1
|
| 62 |
+
elif area_m2 < 12.0:
|
| 63 |
+
return 2
|
| 64 |
+
else:
|
| 65 |
+
return 3
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def get_spaces_by_keywords(model, keywords):
|
| 69 |
+
"""Filter spaces matching any keyword in their name."""
|
| 70 |
+
spaces = model.by_type("IfcSpace")
|
| 71 |
+
matched = []
|
| 72 |
+
for s in spaces:
|
| 73 |
+
label_lower = get_space_label(s).lower()
|
| 74 |
+
if any(k in label_lower for k in keywords):
|
| 75 |
+
matched.append(s)
|
| 76 |
+
return matched
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
# --------------------------
|
| 80 |
+
# Main check function
|
| 81 |
+
# --------------------------
|
| 82 |
+
def check_bedroom_occupancy(model):
|
| 83 |
+
"""
|
| 84 |
+
Check if all bedrooms meet minimum area requirement and calculate occupancy.
|
| 85 |
+
|
| 86 |
+
Regulation:
|
| 87 |
+
- Bedroom min area 5 mΒ² for 1 occupant
|
| 88 |
+
- 8 mΒ² for 2 occupants
|
| 89 |
+
- 12 mΒ² for 3 occupants
|
| 90 |
+
- Studio (no bedrooms): limited to max 2 occupants
|
| 91 |
+
|
| 92 |
+
Args:
|
| 93 |
+
model: ifcopenshell.file object (pre-loaded IFC model)
|
| 94 |
+
|
| 95 |
+
Returns:
|
| 96 |
+
list[dict]: One dict per bedroom/living space, following IFCore schema
|
| 97 |
+
"""
|
| 98 |
+
results = []
|
| 99 |
+
|
| 100 |
+
bedroom_keywords = ["bedroom", "habitacion", "habitaciΓ³n", "dormitorio"]
|
| 101 |
+
living_keywords = ["living", "salon", "salΓ³n", "studio", "estudio", "common"]
|
| 102 |
+
|
| 103 |
+
bedrooms = get_spaces_by_keywords(model, bedroom_keywords)
|
| 104 |
+
living_spaces = get_spaces_by_keywords(model, living_keywords)
|
| 105 |
+
|
| 106 |
+
# --------------------------
|
| 107 |
+
# Studio case (no bedrooms)
|
| 108 |
+
# --------------------------
|
| 109 |
+
if len(bedrooms) == 0:
|
| 110 |
+
if living_spaces:
|
| 111 |
+
space = living_spaces[0]
|
| 112 |
+
label = get_space_label(space)
|
| 113 |
+
area = calculate_space_area(space)
|
| 114 |
+
space_id = getattr(space, "GlobalId", str(space.id()))
|
| 115 |
+
occupancy = min(area_to_occupancy(area), 2)
|
| 116 |
+
passed = occupancy > 0
|
| 117 |
+
|
| 118 |
+
results.append({
|
| 119 |
+
"element_id": space_id,
|
| 120 |
+
"element_type": "IfcSpace",
|
| 121 |
+
"element_name": label,
|
| 122 |
+
"element_name_long": f"{label} (studio dwelling)",
|
| 123 |
+
"check_status": "pass" if passed else "fail",
|
| 124 |
+
"actual_value": f"{area:.2f} mΒ² β {occupancy} occupant(s) max",
|
| 125 |
+
"required_value": "Studio max 2 occupants",
|
| 126 |
+
"comment": "Studio dwelling limited to maximum 2 occupants" if passed else f"Area {area:.2f} mΒ² too small for studio",
|
| 127 |
+
"log": None,
|
| 128 |
+
})
|
| 129 |
+
return results
|
| 130 |
+
|
| 131 |
+
# --------------------------
|
| 132 |
+
# Bedroom-based case
|
| 133 |
+
# --------------------------
|
| 134 |
+
for space in bedrooms:
|
| 135 |
+
label = get_space_label(space)
|
| 136 |
+
area = calculate_space_area(space)
|
| 137 |
+
space_id = getattr(space, "GlobalId", str(space.id()))
|
| 138 |
+
occupancy = area_to_occupancy(area)
|
| 139 |
+
|
| 140 |
+
passed = occupancy > 0
|
| 141 |
+
comment = None
|
| 142 |
+
if not passed:
|
| 143 |
+
comment = f"Area {area:.2f} mΒ² below minimum 5 mΒ² for bedroom"
|
| 144 |
+
|
| 145 |
+
results.append({
|
| 146 |
+
"element_id": space_id,
|
| 147 |
+
"element_type": "IfcSpace",
|
| 148 |
+
"element_name": label,
|
| 149 |
+
"element_name_long": f"{label} (bedroom occupancy)",
|
| 150 |
+
"check_status": "pass" if passed else "fail",
|
| 151 |
+
"actual_value": f"{area:.2f} mΒ² β {occupancy} occupant(s)",
|
| 152 |
+
"required_value": "β₯5 mΒ² (min 1 occupant)",
|
| 153 |
+
"comment": comment,
|
| 154 |
+
"log": None,
|
| 155 |
+
})
|
| 156 |
+
|
| 157 |
+
return results
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
# --------------------------
|
| 161 |
+
# Local testing (optional)
|
| 162 |
+
# --------------------------
|
| 163 |
+
if __name__ == "__main__":
|
| 164 |
+
import ifcopenshell
|
| 165 |
+
ifc_path = "C:/Users/OWNER/Desktop/IAAC/T05/AI_SpeedRun/AI-Speed-Run-Week/ifc_models/arc.ifc"
|
| 166 |
+
model = ifcopenshell.open(ifc_path)
|
| 167 |
+
results = check_bedroom_occupancy(model)
|
| 168 |
+
|
| 169 |
+
print("Bedroom Occupancy Check Results:")
|
| 170 |
+
for r in results:
|
| 171 |
+
print(f"[{r['check_status'].upper()}] {r['element_name']}: {r['actual_value']}")
|
| 172 |
+
if r['comment']:
|
| 173 |
+
print(f" β {r['comment']}")
|
teams/Mastodonte/tools/checker_service_spaces.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import ifcopenshell
|
| 3 |
+
import ifcopenshell.geom
|
| 4 |
+
|
| 5 |
+
# --------------------------
|
| 6 |
+
# Geometry settings
|
| 7 |
+
# --------------------------
|
| 8 |
+
settings = ifcopenshell.geom.settings()
|
| 9 |
+
settings.set(settings.USE_WORLD_COORDS, True)
|
| 10 |
+
|
| 11 |
+
# --------------------------
|
| 12 |
+
# Helper functions
|
| 13 |
+
# --------------------------
|
| 14 |
+
def load_model(ifc_model_path):
|
| 15 |
+
return ifcopenshell.open(ifc_model_path)
|
| 16 |
+
|
| 17 |
+
def get_space_name(space):
|
| 18 |
+
"""Get descriptive name if available, fallback to LongName/Name/GlobalId."""
|
| 19 |
+
if getattr(space, "LongName", None):
|
| 20 |
+
return str(space.LongName)
|
| 21 |
+
if getattr(space, "Name", None):
|
| 22 |
+
return str(space.Name)
|
| 23 |
+
return str(getattr(space, "GlobalId", "Unnamed"))
|
| 24 |
+
|
| 25 |
+
def get_space_height(space):
|
| 26 |
+
"""
|
| 27 |
+
Approximate height of a space from geometry bounding box.
|
| 28 |
+
Uses Z coordinates of all vertices.
|
| 29 |
+
"""
|
| 30 |
+
try:
|
| 31 |
+
shape = ifcopenshell.geom.create_shape(settings, space)
|
| 32 |
+
verts = shape.geometry.verts
|
| 33 |
+
if not verts:
|
| 34 |
+
return 0.0
|
| 35 |
+
zs = verts[2::3]
|
| 36 |
+
return float(max(zs) - min(zs))
|
| 37 |
+
except Exception:
|
| 38 |
+
return 0.0
|
| 39 |
+
|
| 40 |
+
def is_service_space(space_name_lower):
|
| 41 |
+
"""Bathrooms, kitchens, hallways by keywords."""
|
| 42 |
+
service_keywords = [
|
| 43 |
+
"bath", "bathroom", "baΓ±o", "bano", "wc", "toilet",
|
| 44 |
+
"kitchen", "cocina",
|
| 45 |
+
"hall", "hallway", "corridor", "pasillo"
|
| 46 |
+
]
|
| 47 |
+
return any(k in space_name_lower for k in service_keywords)
|
| 48 |
+
|
| 49 |
+
# --------------------------
|
| 50 |
+
# Compliance check
|
| 51 |
+
# --------------------------
|
| 52 |
+
def check_service_spaces_min_height(model, min_height=2.20):
|
| 53 |
+
"""
|
| 54 |
+
Check if bathrooms, kitchens, and hallways meet minimum height requirement.
|
| 55 |
+
|
| 56 |
+
Regulation:
|
| 57 |
+
- Minimum height in bathrooms, kitchens, and hallways is 2.20 m
|
| 58 |
+
|
| 59 |
+
Args:
|
| 60 |
+
model: ifcopenshell.file object (pre-loaded IFC model)
|
| 61 |
+
min_height: minimum height in meters (default 2.20)
|
| 62 |
+
|
| 63 |
+
Returns:
|
| 64 |
+
list[dict]: One dict per service space, following IFCore schema
|
| 65 |
+
"""
|
| 66 |
+
spaces = model.by_type("IfcSpace")
|
| 67 |
+
results = []
|
| 68 |
+
|
| 69 |
+
for space in spaces:
|
| 70 |
+
label = get_space_name(space)
|
| 71 |
+
label_l = label.lower()
|
| 72 |
+
|
| 73 |
+
if not is_service_space(label_l):
|
| 74 |
+
continue
|
| 75 |
+
|
| 76 |
+
height = get_space_height(space)
|
| 77 |
+
space_id = getattr(space, "GlobalId", str(space.id()))
|
| 78 |
+
|
| 79 |
+
status = "pass" if height >= min_height else "fail"
|
| 80 |
+
comment = None if height >= min_height else f"Height {height:.2f}m below required {min_height:.2f}m"
|
| 81 |
+
|
| 82 |
+
results.append({
|
| 83 |
+
"element_id": space_id,
|
| 84 |
+
"element_type": "IfcSpace",
|
| 85 |
+
"element_name": label,
|
| 86 |
+
"element_name_long": f"{label} (service space)",
|
| 87 |
+
"check_status": status,
|
| 88 |
+
"actual_value": f"{height:.2f} m",
|
| 89 |
+
"required_value": f"{min_height:.2f} m",
|
| 90 |
+
"comment": comment,
|
| 91 |
+
"log": None,
|
| 92 |
+
})
|
| 93 |
+
|
| 94 |
+
return results
|
| 95 |
+
|
| 96 |
+
# --------------------------
|
| 97 |
+
# Local testing (optional)
|
| 98 |
+
# --------------------------
|
| 99 |
+
if __name__ == "__main__":
|
| 100 |
+
import ifcopenshell
|
| 101 |
+
ifc_path = "C:/Users/OWNER/Desktop/IAAC/T05/AI_SpeedRun/AI-Speed-Run-Week/ifc_models/arc.ifc"
|
| 102 |
+
model = ifcopenshell.open(ifc_path)
|
| 103 |
+
results = check_service_spaces_min_height(model, min_height=2.20)
|
| 104 |
+
|
| 105 |
+
print("Service Spaces Min Height Check Results:")
|
| 106 |
+
for r in results:
|
| 107 |
+
print(f"[{r['check_status'].upper()}] {r['element_name']}: {r['actual_value']}")
|
| 108 |
+
if r['comment']:
|
| 109 |
+
print(f" β {r['comment']}")
|
teams/demo/tools/checker_demo.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Demo check: counts doors and verifies minimum count."""
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def check_door_count(model, min_doors=2):
|
| 5 |
+
doors = model.by_type("IfcDoor")
|
| 6 |
+
results = []
|
| 7 |
+
for door in doors:
|
| 8 |
+
name = door.Name or f"Door #{door.id()}"
|
| 9 |
+
results.append({
|
| 10 |
+
"element_id": door.GlobalId,
|
| 11 |
+
"element_type": "IfcDoor",
|
| 12 |
+
"element_name": name,
|
| 13 |
+
"element_name_long": None,
|
| 14 |
+
"check_status": "pass",
|
| 15 |
+
"actual_value": "Present",
|
| 16 |
+
"required_value": f">= {min_doors} doors total",
|
| 17 |
+
"comment": None,
|
| 18 |
+
"log": None,
|
| 19 |
+
})
|
| 20 |
+
if len(doors) < min_doors:
|
| 21 |
+
results.append({
|
| 22 |
+
"element_id": None,
|
| 23 |
+
"element_type": "Summary",
|
| 24 |
+
"element_name": "Door Count Check",
|
| 25 |
+
"element_name_long": None,
|
| 26 |
+
"check_status": "fail",
|
| 27 |
+
"actual_value": f"{len(doors)} doors",
|
| 28 |
+
"required_value": f">= {min_doors} doors",
|
| 29 |
+
"comment": f"Found {len(doors)} doors, need >= {min_doors}",
|
| 30 |
+
"log": None,
|
| 31 |
+
})
|
| 32 |
+
return results
|
teams/lux-ai/AGENT_HANDOFF_REPORT.md
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Lux.Ai β IFC Metadata Extraction: Agent Handoff Report
|
| 2 |
+
|
| 3 |
+
**Date:** 2026-02-18
|
| 4 |
+
**Project:** Lux.Ai β AI for Architecture & Urbanism
|
| 5 |
+
**Repo root:** `c:\Users\LEGION\Documents\GitHub\Lux.Ai\`
|
| 6 |
+
**Python:** 3.12.10 via `.venv\` virtual environment
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## What Was Built (Summary)
|
| 11 |
+
|
| 12 |
+
Three things were created in this session:
|
| 13 |
+
|
| 14 |
+
| # | What | File | Status |
|
| 15 |
+
|---|------|------|--------|
|
| 16 |
+
| 1 | IFC model scanner β extracts 4 building metrics from all IFC files | `scan_ifc_models.py` | Done, working |
|
| 17 |
+
| 2 | IFC metadata key discovery β inventories every property/quantity name across all models | `discover_ifc_keys.py` | Done, working |
|
| 18 |
+
| 3 | Canonical alias map β standardised lookup table for future extractors | `key_aliases.json` | Generated |
|
| 19 |
+
|
| 20 |
+
---
|
| 21 |
+
|
| 22 |
+
## Context & Problem Being Solved
|
| 23 |
+
|
| 24 |
+
The repo contains **36 IFC (Industry Foundation Classes) BIM model files** across 20 sample building projects in `Sample projects/projects/`. IFC is the open standard for Building Information Modeling used by Archicad, Revit, Vectorworks, and other tools.
|
| 25 |
+
|
| 26 |
+
**The challenge:** Different IFC exporters use different internal names for the same concept. For example, "floor area" might be stored as:
|
| 27 |
+
- `Qto_SpaceBaseQuantities / NetFloorArea` (IFC4 / buildingSMART standard)
|
| 28 |
+
- `BaseQuantities / NetFloorArea` (IFC2x3 legacy)
|
| 29 |
+
- `GSA Space Areas / GSA BIM Area` (Revit US government exports)
|
| 30 |
+
- `ArchiCADQuantities / Netto-GrundflΓ€che` (Archicad German)
|
| 31 |
+
|
| 32 |
+
The goal was to scan all files, discover every key name actually in use, and produce a normalised mapping so any future function can reliably extract data regardless of which software created the file.
|
| 33 |
+
|
| 34 |
+
---
|
| 35 |
+
|
| 36 |
+
## Repository Structure
|
| 37 |
+
|
| 38 |
+
```
|
| 39 |
+
Lux.Ai/
|
| 40 |
+
βββ scan_ifc_models.py β NEW: extracts 4 metrics from all IFC files
|
| 41 |
+
βββ discover_ifc_keys.py β NEW: inventories all property/quantity keys
|
| 42 |
+
βββ key_aliases.json β NEW: canonical key β alias mapping (config)
|
| 43 |
+
βββ ifc_key_inventory.json β NEW: raw inventory of all keys found
|
| 44 |
+
βββ ifc_scan_results.csv β NEW: output data table (36 rows)
|
| 45 |
+
βββ ifc_scan.log β NEW: per-file processing log
|
| 46 |
+
βββ .venv/ β Python 3.12 virtual environment
|
| 47 |
+
β βββ Scripts/python.exe
|
| 48 |
+
βββ Sample projects/
|
| 49 |
+
βββ projects/ β 36 IFC files across 20 projects
|
| 50 |
+
βββ 4351/arc.ifc
|
| 51 |
+
βββ ac20/arc.ifc
|
| 52 |
+
βββ city_house_munich/arc.ifc
|
| 53 |
+
βββ dental_clinic/arc.ifc + mep.ifc + str.ifc
|
| 54 |
+
βββ digital_hub/arc.ifc + heating.ifc + plumbing.ifc + ventilation.ifc
|
| 55 |
+
βββ duplex/arc.ifc + mep.ifc
|
| 56 |
+
βββ ettenheim_gis/city.ifc
|
| 57 |
+
βββ fantasy_hotel_1/arc.ifc
|
| 58 |
+
βββ fantasy_hotel_2/arc.ifc
|
| 59 |
+
βββ fantasy_office_building_1/arc.ifc
|
| 60 |
+
βββ fantasy_office_building_2/arc.ifc
|
| 61 |
+
βββ fantasy_office_building_3/arc.ifc
|
| 62 |
+
βββ fantasy_residential_building_1/arc.ifc
|
| 63 |
+
βββ fzk_house/arc.ifc
|
| 64 |
+
βββ hitos/arc.ifc
|
| 65 |
+
βββ molio/arc.ifc
|
| 66 |
+
βββ samuel_macalister_sample_house/arc.ifc + mep.ifc
|
| 67 |
+
βββ schependomlaan/arc.ifc
|
| 68 |
+
βββ sixty5/arc.ifc + str.ifc + facade.ifc + electrical.ifc + kitchen.ifc + plumbing.ifc + ventilation.ifc
|
| 69 |
+
βββ smiley_west/arc.ifc
|
| 70 |
+
βββ wbdg_office/arc.ifc + str.ifc + mep.ifc
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
Each project also contains:
|
| 74 |
+
- `model_card.md` β YAML frontmatter with project metadata (source, license, discipline list)
|
| 75 |
+
- `snapshot.png` β thumbnail
|
| 76 |
+
- `license.txt`
|
| 77 |
+
|
| 78 |
+
---
|
| 79 |
+
|
| 80 |
+
## Dependencies Installed
|
| 81 |
+
|
| 82 |
+
```bash
|
| 83 |
+
.venv/Scripts/python.exe -m pip install ifcopenshell tabulate
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
Packages installed:
|
| 87 |
+
- `ifcopenshell==0.8.4.post1` β IFC file parser (binary wheel, no compiler needed)
|
| 88 |
+
- `tabulate==0.9.0` β console table formatting
|
| 89 |
+
- `numpy`, `shapely`, `lark`, `isodate` β pulled in as ifcopenshell dependencies
|
| 90 |
+
|
| 91 |
+
---
|
| 92 |
+
|
| 93 |
+
## File 1: `scan_ifc_models.py`
|
| 94 |
+
|
| 95 |
+
### Purpose
|
| 96 |
+
Scans all IFC files and extracts 4 building metrics per file into a CSV.
|
| 97 |
+
|
| 98 |
+
### Run
|
| 99 |
+
```bash
|
| 100 |
+
.venv/Scripts/python.exe scan_ifc_models.py
|
| 101 |
+
# Optional args:
|
| 102 |
+
# --root "path/to/ifc/directory" (default: Sample projects/projects/)
|
| 103 |
+
# --output results.csv (default: ifc_scan_results.csv)
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
### What it extracts
|
| 107 |
+
|
| 108 |
+
| Canonical Key | IFC Source | Fallback Strategy |
|
| 109 |
+
|---|---|---|
|
| 110 |
+
| `window_area_m2` | `IfcWindow β Qto_WindowBaseQuantities / Area` | `BaseQuantities/Area` β `OverallHeight Γ OverallWidth` |
|
| 111 |
+
| `floor_area_m2` | `IfcSpace β Qto_SpaceBaseQuantities / NetFloorArea` | `GrossFloorArea` β `BaseQuantities/NetFloorArea` β `IfcSlab[FLOOR]/NetArea` |
|
| 112 |
+
| `roof_area_m2` | `IfcRoof β Qto_RoofBaseQuantities / NetArea` | `GrossArea` β `IfcSlab[ROOF]/NetArea` β `GrossArea` |
|
| 113 |
+
| `true_north_angle_deg` | `IfcGeometricRepresentationContext.TrueNorth` | DirectionRatios (X,Y) β `atan2(x,y)` β compass bearing (clockwise from north) |
|
| 114 |
+
| `latitude` | `IfcSite.RefLatitude` | IfcCompoundPlaneAngleMeasure [deg, min, sec, microsec] β decimal degrees |
|
| 115 |
+
| `longitude` | `IfcSite.RefLongitude` | Same decoding |
|
| 116 |
+
|
| 117 |
+
### Key functions
|
| 118 |
+
|
| 119 |
+
```python
|
| 120 |
+
find_ifc_files(root_dir: Path) -> list[Path]
|
| 121 |
+
# Recursively globs *.ifc under root_dir
|
| 122 |
+
|
| 123 |
+
process_ifc_file(ifc_path: Path) -> dict
|
| 124 |
+
# Opens file, calls 4 extractors, returns result dict with all 8 columns
|
| 125 |
+
|
| 126 |
+
extract_window_area(model) -> float | None
|
| 127 |
+
extract_floor_area(model) -> float | None
|
| 128 |
+
extract_roof_area(model) -> float | None
|
| 129 |
+
extract_orientation(model) -> dict # {true_north_angle_deg, latitude, longitude}
|
| 130 |
+
|
| 131 |
+
get_quantity(element, qset_name, qty_name) -> float | None
|
| 132 |
+
# Walks IsDefinedBy β IfcRelDefinesByProperties β IfcElementQuantity
|
| 133 |
+
# Handles both IfcQuantityArea (.AreaValue) and IfcQuantityLength (.LengthValue)
|
| 134 |
+
|
| 135 |
+
get_quantity_multi(element, qset_names: list[str], qty_name) -> float | None
|
| 136 |
+
# Tries each qset name in order β handles IFC2x3 vs IFC4 naming differences
|
| 137 |
+
|
| 138 |
+
get_area_scale(model) -> float
|
| 139 |
+
get_length_scale(model) -> float
|
| 140 |
+
# Uses ifcopenshell.util.unit.calculate_unit_scale to handle feet vs metres
|
| 141 |
+
|
| 142 |
+
decode_compound_angle(compound) -> float | None
|
| 143 |
+
# Converts IfcCompoundPlaneAngleMeasure [deg,min,sec,microsec] to decimal degrees
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
### Output β `ifc_scan_results.csv`
|
| 147 |
+
|
| 148 |
+
36 rows, 9 columns:
|
| 149 |
+
```
|
| 150 |
+
project_name, ifc_file, window_area_m2, floor_area_m2, roof_area_m2,
|
| 151 |
+
true_north_angle_deg, latitude, longitude, error
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
### Actual scan results (key data)
|
| 155 |
+
|
| 156 |
+
| Project | Window mΒ² | Floor mΒ² | Roof mΒ² | TrueNorthΒ° | Lat | Lon |
|
| 157 |
+
|---|---|---|---|---|---|---|
|
| 158 |
+
| 4351 | 10.87 | N/A | N/A | N/A | N/A | N/A |
|
| 159 |
+
| ac20 | 309.50 | 1939.38 | 688.77 | 0.00 | 49.15 | 8.72 |
|
| 160 |
+
| city_house_munich | 29.51 | N/A | N/A | 32.56 | 48.19 | 11.47 |
|
| 161 |
+
| dental_clinic | 100.63 | N/A | N/A | N/A | 42.36 | -71.06 |
|
| 162 |
+
| digital_hub | 375.00 | 2842.12 | N/A | 360.00 | 48.14 | 11.58 |
|
| 163 |
+
| duplex | 65.94 | N/A | N/A | N/A | 41.87 | -87.64 |
|
| 164 |
+
| ettenheim_gis | 725.97 | N/A | N/A | N/A | N/A | N/A |
|
| 165 |
+
| fantasy_hotel_1 | 58.78 | N/A | N/A | 360.00 | 42.36 | -71.06 |
|
| 166 |
+
| fantasy_hotel_2 | 400.31 | N/A | N/A | 360.00 | 42.36 | -71.06 |
|
| 167 |
+
| fantasy_office_building_1 | 156.82 | 1257.07 | 339.92 | 360.00 | 48.14 | 11.58 |
|
| 168 |
+
| fantasy_office_building_2 | 142.91 | 716.06 | 419.57 | 360.00 | 48.14 | 11.58 |
|
| 169 |
+
| fantasy_office_building_3 | 984.96 | N/A | N/A | 360.00 | 48.15 | 11.57 |
|
| 170 |
+
| fantasy_residential_building_1 | 19.56 | N/A | N/A | 360.00 | 48.14 | 11.58 |
|
| 171 |
+
| fzk_house | 23.17 | 173.34 | 165.12 | 310.00 | 49.10 | 8.44 |
|
| 172 |
+
| hitos | 1531.32 | N/A | N/A | 360.00 | 69.67 | 18.83 |
|
| 173 |
+
| molio | N/A | 140.78 | N/A | 0.00 | 55.67 | 12.62 |
|
| 174 |
+
| samuel_macalister_sample_house | 61.64 | N/A | N/A | 323.00 | 42.21 | -71.03 |
|
| 175 |
+
| schependomlaan | N/A | N/A | N/A | N/A | N/A | N/A |
|
| 176 |
+
| sixty5/arc | 4901.15 | 0.00 | N/A | 0.00 | 51.45 | 5.48 |
|
| 177 |
+
| smiley_west | 95.92 | 1821.17 | 614.41 | 147.50 | 49.03 | 8.39 |
|
| 178 |
+
| wbdg_office | 124.50 | N/A | N/A | N/A | 42.36 | -71.06 |
|
| 179 |
+
|
| 180 |
+
> **Note:** `360.00Β°` true north = same as `0.00Β°` (floating-point rounding artefact near exact north alignment β cosmetically different, data is correct). MEP/structural discipline files return N/A for most metrics β expected.
|
| 181 |
+
|
| 182 |
+
### Summary statistics
|
| 183 |
+
- Files scanned: 36, Errors: 0
|
| 184 |
+
- Window data: 19/36 files
|
| 185 |
+
- Floor data: 9/36 files
|
| 186 |
+
- Roof data: 5/36 files
|
| 187 |
+
- Orientation: 25/36 files
|
| 188 |
+
|
| 189 |
+
---
|
| 190 |
+
|
| 191 |
+
## File 2: `discover_ifc_keys.py`
|
| 192 |
+
|
| 193 |
+
### Purpose
|
| 194 |
+
Full metadata key discovery β enumerates every property set and quantity set used across all 36 IFC files, grouped by IFC element type. Produces the raw inventory and the canonical alias map.
|
| 195 |
+
|
| 196 |
+
### Run
|
| 197 |
+
```bash
|
| 198 |
+
.venv/Scripts/python.exe discover_ifc_keys.py
|
| 199 |
+
```
|
| 200 |
+
|
| 201 |
+
### Element types targeted
|
| 202 |
+
`IfcWindow`, `IfcDoor`, `IfcSpace`, `IfcSlab`, `IfcRoof`, `IfcWall`, `IfcWallStandardCase`, `IfcCovering`, `IfcSite`, `IfcBuilding`, `IfcBuildingStorey`
|
| 203 |
+
|
| 204 |
+
### Key functions
|
| 205 |
+
|
| 206 |
+
```python
|
| 207 |
+
collect_qsets(model, element_type: str) -> dict
|
| 208 |
+
# Returns {qset_name: {qty_name: count}} for all elements of that type in the model
|
| 209 |
+
|
| 210 |
+
collect_psets(model, element_type: str) -> dict
|
| 211 |
+
# Returns {pset_name: {prop_name: count}} for all elements of that type in the model
|
| 212 |
+
|
| 213 |
+
merge_into(global_inv, element_type, section, file_data, project_name)
|
| 214 |
+
# Accumulates per-file results: increments file_count, appends project_name
|
| 215 |
+
|
| 216 |
+
inventory_to_plain(global_inv) -> dict
|
| 217 |
+
# Converts defaultdict structure to plain dict for JSON serialisation
|
| 218 |
+
|
| 219 |
+
build_aliases(inventory) -> dict
|
| 220 |
+
# Creates key_aliases.json: pre-seeds known patterns + auto-discovers
|
| 221 |
+
# any area-related quantity keys not already covered
|
| 222 |
+
```
|
| 223 |
+
|
| 224 |
+
### Discovery results summary
|
| 225 |
+
|
| 226 |
+
| Element Type | Qty Sets | Qty Keys | Prop Sets | Prop Keys |
|
| 227 |
+
|---|---|---|---|---|
|
| 228 |
+
| IfcWindow | 59 | 8,834 | 102 | 20,413 |
|
| 229 |
+
| IfcDoor | 21 | 2,887 | 64 | 10,029 |
|
| 230 |
+
| IfcSpace | 8 | 201 | 38 | 1,315 |
|
| 231 |
+
| IfcSlab | 9 | 144 | 35 | 302 |
|
| 232 |
+
| IfcRoof | 1 | 3 | 25 | 117 |
|
| 233 |
+
| IfcWall | 22 | 234 | 41 | 392 |
|
| 234 |
+
| IfcWallStandardCase | 20 | 146 | 18 | 238 |
|
| 235 |
+
| IfcCovering | 3 | 114 | 33 | 325 |
|
| 236 |
+
| IfcSite | 1 | 2 | 10 | 58 |
|
| 237 |
+
| IfcBuilding | 1 | 1 | 13 | 77 |
|
| 238 |
+
| IfcBuildingStorey | 1 | 4 | 28 | 66 |
|
| 239 |
+
|
| 240 |
+
> **Why IfcWindow has 59 qsets and 8,834 keys:** Archicad creates one unique quantity set per window *type* family (named `AC_Equantity_IFC_Fenster_-_ein_Panel`, `AC_Equantity_R1_21`, etc.), each with its own set of dimension keys. The actual area extraction only needs the 2 standard sets below.
|
| 241 |
+
|
| 242 |
+
---
|
| 243 |
+
|
| 244 |
+
## File 3: `key_aliases.json`
|
| 245 |
+
|
| 246 |
+
### Purpose
|
| 247 |
+
A **human-editable JSON config** that maps each canonical building metric to an ordered list of IFC lookup strategies. Any extractor function should iterate this list and return the first strategy that yields a value.
|
| 248 |
+
|
| 249 |
+
### Structure
|
| 250 |
+
```json
|
| 251 |
+
{
|
| 252 |
+
"canonical_key": [
|
| 253 |
+
{
|
| 254 |
+
"entity": "IfcWindow", // IFC element type to query
|
| 255 |
+
"source": "qset", // "qset" (quantity set) or "attr" (direct attribute)
|
| 256 |
+
"set_name": "Qto_WindowBaseQuantities", // quantity set name
|
| 257 |
+
"key": "Area" // quantity name within the set
|
| 258 |
+
},
|
| 259 |
+
...
|
| 260 |
+
{
|
| 261 |
+
"entity": "IfcWindow",
|
| 262 |
+
"source": "attr", // direct IFC attribute (no set lookup)
|
| 263 |
+
"keys": ["OverallHeight", "OverallWidth"],
|
| 264 |
+
"op": "multiply" // operation to apply
|
| 265 |
+
}
|
| 266 |
+
]
|
| 267 |
+
}
|
| 268 |
+
```
|
| 269 |
+
|
| 270 |
+
### Canonical keys defined
|
| 271 |
+
|
| 272 |
+
#### `window_area`
|
| 273 |
+
| Priority | Entity | Source | Set Name | Key | Notes |
|
| 274 |
+
|---|---|---|---|---|---|
|
| 275 |
+
| 1 | IfcWindow | qset | `Qto_WindowBaseQuantities` | `Area` | IFC4 standard |
|
| 276 |
+
| 2 | IfcWindow | qset | `BaseQuantities` | `Area` | IFC2x3 Archicad/Revit |
|
| 277 |
+
| 3 | IfcWindow | qset | `BaseQuantities` | `GrossArea` | IFC2x3 fallback |
|
| 278 |
+
| 4 | IfcWindow | attr | β | `OverallHeight Γ OverallWidth` | Last resort, multiply length attrs |
|
| 279 |
+
|
| 280 |
+
#### `floor_area`
|
| 281 |
+
| Priority | Entity | Source | Set Name | Key | Filter |
|
| 282 |
+
|---|---|---|---|---|---|
|
| 283 |
+
| 1 | IfcSpace | qset | `Qto_SpaceBaseQuantities` | `NetFloorArea` | β |
|
| 284 |
+
| 2 | IfcSpace | qset | `Qto_SpaceBaseQuantities` | `GrossFloorArea` | β |
|
| 285 |
+
| 3 | IfcSpace | qset | `BaseQuantities` | `NetFloorArea` | IFC2x3 |
|
| 286 |
+
| 4 | IfcSpace | qset | `BaseQuantities` | `GrossFloorArea` | IFC2x3 |
|
| 287 |
+
| 5 | IfcSpace | qset | `GSA Space Areas` | `GSA BIM Area` | US Revit exports |
|
| 288 |
+
| 6 | IfcSlab | qset | `Qto_SlabBaseQuantities` | `NetArea` | PredefinedType=FLOOR |
|
| 289 |
+
| 7 | IfcSlab | qset | `BaseQuantities` | `NetArea` | PredefinedType=FLOOR |
|
| 290 |
+
| 8 | IfcSlab | qset | `BaseQuantities` | `GrossArea` | PredefinedType=FLOOR |
|
| 291 |
+
|
| 292 |
+
#### `roof_area`
|
| 293 |
+
| Priority | Entity | Source | Set Name | Key | Filter |
|
| 294 |
+
|---|---|---|---|---|---|
|
| 295 |
+
| 1 | IfcRoof | qset | `Qto_RoofBaseQuantities` | `NetArea` | β |
|
| 296 |
+
| 2 | IfcRoof | qset | `Qto_RoofBaseQuantities` | `GrossArea` | β |
|
| 297 |
+
| 3 | IfcRoof | qset | `BaseQuantities` | `NetArea` | IFC2x3 |
|
| 298 |
+
| 4 | IfcSlab | qset | `Qto_SlabBaseQuantities` | `NetArea` | PredefinedType=ROOF |
|
| 299 |
+
| 5 | IfcSlab | qset | `Qto_SlabBaseQuantities` | `GrossArea` | PredefinedType=ROOF |
|
| 300 |
+
| 6 | IfcSlab | qset | `BaseQuantities` | `NetArea` | PredefinedType=ROOF |
|
| 301 |
+
| 7 | IfcSlab | qset | `BaseQuantities` | `GrossArea` | PredefinedType=ROOF |
|
| 302 |
+
|
| 303 |
+
#### `true_north_angle`
|
| 304 |
+
- Entity: `IfcGeometricRepresentationContext`
|
| 305 |
+
- Source: `attr` β `TrueNorth.DirectionRatios (X, Y)`
|
| 306 |
+
- Conversion: `compass_bearing = (-atan2(X, Y) in degrees) % 360`
|
| 307 |
+
- Meaning: degrees clockwise from north (0Β° = north aligned with +Y axis)
|
| 308 |
+
|
| 309 |
+
#### `latitude` / `longitude`
|
| 310 |
+
- Entity: `IfcSite`
|
| 311 |
+
- Source: `attr` β `RefLatitude` / `RefLongitude`
|
| 312 |
+
- Format: `IfcCompoundPlaneAngleMeasure` = tuple of `[degrees, minutes, seconds, microseconds]`
|
| 313 |
+
- Conversion: `decimal = deg + min/60 + sec/3600 + microsec/3_600_000_000`
|
| 314 |
+
|
| 315 |
+
#### `_auto_discovered_area_keys`
|
| 316 |
+
Any quantity keys containing the word "area" found in the inventory that aren't already in the 6 canonical keys above are appended here automatically by `build_aliases()`. Review these manually to see if any should be promoted to a canonical key.
|
| 317 |
+
|
| 318 |
+
---
|
| 319 |
+
|
| 320 |
+
## File 4: `ifc_key_inventory.json`
|
| 321 |
+
|
| 322 |
+
Full raw inventory of every property/quantity key found across all 36 IFC files. Structure:
|
| 323 |
+
|
| 324 |
+
```json
|
| 325 |
+
{
|
| 326 |
+
"IfcWindow": {
|
| 327 |
+
"quantity_sets": {
|
| 328 |
+
"Qto_WindowBaseQuantities": {
|
| 329 |
+
"Area": { "file_count": 12, "projects": ["ac20", "digital_hub", ...] },
|
| 330 |
+
"Height": { "file_count": 12, "projects": [...] },
|
| 331 |
+
"Width": { "file_count": 12, "projects": [...] }
|
| 332 |
+
},
|
| 333 |
+
"BaseQuantities": {
|
| 334 |
+
"Area": { "file_count": 7, "projects": [...] },
|
| 335 |
+
"GrossArea": { "file_count": 4, "projects": ["duplex", ...] }
|
| 336 |
+
}
|
| 337 |
+
},
|
| 338 |
+
"property_sets": {
|
| 339 |
+
"Pset_WindowCommon": {
|
| 340 |
+
"IsExternal": { "file_count": 18, "projects": [...] },
|
| 341 |
+
"ThermalTransmittance": { "file_count": 8, "projects": [...] }
|
| 342 |
+
}
|
| 343 |
+
}
|
| 344 |
+
},
|
| 345 |
+
"IfcSpace": { ... },
|
| 346 |
+
...
|
| 347 |
+
}
|
| 348 |
+
```
|
| 349 |
+
|
| 350 |
+
Use this file as a reference when adding new canonical keys to `key_aliases.json`.
|
| 351 |
+
|
| 352 |
+
---
|
| 353 |
+
|
| 354 |
+
## IFC Exporter Ecosystem Found in the Dataset
|
| 355 |
+
|
| 356 |
+
| Exporter | Quantity Set Style | Property Set Style | Files |
|
| 357 |
+
|---|---|---|---|
|
| 358 |
+
| **Archicad (German IFC4)** | `BaseQuantities` + per-type `AC_Equantity_*` | `AC_Pset_*`, `ArchiCADProperties` | ac20, fzk_house |
|
| 359 |
+
| **Archicad (Dutch IFC2x3)** | `ArchiCADQuantities`, `BaseQuantities` | Same | sixty5 |
|
| 360 |
+
| **Revit (IFC2x3)** | `BaseQuantities`, `GSA Space Areas` | `PSet_Revit_*` | duplex, dental_clinic, wbdg_office |
|
| 361 |
+
| **IFC4 modern tools** | `Qto_*BaseQuantities` | `Pset_*Common` | digital_hub, schependomlaan |
|
| 362 |
+
| **IFC4X3 (latest)** | `Qto_*BaseQuantities` | `Pset_*Common` | city_house_munich |
|
| 363 |
+
| **Norwegian tool (hitos)** | Material-named sets (`Bindingsverk`, `Isolasjon`) | β | hitos |
|
| 364 |
+
| **Synchro 4D** | β | `SynchroResourceProperty` | some |
|
| 365 |
+
|
| 366 |
+
---
|
| 367 |
+
|
| 368 |
+
## IFC Schema Versions in Dataset
|
| 369 |
+
|
| 370 |
+
| Schema | Files | Example Projects |
|
| 371 |
+
|---|---|---|
|
| 372 |
+
| IFC2X3 | ~20 | 4351, duplex, schependomlaan, sixty5, hitos, molio, wbdg_office |
|
| 373 |
+
| IFC4 | ~14 | ac20, digital_hub, fzk_house, smiley_west, fantasy_* |
|
| 374 |
+
| IFC4X3 | 1 | city_house_munich |
|
| 375 |
+
|
| 376 |
+
---
|
| 377 |
+
|
| 378 |
+
## Key IFC Concepts for the Next Agent
|
| 379 |
+
|
| 380 |
+
### How properties are stored in IFC
|
| 381 |
+
|
| 382 |
+
```
|
| 383 |
+
IfcWindow
|
| 384 |
+
βββ IsDefinedBy (list of IfcRelDefinesByProperties)
|
| 385 |
+
βββ RelatingPropertyDefinition
|
| 386 |
+
βββ IfcPropertySet (name="Pset_WindowCommon")
|
| 387 |
+
β βββ HasProperties β [IfcPropertySingleValue, ...]
|
| 388 |
+
β βββ .Name = "IsExternal", .NominalValue = True
|
| 389 |
+
βββ IfcElementQuantity (name="Qto_WindowBaseQuantities")
|
| 390 |
+
βββ Quantities β [IfcQuantityArea, IfcQuantityLength, ...]
|
| 391 |
+
βββ .Name = "Area", .AreaValue = 2.5
|
| 392 |
+
```
|
| 393 |
+
|
| 394 |
+
### How orientation is stored
|
| 395 |
+
|
| 396 |
+
```
|
| 397 |
+
IfcGeometricRepresentationContext
|
| 398 |
+
βββ TrueNorth β IfcDirection
|
| 399 |
+
βββ DirectionRatios = (X, Y) # 2D vector pointing toward geographic north
|
| 400 |
+
# (0, 1) = north is +Y (no rotation)
|
| 401 |
+
# (1, 0) = north is +X (building rotated 90Β° CCW)
|
| 402 |
+
# compass_bearing = (-atan2(X,Y) in degrees) % 360
|
| 403 |
+
```
|
| 404 |
+
|
| 405 |
+
### How location is stored
|
| 406 |
+
|
| 407 |
+
```
|
| 408 |
+
IfcSite
|
| 409 |
+
βββ RefLatitude β IfcCompoundPlaneAngleMeasure = (51, 27, 0, 0) # 51Β°27'N
|
| 410 |
+
βββ RefLongitude β IfcCompoundPlaneAngleMeasure = (5, 29, 0, 0) # 5Β°29'E
|
| 411 |
+
```
|
| 412 |
+
|
| 413 |
+
### Unit handling
|
| 414 |
+
|
| 415 |
+
Always check the model's units before using extracted values:
|
| 416 |
+
```python
|
| 417 |
+
import ifcopenshell.util.unit as ifc_unit_util
|
| 418 |
+
area_scale = ifc_unit_util.calculate_unit_scale(model, "AREAMEASURE")
|
| 419 |
+
length_scale = ifc_unit_util.calculate_unit_scale(model, "LENGTHUNIT")
|
| 420 |
+
# US Revit models may use square feet β area_scale β 0.0929
|
| 421 |
+
```
|
| 422 |
+
|
| 423 |
+
---
|
| 424 |
+
|
| 425 |
+
## How to Use `key_aliases.json` in a New Extractor
|
| 426 |
+
|
| 427 |
+
```python
|
| 428 |
+
import json
|
| 429 |
+
import ifcopenshell
|
| 430 |
+
import ifcopenshell.util.unit as ifc_unit_util
|
| 431 |
+
import math
|
| 432 |
+
|
| 433 |
+
# Load the alias map once
|
| 434 |
+
with open("key_aliases.json") as f:
|
| 435 |
+
ALIASES = json.load(f)
|
| 436 |
+
|
| 437 |
+
def extract_metric(model, canonical_key: str) -> float | None:
|
| 438 |
+
"""
|
| 439 |
+
Generic extractor that uses key_aliases.json to find a metric
|
| 440 |
+
regardless of which IFC exporter created the file.
|
| 441 |
+
"""
|
| 442 |
+
aliases = ALIASES.get(canonical_key, [])
|
| 443 |
+
area_scale = ifc_unit_util.calculate_unit_scale(model, "AREAMEASURE")
|
| 444 |
+
length_scale = ifc_unit_util.calculate_unit_scale(model, "LENGTHUNIT")
|
| 445 |
+
total = 0.0
|
| 446 |
+
found = False
|
| 447 |
+
|
| 448 |
+
for alias in aliases:
|
| 449 |
+
entity = alias["entity"]
|
| 450 |
+
source = alias["source"]
|
| 451 |
+
pred_req = alias.get("predefined_type")
|
| 452 |
+
|
| 453 |
+
for elem in model.by_type(entity):
|
| 454 |
+
# Filter by PredefinedType if required
|
| 455 |
+
if pred_req and getattr(elem, "PredefinedType", None) != pred_req:
|
| 456 |
+
continue
|
| 457 |
+
|
| 458 |
+
if source == "qset":
|
| 459 |
+
val = get_quantity(elem, alias["set_name"], alias["key"])
|
| 460 |
+
if val is not None:
|
| 461 |
+
total += val * area_scale
|
| 462 |
+
found = True
|
| 463 |
+
|
| 464 |
+
elif source == "attr" and alias.get("op") == "multiply":
|
| 465 |
+
vals = [getattr(elem, k, None) for k in alias["keys"]]
|
| 466 |
+
if all(v is not None for v in vals):
|
| 467 |
+
product = 1.0
|
| 468 |
+
for v in vals:
|
| 469 |
+
product *= float(v)
|
| 470 |
+
total += product * (length_scale ** 2)
|
| 471 |
+
found = True
|
| 472 |
+
|
| 473 |
+
if found:
|
| 474 |
+
return round(total, 4) # Return on first successful strategy
|
| 475 |
+
|
| 476 |
+
return None
|
| 477 |
+
```
|
| 478 |
+
|
| 479 |
+
---
|
| 480 |
+
|
| 481 |
+
## Known Data Quality Notes
|
| 482 |
+
|
| 483 |
+
| Project | Issue | Explanation |
|
| 484 |
+
|---|---|---|
|
| 485 |
+
| `sixty5/arc.ifc` | `floor_area = 0.00` (not N/A) | Qto_SpaceBaseQuantities exists but values are 0 β model may lack space boundaries |
|
| 486 |
+
| `sixty5/str.ifc` | `floor_area = 34,199 mΒ²` | Structural slabs classified as FLOOR β over-counting, architectural file preferred |
|
| 487 |
+
| `schependomlaan/arc.ifc` | All N/A | IFC2x3 model has no quantity sets β geometry only |
|
| 488 |
+
| TrueNorth `360.00Β°` | Same as `0.00Β°` | Floating-point artefact when vector X is tiny negative; `(-Ξ΅) % 360 β 360` |
|
| 489 |
+
| `ettenheim_gis/city.ifc` | Windows = 725.97 mΒ² | GIS city model β likely summing openings across many buildings, not a single building |
|
| 490 |
+
| MEP/structural discipline files | All N/A | Correct β these files don't contain architectural elements |
|
| 491 |
+
|
| 492 |
+
---
|
| 493 |
+
|
| 494 |
+
## Running Everything
|
| 495 |
+
|
| 496 |
+
```bash
|
| 497 |
+
# Activate venv (Windows)
|
| 498 |
+
.venv\Scripts\activate
|
| 499 |
+
|
| 500 |
+
# Run the 4-metric scanner
|
| 501 |
+
python scan_ifc_models.py
|
| 502 |
+
# β prints table to console
|
| 503 |
+
# β writes ifc_scan_results.csv
|
| 504 |
+
# β writes ifc_scan.log
|
| 505 |
+
|
| 506 |
+
# Re-run the key discovery (if new IFC files are added)
|
| 507 |
+
python discover_ifc_keys.py
|
| 508 |
+
# β overwrites ifc_key_inventory.json
|
| 509 |
+
# β overwrites key_aliases.json
|
| 510 |
+
# β prints full inventory to console
|
| 511 |
+
```
|
| 512 |
+
|
| 513 |
+
---
|
| 514 |
+
|
| 515 |
+
## Suggested Next Steps
|
| 516 |
+
|
| 517 |
+
1. **Extend `key_aliases.json`** β add more canonical keys (e.g., `wall_area`, `door_count`, `storey_height`, `building_height`) using the inventory as a reference
|
| 518 |
+
2. **Handle `schependomlaan`** β this IFC2x3 model has no quantity sets; area must be computed from geometry using `ifcopenshell.geom`
|
| 519 |
+
3. **Window-to-floor ratio** β divide `window_area` by `floor_area` per project for glazing ratio analysis
|
| 520 |
+
4. **Solar orientation** β combine `true_north_angle` with `latitude` for solar exposure estimation
|
| 521 |
+
5. **Energy analysis** β roof/floor ratio and glazing ratio are key inputs for energy simulation
|
| 522 |
+
6. **Database ingestion** β load `ifc_scan_results.csv` into a dataframe or database for ML feature extraction
|
| 523 |
+
|
| 524 |
+
---
|
| 525 |
+
|
| 526 |
+
*Report generated by Claude Sonnet 4.6 (claude-sonnet-4-6) on 2026-02-18*
|
teams/lux-ai/API function/SOLAR_PRODUCTION_PIPELINE.md
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# βοΈ Solar Production Pipeline β Full Documentation
|
| 2 |
+
|
| 3 |
+
## 1. Project Overview
|
| 4 |
+
|
| 5 |
+
This pipeline estimates the **annual solar energy production** of a building's roof by:
|
| 6 |
+
|
| 7 |
+
1. **Parsing** an IFC (Industry Foundation Classes) file to extract roof geometry
|
| 8 |
+
2. **Segmenting** the roof into logical surfaces based on orientation
|
| 9 |
+
3. **Querying** the NREL PVWatts v8 API for each segment's solar yield
|
| 10 |
+
4. **Aggregating** results for a total building-level production estimate (kWh/yr)
|
| 11 |
+
|
| 12 |
+
This per-segment approach is critical for **LEED certification** accuracy β a south-facing surface may produce 2.5Γ more energy than a north-facing one on the same roof.
|
| 13 |
+
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
## 2. Architecture
|
| 17 |
+
|
| 18 |
+
```
|
| 19 |
+
βββββββββββββββββββββββ
|
| 20 |
+
β IFC File (.ifc) β
|
| 21 |
+
βββββββββββ¬ββββββββββββ
|
| 22 |
+
β
|
| 23 |
+
βΌ
|
| 24 |
+
βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 25 |
+
β ifc_roof_parser.py β
|
| 26 |
+
β β
|
| 27 |
+
β βββββββββββββββββββββββββββββββββββββββ β
|
| 28 |
+
β β get_roof_elements(model) β β
|
| 29 |
+
β β β finds IfcRoof + decomposed slabs β β
|
| 30 |
+
β ββββββββββββββββ¬βββββββββββββββββββββββ β
|
| 31 |
+
β β β
|
| 32 |
+
β ββββββββββββββββΌβββββββββββββββββββββββ β
|
| 33 |
+
β β extract_geometry(element, settings) β β
|
| 34 |
+
β β β triangulated mesh (verts/faces) β β
|
| 35 |
+
β ββββββββββββββββ¬βββββββββββββββββββββββ β
|
| 36 |
+
β β β
|
| 37 |
+
β ββββββββββββββββΌβββββββββββββββββββββββ β
|
| 38 |
+
β β cluster_faces_by_normal(normals, β β
|
| 39 |
+
β β areas, angle_tolerance=15Β°) β β
|
| 40 |
+
β β β groups triangles by orientation β β
|
| 41 |
+
β ββββββββββββββββ¬βββββββββββββββββββββββ β
|
| 42 |
+
β β β
|
| 43 |
+
β ββββββββββββββββΌβββββββββββββββββββββββ β
|
| 44 |
+
β β compute_segment_properties(normals, β β
|
| 45 |
+
β β areas, cluster_indices) β β
|
| 46 |
+
β β β tilt, azimuth, total area β β
|
| 47 |
+
β ββββββββββββββββ¬βββββββββββββββββββββββ β
|
| 48 |
+
β β β
|
| 49 |
+
β ββββββββββββββββΌβββββββββββββββββββββββ β
|
| 50 |
+
β β parse_roof_segments(ifc_path) β β
|
| 51 |
+
β β β list[{id, area, tilt, azimuth}] β β
|
| 52 |
+
β ββββββββββββββββ¬βββββββββββββββββββββββ β
|
| 53 |
+
βββββββββββββββββββΌββββββββββββββββββββββββββββ
|
| 54 |
+
β
|
| 55 |
+
βΌ list of segment dicts
|
| 56 |
+
βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 57 |
+
β solar_production_engine.py β
|
| 58 |
+
β β
|
| 59 |
+
β βββββββββββββββββββββββββββββββββββββββ β
|
| 60 |
+
β β Location(lat, lon, name) β β
|
| 61 |
+
β β β dataclass for site coordinates β β
|
| 62 |
+
β βββββββββββββββββββββββββββββββββββββββ β
|
| 63 |
+
β β
|
| 64 |
+
β βββββββββββββββββββββββββββββββββββββββ β
|
| 65 |
+
β β calculate_segment_production( β β
|
| 66 |
+
β β area, tilt, azimuth, location) β β
|
| 67 |
+
β β β annual kWh for one segment β β
|
| 68 |
+
β ββββββββββββββββ¬βββββββββββββββββββββββ β
|
| 69 |
+
β β β
|
| 70 |
+
β ββββββββββββββββΌβββββββββββββββββββββββ β
|
| 71 |
+
β β run_production_analysis( β β
|
| 72 |
+
β β segments, location) β β
|
| 73 |
+
β β β total kWh + per-segment results β β
|
| 74 |
+
β ββββββββββββββββ¬βββββββββββββββββββββββ β
|
| 75 |
+
βββββββββββββββββββΌββββββββββββββββββββββββββββ
|
| 76 |
+
β
|
| 77 |
+
βΌ
|
| 78 |
+
βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 79 |
+
β run_solar_analysis.py (orchestrator) β
|
| 80 |
+
β β
|
| 81 |
+
β 1. Reads IFC path (CLI arg or default) β
|
| 82 |
+
β 2. Calls parse_roof_segments() β
|
| 83 |
+
β 3. Calls run_production_analysis() β
|
| 84 |
+
β 4. Prints full report + LEED estimate β
|
| 85 |
+
βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
---
|
| 89 |
+
|
| 90 |
+
## 3. Files Created
|
| 91 |
+
|
| 92 |
+
### 3.1 `app/src/ifc_roof_parser.py` β IFC Geometry Parser
|
| 93 |
+
|
| 94 |
+
**Purpose:** Extract roof segments (area, tilt, azimuth) from any IFC file by analyzing 3D geometry.
|
| 95 |
+
|
| 96 |
+
| Function | Input | Output | Description |
|
| 97 |
+
|---|---|---|---|
|
| 98 |
+
| `get_roof_elements(model)` | `ifcopenshell.file` | `list[IfcElement]` | Finds all roof-related elements: `IfcRoof` entities, their decomposed `IfcSlab` children (via `IfcRelAggregates`), and standalone `IfcSlab` entities with `.ROOF.` predefined type |
|
| 99 |
+
| `extract_geometry(element, settings)` | IFC element + geom settings | `(vertices, faces)` as numpy arrays | Uses `ifcopenshell.geom.create_shape()` with `USE_WORLD_COORDS=True` to get triangulated mesh data |
|
| 100 |
+
| `compute_face_normals(vertices, faces)` | numpy arrays | `(normals, areas)` β Nx3 unit normals + N areas | Computes cross-product normals and triangle areas for every face in the mesh |
|
| 101 |
+
| `cluster_faces_by_normal(normals, areas, angle_tolerance)` | normals, areas, tolerance (default 15Β°) | `list[list[int]]` β cluster indices | Groups triangles whose normals are within `angle_tolerance` degrees of each other. Only considers **upward-facing** triangles (normal Z > 0). Uses greedy angular clustering. |
|
| 102 |
+
| `compute_segment_properties(normals, areas, cluster_indices)` | normals, areas, index groups | `dict{area, tilt, azimuth}` | Calculates **area-weighted average normal** per cluster, then derives: **tilt** = `arccos(nz)` in degrees, **azimuth** = `atan2(nx, ny) mod 360` in degrees |
|
| 103 |
+
| `parse_roof_segments(ifc_path)` | file path string | `list[{"id", "area", "tilt", "azimuth"}]` | **Main entry point.** Orchestrates all the above. Returns the standardized segment list that the production engine consumes. |
|
| 104 |
+
|
| 105 |
+
**Key Design Decisions:**
|
| 106 |
+
- **Geometry-based extraction** β does NOT rely on IFC property sets (`PitchAngle`, etc.) because these are unreliably populated across different BIM authoring tools
|
| 107 |
+
- **Clustering by face normals** β handles curved roofs (barrel vaults β many normals) and planar roofs (hip/gable β few distinct normals) with the same algorithm
|
| 108 |
+
- **Filters downward-facing triangles** β eliminates soffit/interior faces that aren't solar-relevant
|
| 109 |
+
- **Minimum area threshold** β ignores clusters smaller than 1 mΒ² to filter geometric noise
|
| 110 |
+
|
| 111 |
+
---
|
| 112 |
+
|
| 113 |
+
### 3.2 `app/src/solar_production_engine.py` β PVWatts API Client
|
| 114 |
+
|
| 115 |
+
**Purpose:** Calculate annual solar energy production (kWh) for roof segments using the NREL PVWatts v8 API.
|
| 116 |
+
|
| 117 |
+
| Component | Type | Description |
|
| 118 |
+
|---|---|---|
|
| 119 |
+
| `Location` | `@dataclass` | Stores `latitude: float`, `longitude: float`, `name: str` for a project site |
|
| 120 |
+
| `API_KEY` | `str` constant | NREL API key (`0zwEIS1a...` β production key) |
|
| 121 |
+
| `BASE_URL` | `str` constant | `https://developer.nrel.gov/api/pvwatts/v8.json` |
|
| 122 |
+
| `calculate_segment_production(area, tilt, azimuth, location)` | function | Computes `system_capacity = area Γ 0.20` (20% panel efficiency = 1 kW per 5 mΒ²), calls PVWatts API with fixed roof mount settings, returns annual AC output in kWh |
|
| 123 |
+
| `run_production_analysis(segments, location)` | function | Iterates a segment list, calls `calculate_segment_production()` for each, returns `(total_kwh, per_segment_results)` |
|
| 124 |
+
| `main()` | function | Standalone mode with hardcoded dummy segments for testing |
|
| 125 |
+
|
| 126 |
+
**API Parameters Sent:**
|
| 127 |
+
|
| 128 |
+
| Parameter | Value | Meaning |
|
| 129 |
+
|---|---|---|
|
| 130 |
+
| `system_capacity` | `area Γ 0.20` | kW capacity based on roof area |
|
| 131 |
+
| `azimuth` | from segment | Compass bearing (180Β° = south) |
|
| 132 |
+
| `tilt` | from segment | Angle from horizontal in degrees |
|
| 133 |
+
| `array_type` | `1` | Fixed β roof mount |
|
| 134 |
+
| `module_type` | `1` | Premium (monocrystalline) |
|
| 135 |
+
| `losses` | `14` | System losses (wiring, soiling, etc.) |
|
| 136 |
+
|
| 137 |
+
**Error Handling:**
|
| 138 |
+
- `requests.RequestException` β prints error, returns 0 kWh for that segment
|
| 139 |
+
- `KeyError` on missing `outputs` β prints API error response, returns 0
|
| 140 |
+
- `response.raise_for_status()` β catches HTTP 4xx/5xx errors
|
| 141 |
+
- `time.sleep(1)` between API calls β respects rate limits
|
| 142 |
+
|
| 143 |
+
---
|
| 144 |
+
|
| 145 |
+
### 3.3 `app/src/run_solar_analysis.py` β Orchestrator
|
| 146 |
+
|
| 147 |
+
**Purpose:** Glue script that connects the parser to the production engine.
|
| 148 |
+
|
| 149 |
+
| Component | Description |
|
| 150 |
+
|---|---|
|
| 151 |
+
| `SITE` | Hardcoded `Location(41.38, 2.17, "Barcelona_Project_Alpha")` β to be parameterized later |
|
| 152 |
+
| `DEFAULT_IFC` | Points to `00_data/ifc_models/Ifc4_SampleHouse_1_Roof.ifc` |
|
| 153 |
+
| CLI support | Accepts optional IFC file path as first argument: `python run_solar_analysis.py path/to/file.ifc` |
|
| 154 |
+
| Report output | Prints per-segment table + total production + LEED score example |
|
| 155 |
+
|
| 156 |
+
---
|
| 157 |
+
|
| 158 |
+
### 3.4 `requirements.txt` β Updated Dependencies
|
| 159 |
+
|
| 160 |
+
Added: `requests>=2.28.0` (required by the production engine for API calls)
|
| 161 |
+
|
| 162 |
+
Already present: `ifcopenshell`, `numpy`, `trimesh` (used by the parser)
|
| 163 |
+
|
| 164 |
+
---
|
| 165 |
+
|
| 166 |
+
## 4. Inputs & Outputs
|
| 167 |
+
|
| 168 |
+
### Inputs
|
| 169 |
+
|
| 170 |
+
| Input | Format | Source | Required |
|
| 171 |
+
|---|---|---|---|
|
| 172 |
+
| **IFC file** | `.ifc` (IFC2x3 or IFC4) | BIM authoring tool (Revit, ArchiCAD, etc.) | β
|
|
| 173 |
+
| **Site location** | Latitude/Longitude | Hardcoded (future: extracted from `IfcSite`) | β
|
|
| 174 |
+
| **NREL API key** | String | `api.nrel.gov` registration | β
|
|
| 175 |
+
|
| 176 |
+
### Outputs
|
| 177 |
+
|
| 178 |
+
| Output | Format | Description |
|
| 179 |
+
|---|---|---|
|
| 180 |
+
| **Per-segment data** | `list[dict]` | `{"id": str, "area": float, "tilt": float, "azimuth": float}` |
|
| 181 |
+
| **Per-segment yield** | `float` | Annual AC energy production in kWh/yr per roof surface |
|
| 182 |
+
| **Total building production** | `float` | Sum of all segment yields in kWh/yr |
|
| 183 |
+
| **LEED score estimate** | `float` | `(Total Production / Assumed Consumption) Γ 100` |
|
| 184 |
+
|
| 185 |
+
### Example Output (SampleHouse IFC)
|
| 186 |
+
|
| 187 |
+
```
|
| 188 |
+
=== SOLAR PRODUCTION ANALYSIS REPORT ===
|
| 189 |
+
Site: Barcelona_Project_Alpha (41.38Β°N, 2.17Β°E)
|
| 190 |
+
IFC: Ifc4_SampleHouse_1_Roof.ifc
|
| 191 |
+
|
| 192 |
+
--- Roof Segments Detected ---
|
| 193 |
+
Roof_Seg_01 | Area: 71.48 mΒ² | Tilt: 10.3Β° | Azimuth: 180Β° | Capacity: 14.3 kW | Yield: 19,063 kWh/yr
|
| 194 |
+
Roof_Seg_02 | Area: 45.44 mΒ² | Tilt: 27.9Β° | Azimuth: 0Β° | Capacity: 9.1 kW | Yield: 7,566 kWh/yr
|
| 195 |
+
|
| 196 |
+
--- Summary ---
|
| 197 |
+
Total Roof Area: 116.92 mΒ²
|
| 198 |
+
Total System Capacity: 23.4 kW
|
| 199 |
+
TOTAL ANNUAL PRODUCTION: 26,629 kWh/yr
|
| 200 |
+
|
| 201 |
+
--- LEED Estimate ---
|
| 202 |
+
Assumed consumption: 50,000 kWh/yr
|
| 203 |
+
Renewable coverage: 53.3%
|
| 204 |
+
Score = Total Production / Consumption Γ 100
|
| 205 |
+
```
|
| 206 |
+
|
| 207 |
+
---
|
| 208 |
+
|
| 209 |
+
## 5. IFC Compatibility
|
| 210 |
+
|
| 211 |
+
### Does the parser work for different IFC files?
|
| 212 |
+
|
| 213 |
+
**Yes, with the following conditions:**
|
| 214 |
+
|
| 215 |
+
| IFC Feature | Supported | How |
|
| 216 |
+
|---|---|---|
|
| 217 |
+
| **IFC4** (`.ifc`) | β
| Tested with SampleHouse |
|
| 218 |
+
| **IFC2x3** (`.ifc`) | β
| Same `ifcopenshell.geom` API; element types are identical |
|
| 219 |
+
| **IfcRoof (monolithic)** | β
| Geometry is clustered by face normals into logical segments |
|
| 220 |
+
| **IfcRoof β IfcSlab (decomposed)** | β
| Sub-slabs found via `IfcRelAggregates`; each processed individually |
|
| 221 |
+
| **IfcSlab with `.ROOF.` type** | β
| Standalone roof slabs detected by predefined type |
|
| 222 |
+
| **Barrel vault / curved roofs** | β
| Many triangle normals β clustering groups them into ~2-4 orientations |
|
| 223 |
+
| **Hip / gable roofs** | β
| Few distinct planar groups β clustering identifies each face |
|
| 224 |
+
| **Flat roofs** | β
| All normals β (0,0,1) β single segment with tilt β 0Β° |
|
| 225 |
+
| **IFC-XML** (`.ifcXML`) | β οΈ Partial | ifcopenshell can open it, but geometry extraction may differ |
|
| 226 |
+
| **IFC-ZIP** (`.ifcZIP`) | β | Must be extracted first |
|
| 227 |
+
| **No roof elements** | β
Handled | Returns empty list with warning message |
|
| 228 |
+
| **Roof with skylights/openings** | β οΈ | Openings not subtracted from area yet β future enhancement |
|
| 229 |
+
|
| 230 |
+
### Potential Variations Across BIM Software
|
| 231 |
+
|
| 232 |
+
| BIM Tool | Typical IFC Export | Parser Behavior |
|
| 233 |
+
|---|---|---|
|
| 234 |
+
| **Autodesk Revit** | IfcRoof as single Brep; sub-slabs sometimes decomposed | β
Both paths handled |
|
| 235 |
+
| **ArchiCAD** | IfcRoof with clean decomposition into IfcSlab children | β
Ideal case |
|
| 236 |
+
| **Tekla / Trimble** | IfcSlab with `.ROOF.` type (no IfcRoof parent) | β
Caught by type scan |
|
| 237 |
+
| **Blender (BlenderBIM)** | Varies β may use IfcRoof or IfcSlab | β
Both paths handled |
|
| 238 |
+
| **Generic/Unknown** | May have non-standard geometry representations | β οΈ Falls back to Brep triangulation |
|
| 239 |
+
|
| 240 |
+
---
|
| 241 |
+
|
| 242 |
+
## 6. Math Reference
|
| 243 |
+
|
| 244 |
+
### System Capacity
|
| 245 |
+
|
| 246 |
+
$$P_{capacity} = A_{segment} \times 0.20 \text{ kW}$$
|
| 247 |
+
|
| 248 |
+
Where 0.20 = 20% module efficiency (1 kW per 5 mΒ²)
|
| 249 |
+
|
| 250 |
+
### Tilt from Normal Vector
|
| 251 |
+
|
| 252 |
+
$$\theta_{tilt} = \arccos(n_z) \times \frac{180}{\pi}$$
|
| 253 |
+
|
| 254 |
+
### Azimuth from Normal Vector
|
| 255 |
+
|
| 256 |
+
$$\phi_{azimuth} = \text{atan2}(n_x, n_y) \mod 360Β°$$
|
| 257 |
+
|
| 258 |
+
Where 0Β° = North, 90Β° = East, 180Β° = South, 270Β° = West
|
| 259 |
+
|
| 260 |
+
### Area-Weighted Average Normal (per cluster)
|
| 261 |
+
|
| 262 |
+
$$\vec{n}_{avg} = \frac{\sum_{i \in cluster} A_i \cdot \vec{n}_i}{\left\| \sum_{i \in cluster} A_i \cdot \vec{n}_i \right\|}$$
|
| 263 |
+
|
| 264 |
+
### LEED Renewable Energy Score
|
| 265 |
+
|
| 266 |
+
$$Score = \frac{\sum_{s=1}^{N} P_{s}}{C_{total}} \times 100$$
|
| 267 |
+
|
| 268 |
+
Where $P_s$ = annual production of segment $s$, $C_{total}$ = total building consumption
|
| 269 |
+
|
| 270 |
+
---
|
| 271 |
+
|
| 272 |
+
## 7. How to Run
|
| 273 |
+
|
| 274 |
+
### Full Pipeline (IFC β Solar Report)
|
| 275 |
+
|
| 276 |
+
```bash
|
| 277 |
+
cd app/src
|
| 278 |
+
python run_solar_analysis.py
|
| 279 |
+
```
|
| 280 |
+
|
| 281 |
+
With a custom IFC file:
|
| 282 |
+
|
| 283 |
+
```bash
|
| 284 |
+
python run_solar_analysis.py "C:\path\to\your\model.ifc"
|
| 285 |
+
```
|
| 286 |
+
|
| 287 |
+
### Parser Only (IFC β Segment List)
|
| 288 |
+
|
| 289 |
+
```python
|
| 290 |
+
from ifc_roof_parser import parse_roof_segments
|
| 291 |
+
|
| 292 |
+
segments = parse_roof_segments("path/to/model.ifc")
|
| 293 |
+
for seg in segments:
|
| 294 |
+
print(f"{seg['id']}: {seg['area']:.1f} mΒ², tilt={seg['tilt']:.1f}Β°, azimuth={seg['azimuth']:.0f}Β°")
|
| 295 |
+
```
|
| 296 |
+
|
| 297 |
+
### Production Engine Only (Segments β kWh)
|
| 298 |
+
|
| 299 |
+
```python
|
| 300 |
+
from solar_production_engine import Location, run_production_analysis
|
| 301 |
+
|
| 302 |
+
segments = [
|
| 303 |
+
{"id": "South_Face", "area": 120, "tilt": 30, "azimuth": 180},
|
| 304 |
+
{"id": "East_Face", "area": 85, "tilt": 15, "azimuth": 90},
|
| 305 |
+
]
|
| 306 |
+
site = Location(latitude=41.38, longitude=2.17, name="Barcelona")
|
| 307 |
+
|
| 308 |
+
total, results = run_production_analysis(segments, site)
|
| 309 |
+
print(f"Total: {total:,.0f} kWh/yr")
|
| 310 |
+
```
|
| 311 |
+
|
| 312 |
+
---
|
| 313 |
+
|
| 314 |
+
## 8. Future Enhancements
|
| 315 |
+
|
| 316 |
+
| Enhancement | Impact | Difficulty |
|
| 317 |
+
|---|---|---|
|
| 318 |
+
| Extract location from `IfcSite` lat/lon automatically | Eliminates hardcoded coordinates | Low |
|
| 319 |
+
| Subtract skylight/opening areas from roof segments | More accurate area calculation | Medium |
|
| 320 |
+
| Add `pvlib` as offline fallback (no API dependency) | Works without internet | Medium |
|
| 321 |
+
| Export results to JSON/CSV for dashboard integration | Data pipeline ready | Low |
|
| 322 |
+
| Add panel layout optimizer (avoid low-yield segments) | Cost optimization | Medium |
|
| 323 |
+
| Support multiple buildings in one IFC file | Campus-scale analysis | Medium |
|
| 324 |
+
| Read building consumption from IFC `Pset_SpaceCommon` | Auto-calculate LEED score | High |
|
| 325 |
+
|
| 326 |
+
---
|
| 327 |
+
|
| 328 |
+
## 9. Dependencies
|
| 329 |
+
|
| 330 |
+
| Package | Version | Used By | Purpose |
|
| 331 |
+
|---|---|---|---|
|
| 332 |
+
| `ifcopenshell` | β₯ 0.7.0 | `ifc_roof_parser.py` | Open and query IFC files |
|
| 333 |
+
| `numpy` | β₯ 1.21.0 | `ifc_roof_parser.py` | Geometry math (normals, areas, clustering) |
|
| 334 |
+
| `requests` | β₯ 2.28.0 | `solar_production_engine.py` | HTTP calls to NREL PVWatts API |
|
| 335 |
+
| `trimesh` | β₯ 3.0.0 | `ifc_roof_parser.py` | (Optional) mesh utilities β currently using raw numpy |
|
| 336 |
+
|
| 337 |
+
---
|
| 338 |
+
|
| 339 |
+
## 10. File Tree
|
| 340 |
+
|
| 341 |
+
```
|
| 342 |
+
iaac-bimwise-starter/
|
| 343 |
+
βββ 00_data/
|
| 344 |
+
β βββ ifc_models/
|
| 345 |
+
β βββ Ifc4_SampleHouse_1_Roof.ifc β Test IFC file (barrel vault, London)
|
| 346 |
+
βββ app/
|
| 347 |
+
β βββ src/
|
| 348 |
+
β βββ ifc_roof_parser.py β NEW: IFC geometry β roof segments
|
| 349 |
+
β βββ solar_production_engine.py β NEW: Segments β PVWatts API β kWh
|
| 350 |
+
β βββ run_solar_analysis.py β NEW: Orchestrator (parser + engine)
|
| 351 |
+
β βββ ifc_checker.py β Existing: Basic IFC validation
|
| 352 |
+
β βββ ifc_visualizer.py β Existing: 3D IFC viewer
|
| 353 |
+
βββ docs/
|
| 354 |
+
β βββ SOLAR_PRODUCTION_PIPELINE.md β This file
|
| 355 |
+
βββ requirements.txt β Updated with requests>=2.28.0
|
| 356 |
+
```
|
teams/lux-ai/API function/run_solar_analysis.py
ADDED
|
File without changes
|
teams/lux-ai/API function/scan_ifc_models.py
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
scan_ifc_models.py
|
| 3 |
+
Scans all IFC files in the Sample projects directory and extracts:
|
| 4 |
+
- Window area (mΒ²)
|
| 5 |
+
- Floor area (mΒ²)
|
| 6 |
+
- Roof area (mΒ²)
|
| 7 |
+
- Orientation (true north angle, latitude, longitude)
|
| 8 |
+
|
| 9 |
+
Output: ifc_scan_results.csv + console table + ifc_scan.log
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import argparse
|
| 15 |
+
import csv
|
| 16 |
+
import logging
|
| 17 |
+
import math
|
| 18 |
+
from pathlib import Path
|
| 19 |
+
from typing import Optional
|
| 20 |
+
|
| 21 |
+
import ifcopenshell
|
| 22 |
+
import ifcopenshell.util.element as ifc_util
|
| 23 |
+
import ifcopenshell.util.unit as ifc_unit_util
|
| 24 |
+
from tabulate import tabulate
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
# ββ Logging βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 28 |
+
|
| 29 |
+
logging.basicConfig(
|
| 30 |
+
level=logging.INFO,
|
| 31 |
+
format="%(levelname)-8s %(message)s",
|
| 32 |
+
handlers=[
|
| 33 |
+
logging.StreamHandler(),
|
| 34 |
+
logging.FileHandler(
|
| 35 |
+
Path(__file__).parent / "ifc_scan.log", mode="w", encoding="utf-8"
|
| 36 |
+
),
|
| 37 |
+
],
|
| 38 |
+
)
|
| 39 |
+
log = logging.getLogger(__name__)
|
| 40 |
+
|
| 41 |
+
# ββ Constants βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 42 |
+
|
| 43 |
+
CSV_COLUMNS = [
|
| 44 |
+
"project_name",
|
| 45 |
+
"ifc_file",
|
| 46 |
+
"window_area_m2",
|
| 47 |
+
"floor_area_m2",
|
| 48 |
+
"roof_area_m2",
|
| 49 |
+
"true_north_angle_deg",
|
| 50 |
+
"latitude",
|
| 51 |
+
"longitude",
|
| 52 |
+
"error",
|
| 53 |
+
]
|
| 54 |
+
|
| 55 |
+
WINDOW_QSETS = ["Qto_WindowBaseQuantities", "BaseQuantities"]
|
| 56 |
+
SPACE_QSETS = ["Qto_SpaceBaseQuantities", "BaseQuantities"]
|
| 57 |
+
SLAB_QSETS = ["Qto_SlabBaseQuantities", "BaseQuantities"]
|
| 58 |
+
ROOF_QSETS = ["Qto_RoofBaseQuantities", "BaseQuantities"]
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
# ββ Discovery βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 62 |
+
|
| 63 |
+
def find_ifc_files(root_dir: Path) -> list[Path]:
|
| 64 |
+
"""Recursively find all .ifc files under root_dir, sorted by path."""
|
| 65 |
+
files = sorted(root_dir.rglob("*.ifc"))
|
| 66 |
+
return files
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
# ββ Unit helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 70 |
+
|
| 71 |
+
def get_length_scale(model: ifcopenshell.file) -> float:
|
| 72 |
+
"""Return scale factor to convert model length units to metres."""
|
| 73 |
+
try:
|
| 74 |
+
return ifc_unit_util.calculate_unit_scale(model, "LENGTHUNIT")
|
| 75 |
+
except Exception:
|
| 76 |
+
return 1.0
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def get_area_scale(model: ifcopenshell.file) -> float:
|
| 80 |
+
"""Return scale factor to convert model area units to mΒ²."""
|
| 81 |
+
try:
|
| 82 |
+
return ifc_unit_util.calculate_unit_scale(model, "AREAMEASURE")
|
| 83 |
+
except Exception:
|
| 84 |
+
return 1.0
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
# ββ Quantity extraction helpers βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 88 |
+
|
| 89 |
+
def get_quantity(element, qset_name: str, qty_name: str) -> Optional[float]:
|
| 90 |
+
"""
|
| 91 |
+
Walk IsDefinedBy relationships to find an IfcElementQuantity named
|
| 92 |
+
qset_name and return the numeric value of qty_name within it.
|
| 93 |
+
Returns None if not found.
|
| 94 |
+
"""
|
| 95 |
+
for rel in getattr(element, "IsDefinedBy", []):
|
| 96 |
+
if not rel.is_a("IfcRelDefinesByProperties"):
|
| 97 |
+
continue
|
| 98 |
+
qset = rel.RelatingPropertyDefinition
|
| 99 |
+
if not qset.is_a("IfcElementQuantity"):
|
| 100 |
+
continue
|
| 101 |
+
if qset.Name != qset_name:
|
| 102 |
+
continue
|
| 103 |
+
for qty in qset.Quantities:
|
| 104 |
+
if qty.Name != qty_name:
|
| 105 |
+
continue
|
| 106 |
+
if hasattr(qty, "AreaValue") and qty.AreaValue is not None:
|
| 107 |
+
return float(qty.AreaValue)
|
| 108 |
+
if hasattr(qty, "LengthValue") and qty.LengthValue is not None:
|
| 109 |
+
return float(qty.LengthValue)
|
| 110 |
+
if hasattr(qty, "VolumeValue") and qty.VolumeValue is not None:
|
| 111 |
+
return float(qty.VolumeValue)
|
| 112 |
+
return None
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def get_quantity_multi(
|
| 116 |
+
element, qset_names: list[str], qty_name: str
|
| 117 |
+
) -> Optional[float]:
|
| 118 |
+
"""Try multiple quantity set names in order, return first match."""
|
| 119 |
+
for qset_name in qset_names:
|
| 120 |
+
val = get_quantity(element, qset_name, qty_name)
|
| 121 |
+
if val is not None:
|
| 122 |
+
return val
|
| 123 |
+
return None
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
# ββ Four metric extractors ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 127 |
+
|
| 128 |
+
def extract_window_area(model: ifcopenshell.file) -> Optional[float]:
|
| 129 |
+
"""
|
| 130 |
+
Total window area in mΒ².
|
| 131 |
+
Strategy 1: Qto_WindowBaseQuantities / Area (IFC4) or BaseQuantities / Area (IFC2x3)
|
| 132 |
+
Strategy 2: OverallHeight Γ OverallWidth (direct attributes)
|
| 133 |
+
"""
|
| 134 |
+
windows = model.by_type("IfcWindow")
|
| 135 |
+
if not windows:
|
| 136 |
+
return None
|
| 137 |
+
|
| 138 |
+
area_scale = get_area_scale(model)
|
| 139 |
+
length_scale = get_length_scale(model)
|
| 140 |
+
total = 0.0
|
| 141 |
+
found_any = False
|
| 142 |
+
|
| 143 |
+
for win in windows:
|
| 144 |
+
# Strategy 1 β quantity set
|
| 145 |
+
area = get_quantity_multi(win, WINDOW_QSETS, "Area")
|
| 146 |
+
if area is not None:
|
| 147 |
+
total += area * area_scale
|
| 148 |
+
found_any = True
|
| 149 |
+
continue
|
| 150 |
+
|
| 151 |
+
# Strategy 2 β direct attributes
|
| 152 |
+
h = getattr(win, "OverallHeight", None)
|
| 153 |
+
w = getattr(win, "OverallWidth", None)
|
| 154 |
+
if h and w:
|
| 155 |
+
total += float(h) * float(w) * (length_scale ** 2)
|
| 156 |
+
found_any = True
|
| 157 |
+
|
| 158 |
+
return round(total, 4) if found_any else None
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def extract_floor_area(model: ifcopenshell.file) -> Optional[float]:
|
| 162 |
+
"""
|
| 163 |
+
Total floor area in mΒ².
|
| 164 |
+
Strategy 1: IfcSpace β Qto_SpaceBaseQuantities / NetFloorArea
|
| 165 |
+
Strategy 2: IfcSpace β Qto_SpaceBaseQuantities / GrossFloorArea
|
| 166 |
+
Strategy 3: IfcSlab[FLOOR/BASESLAB] β Qto_SlabBaseQuantities / NetArea
|
| 167 |
+
"""
|
| 168 |
+
area_scale = get_area_scale(model)
|
| 169 |
+
total = 0.0
|
| 170 |
+
found_any = False
|
| 171 |
+
|
| 172 |
+
# Strategies 1 & 2 β IfcSpace
|
| 173 |
+
spaces = model.by_type("IfcSpace")
|
| 174 |
+
for space in spaces:
|
| 175 |
+
area = get_quantity_multi(space, SPACE_QSETS, "NetFloorArea")
|
| 176 |
+
if area is None:
|
| 177 |
+
area = get_quantity_multi(space, SPACE_QSETS, "GrossFloorArea")
|
| 178 |
+
if area is not None:
|
| 179 |
+
total += area * area_scale
|
| 180 |
+
found_any = True
|
| 181 |
+
|
| 182 |
+
if found_any:
|
| 183 |
+
return round(total, 4)
|
| 184 |
+
|
| 185 |
+
# Strategy 3 β IfcSlab FLOOR type
|
| 186 |
+
for slab in model.by_type("IfcSlab"):
|
| 187 |
+
pred = getattr(slab, "PredefinedType", None)
|
| 188 |
+
if pred in ("FLOOR", "BASESLAB"):
|
| 189 |
+
area = get_quantity_multi(slab, SLAB_QSETS, "NetArea")
|
| 190 |
+
if area is None:
|
| 191 |
+
area = get_quantity_multi(slab, SLAB_QSETS, "GrossArea")
|
| 192 |
+
if area is not None:
|
| 193 |
+
total += area * area_scale
|
| 194 |
+
found_any = True
|
| 195 |
+
|
| 196 |
+
return round(total, 4) if found_any else None
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def extract_roof_area(model: ifcopenshell.file) -> Optional[float]:
|
| 200 |
+
"""
|
| 201 |
+
Total roof area in mΒ².
|
| 202 |
+
Strategy 1: IfcRoof β Qto_RoofBaseQuantities / NetArea
|
| 203 |
+
Strategy 2: IfcSlab[ROOF] β Qto_SlabBaseQuantities / NetArea
|
| 204 |
+
Strategy 3: IfcSlab[ROOF] β Qto_SlabBaseQuantities / GrossArea
|
| 205 |
+
"""
|
| 206 |
+
area_scale = get_area_scale(model)
|
| 207 |
+
total = 0.0
|
| 208 |
+
found_any = False
|
| 209 |
+
|
| 210 |
+
# Strategy 1 β IfcRoof entity
|
| 211 |
+
for roof in model.by_type("IfcRoof"):
|
| 212 |
+
area = get_quantity_multi(roof, ROOF_QSETS, "NetArea")
|
| 213 |
+
if area is None:
|
| 214 |
+
area = get_quantity_multi(roof, ROOF_QSETS, "GrossArea")
|
| 215 |
+
if area is not None:
|
| 216 |
+
total += area * area_scale
|
| 217 |
+
found_any = True
|
| 218 |
+
|
| 219 |
+
if found_any:
|
| 220 |
+
return round(total, 4)
|
| 221 |
+
|
| 222 |
+
# Strategies 2 & 3 β IfcSlab ROOF type
|
| 223 |
+
for slab in model.by_type("IfcSlab"):
|
| 224 |
+
pred = getattr(slab, "PredefinedType", None)
|
| 225 |
+
if pred == "ROOF":
|
| 226 |
+
area = get_quantity_multi(slab, SLAB_QSETS, "NetArea")
|
| 227 |
+
if area is None:
|
| 228 |
+
area = get_quantity_multi(slab, SLAB_QSETS, "GrossArea")
|
| 229 |
+
if area is not None:
|
| 230 |
+
total += area * area_scale
|
| 231 |
+
found_any = True
|
| 232 |
+
|
| 233 |
+
return round(total, 4) if found_any else None
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def decode_compound_angle(compound) -> Optional[float]:
|
| 237 |
+
"""Convert IfcCompoundPlaneAngleMeasure [deg, min, sec, microsec] to decimal degrees."""
|
| 238 |
+
if compound is None:
|
| 239 |
+
return None
|
| 240 |
+
parts = list(compound)
|
| 241 |
+
if not parts:
|
| 242 |
+
return None
|
| 243 |
+
deg = int(parts[0])
|
| 244 |
+
minutes = int(parts[1]) if len(parts) > 1 else 0
|
| 245 |
+
secs = int(parts[2]) if len(parts) > 2 else 0
|
| 246 |
+
microsecs = int(parts[3]) if len(parts) > 3 else 0
|
| 247 |
+
sign = -1 if deg < 0 else 1
|
| 248 |
+
return sign * (abs(deg) + abs(minutes) / 60 + abs(secs) / 3600 + abs(microsecs) / 3_600_000_000)
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def extract_orientation(model: ifcopenshell.file) -> dict:
|
| 252 |
+
"""
|
| 253 |
+
Extract true north angle (compass bearing, degrees CW from north)
|
| 254 |
+
and geographic coordinates from IfcSite.
|
| 255 |
+
"""
|
| 256 |
+
result = {
|
| 257 |
+
"true_north_angle_deg": None,
|
| 258 |
+
"latitude": None,
|
| 259 |
+
"longitude": None,
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
# True North from IfcGeometricRepresentationContext
|
| 263 |
+
for ctx in model.by_type("IfcGeometricRepresentationContext"):
|
| 264 |
+
if ctx.is_a("IfcGeometricRepresentationSubContext"):
|
| 265 |
+
continue
|
| 266 |
+
true_north = getattr(ctx, "TrueNorth", None)
|
| 267 |
+
if true_north is not None:
|
| 268 |
+
ratios = true_north.DirectionRatios
|
| 269 |
+
x = float(ratios[0])
|
| 270 |
+
y = float(ratios[1])
|
| 271 |
+
# Angle from +Y axis, measured CCW, converted to clockwise compass bearing
|
| 272 |
+
angle_ccw = math.degrees(math.atan2(x, y))
|
| 273 |
+
result["true_north_angle_deg"] = round((-angle_ccw) % 360.0, 2)
|
| 274 |
+
break
|
| 275 |
+
|
| 276 |
+
# Lat/Lon from IfcSite
|
| 277 |
+
sites = model.by_type("IfcSite")
|
| 278 |
+
if sites:
|
| 279 |
+
site = sites[0]
|
| 280 |
+
result["latitude"] = decode_compound_angle(getattr(site, "RefLatitude", None))
|
| 281 |
+
result["longitude"] = decode_compound_angle(getattr(site, "RefLongitude", None))
|
| 282 |
+
if result["latitude"] is not None:
|
| 283 |
+
result["latitude"] = round(result["latitude"], 6)
|
| 284 |
+
if result["longitude"] is not None:
|
| 285 |
+
result["longitude"] = round(result["longitude"], 6)
|
| 286 |
+
|
| 287 |
+
return result
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
# ββ Per-file orchestration ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 291 |
+
|
| 292 |
+
def process_ifc_file(ifc_path: Path) -> dict:
|
| 293 |
+
"""Open one IFC file and extract all metrics."""
|
| 294 |
+
base = {
|
| 295 |
+
"project_name": ifc_path.parent.name,
|
| 296 |
+
"ifc_file": ifc_path.name,
|
| 297 |
+
"window_area_m2": None,
|
| 298 |
+
"floor_area_m2": None,
|
| 299 |
+
"roof_area_m2": None,
|
| 300 |
+
"true_north_angle_deg": None,
|
| 301 |
+
"latitude": None,
|
| 302 |
+
"longitude": None,
|
| 303 |
+
"error": None,
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
try:
|
| 307 |
+
model = ifcopenshell.open(str(ifc_path))
|
| 308 |
+
except Exception as exc:
|
| 309 |
+
log.error(f"Cannot open {ifc_path}: {exc}")
|
| 310 |
+
base["error"] = str(exc)
|
| 311 |
+
return base
|
| 312 |
+
|
| 313 |
+
schema = model.schema
|
| 314 |
+
log.info(f" Schema: {schema}")
|
| 315 |
+
|
| 316 |
+
extractors = {
|
| 317 |
+
"window_area_m2": extract_window_area,
|
| 318 |
+
"floor_area_m2": extract_floor_area,
|
| 319 |
+
"roof_area_m2": extract_roof_area,
|
| 320 |
+
}
|
| 321 |
+
for key, fn in extractors.items():
|
| 322 |
+
try:
|
| 323 |
+
base[key] = fn(model)
|
| 324 |
+
except Exception as exc:
|
| 325 |
+
log.warning(f" {key} extraction failed: {exc}")
|
| 326 |
+
|
| 327 |
+
try:
|
| 328 |
+
orientation = extract_orientation(model)
|
| 329 |
+
base.update(orientation)
|
| 330 |
+
except Exception as exc:
|
| 331 |
+
log.warning(f" orientation extraction failed: {exc}")
|
| 332 |
+
|
| 333 |
+
return base
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
# ββ Output ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 337 |
+
|
| 338 |
+
def write_csv(results: list[dict], output_path: Path) -> None:
|
| 339 |
+
with open(output_path, "w", newline="", encoding="utf-8") as f:
|
| 340 |
+
writer = csv.DictWriter(f, fieldnames=CSV_COLUMNS, extrasaction="ignore")
|
| 341 |
+
writer.writeheader()
|
| 342 |
+
writer.writerows(results)
|
| 343 |
+
log.info(f"CSV written to: {output_path}")
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
def print_summary_table(results: list[dict]) -> None:
|
| 347 |
+
display_cols = [
|
| 348 |
+
"project_name", "ifc_file",
|
| 349 |
+
"window_area_m2", "floor_area_m2", "roof_area_m2",
|
| 350 |
+
"true_north_angle_deg", "latitude", "longitude",
|
| 351 |
+
]
|
| 352 |
+
headers = [
|
| 353 |
+
"Project", "File",
|
| 354 |
+
"Window mΒ²", "Floor mΒ²", "Roof mΒ²",
|
| 355 |
+
"TrueNorthΒ°", "Lat", "Lon",
|
| 356 |
+
]
|
| 357 |
+
|
| 358 |
+
table_data = []
|
| 359 |
+
for r in results:
|
| 360 |
+
row = []
|
| 361 |
+
for col in display_cols:
|
| 362 |
+
val = r.get(col)
|
| 363 |
+
if val is None:
|
| 364 |
+
row.append("N/A")
|
| 365 |
+
elif isinstance(val, float):
|
| 366 |
+
row.append(f"{val:.2f}")
|
| 367 |
+
else:
|
| 368 |
+
row.append(str(val))
|
| 369 |
+
if r.get("error"):
|
| 370 |
+
row[-1] = "ERROR"
|
| 371 |
+
table_data.append(row)
|
| 372 |
+
|
| 373 |
+
print("\n" + tabulate(table_data, headers=headers, tablefmt="github"))
|
| 374 |
+
|
| 375 |
+
processed = [r for r in results if not r.get("error")]
|
| 376 |
+
errors = [r for r in results if r.get("error")]
|
| 377 |
+
print(f"\nFiles scanned : {len(results)}")
|
| 378 |
+
print(f"Errors : {len(errors)}")
|
| 379 |
+
print(f"Window data : {sum(1 for r in processed if r['window_area_m2'] is not None)}/{len(processed)} files")
|
| 380 |
+
print(f"Floor data : {sum(1 for r in processed if r['floor_area_m2'] is not None)}/{len(processed)} files")
|
| 381 |
+
print(f"Roof data : {sum(1 for r in processed if r['roof_area_m2'] is not None)}/{len(processed)} files")
|
| 382 |
+
print(f"Orientation : {sum(1 for r in processed if r['true_north_angle_deg'] is not None)}/{len(processed)} files")
|
| 383 |
+
if errors:
|
| 384 |
+
print("\nFiles with errors:")
|
| 385 |
+
for r in errors:
|
| 386 |
+
print(f" {r['project_name']}/{r['ifc_file']}: {r['error']}")
|
| 387 |
+
|
| 388 |
+
|
| 389 |
+
# ββ Entry point βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 390 |
+
|
| 391 |
+
def main() -> None:
|
| 392 |
+
parser = argparse.ArgumentParser(
|
| 393 |
+
description="Scan IFC files for window/floor/roof area and orientation."
|
| 394 |
+
)
|
| 395 |
+
parser.add_argument(
|
| 396 |
+
"--root",
|
| 397 |
+
type=Path,
|
| 398 |
+
default=Path(__file__).parent / "Sample projects" / "projects",
|
| 399 |
+
help="Root directory to search for .ifc files",
|
| 400 |
+
)
|
| 401 |
+
parser.add_argument(
|
| 402 |
+
"--output",
|
| 403 |
+
type=Path,
|
| 404 |
+
default=Path(__file__).parent / "ifc_scan_results.csv",
|
| 405 |
+
help="Output CSV file path",
|
| 406 |
+
)
|
| 407 |
+
args = parser.parse_args()
|
| 408 |
+
|
| 409 |
+
if not args.root.exists():
|
| 410 |
+
log.error(f"Root directory not found: {args.root}")
|
| 411 |
+
return
|
| 412 |
+
|
| 413 |
+
ifc_files = find_ifc_files(args.root)
|
| 414 |
+
log.info(f"Found {len(ifc_files)} IFC file(s) under: {args.root}")
|
| 415 |
+
|
| 416 |
+
results = []
|
| 417 |
+
for i, ifc_path in enumerate(ifc_files, 1):
|
| 418 |
+
log.info(f"[{i}/{len(ifc_files)}] {ifc_path.parent.name}/{ifc_path.name}")
|
| 419 |
+
result = process_ifc_file(ifc_path)
|
| 420 |
+
results.append(result)
|
| 421 |
+
|
| 422 |
+
write_csv(results, args.output)
|
| 423 |
+
print_summary_table(results)
|
| 424 |
+
|
| 425 |
+
|
| 426 |
+
if __name__ == "__main__":
|
| 427 |
+
main()
|
teams/lux-ai/API function/solar_production_engine.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Solar Production Engine [DEPRECATED β use solar_pipeline/ instead]
|
| 3 |
+
|
| 4 |
+
Superseded by: solar_pipeline/solar_production_engine.py
|
| 5 |
+
|
| 6 |
+
Original description:
|
| 7 |
+
Calculates annual solar energy production for a building by querying the
|
| 8 |
+
NREL PVWatts v8 API for each roof segment (slab) individually.
|
| 9 |
+
|
| 10 |
+
Each segment has its own orientation (tilt & azimuth), so the per-segment
|
| 11 |
+
approach yields a far more accurate LEED renewable-energy score than a
|
| 12 |
+
single whole-roof average.
|
| 13 |
+
|
| 14 |
+
LEED Score = (Ξ£ P_slabs / Consumption_total) Γ 100
|
| 15 |
+
|
| 16 |
+
Future integration:
|
| 17 |
+
An IFC parser will populate `roof_segments` automatically from
|
| 18 |
+
IfcSlab / IfcRoof geometry. For now we use representative dummy data.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
import requests
|
| 22 |
+
import time
|
| 23 |
+
from dataclasses import dataclass
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
# ---------------------------------------------------------------------------
|
| 27 |
+
# Constants
|
| 28 |
+
# ---------------------------------------------------------------------------
|
| 29 |
+
API_KEY = "0zwEIS1adJrx658O3gjQYfI7AprKLjQf4KP420o9"
|
| 30 |
+
BASE_URL = "https://developer.nrel.gov/api/pvwatts/v8.json"
|
| 31 |
+
PANEL_EFFICIENCY = 0.20 # 1 kW per 5 mΒ²
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
# ---------------------------------------------------------------------------
|
| 35 |
+
# Location dataclass
|
| 36 |
+
# ---------------------------------------------------------------------------
|
| 37 |
+
@dataclass
|
| 38 |
+
class Location:
|
| 39 |
+
"""Stores site coordinates and a human-readable project name."""
|
| 40 |
+
latitude: float
|
| 41 |
+
longitude: float
|
| 42 |
+
name: str
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
# Default site β Barcelona
|
| 46 |
+
SITE_LOCATION = Location(
|
| 47 |
+
latitude=41.38,
|
| 48 |
+
longitude=2.17,
|
| 49 |
+
name="Barcelona_Project_Alpha",
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
# ---------------------------------------------------------------------------
|
| 54 |
+
# Dummy roof-segment data (will be populated by IFC parser in the future)
|
| 55 |
+
# ---------------------------------------------------------------------------
|
| 56 |
+
roof_segments: list[dict] = [
|
| 57 |
+
{"id": "Slab_01", "area": 120, "tilt": 30, "azimuth": 180}, # South face
|
| 58 |
+
{"id": "Slab_02", "area": 85, "tilt": 15, "azimuth": 90}, # East face
|
| 59 |
+
{"id": "Slab_03", "area": 45, "tilt": 45, "azimuth": 270}, # West face
|
| 60 |
+
]
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
# ---------------------------------------------------------------------------
|
| 64 |
+
# Core calculation function
|
| 65 |
+
# ---------------------------------------------------------------------------
|
| 66 |
+
def calculate_segment_production(
|
| 67 |
+
area: float,
|
| 68 |
+
tilt: float,
|
| 69 |
+
azimuth: float,
|
| 70 |
+
location: Location,
|
| 71 |
+
) -> float:
|
| 72 |
+
"""
|
| 73 |
+
Query the NREL PVWatts v8 API for a single roof segment and return
|
| 74 |
+
the estimated annual AC energy production in kWh.
|
| 75 |
+
|
| 76 |
+
Parameters
|
| 77 |
+
----------
|
| 78 |
+
area : float
|
| 79 |
+
Segment area in mΒ².
|
| 80 |
+
tilt : float
|
| 81 |
+
Panel tilt angle in degrees from horizontal.
|
| 82 |
+
azimuth : float
|
| 83 |
+
Panel azimuth in degrees (180 = due south).
|
| 84 |
+
location : Location
|
| 85 |
+
Site coordinates and name.
|
| 86 |
+
|
| 87 |
+
Returns
|
| 88 |
+
-------
|
| 89 |
+
float
|
| 90 |
+
Annual AC production in kWh, or 0 on error.
|
| 91 |
+
"""
|
| 92 |
+
system_capacity = area * PANEL_EFFICIENCY # kW
|
| 93 |
+
|
| 94 |
+
params = {
|
| 95 |
+
"api_key": API_KEY,
|
| 96 |
+
"lat": location.latitude,
|
| 97 |
+
"lon": location.longitude,
|
| 98 |
+
"system_capacity": system_capacity,
|
| 99 |
+
"azimuth": azimuth,
|
| 100 |
+
"tilt": tilt,
|
| 101 |
+
"array_type": 1, # Fixed β roof mount
|
| 102 |
+
"module_type": 1, # Premium
|
| 103 |
+
"losses": 14,
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
try:
|
| 107 |
+
response = requests.get(BASE_URL, params=params, timeout=30)
|
| 108 |
+
response.raise_for_status()
|
| 109 |
+
data = response.json()
|
| 110 |
+
|
| 111 |
+
if "errors" in data and data["errors"]:
|
| 112 |
+
print(f" [API Error] {data['errors']}")
|
| 113 |
+
return 0.0
|
| 114 |
+
|
| 115 |
+
annual_kwh = float(data["outputs"]["ac_annual"])
|
| 116 |
+
return annual_kwh
|
| 117 |
+
|
| 118 |
+
except requests.RequestException as exc:
|
| 119 |
+
print(f" [Request Error] {exc}")
|
| 120 |
+
return 0.0
|
| 121 |
+
except (KeyError, TypeError, ValueError) as exc:
|
| 122 |
+
print(f" [Parse Error] Could not read API response: {exc}")
|
| 123 |
+
return 0.0
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
# ---------------------------------------------------------------------------
|
| 127 |
+
# Main execution
|
| 128 |
+
# ---------------------------------------------------------------------------
|
| 129 |
+
def run_production_analysis(
|
| 130 |
+
segments: list[dict] | None = None,
|
| 131 |
+
location: Location | None = None,
|
| 132 |
+
) -> dict:
|
| 133 |
+
"""Iterate over roof segments, query PVWatts, and return results.
|
| 134 |
+
|
| 135 |
+
Parameters
|
| 136 |
+
----------
|
| 137 |
+
segments : list[dict] | None
|
| 138 |
+
Each dict must have keys: id, area, tilt, azimuth.
|
| 139 |
+
Falls back to the module-level ``roof_segments`` dummy data.
|
| 140 |
+
location : Location | None
|
| 141 |
+
Site coordinates. Falls back to ``SITE_LOCATION``.
|
| 142 |
+
|
| 143 |
+
Returns
|
| 144 |
+
-------
|
| 145 |
+
dict with keys:
|
| 146 |
+
"segments" β list of per-segment result dicts
|
| 147 |
+
"total_kwh" β total annual production (float)
|
| 148 |
+
"location" β the Location used
|
| 149 |
+
"""
|
| 150 |
+
segments = segments or roof_segments
|
| 151 |
+
location = location or SITE_LOCATION
|
| 152 |
+
total_building_production = 0.0
|
| 153 |
+
results: list[dict] = []
|
| 154 |
+
|
| 155 |
+
print(f"--- Analyzing Roof Segments for {location.name} ---")
|
| 156 |
+
print(f" Site: ({location.latitude}, {location.longitude})")
|
| 157 |
+
print(f" Panel efficiency factor: {PANEL_EFFICIENCY}")
|
| 158 |
+
print()
|
| 159 |
+
|
| 160 |
+
for slab in segments:
|
| 161 |
+
capacity_kw = slab["area"] * PANEL_EFFICIENCY
|
| 162 |
+
annual_kwh = calculate_segment_production(
|
| 163 |
+
slab["area"],
|
| 164 |
+
slab["tilt"],
|
| 165 |
+
slab["azimuth"],
|
| 166 |
+
location,
|
| 167 |
+
)
|
| 168 |
+
total_building_production += annual_kwh
|
| 169 |
+
|
| 170 |
+
results.append({
|
| 171 |
+
"id": slab["id"],
|
| 172 |
+
"area": slab["area"],
|
| 173 |
+
"tilt": slab["tilt"],
|
| 174 |
+
"azimuth": slab["azimuth"],
|
| 175 |
+
"capacity_kw": capacity_kw,
|
| 176 |
+
"annual_kwh": annual_kwh,
|
| 177 |
+
})
|
| 178 |
+
|
| 179 |
+
print(
|
| 180 |
+
f" {slab['id']:>15s} | "
|
| 181 |
+
f"Area: {slab['area']:>7.1f} mΒ² | "
|
| 182 |
+
f"Tilt: {slab['tilt']:>5.1f}Β° | "
|
| 183 |
+
f"Azimuth: {slab['azimuth']:>5.1f}Β° | "
|
| 184 |
+
f"Capacity: {capacity_kw:>6.1f} kW | "
|
| 185 |
+
f"Yield: {annual_kwh:>10,.2f} kWh/yr"
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
# Respect rate limits
|
| 189 |
+
time.sleep(1)
|
| 190 |
+
|
| 191 |
+
print()
|
| 192 |
+
print("-" * 70)
|
| 193 |
+
print(f" TOTAL BUILDING PRODUCTION: {total_building_production:>12,.2f} kWh/yr")
|
| 194 |
+
print("-" * 70)
|
| 195 |
+
print()
|
| 196 |
+
print(" LEED hint: Score = Total Production / Total Consumption Γ 100")
|
| 197 |
+
|
| 198 |
+
return {
|
| 199 |
+
"segments": results,
|
| 200 |
+
"total_kwh": total_building_production,
|
| 201 |
+
"location": location,
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def main() -> None:
|
| 206 |
+
"""Run with built-in dummy data (backward-compatible entry point)."""
|
| 207 |
+
run_production_analysis()
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
if __name__ == "__main__":
|
| 211 |
+
main()
|
teams/lux-ai/Final pipeline/README.md
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Solar Pipeline β README
|
| 2 |
+
|
| 3 |
+
## What is this?
|
| 4 |
+
|
| 5 |
+
This tool answers **one question**:
|
| 6 |
+
|
| 7 |
+
> **"If I put solar panels on this building's roof, how much energy will they produce?"**
|
| 8 |
+
|
| 9 |
+
You give it a **building file** (`.ifc` format β the standard file that architects export from tools like Revit, ArchiCAD, or Vectorworks).
|
| 10 |
+
|
| 11 |
+
It gives you back a **solar score** β how much of the building's energy the roof solar panels could cover.
|
| 12 |
+
|
| 13 |
+
```
|
| 14 |
+
βββββββββββββββ ββββββββββββββββββ ββββββββββββββββ
|
| 15 |
+
β β β β β β
|
| 16 |
+
β .ifc file β βββββββΆ β analyze_ifc() β βββββββΆ β Solar Score β
|
| 17 |
+
β (building) β β (this tool) β β (% energy) β
|
| 18 |
+
β β β β β β
|
| 19 |
+
βββββββββββββββ ββββββββββββββββββ ββββββββββββββββ
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
**Score = 100%** means the roof produces as much energy as the building consumes.
|
| 23 |
+
That's **net-zero energy** β a key goal for green building certifications like **LEED**.
|
| 24 |
+
|
| 25 |
+
---
|
| 26 |
+
|
| 27 |
+
## Quick Start (for everyone)
|
| 28 |
+
|
| 29 |
+
### Step 1 β Install
|
| 30 |
+
|
| 31 |
+
Open a terminal (Command Prompt, PowerShell, or Mac Terminal) and type:
|
| 32 |
+
|
| 33 |
+
```bash
|
| 34 |
+
pip install ifcopenshell numpy requests tabulate
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
### Step 2 β Run
|
| 38 |
+
|
| 39 |
+
```bash
|
| 40 |
+
python -m solar_pipeline.analyze "path/to/your/building.ifc"
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
That's it. You'll see a report like this:
|
| 44 |
+
|
| 45 |
+
```
|
| 46 |
+
============================================================
|
| 47 |
+
SOLAR ANALYSIS β fzk_house
|
| 48 |
+
============================================================
|
| 49 |
+
|
| 50 |
+
FILE arc.ifc
|
| 51 |
+
LOCATION 49.100435, 8.436539
|
| 52 |
+
|
| 53 |
+
ββ Building Metadata ββββββββββββββββββββββββ
|
| 54 |
+
Window area.................. 23.2 mΒ²
|
| 55 |
+
Floor area................... 173.3 mΒ²
|
| 56 |
+
Roof area (property-set)..... 165.1 mΒ²
|
| 57 |
+
True north................... 310.0Β°
|
| 58 |
+
|
| 59 |
+
ββ Roof Segments (geometry) βββββββββββββββββ
|
| 60 |
+
Roof_Seg_01 Area: 82.6 mΒ² Tilt: 30.0Β° Azimuth: 310.0Β° β 11,105.3 kWh/yr
|
| 61 |
+
Roof_Seg_02 Area: 82.6 mΒ² Tilt: 30.0Β° Azimuth: 130.0Β° β 14,914.6 kWh/yr
|
| 62 |
+
|
| 63 |
+
ββ Solar Production ββββββββββββββββββββββββ
|
| 64 |
+
Total roof area ......... 165.1 mΒ²
|
| 65 |
+
System capacity ......... 33.0 kW
|
| 66 |
+
Annual production ....... 26,020.0 kWh/yr
|
| 67 |
+
|
| 68 |
+
ββ LEED Score ββββββββββββββββββββββββββββββ
|
| 69 |
+
Consumption estimate .... 26,001 kWh/yr
|
| 70 |
+
Renewable production .... 26,020 kWh/yr
|
| 71 |
+
Score ................... 100.1 %
|
| 72 |
+
|
| 73 |
+
β
Net-zero energy achieved!
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
### If the building file has no location
|
| 77 |
+
|
| 78 |
+
Some IFC files don't include coordinates. Just add them manually:
|
| 79 |
+
|
| 80 |
+
```bash
|
| 81 |
+
python -m solar_pipeline.analyze "building.ifc" --lat 48.14 --lon 11.58
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
---
|
| 85 |
+
|
| 86 |
+
## Quick Start (for developers / AI agents)
|
| 87 |
+
|
| 88 |
+
One import, one function, one result:
|
| 89 |
+
|
| 90 |
+
```python
|
| 91 |
+
from solar_pipeline.analyze import analyze_ifc
|
| 92 |
+
|
| 93 |
+
result = analyze_ifc("building.ifc")
|
| 94 |
+
|
| 95 |
+
if result["ok"]:
|
| 96 |
+
print(result["leed_score"]) # 100.1
|
| 97 |
+
print(result["total_production"]) # 26019.96 (kWh/yr)
|
| 98 |
+
print(result["segments"]) # [{id, area, tilt, azimuth, annual_kwh}, ...]
|
| 99 |
+
else:
|
| 100 |
+
print(result["error"]) # what went wrong
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
### Function signature
|
| 104 |
+
|
| 105 |
+
```python
|
| 106 |
+
analyze_ifc(
|
| 107 |
+
ifc_path, # path to .ifc file (required)
|
| 108 |
+
*,
|
| 109 |
+
lat=None, # override latitude
|
| 110 |
+
lon=None, # override longitude
|
| 111 |
+
name=None, # project name for the report
|
| 112 |
+
consumption_kwh_per_m2=None, # energy benchmark (default: 150)
|
| 113 |
+
call_api=True, # set False for offline mode
|
| 114 |
+
)
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
### What the result dict contains
|
| 118 |
+
|
| 119 |
+
| Key | Type | What it means |
|
| 120 |
+
|-----|------|---------------|
|
| 121 |
+
| `ok` | bool | `True` if everything worked |
|
| 122 |
+
| `error` | str or None | Error message if `ok` is False |
|
| 123 |
+
| `project_name` | str | Name of the project |
|
| 124 |
+
| `ifc_file` | str | File name |
|
| 125 |
+
| `window_area_m2` | float or None | Total window area in the building |
|
| 126 |
+
| `floor_area_m2` | float or None | Total floor area |
|
| 127 |
+
| `roof_area_m2` | float or None | Roof area from property sets (metadata) |
|
| 128 |
+
| `true_north_deg` | float or None | Building orientation (0Β° = facing north) |
|
| 129 |
+
| `latitude` | float | Site latitude |
|
| 130 |
+
| `longitude` | float | Site longitude |
|
| 131 |
+
| `segments` | list[dict] | Each roof face with its area, tilt, azimuth, and annual kWh |
|
| 132 |
+
| `total_roof_area_m2` | float | Total roof area from 3D geometry |
|
| 133 |
+
| `total_capacity_kw` | float | Solar panel capacity in kilowatts |
|
| 134 |
+
| `total_production` | float | Annual energy production in kWh/yr |
|
| 135 |
+
| `consumption` | float | Estimated annual consumption in kWh/yr |
|
| 136 |
+
| `leed_score` | float | Production Γ· Consumption Γ 100 (%) |
|
| 137 |
+
|
| 138 |
+
---
|
| 139 |
+
|
| 140 |
+
## How It Works β The DNA of the Tool
|
| 141 |
+
|
| 142 |
+
The pipeline has **5 steps**. Each step is handled by a separate module, but `analyze_ifc()` runs them all for you automatically.
|
| 143 |
+
|
| 144 |
+
```
|
| 145 |
+
STEP 1 STEP 2 STEP 3 STEP 4 STEP 5
|
| 146 |
+
ββββββββββ βββββββββββββ βββββββββββββ ββββββββββββ ββββββββββββ
|
| 147 |
+
β Open β β Read β β Analyse β β Query β β Calculateβ
|
| 148 |
+
β IFC βββββΆβ metadata βββββΆβ roof 3D βββββΆβ PVWatts βββββΆβ LEED β
|
| 149 |
+
β file β β from file β β geometry β β solar APIβ β score β
|
| 150 |
+
ββββββββββ βββββββββββββ βββββββββββββ ββββββββββββ ββββββββββββ
|
| 151 |
+
β β β β
|
| 152 |
+
window area roof segments kWh per score =
|
| 153 |
+
floor area (tilt, azimuth, segment production
|
| 154 |
+
roof area area each) Γ· consumption
|
| 155 |
+
lat, lon Γ 100
|
| 156 |
+
true north
|
| 157 |
+
```
|
| 158 |
+
|
| 159 |
+
### Step 1 β Open the IFC file
|
| 160 |
+
|
| 161 |
+
**IFC** (Industry Foundation Classes) is the universal file format for buildings. It stores the entire building as a database of objects β walls, windows, roofs, floors, etc. β with their geometry and properties.
|
| 162 |
+
|
| 163 |
+
We use the open-source library `ifcopenshell` to read it.
|
| 164 |
+
|
| 165 |
+
### Step 2 β Read building metadata
|
| 166 |
+
|
| 167 |
+
The tool reads **property sets** embedded in the IFC file:
|
| 168 |
+
|
| 169 |
+
| What | Where in the IFC file | Why we need it |
|
| 170 |
+
|------|----------------------|----------------|
|
| 171 |
+
| Window area | `IfcWindow` β quantity sets | Glazing ratio analysis |
|
| 172 |
+
| Floor area | `IfcSpace` or `IfcSlab[FLOOR]` β quantity sets | Estimate building energy consumption |
|
| 173 |
+
| Roof area | `IfcRoof` or `IfcSlab[ROOF]` β quantity sets | Cross-validate with 3D geometry |
|
| 174 |
+
| Latitude & Longitude | `IfcSite.RefLatitude / RefLongitude` | Tell PVWatts where the building is |
|
| 175 |
+
| True North | `IfcGeometricRepresentationContext.TrueNorth` | Correct roof azimuths to real compass |
|
| 176 |
+
|
| 177 |
+
**The alias system**: Different software (Revit, ArchiCAD, Vectorworks) stores these values under different names. For example, floor area might be called `NetFloorArea`, `GrossFloorArea`, `GSA BIM Area`, or `Netto-GrundflΓ€che` (German). The file `key_aliases.json` contains a priority-ordered list of all known names for each property, so the tool tries each one until it finds a match.
|
| 178 |
+
|
| 179 |
+
### Step 3 β Analyse roof 3D geometry
|
| 180 |
+
|
| 181 |
+
This is the key step that makes per-segment solar analysis possible:
|
| 182 |
+
|
| 183 |
+
1. **Find roof elements** β `IfcRoof` objects and `IfcSlab` objects marked as roof type
|
| 184 |
+
2. **Triangulate** β Convert each roof element into a mesh of triangles using world coordinates
|
| 185 |
+
3. **Compute normals** β Each triangle has a direction it faces (its "normal vector")
|
| 186 |
+
4. **Cluster** β Group triangles that face the same direction (within 15Β° tolerance)
|
| 187 |
+
5. **Compute properties** β For each cluster, calculate:
|
| 188 |
+
- **Area** β total mΒ² of that roof face
|
| 189 |
+
- **Tilt** β angle from horizontal (0Β° = flat, 90Β° = vertical wall)
|
| 190 |
+
- **Azimuth** β compass direction the face points (0Β° = north, 180Β° = south)
|
| 191 |
+
6. **True north correction** β Rotate all azimuths by the building's TrueNorth angle so they match real-world compass directions
|
| 192 |
+
|
| 193 |
+
*Why per-segment?* A south-facing roof face in Europe may produce **2.5Γ more energy** than a north-facing one. A single whole-roof average would be misleading.
|
| 194 |
+
|
| 195 |
+
### Step 4 β Query the PVWatts solar API
|
| 196 |
+
|
| 197 |
+
For each roof segment, we call the **NREL PVWatts v8** API (US National Renewable Energy Laboratory):
|
| 198 |
+
|
| 199 |
+
```
|
| 200 |
+
Inputs: lat, lon, roof area, tilt, azimuth
|
| 201 |
+
Output: estimated annual solar energy in kWh
|
| 202 |
+
```
|
| 203 |
+
|
| 204 |
+
The API uses real weather data, sun angles, and atmospheric conditions for the specific location. It accounts for panel losses (wiring, soiling, shading) at 14%.
|
| 205 |
+
|
| 206 |
+
### Step 5 β Calculate LEED score
|
| 207 |
+
|
| 208 |
+
```
|
| 209 |
+
Score = Total Production (kWh/yr) Γ· Total Consumption (kWh/yr) Γ 100
|
| 210 |
+
```
|
| 211 |
+
|
| 212 |
+
- **Production** = sum of all segment yields from PVWatts
|
| 213 |
+
- **Consumption** = floor area Γ 150 kWh/mΒ²/yr (ASHRAE office benchmark)
|
| 214 |
+
- If the IFC file has no floor area data, defaults to 50,000 kWh/yr
|
| 215 |
+
|
| 216 |
+
| Score | Meaning |
|
| 217 |
+
|-------|---------|
|
| 218 |
+
| β₯ 100% | Net-zero energy β roof produces everything the building needs |
|
| 219 |
+
| 50β99% | Strong renewable contribution |
|
| 220 |
+
| 10β49% | Moderate contribution |
|
| 221 |
+
| < 10% | Low contribution β different design or additional renewables needed |
|
| 222 |
+
|
| 223 |
+
---
|
| 224 |
+
|
| 225 |
+
## Configuration β `config.py`
|
| 226 |
+
|
| 227 |
+
All defaults can be changed in [`solar_pipeline/config.py`](solar_pipeline/config.py):
|
| 228 |
+
|
| 229 |
+
```python
|
| 230 |
+
# ββ NREL PVWatts v8 API ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 231 |
+
NREL_API_KEY = "0zwEIS1a..." # Free key from api.nrel.gov
|
| 232 |
+
PVWATTS_BASE_URL = "https://developer.nrel.gov/api/pvwatts/v8.json"
|
| 233 |
+
|
| 234 |
+
# ββ Solar panel assumptions ββββββββββββββββββββββββββββββββββββββββββ
|
| 235 |
+
PANEL_EFFICIENCY = 0.20 # 20% efficiency = 1 kW per 5 mΒ²
|
| 236 |
+
ARRAY_TYPE = 1 # 1 = fixed roof mount
|
| 237 |
+
MODULE_TYPE = 1 # 1 = premium (monocrystalline)
|
| 238 |
+
SYSTEM_LOSSES = 14 # 14% total system losses
|
| 239 |
+
|
| 240 |
+
# ββ Roof geometry ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 241 |
+
DEFAULT_ANGLE_TOLERANCE_DEG = 15.0 # Clustering tolerance (degrees)
|
| 242 |
+
MIN_SEGMENT_AREA_M2 = 1.0 # Ignore tiny fragments
|
| 243 |
+
|
| 244 |
+
# ββ Energy benchmarks ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 245 |
+
DEFAULT_CONSUMPTION_KWH_PER_M2 = 150 # ASHRAE office (kWh/mΒ²/yr)
|
| 246 |
+
FALLBACK_CONSUMPTION_KWH = 50_000 # When floor area is unknown
|
| 247 |
+
```
|
| 248 |
+
|
| 249 |
+
### What each setting means
|
| 250 |
+
|
| 251 |
+
| Setting | Default | What it controls |
|
| 252 |
+
|---------|---------|-----------------|
|
| 253 |
+
| `PANEL_EFFICIENCY` | 0.20 | How much of the roof area converts to electricity. 0.20 means 1 kW of solar capacity per 5 mΒ² of roof. Premium panels are ~20%, budget panels ~15%. |
|
| 254 |
+
| `SYSTEM_LOSSES` | 14% | Energy lost in wiring, inverters, soiling, snow, etc. Industry standard is 14%. |
|
| 255 |
+
| `DEFAULT_ANGLE_TOLERANCE_DEG` | 15Β° | When grouping roof triangles by direction, faces within 15Β° of each other are merged into one segment. Lower = more segments, higher = fewer. |
|
| 256 |
+
| `MIN_SEGMENT_AREA_M2` | 1.0 | Roof faces smaller than 1 mΒ² are ignored (geometric noise). |
|
| 257 |
+
| `DEFAULT_CONSUMPTION_KWH_PER_M2` | 150 | ASHRAE benchmark for a typical office building. Residential is ~100, hospitals ~300. Changes the LEED score denominator. |
|
| 258 |
+
| `NREL_API_KEY` | provided | Free API key from [developer.nrel.gov](https://developer.nrel.gov/signup/). Rate limit: 1 request/second. |
|
| 259 |
+
|
| 260 |
+
---
|
| 261 |
+
|
| 262 |
+
## File Structure
|
| 263 |
+
|
| 264 |
+
```
|
| 265 |
+
solar_pipeline/
|
| 266 |
+
β
|
| 267 |
+
βββ analyze.py β THE MAIN FILE β one function does everything
|
| 268 |
+
β analyze_ifc("building.ifc") β score
|
| 269 |
+
β
|
| 270 |
+
βββ ifc_metadata_extractor.py β Step 2: reads building properties from IFC
|
| 271 |
+
β (areas, location, orientation)
|
| 272 |
+
β
|
| 273 |
+
βββ ifc_roof_parser.py β Step 3: 3D roof geometry β segments
|
| 274 |
+
β (tilt, azimuth, area per face)
|
| 275 |
+
β
|
| 276 |
+
βββ solar_production_engine.py β Step 4: calls PVWatts API β kWh/yr
|
| 277 |
+
β
|
| 278 |
+
βββ run_solar_analysis.py β Advanced CLI with batch modes
|
| 279 |
+
β
|
| 280 |
+
βββ config.py β All configurable settings
|
| 281 |
+
β
|
| 282 |
+
βββ key_aliases.json β Maps property names across BIM software
|
| 283 |
+
β (Revit, ArchiCAD, Vectorworks, etc.)
|
| 284 |
+
β
|
| 285 |
+
βββ __init__.py β Package marker
|
| 286 |
+
βββ README.md β This file
|
| 287 |
+
```
|
| 288 |
+
|
| 289 |
+
---
|
| 290 |
+
|
| 291 |
+
## The Key Aliases System β Why It Exists
|
| 292 |
+
|
| 293 |
+
Different architecture software stores the same data under different names:
|
| 294 |
+
|
| 295 |
+
| Data | Revit calls it | ArchiCAD calls it | IFC4 standard |
|
| 296 |
+
|------|---------------|-------------------|---------------|
|
| 297 |
+
| Floor area | `GSA BIM Area` | `Netto-GrundflΓ€che` | `NetFloorArea` |
|
| 298 |
+
| Window area | `Area` in `BaseQuantities` | `FlΓ€che` in `ArchiCADQuantities` | `Area` in `Qto_WindowBaseQuantities` |
|
| 299 |
+
| Roof area | `GrossArea` in `BaseQuantities` | `OberflΓ€chenbereich` | `NetArea` in `Qto_RoofBaseQuantities` |
|
| 300 |
+
|
| 301 |
+
The file `key_aliases.json` contains a **priority-ordered list** for each property. The tool tries each name in order and uses the first match. This means it works with IFC files from **any** BIM software without manual configuration.
|
| 302 |
+
|
| 303 |
+
You can add new aliases by editing the JSON file β no code changes needed.
|
| 304 |
+
|
| 305 |
+
---
|
| 306 |
+
|
| 307 |
+
## Supported IFC Versions
|
| 308 |
+
|
| 309 |
+
| Version | Status | Example projects |
|
| 310 |
+
|---------|--------|-----------------|
|
| 311 |
+
| IFC2x3 | β
Fully supported | duplex, schependomlaan, hitos |
|
| 312 |
+
| IFC4 | β
Fully supported | fzk_house, digital_hub, fantasy_* |
|
| 313 |
+
| IFC4x3 | β
Supported | city_house_munich |
|
| 314 |
+
|
| 315 |
+
---
|
| 316 |
+
|
| 317 |
+
## Requirements
|
| 318 |
+
|
| 319 |
+
| Package | Version | Why |
|
| 320 |
+
|---------|---------|-----|
|
| 321 |
+
| Python | β₯ 3.10 | Type hints, match statements |
|
| 322 |
+
| ifcopenshell | β₯ 0.7.0 | Open and parse IFC files |
|
| 323 |
+
| numpy | β₯ 1.21 | 3D geometry math |
|
| 324 |
+
| requests | β₯ 2.28 | Call the PVWatts API |
|
| 325 |
+
| tabulate | β₯ 0.9 | Pretty-print tables (optional) |
|
| 326 |
+
|
| 327 |
+
Install all at once:
|
| 328 |
+
```bash
|
| 329 |
+
pip install ifcopenshell numpy requests tabulate
|
| 330 |
+
```
|
| 331 |
+
|
| 332 |
+
---
|
| 333 |
+
|
| 334 |
+
## FAQ
|
| 335 |
+
|
| 336 |
+
**Q: Do I need internet?**
|
| 337 |
+
A: Yes, for the solar production numbers (PVWatts API). You can run offline with `call_api=False` to get just the roof geometry without kWh values.
|
| 338 |
+
|
| 339 |
+
**Q: What if my IFC file has no roof?**
|
| 340 |
+
A: The tool returns `ok=False` with error `"No roof segments found"`.
|
| 341 |
+
|
| 342 |
+
**Q: What if there's no location in the file?**
|
| 343 |
+
A: Pass `--lat` and `--lon` on the command line, or `lat=` and `lon=` in Python.
|
| 344 |
+
|
| 345 |
+
**Q: Can I change the panel type?**
|
| 346 |
+
A: Yes β edit `PANEL_EFFICIENCY`, `MODULE_TYPE`, and `SYSTEM_LOSSES` in `config.py`.
|
| 347 |
+
|
| 348 |
+
**Q: Is the score accurate?**
|
| 349 |
+
A: It's an **estimate**, not a guarantee. PVWatts uses real weather data and is widely trusted in the industry, but the actual production depends on shading, panel brand, inverter quality, and maintenance. This tool gives you a solid ballpark for early-stage design decisions and LEED pre-assessment.
|
| 350 |
+
|
| 351 |
+
**Q: What's LEED?**
|
| 352 |
+
A: Leadership in Energy and Environmental Design β the world's most widely used green building rating system. One of its credits rewards buildings that generate renewable energy on-site. The score this tool gives approximates that credit.
|
teams/lux-ai/Final pipeline/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
solar_pipeline β Unified IFC metadata extraction + solar production analysis.
|
| 3 |
+
|
| 4 |
+
Merges:
|
| 5 |
+
- IFC property-set/quantity-set metadata scanner (window, floor, roof area,
|
| 6 |
+
orientation, lat/lon) with multi-exporter alias fallback
|
| 7 |
+
- Geometry-based roof segment parser (per-face tilt & azimuth via 3D mesh)
|
| 8 |
+
- NREL PVWatts v8 solar production engine
|
| 9 |
+
|
| 10 |
+
Supports single-file solar analysis and batch scanning of project directories.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from solar_pipeline.config import __version__
|
| 14 |
+
|
| 15 |
+
__all__ = ["__version__"]
|
teams/lux-ai/Final pipeline/analyze.py
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
analyze.py β One function. One IFC file in. Full solar score out.
|
| 3 |
+
|
| 4 |
+
from solar_pipeline.analyze import analyze_ifc
|
| 5 |
+
result = analyze_ifc("path/to/building.ifc")
|
| 6 |
+
print(result["leed_score"]) # e.g. 100.1
|
| 7 |
+
print(result["total_production"]) # e.g. 26019.96 (kWh/yr)
|
| 8 |
+
|
| 9 |
+
Also runnable from the command line:
|
| 10 |
+
python -m solar_pipeline.analyze "path/to/building.ifc"
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
import logging
|
| 16 |
+
import sys
|
| 17 |
+
from pathlib import Path
|
| 18 |
+
|
| 19 |
+
log = logging.getLogger(__name__)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def analyze_ifc(
|
| 23 |
+
ifc_path: str | Path,
|
| 24 |
+
*,
|
| 25 |
+
lat: float | None = None,
|
| 26 |
+
lon: float | None = None,
|
| 27 |
+
name: str | None = None,
|
| 28 |
+
consumption_kwh_per_m2: float | None = None,
|
| 29 |
+
call_api: bool = True,
|
| 30 |
+
) -> dict:
|
| 31 |
+
"""
|
| 32 |
+
Analyse one IFC building file and return a full solar production report.
|
| 33 |
+
|
| 34 |
+
ββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββ
|
| 35 |
+
β IFC file ββββββΆβ This function ββββββΆβ Result dict β
|
| 36 |
+
ββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββ
|
| 37 |
+
|
| 38 |
+
What happens inside
|
| 39 |
+
-------------------
|
| 40 |
+
1. Opens the IFC file
|
| 41 |
+
2. Reads building metadata (areas, location, orientation)
|
| 42 |
+
3. Extracts 3-D roof geometry and splits it into segments
|
| 43 |
+
4. Queries the NREL PVWatts v8 API for each segment's solar yield
|
| 44 |
+
5. Calculates a LEED-style renewable-energy score
|
| 45 |
+
|
| 46 |
+
Parameters
|
| 47 |
+
----------
|
| 48 |
+
ifc_path : str or Path
|
| 49 |
+
Path to an .ifc file (IFC2x3, IFC4, or IFC4x3).
|
| 50 |
+
|
| 51 |
+
lat, lon : float, optional
|
| 52 |
+
Override latitude / longitude if the IFC file has no IfcSite
|
| 53 |
+
coordinates. When omitted the tool reads them automatically.
|
| 54 |
+
|
| 55 |
+
name : str, optional
|
| 56 |
+
Project name shown in the report. Defaults to the parent folder
|
| 57 |
+
name of the IFC file.
|
| 58 |
+
|
| 59 |
+
consumption_kwh_per_m2 : float, optional
|
| 60 |
+
Building energy consumption benchmark in kWh/mΒ²/yr.
|
| 61 |
+
Default = 150 (ASHRAE typical office).
|
| 62 |
+
The tool multiplies this by the floor area to estimate annual
|
| 63 |
+
consumption, which is the denominator in the LEED score.
|
| 64 |
+
|
| 65 |
+
call_api : bool, default True
|
| 66 |
+
Set to False to skip the PVWatts API call (useful for offline
|
| 67 |
+
testing). When False the per-segment kWh values are all 0.
|
| 68 |
+
|
| 69 |
+
Returns
|
| 70 |
+
-------
|
| 71 |
+
dict with these keys:
|
| 72 |
+
|
| 73 |
+
ok bool β True if the analysis succeeded
|
| 74 |
+
error str β error message (only if ok=False)
|
| 75 |
+
|
| 76 |
+
# ββ Building metadata ββββββββββββββββββββββββββ
|
| 77 |
+
project_name str β project / folder name
|
| 78 |
+
ifc_file str β file name
|
| 79 |
+
window_area_m2 float|None
|
| 80 |
+
floor_area_m2 float|None
|
| 81 |
+
roof_area_m2 float|None (from property sets)
|
| 82 |
+
true_north_deg float|None (compass bearing)
|
| 83 |
+
latitude float|None
|
| 84 |
+
longitude float|None
|
| 85 |
+
|
| 86 |
+
# ββ Roof geometry ββββββββββββββββββββββββββββββ
|
| 87 |
+
segments list[dict] β per roof-face data
|
| 88 |
+
Each dict: id, area, tilt, azimuth, capacity_kw, annual_kwh
|
| 89 |
+
|
| 90 |
+
# ββ Solar production βββββββββββββββββββββββββββ
|
| 91 |
+
total_roof_area_m2 float β from geometry (sum of segments)
|
| 92 |
+
total_capacity_kw float
|
| 93 |
+
total_production float β kWh/yr
|
| 94 |
+
consumption float β estimated kWh/yr
|
| 95 |
+
leed_score float β production / consumption Γ 100
|
| 96 |
+
|
| 97 |
+
Example
|
| 98 |
+
-------
|
| 99 |
+
>>> result = analyze_ifc("Sample projects/projects/fzk_house/arc.ifc")
|
| 100 |
+
>>> print(f"Score: {result['leed_score']:.1f}%")
|
| 101 |
+
Score: 100.1%
|
| 102 |
+
"""
|
| 103 |
+
|
| 104 |
+
# ββ Lazy imports (so the module is light to load) βββββββββββββββββββββ
|
| 105 |
+
from solar_pipeline.config import (
|
| 106 |
+
DEFAULT_CONSUMPTION_KWH_PER_M2,
|
| 107 |
+
FALLBACK_CONSUMPTION_KWH,
|
| 108 |
+
PANEL_EFFICIENCY,
|
| 109 |
+
)
|
| 110 |
+
from solar_pipeline.ifc_metadata_extractor import (
|
| 111 |
+
Location,
|
| 112 |
+
extract_all,
|
| 113 |
+
extract_location,
|
| 114 |
+
)
|
| 115 |
+
from solar_pipeline.ifc_roof_parser import parse_roof_segments
|
| 116 |
+
from solar_pipeline.solar_production_engine import run_production_analysis
|
| 117 |
+
|
| 118 |
+
ifc_path = Path(ifc_path)
|
| 119 |
+
project = name or ifc_path.parent.name
|
| 120 |
+
|
| 121 |
+
# ββ 1. Validate input βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 122 |
+
if not ifc_path.is_file():
|
| 123 |
+
return _error(f"File not found: {ifc_path}")
|
| 124 |
+
|
| 125 |
+
# ββ 2. Metadata extraction ββββββββββββββββββββββββββββββββββββββββββββ
|
| 126 |
+
try:
|
| 127 |
+
metadata = extract_all(ifc_path)
|
| 128 |
+
except Exception as exc:
|
| 129 |
+
return _error(f"Metadata extraction failed: {exc}")
|
| 130 |
+
|
| 131 |
+
# ββ 3. Resolve location βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 132 |
+
if lat is not None and lon is not None:
|
| 133 |
+
location = Location(latitude=lat, longitude=lon, name=project)
|
| 134 |
+
elif metadata.get("latitude") is not None and metadata.get("longitude") is not None:
|
| 135 |
+
location = Location(
|
| 136 |
+
latitude=metadata["latitude"],
|
| 137 |
+
longitude=metadata["longitude"],
|
| 138 |
+
name=project,
|
| 139 |
+
)
|
| 140 |
+
else:
|
| 141 |
+
return _error(
|
| 142 |
+
"No location found. The IFC file has no IfcSite coordinates. "
|
| 143 |
+
"Pass lat= and lon= manually."
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
# ββ 4. Roof geometry βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 147 |
+
try:
|
| 148 |
+
segments = parse_roof_segments(ifc_path)
|
| 149 |
+
except Exception as exc:
|
| 150 |
+
return _error(f"Roof geometry parsing failed: {exc}")
|
| 151 |
+
|
| 152 |
+
if not segments:
|
| 153 |
+
return _error("No roof segments found in this IFC file.")
|
| 154 |
+
|
| 155 |
+
# ββ 5. Solar production (PVWatts API) βββββββββββββββββββββββββββββββββ
|
| 156 |
+
if call_api:
|
| 157 |
+
try:
|
| 158 |
+
prod = run_production_analysis(
|
| 159 |
+
segments, location, verbose=False,
|
| 160 |
+
)
|
| 161 |
+
except Exception as exc:
|
| 162 |
+
return _error(f"PVWatts API failed: {exc}")
|
| 163 |
+
segment_results = prod["segments"]
|
| 164 |
+
total_kwh = prod["total_kwh"]
|
| 165 |
+
else:
|
| 166 |
+
# Offline mode β geometry only, no kWh
|
| 167 |
+
segment_results = []
|
| 168 |
+
for seg in segments:
|
| 169 |
+
segment_results.append({
|
| 170 |
+
**seg,
|
| 171 |
+
"capacity_kw": round(seg["area"] * PANEL_EFFICIENCY, 2),
|
| 172 |
+
"annual_kwh": 0.0,
|
| 173 |
+
})
|
| 174 |
+
total_kwh = 0.0
|
| 175 |
+
|
| 176 |
+
# ββ 6. LEED score βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 177 |
+
bench = consumption_kwh_per_m2 or DEFAULT_CONSUMPTION_KWH_PER_M2
|
| 178 |
+
floor = metadata.get("floor_area_m2")
|
| 179 |
+
if floor and floor > 0:
|
| 180 |
+
consumption = floor * bench
|
| 181 |
+
else:
|
| 182 |
+
consumption = FALLBACK_CONSUMPTION_KWH
|
| 183 |
+
|
| 184 |
+
leed_score = (total_kwh / consumption * 100) if consumption > 0 else 0.0
|
| 185 |
+
|
| 186 |
+
# ββ Build result ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 187 |
+
total_roof = sum(s["area"] for s in segment_results)
|
| 188 |
+
total_cap = sum(s["capacity_kw"] for s in segment_results)
|
| 189 |
+
|
| 190 |
+
return {
|
| 191 |
+
"ok": True,
|
| 192 |
+
"error": None,
|
| 193 |
+
|
| 194 |
+
# Metadata
|
| 195 |
+
"project_name": project,
|
| 196 |
+
"ifc_file": ifc_path.name,
|
| 197 |
+
"window_area_m2": metadata.get("window_area_m2"),
|
| 198 |
+
"floor_area_m2": metadata.get("floor_area_m2"),
|
| 199 |
+
"roof_area_m2": metadata.get("roof_area_m2"),
|
| 200 |
+
"true_north_deg": metadata.get("true_north_angle_deg"),
|
| 201 |
+
"latitude": location.latitude,
|
| 202 |
+
"longitude": location.longitude,
|
| 203 |
+
|
| 204 |
+
# Segments
|
| 205 |
+
"segments": segment_results,
|
| 206 |
+
|
| 207 |
+
# Totals
|
| 208 |
+
"total_roof_area_m2": round(total_roof, 2),
|
| 209 |
+
"total_capacity_kw": round(total_cap, 2),
|
| 210 |
+
"total_production": round(total_kwh, 2),
|
| 211 |
+
"consumption": round(consumption, 2),
|
| 212 |
+
"leed_score": round(leed_score, 1),
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
# ββ Helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 217 |
+
|
| 218 |
+
def _error(msg: str) -> dict:
|
| 219 |
+
"""Return a minimal error result."""
|
| 220 |
+
log.error(msg)
|
| 221 |
+
return {"ok": False, "error": msg}
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def print_report(result: dict) -> None:
|
| 225 |
+
"""Pretty-print an analysis result to the terminal."""
|
| 226 |
+
if not result.get("ok"):
|
| 227 |
+
print(f"\n ERROR: {result.get('error')}\n")
|
| 228 |
+
return
|
| 229 |
+
|
| 230 |
+
print()
|
| 231 |
+
print("=" * 60)
|
| 232 |
+
print(f" SOLAR ANALYSIS β {result['project_name']}")
|
| 233 |
+
print("=" * 60)
|
| 234 |
+
|
| 235 |
+
print(f"\n FILE {result['ifc_file']}")
|
| 236 |
+
print(f" LOCATION {result['latitude']}, {result['longitude']}")
|
| 237 |
+
|
| 238 |
+
print(f"\n ββ Building Metadata ββββββββββββββββββββββββ")
|
| 239 |
+
for key, label in [
|
| 240 |
+
("window_area_m2", "Window area"),
|
| 241 |
+
("floor_area_m2", "Floor area"),
|
| 242 |
+
("roof_area_m2", "Roof area (property-set)"),
|
| 243 |
+
("true_north_deg", "True north"),
|
| 244 |
+
]:
|
| 245 |
+
val = result.get(key)
|
| 246 |
+
unit = "Β°" if "north" in key else " mΒ²"
|
| 247 |
+
print(f" {label:.<30s} {f'{val:,.1f}{unit}' if val is not None else 'N/A'}")
|
| 248 |
+
|
| 249 |
+
print(f"\n ββ Roof Segments (geometry) βββββββββββββββββ")
|
| 250 |
+
for s in result["segments"]:
|
| 251 |
+
print(f" {s['id']:15s} "
|
| 252 |
+
f"Area: {s['area']:>7.1f} mΒ² "
|
| 253 |
+
f"Tilt: {s['tilt']:>5.1f}Β° "
|
| 254 |
+
f"Azimuth: {s['azimuth']:>5.1f}Β° "
|
| 255 |
+
f"β {s['annual_kwh']:>10,.1f} kWh/yr")
|
| 256 |
+
|
| 257 |
+
print(f"\n ββ Solar Production ββββββββββββββββββββββββ")
|
| 258 |
+
print(f" Total roof area ......... {result['total_roof_area_m2']:>10,.1f} mΒ²")
|
| 259 |
+
print(f" System capacity ......... {result['total_capacity_kw']:>10,.1f} kW")
|
| 260 |
+
print(f" Annual production ....... {result['total_production']:>10,.1f} kWh/yr")
|
| 261 |
+
|
| 262 |
+
print(f"\n ββ LEED Score ββββββββββββββββββββββββββββββ")
|
| 263 |
+
print(f" Consumption estimate .... {result['consumption']:>10,.0f} kWh/yr")
|
| 264 |
+
print(f" Renewable production .... {result['total_production']:>10,.0f} kWh/yr")
|
| 265 |
+
print(f" Score ................... {result['leed_score']:>10.1f} %")
|
| 266 |
+
|
| 267 |
+
if result["leed_score"] >= 100:
|
| 268 |
+
print(f"\n β
Net-zero energy achieved!")
|
| 269 |
+
elif result["leed_score"] >= 50:
|
| 270 |
+
print(f"\n β Over 50% renewable coverage β strong LEED contribution")
|
| 271 |
+
elif result["leed_score"] >= 10:
|
| 272 |
+
print(f"\n β Moderate renewable contribution")
|
| 273 |
+
else:
|
| 274 |
+
print(f"\n Β· Low renewable contribution β consider design changes")
|
| 275 |
+
print()
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
# ββ CLI entry point βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 279 |
+
|
| 280 |
+
def main() -> None:
|
| 281 |
+
"""Minimal CLI: python -m solar_pipeline.analyze building.ifc"""
|
| 282 |
+
logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s")
|
| 283 |
+
|
| 284 |
+
if len(sys.argv) < 2:
|
| 285 |
+
print("Usage: python -m solar_pipeline.analyze <file.ifc> [--lat N] [--lon N]")
|
| 286 |
+
sys.exit(1)
|
| 287 |
+
|
| 288 |
+
# Simple arg parsing (no argparse needed for this one-shot entry)
|
| 289 |
+
ifc_path = sys.argv[1]
|
| 290 |
+
lat = lon = None
|
| 291 |
+
args = sys.argv[2:]
|
| 292 |
+
i = 0
|
| 293 |
+
while i < len(args):
|
| 294 |
+
if args[i] == "--lat" and i + 1 < len(args):
|
| 295 |
+
lat = float(args[i + 1]); i += 2
|
| 296 |
+
elif args[i] == "--lon" and i + 1 < len(args):
|
| 297 |
+
lon = float(args[i + 1]); i += 2
|
| 298 |
+
else:
|
| 299 |
+
i += 1
|
| 300 |
+
|
| 301 |
+
result = analyze_ifc(ifc_path, lat=lat, lon=lon)
|
| 302 |
+
print_report(result)
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
if __name__ == "__main__":
|
| 306 |
+
main()
|
teams/lux-ai/Final pipeline/config.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
config.py β Shared constants and defaults for the solar pipeline.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
__version__ = "1.0.0"
|
| 8 |
+
|
| 9 |
+
# ββ Paths βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 10 |
+
|
| 11 |
+
PACKAGE_DIR = Path(__file__).resolve().parent
|
| 12 |
+
REPO_ROOT = PACKAGE_DIR.parent
|
| 13 |
+
KEY_ALIASES_PATH = PACKAGE_DIR / "key_aliases.json"
|
| 14 |
+
DEFAULT_SAMPLE_ROOT = REPO_ROOT / "Sample projects" / "projects"
|
| 15 |
+
|
| 16 |
+
# ββ NREL PVWatts v8 API ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 17 |
+
|
| 18 |
+
NREL_API_KEY = "0zwEIS1adJrx658O3gjQYfI7AprKLjQf4KP420o9"
|
| 19 |
+
PVWATTS_BASE_URL = "https://developer.nrel.gov/api/pvwatts/v8.json"
|
| 20 |
+
|
| 21 |
+
# ββ Solar panel assumptions βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 22 |
+
|
| 23 |
+
PANEL_EFFICIENCY = 0.20 # 1 kW per 5 mΒ² of panel
|
| 24 |
+
ARRAY_TYPE = 1 # Fixed β roof mount
|
| 25 |
+
MODULE_TYPE = 1 # Premium (monocrystalline)
|
| 26 |
+
SYSTEM_LOSSES = 14 # % (wiring, soiling, mismatch, etc.)
|
| 27 |
+
|
| 28 |
+
# ββ Roof geometry clustering βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 29 |
+
|
| 30 |
+
DEFAULT_ANGLE_TOLERANCE_DEG = 15.0 # max normal deviation within a cluster
|
| 31 |
+
MIN_SEGMENT_AREA_M2 = 1.0 # ignore clusters smaller than this
|
| 32 |
+
|
| 33 |
+
# ββ LEED / energy benchmarks βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 34 |
+
|
| 35 |
+
DEFAULT_CONSUMPTION_KWH_PER_M2 = 150 # ASHRAE typical office (kWh/mΒ²/yr)
|
| 36 |
+
FALLBACK_CONSUMPTION_KWH = 50_000 # used when floor area is unknown
|
| 37 |
+
|
| 38 |
+
# ββ CSV output columns βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 39 |
+
|
| 40 |
+
CSV_COLUMNS = [
|
| 41 |
+
"project_name",
|
| 42 |
+
"ifc_file",
|
| 43 |
+
"window_area_m2",
|
| 44 |
+
"floor_area_m2",
|
| 45 |
+
"roof_area_m2",
|
| 46 |
+
"true_north_angle_deg",
|
| 47 |
+
"latitude",
|
| 48 |
+
"longitude",
|
| 49 |
+
"error",
|
| 50 |
+
]
|
teams/lux-ai/Final pipeline/ifc_metadata_extractor.py
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
ifc_metadata_extractor.py β Alias-driven IFC property-set / quantity-set
|
| 3 |
+
metadata extraction.
|
| 4 |
+
|
| 5 |
+
Reads key_aliases.json to resolve exporter-specific naming differences
|
| 6 |
+
(Archicad, Revit, IFC2x3, IFC4, GSA, BOMA, β¦) and extracts:
|
| 7 |
+
window_area, floor_area, roof_area, true_north_angle, latitude, longitude
|
| 8 |
+
|
| 9 |
+
Also exposes helpers consumed by the roof-geometry parser and the solar
|
| 10 |
+
production engine (extract_location, extract_true_north).
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
import csv
|
| 16 |
+
import json
|
| 17 |
+
import logging
|
| 18 |
+
import math
|
| 19 |
+
from dataclasses import dataclass
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
from typing import Optional
|
| 22 |
+
|
| 23 |
+
import ifcopenshell
|
| 24 |
+
import ifcopenshell.util.unit as ifc_unit_util
|
| 25 |
+
|
| 26 |
+
from solar_pipeline.config import CSV_COLUMNS, KEY_ALIASES_PATH
|
| 27 |
+
|
| 28 |
+
# ββ Logging βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 29 |
+
|
| 30 |
+
log = logging.getLogger(__name__)
|
| 31 |
+
|
| 32 |
+
# ββ Load alias map once at module import ββββββββββββββββββββββββββββββββββββββ
|
| 33 |
+
|
| 34 |
+
def _load_aliases(path: Path | None = None) -> dict:
|
| 35 |
+
p = path or KEY_ALIASES_PATH
|
| 36 |
+
try:
|
| 37 |
+
with open(p, encoding="utf-8") as f:
|
| 38 |
+
return json.load(f)
|
| 39 |
+
except FileNotFoundError:
|
| 40 |
+
log.warning("key_aliases.json not found at %s β using empty alias map", p)
|
| 41 |
+
return {}
|
| 42 |
+
|
| 43 |
+
ALIASES: dict = _load_aliases()
|
| 44 |
+
|
| 45 |
+
# ββ Location dataclass (re-used by solar engine) βββββββββββββββββββββββββββββ
|
| 46 |
+
|
| 47 |
+
@dataclass
|
| 48 |
+
class Location:
|
| 49 |
+
"""Site coordinates extracted from IfcSite."""
|
| 50 |
+
latitude: float
|
| 51 |
+
longitude: float
|
| 52 |
+
name: str = ""
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
# ββ Unit helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 56 |
+
|
| 57 |
+
def get_length_scale(model: ifcopenshell.file) -> float:
|
| 58 |
+
"""Scale factor: model length units β metres."""
|
| 59 |
+
try:
|
| 60 |
+
return ifc_unit_util.calculate_unit_scale(model, "LENGTHUNIT")
|
| 61 |
+
except Exception:
|
| 62 |
+
return 1.0
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def get_area_scale(model: ifcopenshell.file) -> float:
|
| 66 |
+
"""Scale factor: model area units β mΒ²."""
|
| 67 |
+
try:
|
| 68 |
+
return ifc_unit_util.calculate_unit_scale(model, "AREAMEASURE")
|
| 69 |
+
except Exception:
|
| 70 |
+
return 1.0
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
# ββ Low-level quantity / property getters βββββββββββββββββββββββββββββββββββββ
|
| 74 |
+
|
| 75 |
+
def get_quantity(element, qset_name: str, qty_name: str) -> Optional[float]:
|
| 76 |
+
"""
|
| 77 |
+
Walk IsDefinedBy β IfcRelDefinesByProperties β IfcElementQuantity
|
| 78 |
+
and return the numeric value of *qty_name* inside *qset_name*.
|
| 79 |
+
"""
|
| 80 |
+
for rel in getattr(element, "IsDefinedBy", []):
|
| 81 |
+
if not rel.is_a("IfcRelDefinesByProperties"):
|
| 82 |
+
continue
|
| 83 |
+
qset = rel.RelatingPropertyDefinition
|
| 84 |
+
if not qset.is_a("IfcElementQuantity"):
|
| 85 |
+
continue
|
| 86 |
+
if qset.Name != qset_name:
|
| 87 |
+
continue
|
| 88 |
+
for qty in qset.Quantities:
|
| 89 |
+
if qty.Name != qty_name:
|
| 90 |
+
continue
|
| 91 |
+
for attr in ("AreaValue", "LengthValue", "VolumeValue", "CountValue"):
|
| 92 |
+
v = getattr(qty, attr, None)
|
| 93 |
+
if v is not None:
|
| 94 |
+
return float(v)
|
| 95 |
+
return None
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def get_property(element, pset_name: str, prop_name: str) -> Optional[float]:
|
| 99 |
+
"""
|
| 100 |
+
Walk IsDefinedBy β IfcRelDefinesByProperties β IfcPropertySet
|
| 101 |
+
and return the nominal value of *prop_name* inside *pset_name*.
|
| 102 |
+
"""
|
| 103 |
+
for rel in getattr(element, "IsDefinedBy", []):
|
| 104 |
+
if not rel.is_a("IfcRelDefinesByProperties"):
|
| 105 |
+
continue
|
| 106 |
+
pset = rel.RelatingPropertyDefinition
|
| 107 |
+
if not pset.is_a("IfcPropertySet"):
|
| 108 |
+
continue
|
| 109 |
+
if pset.Name != pset_name:
|
| 110 |
+
continue
|
| 111 |
+
for prop in pset.HasProperties:
|
| 112 |
+
if prop.Name != prop_name:
|
| 113 |
+
continue
|
| 114 |
+
nv = getattr(prop, "NominalValue", None)
|
| 115 |
+
if nv is not None:
|
| 116 |
+
try:
|
| 117 |
+
return float(nv.wrappedValue)
|
| 118 |
+
except (TypeError, ValueError, AttributeError):
|
| 119 |
+
return None
|
| 120 |
+
return None
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def get_quantity_multi(
|
| 124 |
+
element, qset_names: list[str], qty_name: str
|
| 125 |
+
) -> Optional[float]:
|
| 126 |
+
"""Try multiple quantity-set names in priority order; return first match."""
|
| 127 |
+
for qset_name in qset_names:
|
| 128 |
+
val = get_quantity(element, qset_name, qty_name)
|
| 129 |
+
if val is not None:
|
| 130 |
+
return val
|
| 131 |
+
return None
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
# ββ Alias-driven generic extractor ββββββββββββββββββββββββββββοΏ½οΏ½ββββββββββββββ
|
| 135 |
+
|
| 136 |
+
def _extract_by_alias(
|
| 137 |
+
model: ifcopenshell.file,
|
| 138 |
+
canonical_key: str,
|
| 139 |
+
area_scale: float,
|
| 140 |
+
length_scale: float,
|
| 141 |
+
) -> Optional[float]:
|
| 142 |
+
"""
|
| 143 |
+
Use the alias chain in key_aliases.json to extract a value.
|
| 144 |
+
Iterates strategies in priority order; returns the first that yields data.
|
| 145 |
+
"""
|
| 146 |
+
strategies = ALIASES.get(canonical_key, [])
|
| 147 |
+
if not strategies:
|
| 148 |
+
return None
|
| 149 |
+
|
| 150 |
+
for strat in strategies:
|
| 151 |
+
entity_type = strat.get("entity")
|
| 152 |
+
source = strat.get("source") # "qset", "pset", or "attr"
|
| 153 |
+
|
| 154 |
+
# ββ Attribute-based strategies ββββββββββββββββββββββββββββββββββββ
|
| 155 |
+
if source == "attr":
|
| 156 |
+
if strat.get("op") == "multiply":
|
| 157 |
+
# e.g. OverallHeight Γ OverallWidth for windows
|
| 158 |
+
elements = model.by_type(entity_type)
|
| 159 |
+
total = 0.0
|
| 160 |
+
found = False
|
| 161 |
+
for elem in elements:
|
| 162 |
+
vals = []
|
| 163 |
+
for k in strat["keys"]:
|
| 164 |
+
v = getattr(elem, k, None)
|
| 165 |
+
if v is not None:
|
| 166 |
+
vals.append(float(v))
|
| 167 |
+
if len(vals) == len(strat["keys"]):
|
| 168 |
+
product = 1.0
|
| 169 |
+
for v in vals:
|
| 170 |
+
product *= v
|
| 171 |
+
total += product * (length_scale ** len(strat["keys"]))
|
| 172 |
+
found = True
|
| 173 |
+
if found:
|
| 174 |
+
return round(total, 4)
|
| 175 |
+
# Other attr strategies (TrueNorth, RefLatitude) are handled
|
| 176 |
+
# by dedicated functions, not this generic extractor.
|
| 177 |
+
continue
|
| 178 |
+
|
| 179 |
+
# ββ Quantity-set or property-set strategies βββββββββββββββββββββββ
|
| 180 |
+
predefined_type = strat.get("predefined_type")
|
| 181 |
+
set_name = strat["set_name"]
|
| 182 |
+
key = strat["key"]
|
| 183 |
+
|
| 184 |
+
elements = model.by_type(entity_type)
|
| 185 |
+
total = 0.0
|
| 186 |
+
found = False
|
| 187 |
+
for elem in elements:
|
| 188 |
+
# Filter by PredefinedType if required
|
| 189 |
+
if predefined_type:
|
| 190 |
+
pt = getattr(elem, "PredefinedType", None)
|
| 191 |
+
if pt != predefined_type:
|
| 192 |
+
continue
|
| 193 |
+
|
| 194 |
+
if source == "qset":
|
| 195 |
+
val = get_quantity(elem, set_name, key)
|
| 196 |
+
elif source == "pset":
|
| 197 |
+
val = get_property(elem, set_name, key)
|
| 198 |
+
else:
|
| 199 |
+
continue
|
| 200 |
+
|
| 201 |
+
if val is not None:
|
| 202 |
+
total += val * area_scale
|
| 203 |
+
found = True
|
| 204 |
+
|
| 205 |
+
if found:
|
| 206 |
+
return round(total, 4)
|
| 207 |
+
|
| 208 |
+
return None
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
# ββ Specific extractors ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 212 |
+
|
| 213 |
+
def extract_window_area(model: ifcopenshell.file) -> Optional[float]:
|
| 214 |
+
"""Total window area in mΒ², using alias fallback chain."""
|
| 215 |
+
return _extract_by_alias(model, "window_area", get_area_scale(model), get_length_scale(model))
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def extract_floor_area(model: ifcopenshell.file) -> Optional[float]:
|
| 219 |
+
"""Total floor area in mΒ², using alias fallback chain."""
|
| 220 |
+
return _extract_by_alias(model, "floor_area", get_area_scale(model), get_length_scale(model))
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def extract_roof_area(model: ifcopenshell.file) -> Optional[float]:
|
| 224 |
+
"""Total roof area (metadata) in mΒ², using alias fallback chain."""
|
| 225 |
+
return _extract_by_alias(model, "roof_area", get_area_scale(model), get_length_scale(model))
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def decode_compound_angle(compound) -> Optional[float]:
|
| 229 |
+
"""Convert IfcCompoundPlaneAngleMeasure [deg, min, sec, Β΅sec] β decimal degrees."""
|
| 230 |
+
if compound is None:
|
| 231 |
+
return None
|
| 232 |
+
parts = list(compound)
|
| 233 |
+
if not parts:
|
| 234 |
+
return None
|
| 235 |
+
deg = int(parts[0])
|
| 236 |
+
minutes = int(parts[1]) if len(parts) > 1 else 0
|
| 237 |
+
secs = int(parts[2]) if len(parts) > 2 else 0
|
| 238 |
+
microsecs = int(parts[3]) if len(parts) > 3 else 0
|
| 239 |
+
sign = -1 if deg < 0 else 1
|
| 240 |
+
return sign * (
|
| 241 |
+
abs(deg) + abs(minutes) / 60 + abs(secs) / 3600 + abs(microsecs) / 3_600_000_000
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def extract_true_north(model: ifcopenshell.file) -> Optional[float]:
|
| 246 |
+
"""
|
| 247 |
+
True north compass bearing (degrees clockwise from +Y axis).
|
| 248 |
+
|
| 249 |
+
Returns None if the model lacks TrueNorth data.
|
| 250 |
+
"""
|
| 251 |
+
for ctx in model.by_type("IfcGeometricRepresentationContext"):
|
| 252 |
+
if ctx.is_a("IfcGeometricRepresentationSubContext"):
|
| 253 |
+
continue
|
| 254 |
+
true_north = getattr(ctx, "TrueNorth", None)
|
| 255 |
+
if true_north is not None:
|
| 256 |
+
ratios = true_north.DirectionRatios
|
| 257 |
+
x, y = float(ratios[0]), float(ratios[1])
|
| 258 |
+
angle_ccw = math.degrees(math.atan2(x, y))
|
| 259 |
+
return round((-angle_ccw) % 360.0, 2)
|
| 260 |
+
return None
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def extract_location(model: ifcopenshell.file, project_name: str = "") -> Optional[Location]:
|
| 264 |
+
"""
|
| 265 |
+
Extract lat/lon from IfcSite and return a Location dataclass.
|
| 266 |
+
|
| 267 |
+
Returns None if the model has no geographic coordinates.
|
| 268 |
+
"""
|
| 269 |
+
sites = model.by_type("IfcSite")
|
| 270 |
+
if not sites:
|
| 271 |
+
return None
|
| 272 |
+
site = sites[0]
|
| 273 |
+
lat = decode_compound_angle(getattr(site, "RefLatitude", None))
|
| 274 |
+
lon = decode_compound_angle(getattr(site, "RefLongitude", None))
|
| 275 |
+
if lat is None or lon is None:
|
| 276 |
+
return None
|
| 277 |
+
return Location(
|
| 278 |
+
latitude=round(lat, 6),
|
| 279 |
+
longitude=round(lon, 6),
|
| 280 |
+
name=project_name or getattr(site, "Name", None) or "",
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
def extract_orientation(model: ifcopenshell.file) -> dict:
|
| 285 |
+
"""
|
| 286 |
+
Extract true north angle + latitude + longitude.
|
| 287 |
+
|
| 288 |
+
Returns dict compatible with the original scan_ifc_models.py output.
|
| 289 |
+
"""
|
| 290 |
+
result = {
|
| 291 |
+
"true_north_angle_deg": extract_true_north(model),
|
| 292 |
+
"latitude": None,
|
| 293 |
+
"longitude": None,
|
| 294 |
+
}
|
| 295 |
+
loc = extract_location(model)
|
| 296 |
+
if loc is not None:
|
| 297 |
+
result["latitude"] = loc.latitude
|
| 298 |
+
result["longitude"] = loc.longitude
|
| 299 |
+
return result
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
# ββ Per-file orchestration ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 303 |
+
|
| 304 |
+
def extract_all(ifc_path: Path | str) -> dict:
|
| 305 |
+
"""
|
| 306 |
+
Open one IFC file and extract all metadata metrics.
|
| 307 |
+
|
| 308 |
+
Returns a dict with keys matching CSV_COLUMNS.
|
| 309 |
+
"""
|
| 310 |
+
ifc_path = Path(ifc_path)
|
| 311 |
+
base = {
|
| 312 |
+
"project_name": ifc_path.parent.name,
|
| 313 |
+
"ifc_file": ifc_path.name,
|
| 314 |
+
"window_area_m2": None,
|
| 315 |
+
"floor_area_m2": None,
|
| 316 |
+
"roof_area_m2": None,
|
| 317 |
+
"true_north_angle_deg": None,
|
| 318 |
+
"latitude": None,
|
| 319 |
+
"longitude": None,
|
| 320 |
+
"error": None,
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
try:
|
| 324 |
+
model = ifcopenshell.open(str(ifc_path))
|
| 325 |
+
except Exception as exc:
|
| 326 |
+
log.error("Cannot open %s: %s", ifc_path, exc)
|
| 327 |
+
base["error"] = str(exc)
|
| 328 |
+
return base
|
| 329 |
+
|
| 330 |
+
log.info(" Schema: %s", model.schema)
|
| 331 |
+
|
| 332 |
+
extractors = {
|
| 333 |
+
"window_area_m2": extract_window_area,
|
| 334 |
+
"floor_area_m2": extract_floor_area,
|
| 335 |
+
"roof_area_m2": extract_roof_area,
|
| 336 |
+
}
|
| 337 |
+
for key, fn in extractors.items():
|
| 338 |
+
try:
|
| 339 |
+
base[key] = fn(model)
|
| 340 |
+
except Exception as exc:
|
| 341 |
+
log.warning(" %s extraction failed: %s", key, exc)
|
| 342 |
+
|
| 343 |
+
try:
|
| 344 |
+
orientation = extract_orientation(model)
|
| 345 |
+
base.update(orientation)
|
| 346 |
+
except Exception as exc:
|
| 347 |
+
log.warning(" Orientation extraction failed: %s", exc)
|
| 348 |
+
|
| 349 |
+
return base
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
def open_model(ifc_path: Path | str) -> ifcopenshell.file:
|
| 353 |
+
"""Open an IFC file and return the ifcopenshell model handle."""
|
| 354 |
+
return ifcopenshell.open(str(ifc_path))
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
# ββ Batch scanning βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 358 |
+
|
| 359 |
+
def find_ifc_files(root_dir: Path) -> list[Path]:
|
| 360 |
+
"""Recursively find all .ifc files under root_dir, sorted."""
|
| 361 |
+
return sorted(root_dir.rglob("*.ifc"))
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
def scan_all(
|
| 365 |
+
root_dir: Path,
|
| 366 |
+
output_csv: Path | None = None,
|
| 367 |
+
) -> list[dict]:
|
| 368 |
+
"""
|
| 369 |
+
Scan all IFC files under *root_dir* and return a list of result dicts.
|
| 370 |
+
Optionally writes results to a CSV file.
|
| 371 |
+
"""
|
| 372 |
+
ifc_files = find_ifc_files(root_dir)
|
| 373 |
+
log.info("Found %d IFC file(s) under: %s", len(ifc_files), root_dir)
|
| 374 |
+
|
| 375 |
+
results: list[dict] = []
|
| 376 |
+
for i, ifc_path in enumerate(ifc_files, 1):
|
| 377 |
+
log.info("[%d/%d] %s/%s", i, len(ifc_files), ifc_path.parent.name, ifc_path.name)
|
| 378 |
+
result = extract_all(ifc_path)
|
| 379 |
+
results.append(result)
|
| 380 |
+
|
| 381 |
+
if output_csv:
|
| 382 |
+
_write_csv(results, output_csv)
|
| 383 |
+
|
| 384 |
+
return results
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
def _write_csv(results: list[dict], output_path: Path) -> None:
|
| 388 |
+
with open(output_path, "w", newline="", encoding="utf-8") as f:
|
| 389 |
+
writer = csv.DictWriter(f, fieldnames=CSV_COLUMNS, extrasaction="ignore")
|
| 390 |
+
writer.writeheader()
|
| 391 |
+
writer.writerows(results)
|
| 392 |
+
log.info("CSV written to: %s", output_path)
|
| 393 |
+
|
| 394 |
+
|
| 395 |
+
def print_summary_table(results: list[dict]) -> None:
|
| 396 |
+
"""Print a tabulate-formatted summary to stdout."""
|
| 397 |
+
try:
|
| 398 |
+
from tabulate import tabulate
|
| 399 |
+
except ImportError:
|
| 400 |
+
log.warning("tabulate not installed β skipping summary table")
|
| 401 |
+
return
|
| 402 |
+
|
| 403 |
+
display_cols = [
|
| 404 |
+
"project_name", "ifc_file",
|
| 405 |
+
"window_area_m2", "floor_area_m2", "roof_area_m2",
|
| 406 |
+
"true_north_angle_deg", "latitude", "longitude",
|
| 407 |
+
]
|
| 408 |
+
headers = [
|
| 409 |
+
"Project", "File",
|
| 410 |
+
"Window mΒ²", "Floor mΒ²", "Roof mΒ²",
|
| 411 |
+
"TrueNorthΒ°", "Lat", "Lon",
|
| 412 |
+
]
|
| 413 |
+
|
| 414 |
+
table_data = []
|
| 415 |
+
for r in results:
|
| 416 |
+
row = []
|
| 417 |
+
for col in display_cols:
|
| 418 |
+
val = r.get(col)
|
| 419 |
+
if val is None:
|
| 420 |
+
row.append("N/A")
|
| 421 |
+
elif isinstance(val, float):
|
| 422 |
+
row.append(f"{val:.2f}")
|
| 423 |
+
else:
|
| 424 |
+
row.append(str(val))
|
| 425 |
+
if r.get("error"):
|
| 426 |
+
row[-1] = "ERROR"
|
| 427 |
+
table_data.append(row)
|
| 428 |
+
|
| 429 |
+
print("\n" + tabulate(table_data, headers=headers, tablefmt="github"))
|
| 430 |
+
|
| 431 |
+
processed = [r for r in results if not r.get("error")]
|
| 432 |
+
errors = [r for r in results if r.get("error")]
|
| 433 |
+
print(f"\nFiles scanned : {len(results)}")
|
| 434 |
+
print(f"Errors : {len(errors)}")
|
| 435 |
+
print(f"Window data : {sum(1 for r in processed if r['window_area_m2'] is not None)}/{len(processed)} files")
|
| 436 |
+
print(f"Floor data : {sum(1 for r in processed if r['floor_area_m2'] is not None)}/{len(processed)} files")
|
| 437 |
+
print(f"Roof data : {sum(1 for r in processed if r['roof_area_m2'] is not None)}/{len(processed)} files")
|
| 438 |
+
print(f"Orientation : {sum(1 for r in processed if r['true_north_angle_deg'] is not None)}/{len(processed)} files")
|
| 439 |
+
if errors:
|
| 440 |
+
print("\nFiles with errors:")
|
| 441 |
+
for r in errors:
|
| 442 |
+
print(f" {r['project_name']}/{r['ifc_file']}: {r['error']}")
|
teams/lux-ai/Final pipeline/ifc_roof_parser.py
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
ifc_roof_parser.py β Geometry-based roof segment extraction from IFC files.
|
| 3 |
+
|
| 4 |
+
Analyses the 3D triangulated mesh of roof elements to produce per-segment
|
| 5 |
+
tilt, azimuth, and area values that the solar production engine consumes.
|
| 6 |
+
|
| 7 |
+
Algorithm:
|
| 8 |
+
1. Find all IfcRoof + decomposed IfcSlab children + standalone ROOF slabs
|
| 9 |
+
2. Triangulate each element via ifcopenshell.geom (world coordinates)
|
| 10 |
+
3. Compute face normals and triangle areas
|
| 11 |
+
4. Cluster upward-facing triangles by normal direction (angular tolerance)
|
| 12 |
+
5. Compute area-weighted average tilt & azimuth per cluster
|
| 13 |
+
6. Apply true-north rotation so azimuths are real-world compass bearings
|
| 14 |
+
|
| 15 |
+
Cross-validates geometry-derived total area against property-set roof_area.
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
from __future__ import annotations
|
| 19 |
+
|
| 20 |
+
import logging
|
| 21 |
+
import math
|
| 22 |
+
from pathlib import Path
|
| 23 |
+
from typing import Optional
|
| 24 |
+
|
| 25 |
+
import numpy as np
|
| 26 |
+
|
| 27 |
+
import ifcopenshell
|
| 28 |
+
import ifcopenshell.geom
|
| 29 |
+
|
| 30 |
+
from solar_pipeline.config import DEFAULT_ANGLE_TOLERANCE_DEG, MIN_SEGMENT_AREA_M2
|
| 31 |
+
from solar_pipeline.ifc_metadata_extractor import (
|
| 32 |
+
extract_roof_area,
|
| 33 |
+
extract_true_north,
|
| 34 |
+
get_area_scale,
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
log = logging.getLogger(__name__)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
# ββ Element discovery βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 41 |
+
|
| 42 |
+
def get_roof_elements(model: ifcopenshell.file) -> list:
|
| 43 |
+
"""
|
| 44 |
+
Collect all roof-related IFC elements:
|
| 45 |
+
- IfcRoof entities (decomposed through IfcRelAggregates if present)
|
| 46 |
+
- Standalone IfcSlab with PredefinedType == ROOF
|
| 47 |
+
Returns leaf elements (slabs or monolithic roofs) ready for geometry extraction.
|
| 48 |
+
"""
|
| 49 |
+
elements: list = []
|
| 50 |
+
seen_ids: set[int] = set()
|
| 51 |
+
|
| 52 |
+
# IfcRoof β decomposed sub-elements
|
| 53 |
+
for roof in model.by_type("IfcRoof"):
|
| 54 |
+
children = _get_decomposed_children(roof)
|
| 55 |
+
if children:
|
| 56 |
+
for child in children:
|
| 57 |
+
if child.id() not in seen_ids:
|
| 58 |
+
elements.append(child)
|
| 59 |
+
seen_ids.add(child.id())
|
| 60 |
+
else:
|
| 61 |
+
# Monolithic roof β process the IfcRoof itself
|
| 62 |
+
if roof.id() not in seen_ids:
|
| 63 |
+
elements.append(roof)
|
| 64 |
+
seen_ids.add(roof.id())
|
| 65 |
+
|
| 66 |
+
# Standalone IfcSlab with ROOF type (not already captured)
|
| 67 |
+
for slab in model.by_type("IfcSlab"):
|
| 68 |
+
pt = getattr(slab, "PredefinedType", None)
|
| 69 |
+
if pt == "ROOF" and slab.id() not in seen_ids:
|
| 70 |
+
elements.append(slab)
|
| 71 |
+
seen_ids.add(slab.id())
|
| 72 |
+
|
| 73 |
+
log.info(" Found %d roof element(s) for geometry extraction", len(elements))
|
| 74 |
+
return elements
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def _get_decomposed_children(element) -> list:
|
| 78 |
+
"""Walk IfcRelAggregates to find child elements (e.g. IfcSlab under IfcRoof)."""
|
| 79 |
+
children: list = []
|
| 80 |
+
for rel in getattr(element, "IsDecomposedBy", []):
|
| 81 |
+
if rel.is_a("IfcRelAggregates"):
|
| 82 |
+
children.extend(rel.RelatedObjects)
|
| 83 |
+
return children
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
# ββ Geometry extraction βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 87 |
+
|
| 88 |
+
def _make_geom_settings() -> ifcopenshell.geom.settings:
|
| 89 |
+
"""Create geometry settings for triangulated mesh in world coordinates."""
|
| 90 |
+
settings = ifcopenshell.geom.settings()
|
| 91 |
+
settings.set("use-world-coords", True)
|
| 92 |
+
return settings
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def extract_geometry(
|
| 96 |
+
element,
|
| 97 |
+
settings: ifcopenshell.geom.settings,
|
| 98 |
+
) -> tuple[np.ndarray, np.ndarray] | None:
|
| 99 |
+
"""
|
| 100 |
+
Triangulate an IFC element and return (vertices, faces).
|
| 101 |
+
|
| 102 |
+
vertices: (N, 3) float64 β XYZ coordinates
|
| 103 |
+
faces: (M, 3) int β triangle vertex indices
|
| 104 |
+
|
| 105 |
+
Returns None if geometry extraction fails.
|
| 106 |
+
"""
|
| 107 |
+
try:
|
| 108 |
+
shape = ifcopenshell.geom.create_shape(settings, element)
|
| 109 |
+
except Exception as exc:
|
| 110 |
+
log.warning(" Geometry extraction failed for #%d (%s): %s",
|
| 111 |
+
element.id(), element.is_a(), exc)
|
| 112 |
+
return None
|
| 113 |
+
|
| 114 |
+
# ifcopenshell returns flat arrays
|
| 115 |
+
verts_flat = shape.geometry.verts
|
| 116 |
+
faces_flat = shape.geometry.faces
|
| 117 |
+
|
| 118 |
+
if not verts_flat or not faces_flat:
|
| 119 |
+
return None
|
| 120 |
+
|
| 121 |
+
vertices = np.array(verts_flat, dtype=np.float64).reshape(-1, 3)
|
| 122 |
+
faces = np.array(faces_flat, dtype=np.int32).reshape(-1, 3)
|
| 123 |
+
|
| 124 |
+
return vertices, faces
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
# ββ Normal & area computation βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 128 |
+
|
| 129 |
+
def compute_face_normals(
|
| 130 |
+
vertices: np.ndarray,
|
| 131 |
+
faces: np.ndarray,
|
| 132 |
+
) -> tuple[np.ndarray, np.ndarray]:
|
| 133 |
+
"""
|
| 134 |
+
Compute unit normals and areas for every triangle.
|
| 135 |
+
|
| 136 |
+
Returns:
|
| 137 |
+
normals: (M, 3) float64 β unit normal per face
|
| 138 |
+
areas: (M,) float64 β area per face in model unitsΒ²
|
| 139 |
+
"""
|
| 140 |
+
v0 = vertices[faces[:, 0]]
|
| 141 |
+
v1 = vertices[faces[:, 1]]
|
| 142 |
+
v2 = vertices[faces[:, 2]]
|
| 143 |
+
|
| 144 |
+
edge1 = v1 - v0
|
| 145 |
+
edge2 = v2 - v0
|
| 146 |
+
cross = np.cross(edge1, edge2)
|
| 147 |
+
|
| 148 |
+
magnitudes = np.linalg.norm(cross, axis=1, keepdims=True)
|
| 149 |
+
# Avoid division by zero for degenerate triangles
|
| 150 |
+
safe_mag = np.where(magnitudes > 1e-12, magnitudes, 1e-12)
|
| 151 |
+
normals = cross / safe_mag
|
| 152 |
+
areas = (magnitudes.flatten() / 2.0)
|
| 153 |
+
|
| 154 |
+
return normals, areas
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
# ββ Clustering ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 158 |
+
|
| 159 |
+
def cluster_faces_by_normal(
|
| 160 |
+
normals: np.ndarray,
|
| 161 |
+
areas: np.ndarray,
|
| 162 |
+
angle_tolerance: float = DEFAULT_ANGLE_TOLERANCE_DEG,
|
| 163 |
+
) -> list[list[int]]:
|
| 164 |
+
"""
|
| 165 |
+
Greedy angular clustering of upward-facing triangles.
|
| 166 |
+
|
| 167 |
+
Only considers triangles where normal Z > 0 (upward-facing β solar relevant).
|
| 168 |
+
Groups triangles whose normals are within *angle_tolerance* degrees.
|
| 169 |
+
|
| 170 |
+
Returns list of clusters, each a list of face indices.
|
| 171 |
+
"""
|
| 172 |
+
cos_tol = math.cos(math.radians(angle_tolerance))
|
| 173 |
+
clusters: list[list[int]] = []
|
| 174 |
+
cluster_normals: list[np.ndarray] = []
|
| 175 |
+
|
| 176 |
+
# Filter to upward-facing triangles
|
| 177 |
+
upward_mask = normals[:, 2] > 0
|
| 178 |
+
upward_indices = np.where(upward_mask)[0]
|
| 179 |
+
|
| 180 |
+
for idx in upward_indices:
|
| 181 |
+
n = normals[idx]
|
| 182 |
+
placed = False
|
| 183 |
+
for ci, cn in enumerate(cluster_normals):
|
| 184 |
+
dot = float(np.dot(n, cn))
|
| 185 |
+
if dot >= cos_tol:
|
| 186 |
+
clusters[ci].append(int(idx))
|
| 187 |
+
# Update cluster normal (area-weighted running average)
|
| 188 |
+
total_area = sum(areas[i] for i in clusters[ci])
|
| 189 |
+
if total_area > 0:
|
| 190 |
+
weighted = sum(areas[i] * normals[i] for i in clusters[ci])
|
| 191 |
+
mag = np.linalg.norm(weighted)
|
| 192 |
+
if mag > 1e-12:
|
| 193 |
+
cluster_normals[ci] = weighted / mag
|
| 194 |
+
placed = True
|
| 195 |
+
break
|
| 196 |
+
|
| 197 |
+
if not placed:
|
| 198 |
+
clusters.append([int(idx)])
|
| 199 |
+
cluster_normals.append(n.copy())
|
| 200 |
+
|
| 201 |
+
return clusters
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
# ββ Segment properties ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 205 |
+
|
| 206 |
+
def compute_segment_properties(
|
| 207 |
+
normals: np.ndarray,
|
| 208 |
+
areas: np.ndarray,
|
| 209 |
+
cluster_indices: list[int],
|
| 210 |
+
area_scale: float = 1.0,
|
| 211 |
+
) -> dict:
|
| 212 |
+
"""
|
| 213 |
+
Compute tilt, azimuth, and total area for one cluster of faces.
|
| 214 |
+
|
| 215 |
+
tilt: degrees from horizontal (0Β° = flat, 90Β° = vertical)
|
| 216 |
+
azimuth: degrees clockwise from north (0Β° N, 90Β° E, 180Β° S, 270Β° W)
|
| 217 |
+
β in MODEL coordinates (true-north correction applied later)
|
| 218 |
+
area: cluster area in mΒ²
|
| 219 |
+
|
| 220 |
+
Returns dict with keys: area, tilt, azimuth.
|
| 221 |
+
"""
|
| 222 |
+
idx = np.array(cluster_indices)
|
| 223 |
+
cluster_areas = areas[idx]
|
| 224 |
+
cluster_normals = normals[idx]
|
| 225 |
+
|
| 226 |
+
total_area = float(cluster_areas.sum()) * area_scale
|
| 227 |
+
|
| 228 |
+
# Area-weighted average normal
|
| 229 |
+
weighted = (cluster_normals.T * cluster_areas).T.sum(axis=0)
|
| 230 |
+
mag = np.linalg.norm(weighted)
|
| 231 |
+
if mag < 1e-12:
|
| 232 |
+
return {"area": total_area, "tilt": 0.0, "azimuth": 0.0}
|
| 233 |
+
avg_n = weighted / mag
|
| 234 |
+
|
| 235 |
+
# Tilt = arccos(nz)
|
| 236 |
+
tilt = math.degrees(math.acos(min(max(avg_n[2], -1.0), 1.0)))
|
| 237 |
+
|
| 238 |
+
# Azimuth = atan2(nx, ny) mod 360
|
| 239 |
+
# Convention: 0Β° = North (+Y), 90Β° = East (+X), 180Β° = South (-Y)
|
| 240 |
+
azimuth = math.degrees(math.atan2(avg_n[0], avg_n[1])) % 360.0
|
| 241 |
+
|
| 242 |
+
return {
|
| 243 |
+
"area": round(total_area, 2),
|
| 244 |
+
"tilt": round(tilt, 1),
|
| 245 |
+
"azimuth": round(azimuth, 1),
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
# ββ Main entry point βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 250 |
+
|
| 251 |
+
def parse_roof_segments(
|
| 252 |
+
ifc_path: str | Path,
|
| 253 |
+
angle_tolerance: float = DEFAULT_ANGLE_TOLERANCE_DEG,
|
| 254 |
+
min_area: float = MIN_SEGMENT_AREA_M2,
|
| 255 |
+
apply_true_north: bool = True,
|
| 256 |
+
) -> list[dict]:
|
| 257 |
+
"""
|
| 258 |
+
Parse an IFC file and return a list of roof segments.
|
| 259 |
+
|
| 260 |
+
Each segment dict has keys: id, area, tilt, azimuth.
|
| 261 |
+
|
| 262 |
+
Parameters
|
| 263 |
+
----------
|
| 264 |
+
ifc_path : path to the .ifc file
|
| 265 |
+
angle_tolerance : degrees β max normal deviation within a cluster
|
| 266 |
+
min_area : mΒ² β ignore clusters smaller than this
|
| 267 |
+
apply_true_north : rotate azimuths by the model's TrueNorth angle
|
| 268 |
+
|
| 269 |
+
Returns
|
| 270 |
+
-------
|
| 271 |
+
list of dicts: [{"id": str, "area": float, "tilt": float, "azimuth": float}, ...]
|
| 272 |
+
"""
|
| 273 |
+
ifc_path = Path(ifc_path)
|
| 274 |
+
log.info("Parsing roof geometry: %s", ifc_path.name)
|
| 275 |
+
|
| 276 |
+
try:
|
| 277 |
+
model = ifcopenshell.open(str(ifc_path))
|
| 278 |
+
except Exception as exc:
|
| 279 |
+
log.error("Cannot open %s: %s", ifc_path, exc)
|
| 280 |
+
return []
|
| 281 |
+
|
| 282 |
+
area_scale = get_area_scale(model)
|
| 283 |
+
# For geometry (vertex coordinates) we need the length scale squared
|
| 284 |
+
# because area = lengthΒ². However ifcopenshell.geom with world-coords
|
| 285 |
+
# already applies the unit conversion, so we assume metres here.
|
| 286 |
+
# If models are in feet, the geom API still returns metres when
|
| 287 |
+
# USE_WORLD_COORDS is True. We keep area_scale for the cross-validation
|
| 288 |
+
# with property-set values only.
|
| 289 |
+
geom_area_scale = 1.0 # world-coords are in metres
|
| 290 |
+
|
| 291 |
+
elements = get_roof_elements(model)
|
| 292 |
+
if not elements:
|
| 293 |
+
log.warning(" No roof elements found in %s", ifc_path.name)
|
| 294 |
+
return []
|
| 295 |
+
|
| 296 |
+
settings = _make_geom_settings()
|
| 297 |
+
|
| 298 |
+
# Collect all face normals + areas across all roof elements
|
| 299 |
+
all_normals: list[np.ndarray] = []
|
| 300 |
+
all_areas: list[np.ndarray] = []
|
| 301 |
+
|
| 302 |
+
for elem in elements:
|
| 303 |
+
geom_data = extract_geometry(elem, settings)
|
| 304 |
+
if geom_data is None:
|
| 305 |
+
continue
|
| 306 |
+
verts, faces = geom_data
|
| 307 |
+
normals, areas = compute_face_normals(verts, faces)
|
| 308 |
+
all_normals.append(normals)
|
| 309 |
+
all_areas.append(areas)
|
| 310 |
+
|
| 311 |
+
if not all_normals:
|
| 312 |
+
log.warning(" Could not extract geometry from any roof element in %s",
|
| 313 |
+
ifc_path.name)
|
| 314 |
+
return []
|
| 315 |
+
|
| 316 |
+
normals = np.vstack(all_normals)
|
| 317 |
+
areas = np.concatenate(all_areas)
|
| 318 |
+
|
| 319 |
+
# Cluster faces by orientation
|
| 320 |
+
clusters = cluster_faces_by_normal(normals, areas, angle_tolerance)
|
| 321 |
+
|
| 322 |
+
# Build segment list
|
| 323 |
+
segments: list[dict] = []
|
| 324 |
+
seg_idx = 1
|
| 325 |
+
for cluster in clusters:
|
| 326 |
+
props = compute_segment_properties(normals, areas, cluster, geom_area_scale)
|
| 327 |
+
if props["area"] < min_area:
|
| 328 |
+
continue
|
| 329 |
+
props["id"] = f"Roof_Seg_{seg_idx:02d}"
|
| 330 |
+
segments.append(props)
|
| 331 |
+
seg_idx += 1
|
| 332 |
+
|
| 333 |
+
# ββ True-north azimuth correction βββββββββββββββββββββββββββββββββββββ
|
| 334 |
+
if apply_true_north and segments:
|
| 335 |
+
tn = extract_true_north(model)
|
| 336 |
+
if tn is not None and abs(tn) > 0.01 and abs(tn - 360.0) > 0.01:
|
| 337 |
+
log.info(" Applying true-north correction: %.2fΒ°", tn)
|
| 338 |
+
for seg in segments:
|
| 339 |
+
seg["azimuth"] = round((seg["azimuth"] + tn) % 360.0, 1)
|
| 340 |
+
|
| 341 |
+
# ββ Cross-validate against property-set roof area βββββββββββββββββββββ
|
| 342 |
+
if segments:
|
| 343 |
+
geom_total = sum(s["area"] for s in segments)
|
| 344 |
+
pset_area = extract_roof_area(model)
|
| 345 |
+
if pset_area is not None and pset_area > 0:
|
| 346 |
+
diff_pct = abs(geom_total - pset_area) / pset_area * 100
|
| 347 |
+
if diff_pct > 20:
|
| 348 |
+
log.warning(
|
| 349 |
+
" Roof area mismatch: geometry=%.1f mΒ², property-set=%.1f mΒ² (%.0f%% diff)",
|
| 350 |
+
geom_total, pset_area, diff_pct,
|
| 351 |
+
)
|
| 352 |
+
else:
|
| 353 |
+
log.info(
|
| 354 |
+
" Roof area validated: geometry=%.1f mΒ², property-set=%.1f mΒ² (%.0f%% diff)",
|
| 355 |
+
geom_total, pset_area, diff_pct,
|
| 356 |
+
)
|
| 357 |
+
|
| 358 |
+
log.info(" Extracted %d roof segment(s) from %s", len(segments), ifc_path.name)
|
| 359 |
+
return segments
|
teams/lux-ai/Final pipeline/key_aliases.json
ADDED
|
@@ -0,0 +1,3323 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"window_area": [
|
| 3 |
+
{
|
| 4 |
+
"entity": "IfcWindow",
|
| 5 |
+
"source": "qset",
|
| 6 |
+
"set_name": "Qto_WindowBaseQuantities",
|
| 7 |
+
"key": "Area"
|
| 8 |
+
},
|
| 9 |
+
{
|
| 10 |
+
"entity": "IfcWindow",
|
| 11 |
+
"source": "qset",
|
| 12 |
+
"set_name": "BaseQuantities",
|
| 13 |
+
"key": "Area"
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"entity": "IfcWindow",
|
| 17 |
+
"source": "qset",
|
| 18 |
+
"set_name": "BaseQuantities",
|
| 19 |
+
"key": "GrossArea"
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"entity": "IfcWindow",
|
| 23 |
+
"source": "attr",
|
| 24 |
+
"keys": [
|
| 25 |
+
"OverallHeight",
|
| 26 |
+
"OverallWidth"
|
| 27 |
+
],
|
| 28 |
+
"op": "multiply"
|
| 29 |
+
}
|
| 30 |
+
],
|
| 31 |
+
"floor_area": [
|
| 32 |
+
{
|
| 33 |
+
"entity": "IfcSpace",
|
| 34 |
+
"source": "qset",
|
| 35 |
+
"set_name": "Qto_SpaceBaseQuantities",
|
| 36 |
+
"key": "NetFloorArea"
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"entity": "IfcSpace",
|
| 40 |
+
"source": "qset",
|
| 41 |
+
"set_name": "Qto_SpaceBaseQuantities",
|
| 42 |
+
"key": "GrossFloorArea"
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"entity": "IfcSpace",
|
| 46 |
+
"source": "qset",
|
| 47 |
+
"set_name": "BaseQuantities",
|
| 48 |
+
"key": "NetFloorArea"
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"entity": "IfcSpace",
|
| 52 |
+
"source": "qset",
|
| 53 |
+
"set_name": "BaseQuantities",
|
| 54 |
+
"key": "GrossFloorArea"
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
"entity": "IfcSpace",
|
| 58 |
+
"source": "qset",
|
| 59 |
+
"set_name": "GSA Space Areas",
|
| 60 |
+
"key": "GSA BIM Area"
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"entity": "IfcSlab",
|
| 64 |
+
"source": "qset",
|
| 65 |
+
"set_name": "Qto_SlabBaseQuantities",
|
| 66 |
+
"key": "NetArea",
|
| 67 |
+
"predefined_type": "FLOOR"
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
"entity": "IfcSlab",
|
| 71 |
+
"source": "qset",
|
| 72 |
+
"set_name": "BaseQuantities",
|
| 73 |
+
"key": "NetArea",
|
| 74 |
+
"predefined_type": "FLOOR"
|
| 75 |
+
},
|
| 76 |
+
{
|
| 77 |
+
"entity": "IfcSlab",
|
| 78 |
+
"source": "qset",
|
| 79 |
+
"set_name": "BaseQuantities",
|
| 80 |
+
"key": "GrossArea",
|
| 81 |
+
"predefined_type": "FLOOR"
|
| 82 |
+
}
|
| 83 |
+
],
|
| 84 |
+
"roof_area": [
|
| 85 |
+
{
|
| 86 |
+
"entity": "IfcRoof",
|
| 87 |
+
"source": "qset",
|
| 88 |
+
"set_name": "Qto_RoofBaseQuantities",
|
| 89 |
+
"key": "NetArea"
|
| 90 |
+
},
|
| 91 |
+
{
|
| 92 |
+
"entity": "IfcRoof",
|
| 93 |
+
"source": "qset",
|
| 94 |
+
"set_name": "Qto_RoofBaseQuantities",
|
| 95 |
+
"key": "GrossArea"
|
| 96 |
+
},
|
| 97 |
+
{
|
| 98 |
+
"entity": "IfcRoof",
|
| 99 |
+
"source": "qset",
|
| 100 |
+
"set_name": "BaseQuantities",
|
| 101 |
+
"key": "NetArea"
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
"entity": "IfcSlab",
|
| 105 |
+
"source": "qset",
|
| 106 |
+
"set_name": "Qto_SlabBaseQuantities",
|
| 107 |
+
"key": "NetArea",
|
| 108 |
+
"predefined_type": "ROOF"
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"entity": "IfcSlab",
|
| 112 |
+
"source": "qset",
|
| 113 |
+
"set_name": "Qto_SlabBaseQuantities",
|
| 114 |
+
"key": "GrossArea",
|
| 115 |
+
"predefined_type": "ROOF"
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"entity": "IfcSlab",
|
| 119 |
+
"source": "qset",
|
| 120 |
+
"set_name": "BaseQuantities",
|
| 121 |
+
"key": "NetArea",
|
| 122 |
+
"predefined_type": "ROOF"
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
"entity": "IfcSlab",
|
| 126 |
+
"source": "qset",
|
| 127 |
+
"set_name": "BaseQuantities",
|
| 128 |
+
"key": "GrossArea",
|
| 129 |
+
"predefined_type": "ROOF"
|
| 130 |
+
}
|
| 131 |
+
],
|
| 132 |
+
"true_north_angle": [
|
| 133 |
+
{
|
| 134 |
+
"entity": "IfcGeometricRepresentationContext",
|
| 135 |
+
"source": "attr",
|
| 136 |
+
"key": "TrueNorth",
|
| 137 |
+
"note": "DirectionRatios (X,Y) β atan2(x,y) β compass bearing"
|
| 138 |
+
}
|
| 139 |
+
],
|
| 140 |
+
"latitude": [
|
| 141 |
+
{
|
| 142 |
+
"entity": "IfcSite",
|
| 143 |
+
"source": "attr",
|
| 144 |
+
"key": "RefLatitude",
|
| 145 |
+
"note": "IfcCompoundPlaneAngleMeasure [deg,min,sec,microsec]"
|
| 146 |
+
}
|
| 147 |
+
],
|
| 148 |
+
"longitude": [
|
| 149 |
+
{
|
| 150 |
+
"entity": "IfcSite",
|
| 151 |
+
"source": "attr",
|
| 152 |
+
"key": "RefLongitude",
|
| 153 |
+
"note": "IfcCompoundPlaneAngleMeasure [deg,min,sec,microsec]"
|
| 154 |
+
}
|
| 155 |
+
],
|
| 156 |
+
"roof_slope": [
|
| 157 |
+
{
|
| 158 |
+
"entity": "IfcRoof",
|
| 159 |
+
"source": "pset",
|
| 160 |
+
"set_name": "Dimensions",
|
| 161 |
+
"key": "Slope",
|
| 162 |
+
"note": "Revit-exported roof slope in degrees"
|
| 163 |
+
},
|
| 164 |
+
{
|
| 165 |
+
"entity": "IfcRoof",
|
| 166 |
+
"source": "pset",
|
| 167 |
+
"set_name": "Abmessungen",
|
| 168 |
+
"key": "Neigung",
|
| 169 |
+
"note": "ArchiCAD German β roof slope"
|
| 170 |
+
},
|
| 171 |
+
{
|
| 172 |
+
"entity": "IfcSlab",
|
| 173 |
+
"source": "pset",
|
| 174 |
+
"set_name": "Dimensions",
|
| 175 |
+
"key": "Slope",
|
| 176 |
+
"predefined_type": "ROOF",
|
| 177 |
+
"note": "Revit-exported roof slab slope"
|
| 178 |
+
}
|
| 179 |
+
],
|
| 180 |
+
"thermal_transmittance": [
|
| 181 |
+
{
|
| 182 |
+
"entity": "IfcRoof",
|
| 183 |
+
"source": "pset",
|
| 184 |
+
"set_name": "Pset_RoofCommon",
|
| 185 |
+
"key": "ThermalTransmittance",
|
| 186 |
+
"note": "U-value in W/(mΒ²Β·K) β for energy modelling"
|
| 187 |
+
},
|
| 188 |
+
{
|
| 189 |
+
"entity": "IfcSlab",
|
| 190 |
+
"source": "pset",
|
| 191 |
+
"set_name": "Pset_SlabCommon",
|
| 192 |
+
"key": "ThermalTransmittance",
|
| 193 |
+
"predefined_type": "ROOF",
|
| 194 |
+
"note": "U-value for roof slabs"
|
| 195 |
+
},
|
| 196 |
+
{
|
| 197 |
+
"entity": "IfcWall",
|
| 198 |
+
"source": "pset",
|
| 199 |
+
"set_name": "Pset_WallCommon",
|
| 200 |
+
"key": "ThermalTransmittance",
|
| 201 |
+
"note": "U-value for walls β future energy modelling"
|
| 202 |
+
}
|
| 203 |
+
],
|
| 204 |
+
"_auto_discovered_area_keys": [
|
| 205 |
+
{
|
| 206 |
+
"entity": "IfcWindow",
|
| 207 |
+
"source": "qset",
|
| 208 |
+
"set_name": "ArchiCADQuantities",
|
| 209 |
+
"key": "OberflΓ€chenbereich",
|
| 210 |
+
"file_count": 2,
|
| 211 |
+
"projects": [
|
| 212 |
+
"ac20",
|
| 213 |
+
"fzk_house"
|
| 214 |
+
],
|
| 215 |
+
"auto_discovered": true
|
| 216 |
+
},
|
| 217 |
+
{
|
| 218 |
+
"entity": "IfcWindow",
|
| 219 |
+
"source": "qset",
|
| 220 |
+
"set_name": "ArchiCADQuantities",
|
| 221 |
+
"key": "F/T OberflΓ€che der Γffnung auf der Anschlagseite",
|
| 222 |
+
"file_count": 2,
|
| 223 |
+
"projects": [
|
| 224 |
+
"ac20",
|
| 225 |
+
"fzk_house"
|
| 226 |
+
],
|
| 227 |
+
"auto_discovered": true
|
| 228 |
+
},
|
| 229 |
+
{
|
| 230 |
+
"entity": "IfcWindow",
|
| 231 |
+
"source": "qset",
|
| 232 |
+
"set_name": "ArchiCADQuantities",
|
| 233 |
+
"key": "F/T OberflΓ€che der Γffnung gegenΓΌber der Anschlagseite",
|
| 234 |
+
"file_count": 2,
|
| 235 |
+
"projects": [
|
| 236 |
+
"ac20",
|
| 237 |
+
"fzk_house"
|
| 238 |
+
],
|
| 239 |
+
"auto_discovered": true
|
| 240 |
+
},
|
| 241 |
+
{
|
| 242 |
+
"entity": "IfcWindow",
|
| 243 |
+
"source": "qset",
|
| 244 |
+
"set_name": "ArchiCADQuantities",
|
| 245 |
+
"key": "Nominale T/F ΓffnungsoberflΓ€che auf der Anschlagsseite",
|
| 246 |
+
"file_count": 2,
|
| 247 |
+
"projects": [
|
| 248 |
+
"ac20",
|
| 249 |
+
"fzk_house"
|
| 250 |
+
],
|
| 251 |
+
"auto_discovered": true
|
| 252 |
+
},
|
| 253 |
+
{
|
| 254 |
+
"entity": "IfcWindow",
|
| 255 |
+
"source": "qset",
|
| 256 |
+
"set_name": "ArchiCADQuantities",
|
| 257 |
+
"key": "Nominale T/F ΓffnungsoberflΓ€che auf der Seite gegenΓΌber der Anschlagsseite",
|
| 258 |
+
"file_count": 2,
|
| 259 |
+
"projects": [
|
| 260 |
+
"ac20",
|
| 261 |
+
"fzk_house"
|
| 262 |
+
],
|
| 263 |
+
"auto_discovered": true
|
| 264 |
+
},
|
| 265 |
+
{
|
| 266 |
+
"entity": "IfcWindow",
|
| 267 |
+
"source": "qset",
|
| 268 |
+
"set_name": "ArchiCADQuantities",
|
| 269 |
+
"key": "T/F Γffnung Nominaler OberflΓ€chenbereich",
|
| 270 |
+
"file_count": 2,
|
| 271 |
+
"projects": [
|
| 272 |
+
"ac20",
|
| 273 |
+
"fzk_house"
|
| 274 |
+
],
|
| 275 |
+
"auto_discovered": true
|
| 276 |
+
},
|
| 277 |
+
{
|
| 278 |
+
"entity": "IfcWindow",
|
| 279 |
+
"source": "qset",
|
| 280 |
+
"set_name": "ArchiCADQuantities",
|
| 281 |
+
"key": "Surface Area",
|
| 282 |
+
"file_count": 1,
|
| 283 |
+
"projects": [
|
| 284 |
+
"sixty5"
|
| 285 |
+
],
|
| 286 |
+
"auto_discovered": true
|
| 287 |
+
},
|
| 288 |
+
{
|
| 289 |
+
"entity": "IfcWindow",
|
| 290 |
+
"source": "qset",
|
| 291 |
+
"set_name": "ArchiCADQuantities",
|
| 292 |
+
"key": "W/D Opening Surface Area",
|
| 293 |
+
"file_count": 1,
|
| 294 |
+
"projects": [
|
| 295 |
+
"sixty5"
|
| 296 |
+
],
|
| 297 |
+
"auto_discovered": true
|
| 298 |
+
},
|
| 299 |
+
{
|
| 300 |
+
"entity": "IfcWindow",
|
| 301 |
+
"source": "qset",
|
| 302 |
+
"set_name": "ArchiCADQuantities",
|
| 303 |
+
"key": "W/D Opening Nominal Surface Area",
|
| 304 |
+
"file_count": 1,
|
| 305 |
+
"projects": [
|
| 306 |
+
"sixty5"
|
| 307 |
+
],
|
| 308 |
+
"auto_discovered": true
|
| 309 |
+
},
|
| 310 |
+
{
|
| 311 |
+
"entity": "IfcWindow",
|
| 312 |
+
"source": "qset",
|
| 313 |
+
"set_name": "ArchiCADQuantities",
|
| 314 |
+
"key": "Nominal W/D Opening Surface Area on the Reveal Side",
|
| 315 |
+
"file_count": 1,
|
| 316 |
+
"projects": [
|
| 317 |
+
"sixty5"
|
| 318 |
+
],
|
| 319 |
+
"auto_discovered": true
|
| 320 |
+
},
|
| 321 |
+
{
|
| 322 |
+
"entity": "IfcWindow",
|
| 323 |
+
"source": "qset",
|
| 324 |
+
"set_name": "ArchiCADQuantities",
|
| 325 |
+
"key": "Nominal W/D Opening Surface Area on the Side Opposite to the Reveal Side",
|
| 326 |
+
"file_count": 1,
|
| 327 |
+
"projects": [
|
| 328 |
+
"sixty5"
|
| 329 |
+
],
|
| 330 |
+
"auto_discovered": true
|
| 331 |
+
},
|
| 332 |
+
{
|
| 333 |
+
"entity": "IfcWindow",
|
| 334 |
+
"source": "qset",
|
| 335 |
+
"set_name": "ArchiCADQuantities",
|
| 336 |
+
"key": "Area",
|
| 337 |
+
"file_count": 1,
|
| 338 |
+
"projects": [
|
| 339 |
+
"sixty5"
|
| 340 |
+
],
|
| 341 |
+
"auto_discovered": true
|
| 342 |
+
},
|
| 343 |
+
{
|
| 344 |
+
"entity": "IfcDoor",
|
| 345 |
+
"source": "qset",
|
| 346 |
+
"set_name": "BaseQuantities",
|
| 347 |
+
"key": "Area",
|
| 348 |
+
"file_count": 5,
|
| 349 |
+
"projects": [
|
| 350 |
+
"ac20",
|
| 351 |
+
"fzk_house",
|
| 352 |
+
"molio",
|
| 353 |
+
"sixty5",
|
| 354 |
+
"smiley_west"
|
| 355 |
+
],
|
| 356 |
+
"auto_discovered": true
|
| 357 |
+
},
|
| 358 |
+
{
|
| 359 |
+
"entity": "IfcDoor",
|
| 360 |
+
"source": "qset",
|
| 361 |
+
"set_name": "ArchiCADQuantities",
|
| 362 |
+
"key": "OberflΓ€chenbereich",
|
| 363 |
+
"file_count": 2,
|
| 364 |
+
"projects": [
|
| 365 |
+
"ac20",
|
| 366 |
+
"fzk_house"
|
| 367 |
+
],
|
| 368 |
+
"auto_discovered": true
|
| 369 |
+
},
|
| 370 |
+
{
|
| 371 |
+
"entity": "IfcDoor",
|
| 372 |
+
"source": "qset",
|
| 373 |
+
"set_name": "ArchiCADQuantities",
|
| 374 |
+
"key": "F/T OberflΓ€che der Γffnung auf der Anschlagseite",
|
| 375 |
+
"file_count": 2,
|
| 376 |
+
"projects": [
|
| 377 |
+
"ac20",
|
| 378 |
+
"fzk_house"
|
| 379 |
+
],
|
| 380 |
+
"auto_discovered": true
|
| 381 |
+
},
|
| 382 |
+
{
|
| 383 |
+
"entity": "IfcDoor",
|
| 384 |
+
"source": "qset",
|
| 385 |
+
"set_name": "ArchiCADQuantities",
|
| 386 |
+
"key": "F/T OberflΓ€che der Γffnung gegenΓΌber der Anschlagseite",
|
| 387 |
+
"file_count": 2,
|
| 388 |
+
"projects": [
|
| 389 |
+
"ac20",
|
| 390 |
+
"fzk_house"
|
| 391 |
+
],
|
| 392 |
+
"auto_discovered": true
|
| 393 |
+
},
|
| 394 |
+
{
|
| 395 |
+
"entity": "IfcDoor",
|
| 396 |
+
"source": "qset",
|
| 397 |
+
"set_name": "ArchiCADQuantities",
|
| 398 |
+
"key": "Nominale T/F ΓffnungsoberflΓ€che auf der Anschlagsseite",
|
| 399 |
+
"file_count": 2,
|
| 400 |
+
"projects": [
|
| 401 |
+
"ac20",
|
| 402 |
+
"fzk_house"
|
| 403 |
+
],
|
| 404 |
+
"auto_discovered": true
|
| 405 |
+
},
|
| 406 |
+
{
|
| 407 |
+
"entity": "IfcDoor",
|
| 408 |
+
"source": "qset",
|
| 409 |
+
"set_name": "ArchiCADQuantities",
|
| 410 |
+
"key": "Nominale T/F ΓffnungsoberflΓ€che auf der Seite gegenΓΌber der Anschlagsseite",
|
| 411 |
+
"file_count": 2,
|
| 412 |
+
"projects": [
|
| 413 |
+
"ac20",
|
| 414 |
+
"fzk_house"
|
| 415 |
+
],
|
| 416 |
+
"auto_discovered": true
|
| 417 |
+
},
|
| 418 |
+
{
|
| 419 |
+
"entity": "IfcDoor",
|
| 420 |
+
"source": "qset",
|
| 421 |
+
"set_name": "ArchiCADQuantities",
|
| 422 |
+
"key": "T/F Γffnung Nominaler OberflΓ€chenbereich",
|
| 423 |
+
"file_count": 2,
|
| 424 |
+
"projects": [
|
| 425 |
+
"ac20",
|
| 426 |
+
"fzk_house"
|
| 427 |
+
],
|
| 428 |
+
"auto_discovered": true
|
| 429 |
+
},
|
| 430 |
+
{
|
| 431 |
+
"entity": "IfcDoor",
|
| 432 |
+
"source": "qset",
|
| 433 |
+
"set_name": "ArchiCADQuantities",
|
| 434 |
+
"key": "Surface Area",
|
| 435 |
+
"file_count": 2,
|
| 436 |
+
"projects": [
|
| 437 |
+
"molio",
|
| 438 |
+
"sixty5"
|
| 439 |
+
],
|
| 440 |
+
"auto_discovered": true
|
| 441 |
+
},
|
| 442 |
+
{
|
| 443 |
+
"entity": "IfcDoor",
|
| 444 |
+
"source": "qset",
|
| 445 |
+
"set_name": "ArchiCADQuantities",
|
| 446 |
+
"key": "Bottom Surface Area (Net)",
|
| 447 |
+
"file_count": 1,
|
| 448 |
+
"projects": [
|
| 449 |
+
"molio"
|
| 450 |
+
],
|
| 451 |
+
"auto_discovered": true
|
| 452 |
+
},
|
| 453 |
+
{
|
| 454 |
+
"entity": "IfcDoor",
|
| 455 |
+
"source": "qset",
|
| 456 |
+
"set_name": "ArchiCADQuantities",
|
| 457 |
+
"key": "W/D Opening Surface Area",
|
| 458 |
+
"file_count": 2,
|
| 459 |
+
"projects": [
|
| 460 |
+
"molio",
|
| 461 |
+
"sixty5"
|
| 462 |
+
],
|
| 463 |
+
"auto_discovered": true
|
| 464 |
+
},
|
| 465 |
+
{
|
| 466 |
+
"entity": "IfcDoor",
|
| 467 |
+
"source": "qset",
|
| 468 |
+
"set_name": "ArchiCADQuantities",
|
| 469 |
+
"key": "W/D Opening Nominal Surface Area",
|
| 470 |
+
"file_count": 2,
|
| 471 |
+
"projects": [
|
| 472 |
+
"molio",
|
| 473 |
+
"sixty5"
|
| 474 |
+
],
|
| 475 |
+
"auto_discovered": true
|
| 476 |
+
},
|
| 477 |
+
{
|
| 478 |
+
"entity": "IfcDoor",
|
| 479 |
+
"source": "qset",
|
| 480 |
+
"set_name": "ArchiCADQuantities",
|
| 481 |
+
"key": "W/D Opening Surface Area on the Reveal Side",
|
| 482 |
+
"file_count": 1,
|
| 483 |
+
"projects": [
|
| 484 |
+
"molio"
|
| 485 |
+
],
|
| 486 |
+
"auto_discovered": true
|
| 487 |
+
},
|
| 488 |
+
{
|
| 489 |
+
"entity": "IfcDoor",
|
| 490 |
+
"source": "qset",
|
| 491 |
+
"set_name": "ArchiCADQuantities",
|
| 492 |
+
"key": "W/D Opening Surface Area on the Side Opposite to the Reveal Side",
|
| 493 |
+
"file_count": 1,
|
| 494 |
+
"projects": [
|
| 495 |
+
"molio"
|
| 496 |
+
],
|
| 497 |
+
"auto_discovered": true
|
| 498 |
+
},
|
| 499 |
+
{
|
| 500 |
+
"entity": "IfcDoor",
|
| 501 |
+
"source": "qset",
|
| 502 |
+
"set_name": "ArchiCADQuantities",
|
| 503 |
+
"key": "Nominal W/D Opening Surface Area on the Reveal Side",
|
| 504 |
+
"file_count": 2,
|
| 505 |
+
"projects": [
|
| 506 |
+
"molio",
|
| 507 |
+
"sixty5"
|
| 508 |
+
],
|
| 509 |
+
"auto_discovered": true
|
| 510 |
+
},
|
| 511 |
+
{
|
| 512 |
+
"entity": "IfcDoor",
|
| 513 |
+
"source": "qset",
|
| 514 |
+
"set_name": "ArchiCADQuantities",
|
| 515 |
+
"key": "Nominal W/D Opening Surface Area on the Side Opposite to the Reveal Side",
|
| 516 |
+
"file_count": 2,
|
| 517 |
+
"projects": [
|
| 518 |
+
"molio",
|
| 519 |
+
"sixty5"
|
| 520 |
+
],
|
| 521 |
+
"auto_discovered": true
|
| 522 |
+
},
|
| 523 |
+
{
|
| 524 |
+
"entity": "IfcDoor",
|
| 525 |
+
"source": "qset",
|
| 526 |
+
"set_name": "Qto_DoorBaseQuantities",
|
| 527 |
+
"key": "Area",
|
| 528 |
+
"file_count": 3,
|
| 529 |
+
"projects": [
|
| 530 |
+
"digital_hub",
|
| 531 |
+
"fantasy_office_building_1",
|
| 532 |
+
"fantasy_office_building_2"
|
| 533 |
+
],
|
| 534 |
+
"auto_discovered": true
|
| 535 |
+
},
|
| 536 |
+
{
|
| 537 |
+
"entity": "IfcSpace",
|
| 538 |
+
"source": "qset",
|
| 539 |
+
"set_name": "GSA Space Areas",
|
| 540 |
+
"key": "GSA Space Areas",
|
| 541 |
+
"file_count": 1,
|
| 542 |
+
"projects": [
|
| 543 |
+
"samuel_macalister_sample_house"
|
| 544 |
+
],
|
| 545 |
+
"auto_discovered": true
|
| 546 |
+
},
|
| 547 |
+
{
|
| 548 |
+
"entity": "IfcSpace",
|
| 549 |
+
"source": "qset",
|
| 550 |
+
"set_name": "BaseQuantities",
|
| 551 |
+
"key": "SpaceNetFloorAreaBOMA",
|
| 552 |
+
"file_count": 5,
|
| 553 |
+
"projects": [
|
| 554 |
+
"ac20",
|
| 555 |
+
"fzk_house",
|
| 556 |
+
"molio",
|
| 557 |
+
"sixty5",
|
| 558 |
+
"smiley_west"
|
| 559 |
+
],
|
| 560 |
+
"auto_discovered": true
|
| 561 |
+
},
|
| 562 |
+
{
|
| 563 |
+
"entity": "IfcSpace",
|
| 564 |
+
"source": "qset",
|
| 565 |
+
"set_name": "BaseQuantities",
|
| 566 |
+
"key": "SpaceUsableFloorAreaBOMA",
|
| 567 |
+
"file_count": 5,
|
| 568 |
+
"projects": [
|
| 569 |
+
"ac20",
|
| 570 |
+
"fzk_house",
|
| 571 |
+
"molio",
|
| 572 |
+
"sixty5",
|
| 573 |
+
"smiley_west"
|
| 574 |
+
],
|
| 575 |
+
"auto_discovered": true
|
| 576 |
+
},
|
| 577 |
+
{
|
| 578 |
+
"entity": "IfcSpace",
|
| 579 |
+
"source": "qset",
|
| 580 |
+
"set_name": "BaseQuantities",
|
| 581 |
+
"key": "GrossCeilingArea",
|
| 582 |
+
"file_count": 5,
|
| 583 |
+
"projects": [
|
| 584 |
+
"ac20",
|
| 585 |
+
"fzk_house",
|
| 586 |
+
"molio",
|
| 587 |
+
"sixty5",
|
| 588 |
+
"smiley_west"
|
| 589 |
+
],
|
| 590 |
+
"auto_discovered": true
|
| 591 |
+
},
|
| 592 |
+
{
|
| 593 |
+
"entity": "IfcSpace",
|
| 594 |
+
"source": "qset",
|
| 595 |
+
"set_name": "BaseQuantities",
|
| 596 |
+
"key": "NetCeilingArea",
|
| 597 |
+
"file_count": 5,
|
| 598 |
+
"projects": [
|
| 599 |
+
"ac20",
|
| 600 |
+
"fzk_house",
|
| 601 |
+
"molio",
|
| 602 |
+
"sixty5",
|
| 603 |
+
"smiley_west"
|
| 604 |
+
],
|
| 605 |
+
"auto_discovered": true
|
| 606 |
+
},
|
| 607 |
+
{
|
| 608 |
+
"entity": "IfcSpace",
|
| 609 |
+
"source": "qset",
|
| 610 |
+
"set_name": "BaseQuantities",
|
| 611 |
+
"key": "GrossWallArea",
|
| 612 |
+
"file_count": 5,
|
| 613 |
+
"projects": [
|
| 614 |
+
"ac20",
|
| 615 |
+
"fzk_house",
|
| 616 |
+
"molio",
|
| 617 |
+
"sixty5",
|
| 618 |
+
"smiley_west"
|
| 619 |
+
],
|
| 620 |
+
"auto_discovered": true
|
| 621 |
+
},
|
| 622 |
+
{
|
| 623 |
+
"entity": "IfcSpace",
|
| 624 |
+
"source": "qset",
|
| 625 |
+
"set_name": "BaseQuantities",
|
| 626 |
+
"key": "NetWallArea",
|
| 627 |
+
"file_count": 5,
|
| 628 |
+
"projects": [
|
| 629 |
+
"ac20",
|
| 630 |
+
"fzk_house",
|
| 631 |
+
"molio",
|
| 632 |
+
"sixty5",
|
| 633 |
+
"smiley_west"
|
| 634 |
+
],
|
| 635 |
+
"auto_discovered": true
|
| 636 |
+
},
|
| 637 |
+
{
|
| 638 |
+
"entity": "IfcSpace",
|
| 639 |
+
"source": "qset",
|
| 640 |
+
"set_name": "ArchiCADQuantities",
|
| 641 |
+
"key": "OberflΓ€chenbereich",
|
| 642 |
+
"file_count": 2,
|
| 643 |
+
"projects": [
|
| 644 |
+
"ac20",
|
| 645 |
+
"fzk_house"
|
| 646 |
+
],
|
| 647 |
+
"auto_discovered": true
|
| 648 |
+
},
|
| 649 |
+
{
|
| 650 |
+
"entity": "IfcSpace",
|
| 651 |
+
"source": "qset",
|
| 652 |
+
"set_name": "ArchiCADQuantities",
|
| 653 |
+
"key": "FlΓ€che",
|
| 654 |
+
"file_count": 2,
|
| 655 |
+
"projects": [
|
| 656 |
+
"ac20",
|
| 657 |
+
"fzk_house"
|
| 658 |
+
],
|
| 659 |
+
"auto_discovered": true
|
| 660 |
+
},
|
| 661 |
+
{
|
| 662 |
+
"entity": "IfcSpace",
|
| 663 |
+
"source": "qset",
|
| 664 |
+
"set_name": "ArchiCADQuantities",
|
| 665 |
+
"key": "Berechnete FlΓ€che (NRF)",
|
| 666 |
+
"file_count": 2,
|
| 667 |
+
"projects": [
|
| 668 |
+
"ac20",
|
| 669 |
+
"fzk_house"
|
| 670 |
+
],
|
| 671 |
+
"auto_discovered": true
|
| 672 |
+
},
|
| 673 |
+
{
|
| 674 |
+
"entity": "IfcSpace",
|
| 675 |
+
"source": "qset",
|
| 676 |
+
"set_name": "ArchiCADQuantities",
|
| 677 |
+
"key": "TΓΌren-OberflΓ€chenbereich",
|
| 678 |
+
"file_count": 2,
|
| 679 |
+
"projects": [
|
| 680 |
+
"ac20",
|
| 681 |
+
"fzk_house"
|
| 682 |
+
],
|
| 683 |
+
"auto_discovered": true
|
| 684 |
+
},
|
| 685 |
+
{
|
| 686 |
+
"entity": "IfcSpace",
|
| 687 |
+
"source": "qset",
|
| 688 |
+
"set_name": "ArchiCADQuantities",
|
| 689 |
+
"key": "StΓΌtzenabzugsflΓ€che",
|
| 690 |
+
"file_count": 2,
|
| 691 |
+
"projects": [
|
| 692 |
+
"ac20",
|
| 693 |
+
"fzk_house"
|
| 694 |
+
],
|
| 695 |
+
"auto_discovered": true
|
| 696 |
+
},
|
| 697 |
+
{
|
| 698 |
+
"entity": "IfcSpace",
|
| 699 |
+
"source": "qset",
|
| 700 |
+
"set_name": "ArchiCADQuantities",
|
| 701 |
+
"key": "SchraffurabzugsflΓ€che",
|
| 702 |
+
"file_count": 2,
|
| 703 |
+
"projects": [
|
| 704 |
+
"ac20",
|
| 705 |
+
"fzk_house"
|
| 706 |
+
],
|
| 707 |
+
"auto_discovered": true
|
| 708 |
+
},
|
| 709 |
+
{
|
| 710 |
+
"entity": "IfcSpace",
|
| 711 |
+
"source": "qset",
|
| 712 |
+
"set_name": "ArchiCADQuantities",
|
| 713 |
+
"key": "Niedrige AbzugsflΓ€che",
|
| 714 |
+
"file_count": 2,
|
| 715 |
+
"projects": [
|
| 716 |
+
"ac20",
|
| 717 |
+
"fzk_house"
|
| 718 |
+
],
|
| 719 |
+
"auto_discovered": true
|
| 720 |
+
},
|
| 721 |
+
{
|
| 722 |
+
"entity": "IfcSpace",
|
| 723 |
+
"source": "qset",
|
| 724 |
+
"set_name": "ArchiCADQuantities",
|
| 725 |
+
"key": "WandabzugsflΓ€che",
|
| 726 |
+
"file_count": 2,
|
| 727 |
+
"projects": [
|
| 728 |
+
"ac20",
|
| 729 |
+
"fzk_house"
|
| 730 |
+
],
|
| 731 |
+
"auto_discovered": true
|
| 732 |
+
},
|
| 733 |
+
{
|
| 734 |
+
"entity": "IfcSpace",
|
| 735 |
+
"source": "qset",
|
| 736 |
+
"set_name": "ArchiCADQuantities",
|
| 737 |
+
"key": "Gemessene FlΓ€che",
|
| 738 |
+
"file_count": 2,
|
| 739 |
+
"projects": [
|
| 740 |
+
"ac20",
|
| 741 |
+
"fzk_house"
|
| 742 |
+
],
|
| 743 |
+
"auto_discovered": true
|
| 744 |
+
},
|
| 745 |
+
{
|
| 746 |
+
"entity": "IfcSpace",
|
| 747 |
+
"source": "qset",
|
| 748 |
+
"set_name": "ArchiCADQuantities",
|
| 749 |
+
"key": "NettoflΓ€che",
|
| 750 |
+
"file_count": 2,
|
| 751 |
+
"projects": [
|
| 752 |
+
"ac20",
|
| 753 |
+
"fzk_house"
|
| 754 |
+
],
|
| 755 |
+
"auto_discovered": true
|
| 756 |
+
},
|
| 757 |
+
{
|
| 758 |
+
"entity": "IfcSpace",
|
| 759 |
+
"source": "qset",
|
| 760 |
+
"set_name": "ArchiCADQuantities",
|
| 761 |
+
"key": "Reduzierte FlΓ€che",
|
| 762 |
+
"file_count": 2,
|
| 763 |
+
"projects": [
|
| 764 |
+
"ac20",
|
| 765 |
+
"fzk_house"
|
| 766 |
+
],
|
| 767 |
+
"auto_discovered": true
|
| 768 |
+
},
|
| 769 |
+
{
|
| 770 |
+
"entity": "IfcSpace",
|
| 771 |
+
"source": "qset",
|
| 772 |
+
"set_name": "ArchiCADQuantities",
|
| 773 |
+
"key": "RaumflΓ€chen Reduzierung",
|
| 774 |
+
"file_count": 2,
|
| 775 |
+
"projects": [
|
| 776 |
+
"ac20",
|
| 777 |
+
"fzk_house"
|
| 778 |
+
],
|
| 779 |
+
"auto_discovered": true
|
| 780 |
+
},
|
| 781 |
+
{
|
| 782 |
+
"entity": "IfcSpace",
|
| 783 |
+
"source": "qset",
|
| 784 |
+
"set_name": "ArchiCADQuantities",
|
| 785 |
+
"key": "GesamtabzugsflΓ€che",
|
| 786 |
+
"file_count": 2,
|
| 787 |
+
"projects": [
|
| 788 |
+
"ac20",
|
| 789 |
+
"fzk_house"
|
| 790 |
+
],
|
| 791 |
+
"auto_discovered": true
|
| 792 |
+
},
|
| 793 |
+
{
|
| 794 |
+
"entity": "IfcSpace",
|
| 795 |
+
"source": "qset",
|
| 796 |
+
"set_name": "ArchiCADQuantities",
|
| 797 |
+
"key": "HeizkΓΆrpernische OberflΓ€chenbereich hinten",
|
| 798 |
+
"file_count": 2,
|
| 799 |
+
"projects": [
|
| 800 |
+
"ac20",
|
| 801 |
+
"fzk_house"
|
| 802 |
+
],
|
| 803 |
+
"auto_discovered": true
|
| 804 |
+
},
|
| 805 |
+
{
|
| 806 |
+
"entity": "IfcSpace",
|
| 807 |
+
"source": "qset",
|
| 808 |
+
"set_name": "ArchiCADQuantities",
|
| 809 |
+
"key": "HeizkΓΆrpernische OberflΓ€chenbereich seitlich",
|
| 810 |
+
"file_count": 2,
|
| 811 |
+
"projects": [
|
| 812 |
+
"ac20",
|
| 813 |
+
"fzk_house"
|
| 814 |
+
],
|
| 815 |
+
"auto_discovered": true
|
| 816 |
+
},
|
| 817 |
+
{
|
| 818 |
+
"entity": "IfcSpace",
|
| 819 |
+
"source": "qset",
|
| 820 |
+
"set_name": "ArchiCADQuantities",
|
| 821 |
+
"key": "HeizkΓΆrpernische OberflΓ€chenbereich oben",
|
| 822 |
+
"file_count": 2,
|
| 823 |
+
"projects": [
|
| 824 |
+
"ac20",
|
| 825 |
+
"fzk_house"
|
| 826 |
+
],
|
| 827 |
+
"auto_discovered": true
|
| 828 |
+
},
|
| 829 |
+
{
|
| 830 |
+
"entity": "IfcSpace",
|
| 831 |
+
"source": "qset",
|
| 832 |
+
"set_name": "ArchiCADQuantities",
|
| 833 |
+
"key": "Wand-OberflΓ€chenbereich",
|
| 834 |
+
"file_count": 2,
|
| 835 |
+
"projects": [
|
| 836 |
+
"ac20",
|
| 837 |
+
"fzk_house"
|
| 838 |
+
],
|
| 839 |
+
"auto_discovered": true
|
| 840 |
+
},
|
| 841 |
+
{
|
| 842 |
+
"entity": "IfcSpace",
|
| 843 |
+
"source": "qset",
|
| 844 |
+
"set_name": "ArchiCADQuantities",
|
| 845 |
+
"key": "Fenster-OberflΓ€chenbereich",
|
| 846 |
+
"file_count": 2,
|
| 847 |
+
"projects": [
|
| 848 |
+
"ac20",
|
| 849 |
+
"fzk_house"
|
| 850 |
+
],
|
| 851 |
+
"auto_discovered": true
|
| 852 |
+
},
|
| 853 |
+
{
|
| 854 |
+
"entity": "IfcSpace",
|
| 855 |
+
"source": "qset",
|
| 856 |
+
"set_name": "ArchiCADQuantities",
|
| 857 |
+
"key": "Area",
|
| 858 |
+
"file_count": 2,
|
| 859 |
+
"projects": [
|
| 860 |
+
"molio",
|
| 861 |
+
"sixty5"
|
| 862 |
+
],
|
| 863 |
+
"auto_discovered": true
|
| 864 |
+
},
|
| 865 |
+
{
|
| 866 |
+
"entity": "IfcSpace",
|
| 867 |
+
"source": "qset",
|
| 868 |
+
"set_name": "ArchiCADQuantities",
|
| 869 |
+
"key": "Top Surface Area (Net)",
|
| 870 |
+
"file_count": 1,
|
| 871 |
+
"projects": [
|
| 872 |
+
"molio"
|
| 873 |
+
],
|
| 874 |
+
"auto_discovered": true
|
| 875 |
+
},
|
| 876 |
+
{
|
| 877 |
+
"entity": "IfcSpace",
|
| 878 |
+
"source": "qset",
|
| 879 |
+
"set_name": "ArchiCADQuantities",
|
| 880 |
+
"key": "Edge Surface Area (Net)",
|
| 881 |
+
"file_count": 1,
|
| 882 |
+
"projects": [
|
| 883 |
+
"molio"
|
| 884 |
+
],
|
| 885 |
+
"auto_discovered": true
|
| 886 |
+
},
|
| 887 |
+
{
|
| 888 |
+
"entity": "IfcSpace",
|
| 889 |
+
"source": "qset",
|
| 890 |
+
"set_name": "ArchiCADQuantities",
|
| 891 |
+
"key": "Bottom Surface Area (Net)",
|
| 892 |
+
"file_count": 1,
|
| 893 |
+
"projects": [
|
| 894 |
+
"molio"
|
| 895 |
+
],
|
| 896 |
+
"auto_discovered": true
|
| 897 |
+
},
|
| 898 |
+
{
|
| 899 |
+
"entity": "IfcSpace",
|
| 900 |
+
"source": "qset",
|
| 901 |
+
"set_name": "ArchiCADQuantities",
|
| 902 |
+
"key": "Calculated Area",
|
| 903 |
+
"file_count": 2,
|
| 904 |
+
"projects": [
|
| 905 |
+
"molio",
|
| 906 |
+
"sixty5"
|
| 907 |
+
],
|
| 908 |
+
"auto_discovered": true
|
| 909 |
+
},
|
| 910 |
+
{
|
| 911 |
+
"entity": "IfcSpace",
|
| 912 |
+
"source": "qset",
|
| 913 |
+
"set_name": "ArchiCADQuantities",
|
| 914 |
+
"key": "Measured Area",
|
| 915 |
+
"file_count": 2,
|
| 916 |
+
"projects": [
|
| 917 |
+
"molio",
|
| 918 |
+
"sixty5"
|
| 919 |
+
],
|
| 920 |
+
"auto_discovered": true
|
| 921 |
+
},
|
| 922 |
+
{
|
| 923 |
+
"entity": "IfcSpace",
|
| 924 |
+
"source": "qset",
|
| 925 |
+
"set_name": "ArchiCADQuantities",
|
| 926 |
+
"key": "Windows Surface Area",
|
| 927 |
+
"file_count": 2,
|
| 928 |
+
"projects": [
|
| 929 |
+
"molio",
|
| 930 |
+
"sixty5"
|
| 931 |
+
],
|
| 932 |
+
"auto_discovered": true
|
| 933 |
+
},
|
| 934 |
+
{
|
| 935 |
+
"entity": "IfcSpace",
|
| 936 |
+
"source": "qset",
|
| 937 |
+
"set_name": "ArchiCADQuantities",
|
| 938 |
+
"key": "Doors Surface Area",
|
| 939 |
+
"file_count": 2,
|
| 940 |
+
"projects": [
|
| 941 |
+
"molio",
|
| 942 |
+
"sixty5"
|
| 943 |
+
],
|
| 944 |
+
"auto_discovered": true
|
| 945 |
+
},
|
| 946 |
+
{
|
| 947 |
+
"entity": "IfcSpace",
|
| 948 |
+
"source": "qset",
|
| 949 |
+
"set_name": "ArchiCADQuantities",
|
| 950 |
+
"key": "Walls Surface Area",
|
| 951 |
+
"file_count": 2,
|
| 952 |
+
"projects": [
|
| 953 |
+
"molio",
|
| 954 |
+
"sixty5"
|
| 955 |
+
],
|
| 956 |
+
"auto_discovered": true
|
| 957 |
+
},
|
| 958 |
+
{
|
| 959 |
+
"entity": "IfcSpace",
|
| 960 |
+
"source": "qset",
|
| 961 |
+
"set_name": "ArchiCADQuantities",
|
| 962 |
+
"key": "Area Reducement",
|
| 963 |
+
"file_count": 2,
|
| 964 |
+
"projects": [
|
| 965 |
+
"molio",
|
| 966 |
+
"sixty5"
|
| 967 |
+
],
|
| 968 |
+
"auto_discovered": true
|
| 969 |
+
},
|
| 970 |
+
{
|
| 971 |
+
"entity": "IfcSpace",
|
| 972 |
+
"source": "qset",
|
| 973 |
+
"set_name": "ArchiCADQuantities",
|
| 974 |
+
"key": "Extracted Column Area",
|
| 975 |
+
"file_count": 2,
|
| 976 |
+
"projects": [
|
| 977 |
+
"molio",
|
| 978 |
+
"sixty5"
|
| 979 |
+
],
|
| 980 |
+
"auto_discovered": true
|
| 981 |
+
},
|
| 982 |
+
{
|
| 983 |
+
"entity": "IfcSpace",
|
| 984 |
+
"source": "qset",
|
| 985 |
+
"set_name": "ArchiCADQuantities",
|
| 986 |
+
"key": "Extracted Fill Area",
|
| 987 |
+
"file_count": 2,
|
| 988 |
+
"projects": [
|
| 989 |
+
"molio",
|
| 990 |
+
"sixty5"
|
| 991 |
+
],
|
| 992 |
+
"auto_discovered": true
|
| 993 |
+
},
|
| 994 |
+
{
|
| 995 |
+
"entity": "IfcSpace",
|
| 996 |
+
"source": "qset",
|
| 997 |
+
"set_name": "ArchiCADQuantities",
|
| 998 |
+
"key": "Extracted Low Area",
|
| 999 |
+
"file_count": 2,
|
| 1000 |
+
"projects": [
|
| 1001 |
+
"molio",
|
| 1002 |
+
"sixty5"
|
| 1003 |
+
],
|
| 1004 |
+
"auto_discovered": true
|
| 1005 |
+
},
|
| 1006 |
+
{
|
| 1007 |
+
"entity": "IfcSpace",
|
| 1008 |
+
"source": "qset",
|
| 1009 |
+
"set_name": "ArchiCADQuantities",
|
| 1010 |
+
"key": "Extracted Wall Area",
|
| 1011 |
+
"file_count": 2,
|
| 1012 |
+
"projects": [
|
| 1013 |
+
"molio",
|
| 1014 |
+
"sixty5"
|
| 1015 |
+
],
|
| 1016 |
+
"auto_discovered": true
|
| 1017 |
+
},
|
| 1018 |
+
{
|
| 1019 |
+
"entity": "IfcSpace",
|
| 1020 |
+
"source": "qset",
|
| 1021 |
+
"set_name": "ArchiCADQuantities",
|
| 1022 |
+
"key": "Measured Net Area",
|
| 1023 |
+
"file_count": 1,
|
| 1024 |
+
"projects": [
|
| 1025 |
+
"molio"
|
| 1026 |
+
],
|
| 1027 |
+
"auto_discovered": true
|
| 1028 |
+
},
|
| 1029 |
+
{
|
| 1030 |
+
"entity": "IfcSpace",
|
| 1031 |
+
"source": "qset",
|
| 1032 |
+
"set_name": "ArchiCADQuantities",
|
| 1033 |
+
"key": "Reduced Area",
|
| 1034 |
+
"file_count": 1,
|
| 1035 |
+
"projects": [
|
| 1036 |
+
"molio"
|
| 1037 |
+
],
|
| 1038 |
+
"auto_discovered": true
|
| 1039 |
+
},
|
| 1040 |
+
{
|
| 1041 |
+
"entity": "IfcSpace",
|
| 1042 |
+
"source": "qset",
|
| 1043 |
+
"set_name": "ArchiCADQuantities",
|
| 1044 |
+
"key": "Total Extracted Area",
|
| 1045 |
+
"file_count": 2,
|
| 1046 |
+
"projects": [
|
| 1047 |
+
"molio",
|
| 1048 |
+
"sixty5"
|
| 1049 |
+
],
|
| 1050 |
+
"auto_discovered": true
|
| 1051 |
+
},
|
| 1052 |
+
{
|
| 1053 |
+
"entity": "IfcSpace",
|
| 1054 |
+
"source": "qset",
|
| 1055 |
+
"set_name": "ArchiCADQuantities",
|
| 1056 |
+
"key": "Wall Inset Back Side Surface Area",
|
| 1057 |
+
"file_count": 2,
|
| 1058 |
+
"projects": [
|
| 1059 |
+
"molio",
|
| 1060 |
+
"sixty5"
|
| 1061 |
+
],
|
| 1062 |
+
"auto_discovered": true
|
| 1063 |
+
},
|
| 1064 |
+
{
|
| 1065 |
+
"entity": "IfcSpace",
|
| 1066 |
+
"source": "qset",
|
| 1067 |
+
"set_name": "ArchiCADQuantities",
|
| 1068 |
+
"key": "Wall Inset Side Surface Area",
|
| 1069 |
+
"file_count": 2,
|
| 1070 |
+
"projects": [
|
| 1071 |
+
"molio",
|
| 1072 |
+
"sixty5"
|
| 1073 |
+
],
|
| 1074 |
+
"auto_discovered": true
|
| 1075 |
+
},
|
| 1076 |
+
{
|
| 1077 |
+
"entity": "IfcSpace",
|
| 1078 |
+
"source": "qset",
|
| 1079 |
+
"set_name": "ArchiCADQuantities",
|
| 1080 |
+
"key": "Wall Inset Top Surface Area",
|
| 1081 |
+
"file_count": 2,
|
| 1082 |
+
"projects": [
|
| 1083 |
+
"molio",
|
| 1084 |
+
"sixty5"
|
| 1085 |
+
],
|
| 1086 |
+
"auto_discovered": true
|
| 1087 |
+
},
|
| 1088 |
+
{
|
| 1089 |
+
"entity": "IfcSpace",
|
| 1090 |
+
"source": "qset",
|
| 1091 |
+
"set_name": "ArchiCADQuantities",
|
| 1092 |
+
"key": "Extracted Curtain Wall Area",
|
| 1093 |
+
"file_count": 1,
|
| 1094 |
+
"projects": [
|
| 1095 |
+
"molio"
|
| 1096 |
+
],
|
| 1097 |
+
"auto_discovered": true
|
| 1098 |
+
},
|
| 1099 |
+
{
|
| 1100 |
+
"entity": "IfcSpace",
|
| 1101 |
+
"source": "qset",
|
| 1102 |
+
"set_name": "ArchiCADQuantities",
|
| 1103 |
+
"key": "Net Area",
|
| 1104 |
+
"file_count": 1,
|
| 1105 |
+
"projects": [
|
| 1106 |
+
"sixty5"
|
| 1107 |
+
],
|
| 1108 |
+
"auto_discovered": true
|
| 1109 |
+
},
|
| 1110 |
+
{
|
| 1111 |
+
"entity": "IfcSpace",
|
| 1112 |
+
"source": "qset",
|
| 1113 |
+
"set_name": "Qto_SpaceBaseQuantities",
|
| 1114 |
+
"key": "GrossCeilingArea",
|
| 1115 |
+
"file_count": 2,
|
| 1116 |
+
"projects": [
|
| 1117 |
+
"fantasy_office_building_1",
|
| 1118 |
+
"fantasy_office_building_2"
|
| 1119 |
+
],
|
| 1120 |
+
"auto_discovered": true
|
| 1121 |
+
},
|
| 1122 |
+
{
|
| 1123 |
+
"entity": "IfcSpace",
|
| 1124 |
+
"source": "qset",
|
| 1125 |
+
"set_name": "(unnamed)",
|
| 1126 |
+
"key": "NetFootprintArea",
|
| 1127 |
+
"file_count": 1,
|
| 1128 |
+
"projects": [
|
| 1129 |
+
"hitos"
|
| 1130 |
+
],
|
| 1131 |
+
"auto_discovered": true
|
| 1132 |
+
},
|
| 1133 |
+
{
|
| 1134 |
+
"entity": "IfcSlab",
|
| 1135 |
+
"source": "qset",
|
| 1136 |
+
"set_name": "ArchiCADQuantities",
|
| 1137 |
+
"key": "OberflΓ€chenbereich",
|
| 1138 |
+
"file_count": 2,
|
| 1139 |
+
"projects": [
|
| 1140 |
+
"ac20",
|
| 1141 |
+
"fzk_house"
|
| 1142 |
+
],
|
| 1143 |
+
"auto_discovered": true
|
| 1144 |
+
},
|
| 1145 |
+
{
|
| 1146 |
+
"entity": "IfcSlab",
|
| 1147 |
+
"source": "qset",
|
| 1148 |
+
"set_name": "ArchiCADQuantities",
|
| 1149 |
+
"key": "FlΓ€che",
|
| 1150 |
+
"file_count": 2,
|
| 1151 |
+
"projects": [
|
| 1152 |
+
"ac20",
|
| 1153 |
+
"fzk_house"
|
| 1154 |
+
],
|
| 1155 |
+
"auto_discovered": true
|
| 1156 |
+
},
|
| 1157 |
+
{
|
| 1158 |
+
"entity": "IfcSlab",
|
| 1159 |
+
"source": "qset",
|
| 1160 |
+
"set_name": "ArchiCADQuantities",
|
| 1161 |
+
"key": "OberflΓ€chenbereich Unterseite",
|
| 1162 |
+
"file_count": 2,
|
| 1163 |
+
"projects": [
|
| 1164 |
+
"ac20",
|
| 1165 |
+
"fzk_house"
|
| 1166 |
+
],
|
| 1167 |
+
"auto_discovered": true
|
| 1168 |
+
},
|
| 1169 |
+
{
|
| 1170 |
+
"entity": "IfcSlab",
|
| 1171 |
+
"source": "qset",
|
| 1172 |
+
"set_name": "ArchiCADQuantities",
|
| 1173 |
+
"key": "OberflΓ€chenbereich Oberseite",
|
| 1174 |
+
"file_count": 2,
|
| 1175 |
+
"projects": [
|
| 1176 |
+
"ac20",
|
| 1177 |
+
"fzk_house"
|
| 1178 |
+
],
|
| 1179 |
+
"auto_discovered": true
|
| 1180 |
+
},
|
| 1181 |
+
{
|
| 1182 |
+
"entity": "IfcSlab",
|
| 1183 |
+
"source": "qset",
|
| 1184 |
+
"set_name": "ArchiCADQuantities",
|
| 1185 |
+
"key": "Kante OberflΓ€chenbereich",
|
| 1186 |
+
"file_count": 2,
|
| 1187 |
+
"projects": [
|
| 1188 |
+
"ac20",
|
| 1189 |
+
"fzk_house"
|
| 1190 |
+
],
|
| 1191 |
+
"auto_discovered": true
|
| 1192 |
+
},
|
| 1193 |
+
{
|
| 1194 |
+
"entity": "IfcSlab",
|
| 1195 |
+
"source": "qset",
|
| 1196 |
+
"set_name": "ArchiCADQuantities",
|
| 1197 |
+
"key": "LΓΆcher OberflΓ€chenbereich",
|
| 1198 |
+
"file_count": 2,
|
| 1199 |
+
"projects": [
|
| 1200 |
+
"ac20",
|
| 1201 |
+
"fzk_house"
|
| 1202 |
+
],
|
| 1203 |
+
"auto_discovered": true
|
| 1204 |
+
},
|
| 1205 |
+
{
|
| 1206 |
+
"entity": "IfcSlab",
|
| 1207 |
+
"source": "qset",
|
| 1208 |
+
"set_name": "ArchiCADQuantities",
|
| 1209 |
+
"key": "Brutto-OberflΓ€che der Deckenoberseite",
|
| 1210 |
+
"file_count": 2,
|
| 1211 |
+
"projects": [
|
| 1212 |
+
"ac20",
|
| 1213 |
+
"fzk_house"
|
| 1214 |
+
],
|
| 1215 |
+
"auto_discovered": true
|
| 1216 |
+
},
|
| 1217 |
+
{
|
| 1218 |
+
"entity": "IfcSlab",
|
| 1219 |
+
"source": "qset",
|
| 1220 |
+
"set_name": "ArchiCADQuantities",
|
| 1221 |
+
"key": "Brutto-OberflΓ€chenbereich der Deckenunterseite",
|
| 1222 |
+
"file_count": 2,
|
| 1223 |
+
"projects": [
|
| 1224 |
+
"ac20",
|
| 1225 |
+
"fzk_house"
|
| 1226 |
+
],
|
| 1227 |
+
"auto_discovered": true
|
| 1228 |
+
},
|
| 1229 |
+
{
|
| 1230 |
+
"entity": "IfcSlab",
|
| 1231 |
+
"source": "qset",
|
| 1232 |
+
"set_name": "ArchiCADQuantities",
|
| 1233 |
+
"key": "Brutto-OberflΓ€chenbereich der Deckenkanten",
|
| 1234 |
+
"file_count": 2,
|
| 1235 |
+
"projects": [
|
| 1236 |
+
"ac20",
|
| 1237 |
+
"fzk_house"
|
| 1238 |
+
],
|
| 1239 |
+
"auto_discovered": true
|
| 1240 |
+
},
|
| 1241 |
+
{
|
| 1242 |
+
"entity": "IfcSlab",
|
| 1243 |
+
"source": "qset",
|
| 1244 |
+
"set_name": "ArchiCADQuantities",
|
| 1245 |
+
"key": "Konditionaler OberflΓ€chenbereich der Unterseite",
|
| 1246 |
+
"file_count": 2,
|
| 1247 |
+
"projects": [
|
| 1248 |
+
"ac20",
|
| 1249 |
+
"fzk_house"
|
| 1250 |
+
],
|
| 1251 |
+
"auto_discovered": true
|
| 1252 |
+
},
|
| 1253 |
+
{
|
| 1254 |
+
"entity": "IfcSlab",
|
| 1255 |
+
"source": "qset",
|
| 1256 |
+
"set_name": "ArchiCADQuantities",
|
| 1257 |
+
"key": "Konditionaler OberflΓ€chenbereich der Oberseite",
|
| 1258 |
+
"file_count": 2,
|
| 1259 |
+
"projects": [
|
| 1260 |
+
"ac20",
|
| 1261 |
+
"fzk_house"
|
| 1262 |
+
],
|
| 1263 |
+
"auto_discovered": true
|
| 1264 |
+
},
|
| 1265 |
+
{
|
| 1266 |
+
"entity": "IfcSlab",
|
| 1267 |
+
"source": "qset",
|
| 1268 |
+
"set_name": "ArchiCADQuantities",
|
| 1269 |
+
"key": "Brutto-OberflΓ€chenbereich der Deckenoberseite mit LΓΆchern",
|
| 1270 |
+
"file_count": 2,
|
| 1271 |
+
"projects": [
|
| 1272 |
+
"ac20",
|
| 1273 |
+
"fzk_house"
|
| 1274 |
+
],
|
| 1275 |
+
"auto_discovered": true
|
| 1276 |
+
},
|
| 1277 |
+
{
|
| 1278 |
+
"entity": "IfcSlab",
|
| 1279 |
+
"source": "qset",
|
| 1280 |
+
"set_name": "ArchiCADQuantities",
|
| 1281 |
+
"key": "Brutto-OberflΓ€chenbereich der Deckenunterseite mit LΓΆchern",
|
| 1282 |
+
"file_count": 2,
|
| 1283 |
+
"projects": [
|
| 1284 |
+
"ac20",
|
| 1285 |
+
"fzk_house"
|
| 1286 |
+
],
|
| 1287 |
+
"auto_discovered": true
|
| 1288 |
+
},
|
| 1289 |
+
{
|
| 1290 |
+
"entity": "IfcSlab",
|
| 1291 |
+
"source": "qset",
|
| 1292 |
+
"set_name": "ArchiCADQuantities",
|
| 1293 |
+
"key": "Brutto-OberflΓ€chenbereich der Deckenkanten mit LΓΆchern",
|
| 1294 |
+
"file_count": 2,
|
| 1295 |
+
"projects": [
|
| 1296 |
+
"ac20",
|
| 1297 |
+
"fzk_house"
|
| 1298 |
+
],
|
| 1299 |
+
"auto_discovered": true
|
| 1300 |
+
},
|
| 1301 |
+
{
|
| 1302 |
+
"entity": "IfcSlab",
|
| 1303 |
+
"source": "qset",
|
| 1304 |
+
"set_name": "ArchiCADQuantities",
|
| 1305 |
+
"key": "Netto-OberflΓ€chenbereich Unterseite",
|
| 1306 |
+
"file_count": 2,
|
| 1307 |
+
"projects": [
|
| 1308 |
+
"ac20",
|
| 1309 |
+
"fzk_house"
|
| 1310 |
+
],
|
| 1311 |
+
"auto_discovered": true
|
| 1312 |
+
},
|
| 1313 |
+
{
|
| 1314 |
+
"entity": "IfcSlab",
|
| 1315 |
+
"source": "qset",
|
| 1316 |
+
"set_name": "ArchiCADQuantities",
|
| 1317 |
+
"key": "Netto-OberflΓ€chenbereich Oberseite",
|
| 1318 |
+
"file_count": 2,
|
| 1319 |
+
"projects": [
|
| 1320 |
+
"ac20",
|
| 1321 |
+
"fzk_house"
|
| 1322 |
+
],
|
| 1323 |
+
"auto_discovered": true
|
| 1324 |
+
},
|
| 1325 |
+
{
|
| 1326 |
+
"entity": "IfcSlab",
|
| 1327 |
+
"source": "qset",
|
| 1328 |
+
"set_name": "ArchiCADQuantities",
|
| 1329 |
+
"key": "Netto-OberflΓ€chenbereich der Kante",
|
| 1330 |
+
"file_count": 2,
|
| 1331 |
+
"projects": [
|
| 1332 |
+
"ac20",
|
| 1333 |
+
"fzk_house"
|
| 1334 |
+
],
|
| 1335 |
+
"auto_discovered": true
|
| 1336 |
+
},
|
| 1337 |
+
{
|
| 1338 |
+
"entity": "IfcSlab",
|
| 1339 |
+
"source": "qset",
|
| 1340 |
+
"set_name": "ArchiCADQuantities",
|
| 1341 |
+
"key": "Brutto-OberflΓ€chenbereich an der Unterseite",
|
| 1342 |
+
"file_count": 2,
|
| 1343 |
+
"projects": [
|
| 1344 |
+
"ac20",
|
| 1345 |
+
"fzk_house"
|
| 1346 |
+
],
|
| 1347 |
+
"auto_discovered": true
|
| 1348 |
+
},
|
| 1349 |
+
{
|
| 1350 |
+
"entity": "IfcSlab",
|
| 1351 |
+
"source": "qset",
|
| 1352 |
+
"set_name": "ArchiCADQuantities",
|
| 1353 |
+
"key": "Brutto-OberflΓ€chenbereich an der Oberseite",
|
| 1354 |
+
"file_count": 2,
|
| 1355 |
+
"projects": [
|
| 1356 |
+
"ac20",
|
| 1357 |
+
"fzk_house"
|
| 1358 |
+
],
|
| 1359 |
+
"auto_discovered": true
|
| 1360 |
+
},
|
| 1361 |
+
{
|
| 1362 |
+
"entity": "IfcSlab",
|
| 1363 |
+
"source": "qset",
|
| 1364 |
+
"set_name": "ArchiCADQuantities",
|
| 1365 |
+
"key": "Brutto-OberflΓ€chenbereich der Dachkanten",
|
| 1366 |
+
"file_count": 2,
|
| 1367 |
+
"projects": [
|
| 1368 |
+
"ac20",
|
| 1369 |
+
"fzk_house"
|
| 1370 |
+
],
|
| 1371 |
+
"auto_discovered": true
|
| 1372 |
+
},
|
| 1373 |
+
{
|
| 1374 |
+
"entity": "IfcSlab",
|
| 1375 |
+
"source": "qset",
|
| 1376 |
+
"set_name": "ArchiCADQuantities",
|
| 1377 |
+
"key": "Konditionaler OberflΓ€chenbereich unten",
|
| 1378 |
+
"file_count": 2,
|
| 1379 |
+
"projects": [
|
| 1380 |
+
"ac20",
|
| 1381 |
+
"fzk_house"
|
| 1382 |
+
],
|
| 1383 |
+
"auto_discovered": true
|
| 1384 |
+
},
|
| 1385 |
+
{
|
| 1386 |
+
"entity": "IfcSlab",
|
| 1387 |
+
"source": "qset",
|
| 1388 |
+
"set_name": "ArchiCADQuantities",
|
| 1389 |
+
"key": "Konditionaler OberflΓ€chenbereich oben",
|
| 1390 |
+
"file_count": 2,
|
| 1391 |
+
"projects": [
|
| 1392 |
+
"ac20",
|
| 1393 |
+
"fzk_house"
|
| 1394 |
+
],
|
| 1395 |
+
"auto_discovered": true
|
| 1396 |
+
},
|
| 1397 |
+
{
|
| 1398 |
+
"entity": "IfcSlab",
|
| 1399 |
+
"source": "qset",
|
| 1400 |
+
"set_name": "ArchiCADQuantities",
|
| 1401 |
+
"key": "OberflΓ€chenbereich der Γffnungen",
|
| 1402 |
+
"file_count": 2,
|
| 1403 |
+
"projects": [
|
| 1404 |
+
"ac20",
|
| 1405 |
+
"fzk_house"
|
| 1406 |
+
],
|
| 1407 |
+
"auto_discovered": true
|
| 1408 |
+
},
|
| 1409 |
+
{
|
| 1410 |
+
"entity": "IfcSlab",
|
| 1411 |
+
"source": "qset",
|
| 1412 |
+
"set_name": "ArchiCADQuantities",
|
| 1413 |
+
"key": "Area",
|
| 1414 |
+
"file_count": 2,
|
| 1415 |
+
"projects": [
|
| 1416 |
+
"molio",
|
| 1417 |
+
"sixty5"
|
| 1418 |
+
],
|
| 1419 |
+
"auto_discovered": true
|
| 1420 |
+
},
|
| 1421 |
+
{
|
| 1422 |
+
"entity": "IfcSlab",
|
| 1423 |
+
"source": "qset",
|
| 1424 |
+
"set_name": "ArchiCADQuantities",
|
| 1425 |
+
"key": "Surface Area",
|
| 1426 |
+
"file_count": 2,
|
| 1427 |
+
"projects": [
|
| 1428 |
+
"molio",
|
| 1429 |
+
"sixty5"
|
| 1430 |
+
],
|
| 1431 |
+
"auto_discovered": true
|
| 1432 |
+
},
|
| 1433 |
+
{
|
| 1434 |
+
"entity": "IfcSlab",
|
| 1435 |
+
"source": "qset",
|
| 1436 |
+
"set_name": "ArchiCADQuantities",
|
| 1437 |
+
"key": "Top Surface Area (Net)",
|
| 1438 |
+
"file_count": 1,
|
| 1439 |
+
"projects": [
|
| 1440 |
+
"molio"
|
| 1441 |
+
],
|
| 1442 |
+
"auto_discovered": true
|
| 1443 |
+
},
|
| 1444 |
+
{
|
| 1445 |
+
"entity": "IfcSlab",
|
| 1446 |
+
"source": "qset",
|
| 1447 |
+
"set_name": "ArchiCADQuantities",
|
| 1448 |
+
"key": "Top Surface Area (Conditional)",
|
| 1449 |
+
"file_count": 1,
|
| 1450 |
+
"projects": [
|
| 1451 |
+
"molio"
|
| 1452 |
+
],
|
| 1453 |
+
"auto_discovered": true
|
| 1454 |
+
},
|
| 1455 |
+
{
|
| 1456 |
+
"entity": "IfcSlab",
|
| 1457 |
+
"source": "qset",
|
| 1458 |
+
"set_name": "ArchiCADQuantities",
|
| 1459 |
+
"key": "Edge Surface Area (Net)",
|
| 1460 |
+
"file_count": 1,
|
| 1461 |
+
"projects": [
|
| 1462 |
+
"molio"
|
| 1463 |
+
],
|
| 1464 |
+
"auto_discovered": true
|
| 1465 |
+
},
|
| 1466 |
+
{
|
| 1467 |
+
"entity": "IfcSlab",
|
| 1468 |
+
"source": "qset",
|
| 1469 |
+
"set_name": "ArchiCADQuantities",
|
| 1470 |
+
"key": "Bottom Surface Area (Net)",
|
| 1471 |
+
"file_count": 1,
|
| 1472 |
+
"projects": [
|
| 1473 |
+
"molio"
|
| 1474 |
+
],
|
| 1475 |
+
"auto_discovered": true
|
| 1476 |
+
},
|
| 1477 |
+
{
|
| 1478 |
+
"entity": "IfcSlab",
|
| 1479 |
+
"source": "qset",
|
| 1480 |
+
"set_name": "ArchiCADQuantities",
|
| 1481 |
+
"key": "Bottom Surface Area (Conditional)",
|
| 1482 |
+
"file_count": 1,
|
| 1483 |
+
"projects": [
|
| 1484 |
+
"molio"
|
| 1485 |
+
],
|
| 1486 |
+
"auto_discovered": true
|
| 1487 |
+
},
|
| 1488 |
+
{
|
| 1489 |
+
"entity": "IfcSlab",
|
| 1490 |
+
"source": "qset",
|
| 1491 |
+
"set_name": "ArchiCADQuantities",
|
| 1492 |
+
"key": "Bottom Surface Area (Gross)",
|
| 1493 |
+
"file_count": 1,
|
| 1494 |
+
"projects": [
|
| 1495 |
+
"molio"
|
| 1496 |
+
],
|
| 1497 |
+
"auto_discovered": true
|
| 1498 |
+
},
|
| 1499 |
+
{
|
| 1500 |
+
"entity": "IfcSlab",
|
| 1501 |
+
"source": "qset",
|
| 1502 |
+
"set_name": "ArchiCADQuantities",
|
| 1503 |
+
"key": "Edge Surface Area (Gross)",
|
| 1504 |
+
"file_count": 1,
|
| 1505 |
+
"projects": [
|
| 1506 |
+
"molio"
|
| 1507 |
+
],
|
| 1508 |
+
"auto_discovered": true
|
| 1509 |
+
},
|
| 1510 |
+
{
|
| 1511 |
+
"entity": "IfcSlab",
|
| 1512 |
+
"source": "qset",
|
| 1513 |
+
"set_name": "ArchiCADQuantities",
|
| 1514 |
+
"key": "Top Surface Area (Gross)",
|
| 1515 |
+
"file_count": 1,
|
| 1516 |
+
"projects": [
|
| 1517 |
+
"molio"
|
| 1518 |
+
],
|
| 1519 |
+
"auto_discovered": true
|
| 1520 |
+
},
|
| 1521 |
+
{
|
| 1522 |
+
"entity": "IfcSlab",
|
| 1523 |
+
"source": "qset",
|
| 1524 |
+
"set_name": "ArchiCADQuantities",
|
| 1525 |
+
"key": "Surface Area of the Slab Top (Conditional)",
|
| 1526 |
+
"file_count": 1,
|
| 1527 |
+
"projects": [
|
| 1528 |
+
"molio"
|
| 1529 |
+
],
|
| 1530 |
+
"auto_discovered": true
|
| 1531 |
+
},
|
| 1532 |
+
{
|
| 1533 |
+
"entity": "IfcSlab",
|
| 1534 |
+
"source": "qset",
|
| 1535 |
+
"set_name": "ArchiCADQuantities",
|
| 1536 |
+
"key": "Surface Area of the Slab Bottom (Gross)",
|
| 1537 |
+
"file_count": 1,
|
| 1538 |
+
"projects": [
|
| 1539 |
+
"molio"
|
| 1540 |
+
],
|
| 1541 |
+
"auto_discovered": true
|
| 1542 |
+
},
|
| 1543 |
+
{
|
| 1544 |
+
"entity": "IfcSlab",
|
| 1545 |
+
"source": "qset",
|
| 1546 |
+
"set_name": "ArchiCADQuantities",
|
| 1547 |
+
"key": "Surface Area of the Slab Bottom (Gross, with holes)",
|
| 1548 |
+
"file_count": 1,
|
| 1549 |
+
"projects": [
|
| 1550 |
+
"molio"
|
| 1551 |
+
],
|
| 1552 |
+
"auto_discovered": true
|
| 1553 |
+
},
|
| 1554 |
+
{
|
| 1555 |
+
"entity": "IfcSlab",
|
| 1556 |
+
"source": "qset",
|
| 1557 |
+
"set_name": "ArchiCADQuantities",
|
| 1558 |
+
"key": "Surface Area of the Slab Edges (Gross)",
|
| 1559 |
+
"file_count": 1,
|
| 1560 |
+
"projects": [
|
| 1561 |
+
"molio"
|
| 1562 |
+
],
|
| 1563 |
+
"auto_discovered": true
|
| 1564 |
+
},
|
| 1565 |
+
{
|
| 1566 |
+
"entity": "IfcSlab",
|
| 1567 |
+
"source": "qset",
|
| 1568 |
+
"set_name": "ArchiCADQuantities",
|
| 1569 |
+
"key": "Surface Area of the Slab Edges (Gross, with holes)",
|
| 1570 |
+
"file_count": 1,
|
| 1571 |
+
"projects": [
|
| 1572 |
+
"molio"
|
| 1573 |
+
],
|
| 1574 |
+
"auto_discovered": true
|
| 1575 |
+
},
|
| 1576 |
+
{
|
| 1577 |
+
"entity": "IfcSlab",
|
| 1578 |
+
"source": "qset",
|
| 1579 |
+
"set_name": "ArchiCADQuantities",
|
| 1580 |
+
"key": "Surface Area of the Slab Top (Gross)",
|
| 1581 |
+
"file_count": 1,
|
| 1582 |
+
"projects": [
|
| 1583 |
+
"molio"
|
| 1584 |
+
],
|
| 1585 |
+
"auto_discovered": true
|
| 1586 |
+
},
|
| 1587 |
+
{
|
| 1588 |
+
"entity": "IfcSlab",
|
| 1589 |
+
"source": "qset",
|
| 1590 |
+
"set_name": "ArchiCADQuantities",
|
| 1591 |
+
"key": "Surface Area of the Slab Top (Gross, with holes)",
|
| 1592 |
+
"file_count": 1,
|
| 1593 |
+
"projects": [
|
| 1594 |
+
"molio"
|
| 1595 |
+
],
|
| 1596 |
+
"auto_discovered": true
|
| 1597 |
+
},
|
| 1598 |
+
{
|
| 1599 |
+
"entity": "IfcSlab",
|
| 1600 |
+
"source": "qset",
|
| 1601 |
+
"set_name": "ArchiCADQuantities",
|
| 1602 |
+
"key": "Holes Surface Area",
|
| 1603 |
+
"file_count": 2,
|
| 1604 |
+
"projects": [
|
| 1605 |
+
"molio",
|
| 1606 |
+
"sixty5"
|
| 1607 |
+
],
|
| 1608 |
+
"auto_discovered": true
|
| 1609 |
+
},
|
| 1610 |
+
{
|
| 1611 |
+
"entity": "IfcSlab",
|
| 1612 |
+
"source": "qset",
|
| 1613 |
+
"set_name": "ArchiCADQuantities",
|
| 1614 |
+
"key": "Bottom Surface Area",
|
| 1615 |
+
"file_count": 1,
|
| 1616 |
+
"projects": [
|
| 1617 |
+
"sixty5"
|
| 1618 |
+
],
|
| 1619 |
+
"auto_discovered": true
|
| 1620 |
+
},
|
| 1621 |
+
{
|
| 1622 |
+
"entity": "IfcSlab",
|
| 1623 |
+
"source": "qset",
|
| 1624 |
+
"set_name": "ArchiCADQuantities",
|
| 1625 |
+
"key": "Conditional Surface Area of the Bottom",
|
| 1626 |
+
"file_count": 1,
|
| 1627 |
+
"projects": [
|
| 1628 |
+
"sixty5"
|
| 1629 |
+
],
|
| 1630 |
+
"auto_discovered": true
|
| 1631 |
+
},
|
| 1632 |
+
{
|
| 1633 |
+
"entity": "IfcSlab",
|
| 1634 |
+
"source": "qset",
|
| 1635 |
+
"set_name": "ArchiCADQuantities",
|
| 1636 |
+
"key": "Conditional Surface Area of the Top",
|
| 1637 |
+
"file_count": 1,
|
| 1638 |
+
"projects": [
|
| 1639 |
+
"sixty5"
|
| 1640 |
+
],
|
| 1641 |
+
"auto_discovered": true
|
| 1642 |
+
},
|
| 1643 |
+
{
|
| 1644 |
+
"entity": "IfcSlab",
|
| 1645 |
+
"source": "qset",
|
| 1646 |
+
"set_name": "ArchiCADQuantities",
|
| 1647 |
+
"key": "Edge Surface Area",
|
| 1648 |
+
"file_count": 1,
|
| 1649 |
+
"projects": [
|
| 1650 |
+
"sixty5"
|
| 1651 |
+
],
|
| 1652 |
+
"auto_discovered": true
|
| 1653 |
+
},
|
| 1654 |
+
{
|
| 1655 |
+
"entity": "IfcSlab",
|
| 1656 |
+
"source": "qset",
|
| 1657 |
+
"set_name": "ArchiCADQuantities",
|
| 1658 |
+
"key": "Gross Surface Area of the Slab Bottom",
|
| 1659 |
+
"file_count": 1,
|
| 1660 |
+
"projects": [
|
| 1661 |
+
"sixty5"
|
| 1662 |
+
],
|
| 1663 |
+
"auto_discovered": true
|
| 1664 |
+
},
|
| 1665 |
+
{
|
| 1666 |
+
"entity": "IfcSlab",
|
| 1667 |
+
"source": "qset",
|
| 1668 |
+
"set_name": "ArchiCADQuantities",
|
| 1669 |
+
"key": "Gross Surface Area of the Slab Bottom with Holes",
|
| 1670 |
+
"file_count": 1,
|
| 1671 |
+
"projects": [
|
| 1672 |
+
"sixty5"
|
| 1673 |
+
],
|
| 1674 |
+
"auto_discovered": true
|
| 1675 |
+
},
|
| 1676 |
+
{
|
| 1677 |
+
"entity": "IfcSlab",
|
| 1678 |
+
"source": "qset",
|
| 1679 |
+
"set_name": "ArchiCADQuantities",
|
| 1680 |
+
"key": "Gross Surface Area of the Slab Edges",
|
| 1681 |
+
"file_count": 1,
|
| 1682 |
+
"projects": [
|
| 1683 |
+
"sixty5"
|
| 1684 |
+
],
|
| 1685 |
+
"auto_discovered": true
|
| 1686 |
+
},
|
| 1687 |
+
{
|
| 1688 |
+
"entity": "IfcSlab",
|
| 1689 |
+
"source": "qset",
|
| 1690 |
+
"set_name": "ArchiCADQuantities",
|
| 1691 |
+
"key": "Gross Surface Area of the Slab Edges with Holes",
|
| 1692 |
+
"file_count": 1,
|
| 1693 |
+
"projects": [
|
| 1694 |
+
"sixty5"
|
| 1695 |
+
],
|
| 1696 |
+
"auto_discovered": true
|
| 1697 |
+
},
|
| 1698 |
+
{
|
| 1699 |
+
"entity": "IfcSlab",
|
| 1700 |
+
"source": "qset",
|
| 1701 |
+
"set_name": "ArchiCADQuantities",
|
| 1702 |
+
"key": "Gross Surface Area of the Slab Top",
|
| 1703 |
+
"file_count": 1,
|
| 1704 |
+
"projects": [
|
| 1705 |
+
"sixty5"
|
| 1706 |
+
],
|
| 1707 |
+
"auto_discovered": true
|
| 1708 |
+
},
|
| 1709 |
+
{
|
| 1710 |
+
"entity": "IfcSlab",
|
| 1711 |
+
"source": "qset",
|
| 1712 |
+
"set_name": "ArchiCADQuantities",
|
| 1713 |
+
"key": "Gross Surface Area of the Slab Top with Holes",
|
| 1714 |
+
"file_count": 1,
|
| 1715 |
+
"projects": [
|
| 1716 |
+
"sixty5"
|
| 1717 |
+
],
|
| 1718 |
+
"auto_discovered": true
|
| 1719 |
+
},
|
| 1720 |
+
{
|
| 1721 |
+
"entity": "IfcSlab",
|
| 1722 |
+
"source": "qset",
|
| 1723 |
+
"set_name": "ArchiCADQuantities",
|
| 1724 |
+
"key": "Top Surface Area",
|
| 1725 |
+
"file_count": 1,
|
| 1726 |
+
"projects": [
|
| 1727 |
+
"sixty5"
|
| 1728 |
+
],
|
| 1729 |
+
"auto_discovered": true
|
| 1730 |
+
},
|
| 1731 |
+
{
|
| 1732 |
+
"entity": "IfcSlab",
|
| 1733 |
+
"source": "qset",
|
| 1734 |
+
"set_name": "(unnamed)",
|
| 1735 |
+
"key": "NetFootprintArea",
|
| 1736 |
+
"file_count": 1,
|
| 1737 |
+
"projects": [
|
| 1738 |
+
"hitos"
|
| 1739 |
+
],
|
| 1740 |
+
"auto_discovered": true
|
| 1741 |
+
},
|
| 1742 |
+
{
|
| 1743 |
+
"entity": "IfcRoof",
|
| 1744 |
+
"source": "qset",
|
| 1745 |
+
"set_name": "Qto_RoofBaseQuantities",
|
| 1746 |
+
"key": "ProjectedArea",
|
| 1747 |
+
"file_count": 2,
|
| 1748 |
+
"projects": [
|
| 1749 |
+
"fantasy_office_building_1",
|
| 1750 |
+
"fantasy_office_building_2"
|
| 1751 |
+
],
|
| 1752 |
+
"auto_discovered": true
|
| 1753 |
+
},
|
| 1754 |
+
{
|
| 1755 |
+
"entity": "IfcWall",
|
| 1756 |
+
"source": "qset",
|
| 1757 |
+
"set_name": "BaseQuantities",
|
| 1758 |
+
"key": "GrossFootprintArea",
|
| 1759 |
+
"file_count": 7,
|
| 1760 |
+
"projects": [
|
| 1761 |
+
"ac20",
|
| 1762 |
+
"fzk_house",
|
| 1763 |
+
"molio",
|
| 1764 |
+
"sixty5",
|
| 1765 |
+
"smiley_west"
|
| 1766 |
+
],
|
| 1767 |
+
"auto_discovered": true
|
| 1768 |
+
},
|
| 1769 |
+
{
|
| 1770 |
+
"entity": "IfcWall",
|
| 1771 |
+
"source": "qset",
|
| 1772 |
+
"set_name": "BaseQuantities",
|
| 1773 |
+
"key": "NetFootprintArea",
|
| 1774 |
+
"file_count": 6,
|
| 1775 |
+
"projects": [
|
| 1776 |
+
"ac20",
|
| 1777 |
+
"fzk_house",
|
| 1778 |
+
"molio",
|
| 1779 |
+
"sixty5",
|
| 1780 |
+
"smiley_west"
|
| 1781 |
+
],
|
| 1782 |
+
"auto_discovered": true
|
| 1783 |
+
},
|
| 1784 |
+
{
|
| 1785 |
+
"entity": "IfcWall",
|
| 1786 |
+
"source": "qset",
|
| 1787 |
+
"set_name": "BaseQuantities",
|
| 1788 |
+
"key": "GrossSideArea",
|
| 1789 |
+
"file_count": 7,
|
| 1790 |
+
"projects": [
|
| 1791 |
+
"ac20",
|
| 1792 |
+
"fzk_house",
|
| 1793 |
+
"molio",
|
| 1794 |
+
"sixty5",
|
| 1795 |
+
"smiley_west"
|
| 1796 |
+
],
|
| 1797 |
+
"auto_discovered": true
|
| 1798 |
+
},
|
| 1799 |
+
{
|
| 1800 |
+
"entity": "IfcWall",
|
| 1801 |
+
"source": "qset",
|
| 1802 |
+
"set_name": "BaseQuantities",
|
| 1803 |
+
"key": "NetSideArea",
|
| 1804 |
+
"file_count": 7,
|
| 1805 |
+
"projects": [
|
| 1806 |
+
"ac20",
|
| 1807 |
+
"fzk_house",
|
| 1808 |
+
"molio",
|
| 1809 |
+
"sixty5",
|
| 1810 |
+
"smiley_west"
|
| 1811 |
+
],
|
| 1812 |
+
"auto_discovered": true
|
| 1813 |
+
},
|
| 1814 |
+
{
|
| 1815 |
+
"entity": "IfcWall",
|
| 1816 |
+
"source": "qset",
|
| 1817 |
+
"set_name": "ArchiCADQuantities",
|
| 1818 |
+
"key": "OberflΓ€chenbereich",
|
| 1819 |
+
"file_count": 2,
|
| 1820 |
+
"projects": [
|
| 1821 |
+
"ac20",
|
| 1822 |
+
"fzk_house"
|
| 1823 |
+
],
|
| 1824 |
+
"auto_discovered": true
|
| 1825 |
+
},
|
| 1826 |
+
{
|
| 1827 |
+
"entity": "IfcWall",
|
| 1828 |
+
"source": "qset",
|
| 1829 |
+
"set_name": "ArchiCADQuantities",
|
| 1830 |
+
"key": "FlΓ€che",
|
| 1831 |
+
"file_count": 2,
|
| 1832 |
+
"projects": [
|
| 1833 |
+
"ac20",
|
| 1834 |
+
"fzk_house"
|
| 1835 |
+
],
|
| 1836 |
+
"auto_discovered": true
|
| 1837 |
+
},
|
| 1838 |
+
{
|
| 1839 |
+
"entity": "IfcWall",
|
| 1840 |
+
"source": "qset",
|
| 1841 |
+
"set_name": "ArchiCADQuantities",
|
| 1842 |
+
"key": "Netto-OberflΓ€chenbereich an der AuΓenseite",
|
| 1843 |
+
"file_count": 2,
|
| 1844 |
+
"projects": [
|
| 1845 |
+
"ac20",
|
| 1846 |
+
"fzk_house"
|
| 1847 |
+
],
|
| 1848 |
+
"auto_discovered": true
|
| 1849 |
+
},
|
| 1850 |
+
{
|
| 1851 |
+
"entity": "IfcWall",
|
| 1852 |
+
"source": "qset",
|
| 1853 |
+
"set_name": "ArchiCADQuantities",
|
| 1854 |
+
"key": "Netto-OberflΓ€chenbereich an der Innenseite",
|
| 1855 |
+
"file_count": 2,
|
| 1856 |
+
"projects": [
|
| 1857 |
+
"ac20",
|
| 1858 |
+
"fzk_house"
|
| 1859 |
+
],
|
| 1860 |
+
"auto_discovered": true
|
| 1861 |
+
},
|
| 1862 |
+
{
|
| 1863 |
+
"entity": "IfcWall",
|
| 1864 |
+
"source": "qset",
|
| 1865 |
+
"set_name": "ArchiCADQuantities",
|
| 1866 |
+
"key": "Netto-OberflΓ€chenbereich an den Kanten",
|
| 1867 |
+
"file_count": 2,
|
| 1868 |
+
"projects": [
|
| 1869 |
+
"ac20",
|
| 1870 |
+
"fzk_house"
|
| 1871 |
+
],
|
| 1872 |
+
"auto_discovered": true
|
| 1873 |
+
},
|
| 1874 |
+
{
|
| 1875 |
+
"entity": "IfcWall",
|
| 1876 |
+
"source": "qset",
|
| 1877 |
+
"set_name": "ArchiCADQuantities",
|
| 1878 |
+
"key": "FlΓ€che der Wand",
|
| 1879 |
+
"file_count": 2,
|
| 1880 |
+
"projects": [
|
| 1881 |
+
"ac20",
|
| 1882 |
+
"fzk_house"
|
| 1883 |
+
],
|
| 1884 |
+
"auto_discovered": true
|
| 1885 |
+
},
|
| 1886 |
+
{
|
| 1887 |
+
"entity": "IfcWall",
|
| 1888 |
+
"source": "qset",
|
| 1889 |
+
"set_name": "ArchiCADQuantities",
|
| 1890 |
+
"key": "Brutto-WandoberflΓ€chenbereich an der Innenseite",
|
| 1891 |
+
"file_count": 2,
|
| 1892 |
+
"projects": [
|
| 1893 |
+
"ac20",
|
| 1894 |
+
"fzk_house"
|
| 1895 |
+
],
|
| 1896 |
+
"auto_discovered": true
|
| 1897 |
+
},
|
| 1898 |
+
{
|
| 1899 |
+
"entity": "IfcWall",
|
| 1900 |
+
"source": "qset",
|
| 1901 |
+
"set_name": "ArchiCADQuantities",
|
| 1902 |
+
"key": "Brutto-WandoberflΓ€chenbereich an der AuΓenseite",
|
| 1903 |
+
"file_count": 2,
|
| 1904 |
+
"projects": [
|
| 1905 |
+
"ac20",
|
| 1906 |
+
"fzk_house"
|
| 1907 |
+
],
|
| 1908 |
+
"auto_discovered": true
|
| 1909 |
+
},
|
| 1910 |
+
{
|
| 1911 |
+
"entity": "IfcWall",
|
| 1912 |
+
"source": "qset",
|
| 1913 |
+
"set_name": "ArchiCADQuantities",
|
| 1914 |
+
"key": "Analytische OberflΓ€che der Γffnungen an der Innenseite",
|
| 1915 |
+
"file_count": 2,
|
| 1916 |
+
"projects": [
|
| 1917 |
+
"ac20",
|
| 1918 |
+
"fzk_house"
|
| 1919 |
+
],
|
| 1920 |
+
"auto_discovered": true
|
| 1921 |
+
},
|
| 1922 |
+
{
|
| 1923 |
+
"entity": "IfcWall",
|
| 1924 |
+
"source": "qset",
|
| 1925 |
+
"set_name": "ArchiCADQuantities",
|
| 1926 |
+
"key": "Analytische OberflΓ€che der Γffnungen an der AuΓenseite",
|
| 1927 |
+
"file_count": 2,
|
| 1928 |
+
"projects": [
|
| 1929 |
+
"ac20",
|
| 1930 |
+
"fzk_house"
|
| 1931 |
+
],
|
| 1932 |
+
"auto_discovered": true
|
| 1933 |
+
},
|
| 1934 |
+
{
|
| 1935 |
+
"entity": "IfcWall",
|
| 1936 |
+
"source": "qset",
|
| 1937 |
+
"set_name": "ArchiCADQuantities",
|
| 1938 |
+
"key": "Konditionaler OberflΓ€chenbereich an der AuΓenseite",
|
| 1939 |
+
"file_count": 2,
|
| 1940 |
+
"projects": [
|
| 1941 |
+
"ac20",
|
| 1942 |
+
"fzk_house"
|
| 1943 |
+
],
|
| 1944 |
+
"auto_discovered": true
|
| 1945 |
+
},
|
| 1946 |
+
{
|
| 1947 |
+
"entity": "IfcWall",
|
| 1948 |
+
"source": "qset",
|
| 1949 |
+
"set_name": "ArchiCADQuantities",
|
| 1950 |
+
"key": "Konditionaler OberflΓ€chenbereich an der Innenseite",
|
| 1951 |
+
"file_count": 2,
|
| 1952 |
+
"projects": [
|
| 1953 |
+
"ac20",
|
| 1954 |
+
"fzk_house"
|
| 1955 |
+
],
|
| 1956 |
+
"auto_discovered": true
|
| 1957 |
+
},
|
| 1958 |
+
{
|
| 1959 |
+
"entity": "IfcWall",
|
| 1960 |
+
"source": "qset",
|
| 1961 |
+
"set_name": "ArchiCADQuantities",
|
| 1962 |
+
"key": "OberflΓ€chenbereich der Fenster in der Wand",
|
| 1963 |
+
"file_count": 2,
|
| 1964 |
+
"projects": [
|
| 1965 |
+
"ac20",
|
| 1966 |
+
"fzk_house"
|
| 1967 |
+
],
|
| 1968 |
+
"auto_discovered": true
|
| 1969 |
+
},
|
| 1970 |
+
{
|
| 1971 |
+
"entity": "IfcWall",
|
| 1972 |
+
"source": "qset",
|
| 1973 |
+
"set_name": "ArchiCADQuantities",
|
| 1974 |
+
"key": "OberflΓ€chenbereich der TΓΌren in der Wand",
|
| 1975 |
+
"file_count": 2,
|
| 1976 |
+
"projects": [
|
| 1977 |
+
"ac20",
|
| 1978 |
+
"fzk_house"
|
| 1979 |
+
],
|
| 1980 |
+
"auto_discovered": true
|
| 1981 |
+
},
|
| 1982 |
+
{
|
| 1983 |
+
"entity": "IfcWall",
|
| 1984 |
+
"source": "qset",
|
| 1985 |
+
"set_name": "ArchiCADQuantities",
|
| 1986 |
+
"key": "OberflΓ€chenbereich der leeren Γffnungen in der Wand",
|
| 1987 |
+
"file_count": 2,
|
| 1988 |
+
"projects": [
|
| 1989 |
+
"ac20",
|
| 1990 |
+
"fzk_house"
|
| 1991 |
+
],
|
| 1992 |
+
"auto_discovered": true
|
| 1993 |
+
},
|
| 1994 |
+
{
|
| 1995 |
+
"entity": "IfcWall",
|
| 1996 |
+
"source": "qset",
|
| 1997 |
+
"set_name": "ArchiCADQuantities",
|
| 1998 |
+
"key": "Area",
|
| 1999 |
+
"file_count": 3,
|
| 2000 |
+
"projects": [
|
| 2001 |
+
"molio",
|
| 2002 |
+
"sixty5"
|
| 2003 |
+
],
|
| 2004 |
+
"auto_discovered": true
|
| 2005 |
+
},
|
| 2006 |
+
{
|
| 2007 |
+
"entity": "IfcWall",
|
| 2008 |
+
"source": "qset",
|
| 2009 |
+
"set_name": "ArchiCADQuantities",
|
| 2010 |
+
"key": "Surface Area",
|
| 2011 |
+
"file_count": 3,
|
| 2012 |
+
"projects": [
|
| 2013 |
+
"molio",
|
| 2014 |
+
"sixty5"
|
| 2015 |
+
],
|
| 2016 |
+
"auto_discovered": true
|
| 2017 |
+
},
|
| 2018 |
+
{
|
| 2019 |
+
"entity": "IfcWall",
|
| 2020 |
+
"source": "qset",
|
| 2021 |
+
"set_name": "ArchiCADQuantities",
|
| 2022 |
+
"key": "Top Surface Area (Net)",
|
| 2023 |
+
"file_count": 1,
|
| 2024 |
+
"projects": [
|
| 2025 |
+
"molio"
|
| 2026 |
+
],
|
| 2027 |
+
"auto_discovered": true
|
| 2028 |
+
},
|
| 2029 |
+
{
|
| 2030 |
+
"entity": "IfcWall",
|
| 2031 |
+
"source": "qset",
|
| 2032 |
+
"set_name": "ArchiCADQuantities",
|
| 2033 |
+
"key": "Edge Surface Area (Net)",
|
| 2034 |
+
"file_count": 1,
|
| 2035 |
+
"projects": [
|
| 2036 |
+
"molio"
|
| 2037 |
+
],
|
| 2038 |
+
"auto_discovered": true
|
| 2039 |
+
},
|
| 2040 |
+
{
|
| 2041 |
+
"entity": "IfcWall",
|
| 2042 |
+
"source": "qset",
|
| 2043 |
+
"set_name": "ArchiCADQuantities",
|
| 2044 |
+
"key": "Bottom Surface Area (Net)",
|
| 2045 |
+
"file_count": 1,
|
| 2046 |
+
"projects": [
|
| 2047 |
+
"molio"
|
| 2048 |
+
],
|
| 2049 |
+
"auto_discovered": true
|
| 2050 |
+
},
|
| 2051 |
+
{
|
| 2052 |
+
"entity": "IfcWall",
|
| 2053 |
+
"source": "qset",
|
| 2054 |
+
"set_name": "ArchiCADQuantities",
|
| 2055 |
+
"key": "Area of the Doors",
|
| 2056 |
+
"file_count": 3,
|
| 2057 |
+
"projects": [
|
| 2058 |
+
"molio",
|
| 2059 |
+
"sixty5"
|
| 2060 |
+
],
|
| 2061 |
+
"auto_discovered": true
|
| 2062 |
+
},
|
| 2063 |
+
{
|
| 2064 |
+
"entity": "IfcWall",
|
| 2065 |
+
"source": "qset",
|
| 2066 |
+
"set_name": "ArchiCADQuantities",
|
| 2067 |
+
"key": "Area of the Windows",
|
| 2068 |
+
"file_count": 3,
|
| 2069 |
+
"projects": [
|
| 2070 |
+
"molio",
|
| 2071 |
+
"sixty5"
|
| 2072 |
+
],
|
| 2073 |
+
"auto_discovered": true
|
| 2074 |
+
},
|
| 2075 |
+
{
|
| 2076 |
+
"entity": "IfcWall",
|
| 2077 |
+
"source": "qset",
|
| 2078 |
+
"set_name": "ArchiCADQuantities",
|
| 2079 |
+
"key": "Analytic Surface Area of Openings on the Inside Face",
|
| 2080 |
+
"file_count": 3,
|
| 2081 |
+
"projects": [
|
| 2082 |
+
"molio",
|
| 2083 |
+
"sixty5"
|
| 2084 |
+
],
|
| 2085 |
+
"auto_discovered": true
|
| 2086 |
+
},
|
| 2087 |
+
{
|
| 2088 |
+
"entity": "IfcWall",
|
| 2089 |
+
"source": "qset",
|
| 2090 |
+
"set_name": "ArchiCADQuantities",
|
| 2091 |
+
"key": "Analytic Surface Area of Openings on the Outside Face",
|
| 2092 |
+
"file_count": 3,
|
| 2093 |
+
"projects": [
|
| 2094 |
+
"molio",
|
| 2095 |
+
"sixty5"
|
| 2096 |
+
],
|
| 2097 |
+
"auto_discovered": true
|
| 2098 |
+
},
|
| 2099 |
+
{
|
| 2100 |
+
"entity": "IfcWall",
|
| 2101 |
+
"source": "qset",
|
| 2102 |
+
"set_name": "ArchiCADQuantities",
|
| 2103 |
+
"key": "Surface Area of the Wall Inside Face (Net)",
|
| 2104 |
+
"file_count": 1,
|
| 2105 |
+
"projects": [
|
| 2106 |
+
"molio"
|
| 2107 |
+
],
|
| 2108 |
+
"auto_discovered": true
|
| 2109 |
+
},
|
| 2110 |
+
{
|
| 2111 |
+
"entity": "IfcWall",
|
| 2112 |
+
"source": "qset",
|
| 2113 |
+
"set_name": "ArchiCADQuantities",
|
| 2114 |
+
"key": "Surface Area of the Wall Outside Face (Net)",
|
| 2115 |
+
"file_count": 1,
|
| 2116 |
+
"projects": [
|
| 2117 |
+
"molio"
|
| 2118 |
+
],
|
| 2119 |
+
"auto_discovered": true
|
| 2120 |
+
},
|
| 2121 |
+
{
|
| 2122 |
+
"entity": "IfcWall",
|
| 2123 |
+
"source": "qset",
|
| 2124 |
+
"set_name": "ArchiCADQuantities",
|
| 2125 |
+
"key": "Surface Area of the Wall Inside Face (Conditional)",
|
| 2126 |
+
"file_count": 1,
|
| 2127 |
+
"projects": [
|
| 2128 |
+
"molio"
|
| 2129 |
+
],
|
| 2130 |
+
"auto_discovered": true
|
| 2131 |
+
},
|
| 2132 |
+
{
|
| 2133 |
+
"entity": "IfcWall",
|
| 2134 |
+
"source": "qset",
|
| 2135 |
+
"set_name": "ArchiCADQuantities",
|
| 2136 |
+
"key": "Surface Area of the Wall Outside Face (Conditional)",
|
| 2137 |
+
"file_count": 1,
|
| 2138 |
+
"projects": [
|
| 2139 |
+
"molio"
|
| 2140 |
+
],
|
| 2141 |
+
"auto_discovered": true
|
| 2142 |
+
},
|
| 2143 |
+
{
|
| 2144 |
+
"entity": "IfcWall",
|
| 2145 |
+
"source": "qset",
|
| 2146 |
+
"set_name": "ArchiCADQuantities",
|
| 2147 |
+
"key": "Surface Area of the Wall Inside Face (Gross)",
|
| 2148 |
+
"file_count": 1,
|
| 2149 |
+
"projects": [
|
| 2150 |
+
"molio"
|
| 2151 |
+
],
|
| 2152 |
+
"auto_discovered": true
|
| 2153 |
+
},
|
| 2154 |
+
{
|
| 2155 |
+
"entity": "IfcWall",
|
| 2156 |
+
"source": "qset",
|
| 2157 |
+
"set_name": "ArchiCADQuantities",
|
| 2158 |
+
"key": "Surface Area of the Wall Outside Face (Gross)",
|
| 2159 |
+
"file_count": 1,
|
| 2160 |
+
"projects": [
|
| 2161 |
+
"molio"
|
| 2162 |
+
],
|
| 2163 |
+
"auto_discovered": true
|
| 2164 |
+
},
|
| 2165 |
+
{
|
| 2166 |
+
"entity": "IfcWall",
|
| 2167 |
+
"source": "qset",
|
| 2168 |
+
"set_name": "ArchiCADQuantities",
|
| 2169 |
+
"key": "Surface Area of Empty Openings in the Wall",
|
| 2170 |
+
"file_count": 3,
|
| 2171 |
+
"projects": [
|
| 2172 |
+
"molio",
|
| 2173 |
+
"sixty5"
|
| 2174 |
+
],
|
| 2175 |
+
"auto_discovered": true
|
| 2176 |
+
},
|
| 2177 |
+
{
|
| 2178 |
+
"entity": "IfcWall",
|
| 2179 |
+
"source": "qset",
|
| 2180 |
+
"set_name": "ArchiCADQuantities",
|
| 2181 |
+
"key": "Net Surface Area of the Edges",
|
| 2182 |
+
"file_count": 2,
|
| 2183 |
+
"projects": [
|
| 2184 |
+
"sixty5"
|
| 2185 |
+
],
|
| 2186 |
+
"auto_discovered": true
|
| 2187 |
+
},
|
| 2188 |
+
{
|
| 2189 |
+
"entity": "IfcWall",
|
| 2190 |
+
"source": "qset",
|
| 2191 |
+
"set_name": "ArchiCADQuantities",
|
| 2192 |
+
"key": "Net Surface Area on the Inside Face",
|
| 2193 |
+
"file_count": 2,
|
| 2194 |
+
"projects": [
|
| 2195 |
+
"sixty5"
|
| 2196 |
+
],
|
| 2197 |
+
"auto_discovered": true
|
| 2198 |
+
},
|
| 2199 |
+
{
|
| 2200 |
+
"entity": "IfcWall",
|
| 2201 |
+
"source": "qset",
|
| 2202 |
+
"set_name": "ArchiCADQuantities",
|
| 2203 |
+
"key": "Net Surface Area on the Outside Face",
|
| 2204 |
+
"file_count": 2,
|
| 2205 |
+
"projects": [
|
| 2206 |
+
"sixty5"
|
| 2207 |
+
],
|
| 2208 |
+
"auto_discovered": true
|
| 2209 |
+
},
|
| 2210 |
+
{
|
| 2211 |
+
"entity": "IfcWall",
|
| 2212 |
+
"source": "qset",
|
| 2213 |
+
"set_name": "ArchiCADQuantities",
|
| 2214 |
+
"key": "Conditional Surface Area on the Inside Face",
|
| 2215 |
+
"file_count": 2,
|
| 2216 |
+
"projects": [
|
| 2217 |
+
"sixty5"
|
| 2218 |
+
],
|
| 2219 |
+
"auto_discovered": true
|
| 2220 |
+
},
|
| 2221 |
+
{
|
| 2222 |
+
"entity": "IfcWall",
|
| 2223 |
+
"source": "qset",
|
| 2224 |
+
"set_name": "ArchiCADQuantities",
|
| 2225 |
+
"key": "Conditional Surface Area on the Outside Face",
|
| 2226 |
+
"file_count": 2,
|
| 2227 |
+
"projects": [
|
| 2228 |
+
"sixty5"
|
| 2229 |
+
],
|
| 2230 |
+
"auto_discovered": true
|
| 2231 |
+
},
|
| 2232 |
+
{
|
| 2233 |
+
"entity": "IfcWall",
|
| 2234 |
+
"source": "qset",
|
| 2235 |
+
"set_name": "ArchiCADQuantities",
|
| 2236 |
+
"key": "Gross Surface Area of the Wall on the Inside Face",
|
| 2237 |
+
"file_count": 2,
|
| 2238 |
+
"projects": [
|
| 2239 |
+
"sixty5"
|
| 2240 |
+
],
|
| 2241 |
+
"auto_discovered": true
|
| 2242 |
+
},
|
| 2243 |
+
{
|
| 2244 |
+
"entity": "IfcWall",
|
| 2245 |
+
"source": "qset",
|
| 2246 |
+
"set_name": "ArchiCADQuantities",
|
| 2247 |
+
"key": "Gross Surface Area of the Wall on the Outside Face",
|
| 2248 |
+
"file_count": 2,
|
| 2249 |
+
"projects": [
|
| 2250 |
+
"sixty5"
|
| 2251 |
+
],
|
| 2252 |
+
"auto_discovered": true
|
| 2253 |
+
},
|
| 2254 |
+
{
|
| 2255 |
+
"entity": "IfcWall",
|
| 2256 |
+
"source": "qset",
|
| 2257 |
+
"set_name": "ArchiCADQuantities",
|
| 2258 |
+
"key": "Area of the Wall",
|
| 2259 |
+
"file_count": 2,
|
| 2260 |
+
"projects": [
|
| 2261 |
+
"sixty5"
|
| 2262 |
+
],
|
| 2263 |
+
"auto_discovered": true
|
| 2264 |
+
},
|
| 2265 |
+
{
|
| 2266 |
+
"entity": "IfcWall",
|
| 2267 |
+
"source": "qset",
|
| 2268 |
+
"set_name": "Qto_WallBaseQuantities",
|
| 2269 |
+
"key": "GrossFootprintArea",
|
| 2270 |
+
"file_count": 3,
|
| 2271 |
+
"projects": [
|
| 2272 |
+
"digital_hub",
|
| 2273 |
+
"fantasy_office_building_1",
|
| 2274 |
+
"fantasy_office_building_2"
|
| 2275 |
+
],
|
| 2276 |
+
"auto_discovered": true
|
| 2277 |
+
},
|
| 2278 |
+
{
|
| 2279 |
+
"entity": "IfcWall",
|
| 2280 |
+
"source": "qset",
|
| 2281 |
+
"set_name": "Qto_WallBaseQuantities",
|
| 2282 |
+
"key": "GrossSideArea",
|
| 2283 |
+
"file_count": 3,
|
| 2284 |
+
"projects": [
|
| 2285 |
+
"digital_hub",
|
| 2286 |
+
"fantasy_office_building_1",
|
| 2287 |
+
"fantasy_office_building_2"
|
| 2288 |
+
],
|
| 2289 |
+
"auto_discovered": true
|
| 2290 |
+
},
|
| 2291 |
+
{
|
| 2292 |
+
"entity": "IfcWall",
|
| 2293 |
+
"source": "qset",
|
| 2294 |
+
"set_name": "Qto_WallBaseQuantities",
|
| 2295 |
+
"key": "NetSideArea",
|
| 2296 |
+
"file_count": 3,
|
| 2297 |
+
"projects": [
|
| 2298 |
+
"digital_hub",
|
| 2299 |
+
"fantasy_office_building_1",
|
| 2300 |
+
"fantasy_office_building_2"
|
| 2301 |
+
],
|
| 2302 |
+
"auto_discovered": true
|
| 2303 |
+
},
|
| 2304 |
+
{
|
| 2305 |
+
"entity": "IfcWall",
|
| 2306 |
+
"source": "qset",
|
| 2307 |
+
"set_name": "(unnamed)",
|
| 2308 |
+
"key": "NetFootprintArea",
|
| 2309 |
+
"file_count": 1,
|
| 2310 |
+
"projects": [
|
| 2311 |
+
"hitos"
|
| 2312 |
+
],
|
| 2313 |
+
"auto_discovered": true
|
| 2314 |
+
},
|
| 2315 |
+
{
|
| 2316 |
+
"entity": "IfcWall",
|
| 2317 |
+
"source": "qset",
|
| 2318 |
+
"set_name": "(unnamed)",
|
| 2319 |
+
"key": "NetSideAreaLeft",
|
| 2320 |
+
"file_count": 1,
|
| 2321 |
+
"projects": [
|
| 2322 |
+
"hitos"
|
| 2323 |
+
],
|
| 2324 |
+
"auto_discovered": true
|
| 2325 |
+
},
|
| 2326 |
+
{
|
| 2327 |
+
"entity": "IfcWall",
|
| 2328 |
+
"source": "qset",
|
| 2329 |
+
"set_name": "(unnamed)",
|
| 2330 |
+
"key": "NetSideAreaRight",
|
| 2331 |
+
"file_count": 1,
|
| 2332 |
+
"projects": [
|
| 2333 |
+
"hitos"
|
| 2334 |
+
],
|
| 2335 |
+
"auto_discovered": true
|
| 2336 |
+
},
|
| 2337 |
+
{
|
| 2338 |
+
"entity": "IfcWallStandardCase",
|
| 2339 |
+
"source": "qset",
|
| 2340 |
+
"set_name": "BaseQuantities",
|
| 2341 |
+
"key": "GrossFootprintArea",
|
| 2342 |
+
"file_count": 5,
|
| 2343 |
+
"projects": [
|
| 2344 |
+
"ac20",
|
| 2345 |
+
"fzk_house",
|
| 2346 |
+
"sixty5",
|
| 2347 |
+
"smiley_west"
|
| 2348 |
+
],
|
| 2349 |
+
"auto_discovered": true
|
| 2350 |
+
},
|
| 2351 |
+
{
|
| 2352 |
+
"entity": "IfcWallStandardCase",
|
| 2353 |
+
"source": "qset",
|
| 2354 |
+
"set_name": "BaseQuantities",
|
| 2355 |
+
"key": "NetFootprintArea",
|
| 2356 |
+
"file_count": 4,
|
| 2357 |
+
"projects": [
|
| 2358 |
+
"ac20",
|
| 2359 |
+
"fzk_house",
|
| 2360 |
+
"sixty5",
|
| 2361 |
+
"smiley_west"
|
| 2362 |
+
],
|
| 2363 |
+
"auto_discovered": true
|
| 2364 |
+
},
|
| 2365 |
+
{
|
| 2366 |
+
"entity": "IfcWallStandardCase",
|
| 2367 |
+
"source": "qset",
|
| 2368 |
+
"set_name": "BaseQuantities",
|
| 2369 |
+
"key": "GrossSideArea",
|
| 2370 |
+
"file_count": 5,
|
| 2371 |
+
"projects": [
|
| 2372 |
+
"ac20",
|
| 2373 |
+
"fzk_house",
|
| 2374 |
+
"sixty5",
|
| 2375 |
+
"smiley_west"
|
| 2376 |
+
],
|
| 2377 |
+
"auto_discovered": true
|
| 2378 |
+
},
|
| 2379 |
+
{
|
| 2380 |
+
"entity": "IfcWallStandardCase",
|
| 2381 |
+
"source": "qset",
|
| 2382 |
+
"set_name": "BaseQuantities",
|
| 2383 |
+
"key": "NetSideArea",
|
| 2384 |
+
"file_count": 5,
|
| 2385 |
+
"projects": [
|
| 2386 |
+
"ac20",
|
| 2387 |
+
"fzk_house",
|
| 2388 |
+
"sixty5",
|
| 2389 |
+
"smiley_west"
|
| 2390 |
+
],
|
| 2391 |
+
"auto_discovered": true
|
| 2392 |
+
},
|
| 2393 |
+
{
|
| 2394 |
+
"entity": "IfcWallStandardCase",
|
| 2395 |
+
"source": "qset",
|
| 2396 |
+
"set_name": "ArchiCADQuantities",
|
| 2397 |
+
"key": "OberflΓ€chenbereich",
|
| 2398 |
+
"file_count": 2,
|
| 2399 |
+
"projects": [
|
| 2400 |
+
"ac20",
|
| 2401 |
+
"fzk_house"
|
| 2402 |
+
],
|
| 2403 |
+
"auto_discovered": true
|
| 2404 |
+
},
|
| 2405 |
+
{
|
| 2406 |
+
"entity": "IfcWallStandardCase",
|
| 2407 |
+
"source": "qset",
|
| 2408 |
+
"set_name": "ArchiCADQuantities",
|
| 2409 |
+
"key": "FlΓ€che",
|
| 2410 |
+
"file_count": 2,
|
| 2411 |
+
"projects": [
|
| 2412 |
+
"ac20",
|
| 2413 |
+
"fzk_house"
|
| 2414 |
+
],
|
| 2415 |
+
"auto_discovered": true
|
| 2416 |
+
},
|
| 2417 |
+
{
|
| 2418 |
+
"entity": "IfcWallStandardCase",
|
| 2419 |
+
"source": "qset",
|
| 2420 |
+
"set_name": "ArchiCADQuantities",
|
| 2421 |
+
"key": "Netto-OberflΓ€chenbereich an der AuΓenseite",
|
| 2422 |
+
"file_count": 2,
|
| 2423 |
+
"projects": [
|
| 2424 |
+
"ac20",
|
| 2425 |
+
"fzk_house"
|
| 2426 |
+
],
|
| 2427 |
+
"auto_discovered": true
|
| 2428 |
+
},
|
| 2429 |
+
{
|
| 2430 |
+
"entity": "IfcWallStandardCase",
|
| 2431 |
+
"source": "qset",
|
| 2432 |
+
"set_name": "ArchiCADQuantities",
|
| 2433 |
+
"key": "Netto-OberflΓ€chenbereich an der Innenseite",
|
| 2434 |
+
"file_count": 2,
|
| 2435 |
+
"projects": [
|
| 2436 |
+
"ac20",
|
| 2437 |
+
"fzk_house"
|
| 2438 |
+
],
|
| 2439 |
+
"auto_discovered": true
|
| 2440 |
+
},
|
| 2441 |
+
{
|
| 2442 |
+
"entity": "IfcWallStandardCase",
|
| 2443 |
+
"source": "qset",
|
| 2444 |
+
"set_name": "ArchiCADQuantities",
|
| 2445 |
+
"key": "Netto-OberflΓ€chenbereich an den Kanten",
|
| 2446 |
+
"file_count": 2,
|
| 2447 |
+
"projects": [
|
| 2448 |
+
"ac20",
|
| 2449 |
+
"fzk_house"
|
| 2450 |
+
],
|
| 2451 |
+
"auto_discovered": true
|
| 2452 |
+
},
|
| 2453 |
+
{
|
| 2454 |
+
"entity": "IfcWallStandardCase",
|
| 2455 |
+
"source": "qset",
|
| 2456 |
+
"set_name": "ArchiCADQuantities",
|
| 2457 |
+
"key": "FlΓ€che der Wand",
|
| 2458 |
+
"file_count": 2,
|
| 2459 |
+
"projects": [
|
| 2460 |
+
"ac20",
|
| 2461 |
+
"fzk_house"
|
| 2462 |
+
],
|
| 2463 |
+
"auto_discovered": true
|
| 2464 |
+
},
|
| 2465 |
+
{
|
| 2466 |
+
"entity": "IfcWallStandardCase",
|
| 2467 |
+
"source": "qset",
|
| 2468 |
+
"set_name": "ArchiCADQuantities",
|
| 2469 |
+
"key": "Brutto-WandoberflΓ€chenbereich an der Innenseite",
|
| 2470 |
+
"file_count": 2,
|
| 2471 |
+
"projects": [
|
| 2472 |
+
"ac20",
|
| 2473 |
+
"fzk_house"
|
| 2474 |
+
],
|
| 2475 |
+
"auto_discovered": true
|
| 2476 |
+
},
|
| 2477 |
+
{
|
| 2478 |
+
"entity": "IfcWallStandardCase",
|
| 2479 |
+
"source": "qset",
|
| 2480 |
+
"set_name": "ArchiCADQuantities",
|
| 2481 |
+
"key": "Brutto-WandoberflΓ€chenbereich an der AuΓenseite",
|
| 2482 |
+
"file_count": 2,
|
| 2483 |
+
"projects": [
|
| 2484 |
+
"ac20",
|
| 2485 |
+
"fzk_house"
|
| 2486 |
+
],
|
| 2487 |
+
"auto_discovered": true
|
| 2488 |
+
},
|
| 2489 |
+
{
|
| 2490 |
+
"entity": "IfcWallStandardCase",
|
| 2491 |
+
"source": "qset",
|
| 2492 |
+
"set_name": "ArchiCADQuantities",
|
| 2493 |
+
"key": "Analytische OberflΓ€che der Γffnungen an der Innenseite",
|
| 2494 |
+
"file_count": 2,
|
| 2495 |
+
"projects": [
|
| 2496 |
+
"ac20",
|
| 2497 |
+
"fzk_house"
|
| 2498 |
+
],
|
| 2499 |
+
"auto_discovered": true
|
| 2500 |
+
},
|
| 2501 |
+
{
|
| 2502 |
+
"entity": "IfcWallStandardCase",
|
| 2503 |
+
"source": "qset",
|
| 2504 |
+
"set_name": "ArchiCADQuantities",
|
| 2505 |
+
"key": "Analytische OberflΓ€che der Γffnungen an der AuΓenseite",
|
| 2506 |
+
"file_count": 2,
|
| 2507 |
+
"projects": [
|
| 2508 |
+
"ac20",
|
| 2509 |
+
"fzk_house"
|
| 2510 |
+
],
|
| 2511 |
+
"auto_discovered": true
|
| 2512 |
+
},
|
| 2513 |
+
{
|
| 2514 |
+
"entity": "IfcWallStandardCase",
|
| 2515 |
+
"source": "qset",
|
| 2516 |
+
"set_name": "ArchiCADQuantities",
|
| 2517 |
+
"key": "Konditionaler OberflΓ€chenbereich an der AuΓenseite",
|
| 2518 |
+
"file_count": 2,
|
| 2519 |
+
"projects": [
|
| 2520 |
+
"ac20",
|
| 2521 |
+
"fzk_house"
|
| 2522 |
+
],
|
| 2523 |
+
"auto_discovered": true
|
| 2524 |
+
},
|
| 2525 |
+
{
|
| 2526 |
+
"entity": "IfcWallStandardCase",
|
| 2527 |
+
"source": "qset",
|
| 2528 |
+
"set_name": "ArchiCADQuantities",
|
| 2529 |
+
"key": "Konditionaler OberflΓ€chenbereich an der Innenseite",
|
| 2530 |
+
"file_count": 2,
|
| 2531 |
+
"projects": [
|
| 2532 |
+
"ac20",
|
| 2533 |
+
"fzk_house"
|
| 2534 |
+
],
|
| 2535 |
+
"auto_discovered": true
|
| 2536 |
+
},
|
| 2537 |
+
{
|
| 2538 |
+
"entity": "IfcWallStandardCase",
|
| 2539 |
+
"source": "qset",
|
| 2540 |
+
"set_name": "ArchiCADQuantities",
|
| 2541 |
+
"key": "OberflΓ€chenbereich der Fenster in der Wand",
|
| 2542 |
+
"file_count": 2,
|
| 2543 |
+
"projects": [
|
| 2544 |
+
"ac20",
|
| 2545 |
+
"fzk_house"
|
| 2546 |
+
],
|
| 2547 |
+
"auto_discovered": true
|
| 2548 |
+
},
|
| 2549 |
+
{
|
| 2550 |
+
"entity": "IfcWallStandardCase",
|
| 2551 |
+
"source": "qset",
|
| 2552 |
+
"set_name": "ArchiCADQuantities",
|
| 2553 |
+
"key": "OberflΓ€chenbereich der TΓΌren in der Wand",
|
| 2554 |
+
"file_count": 2,
|
| 2555 |
+
"projects": [
|
| 2556 |
+
"ac20",
|
| 2557 |
+
"fzk_house"
|
| 2558 |
+
],
|
| 2559 |
+
"auto_discovered": true
|
| 2560 |
+
},
|
| 2561 |
+
{
|
| 2562 |
+
"entity": "IfcWallStandardCase",
|
| 2563 |
+
"source": "qset",
|
| 2564 |
+
"set_name": "ArchiCADQuantities",
|
| 2565 |
+
"key": "OberflΓ€chenbereich der leeren Γffnungen in der Wand",
|
| 2566 |
+
"file_count": 2,
|
| 2567 |
+
"projects": [
|
| 2568 |
+
"ac20",
|
| 2569 |
+
"fzk_house"
|
| 2570 |
+
],
|
| 2571 |
+
"auto_discovered": true
|
| 2572 |
+
},
|
| 2573 |
+
{
|
| 2574 |
+
"entity": "IfcWallStandardCase",
|
| 2575 |
+
"source": "qset",
|
| 2576 |
+
"set_name": "ArchiCADQuantities",
|
| 2577 |
+
"key": "Area",
|
| 2578 |
+
"file_count": 1,
|
| 2579 |
+
"projects": [
|
| 2580 |
+
"sixty5"
|
| 2581 |
+
],
|
| 2582 |
+
"auto_discovered": true
|
| 2583 |
+
},
|
| 2584 |
+
{
|
| 2585 |
+
"entity": "IfcWallStandardCase",
|
| 2586 |
+
"source": "qset",
|
| 2587 |
+
"set_name": "ArchiCADQuantities",
|
| 2588 |
+
"key": "Surface Area",
|
| 2589 |
+
"file_count": 1,
|
| 2590 |
+
"projects": [
|
| 2591 |
+
"sixty5"
|
| 2592 |
+
],
|
| 2593 |
+
"auto_discovered": true
|
| 2594 |
+
},
|
| 2595 |
+
{
|
| 2596 |
+
"entity": "IfcWallStandardCase",
|
| 2597 |
+
"source": "qset",
|
| 2598 |
+
"set_name": "ArchiCADQuantities",
|
| 2599 |
+
"key": "Area of the Doors",
|
| 2600 |
+
"file_count": 1,
|
| 2601 |
+
"projects": [
|
| 2602 |
+
"sixty5"
|
| 2603 |
+
],
|
| 2604 |
+
"auto_discovered": true
|
| 2605 |
+
},
|
| 2606 |
+
{
|
| 2607 |
+
"entity": "IfcWallStandardCase",
|
| 2608 |
+
"source": "qset",
|
| 2609 |
+
"set_name": "ArchiCADQuantities",
|
| 2610 |
+
"key": "Area of the Windows",
|
| 2611 |
+
"file_count": 1,
|
| 2612 |
+
"projects": [
|
| 2613 |
+
"sixty5"
|
| 2614 |
+
],
|
| 2615 |
+
"auto_discovered": true
|
| 2616 |
+
},
|
| 2617 |
+
{
|
| 2618 |
+
"entity": "IfcWallStandardCase",
|
| 2619 |
+
"source": "qset",
|
| 2620 |
+
"set_name": "ArchiCADQuantities",
|
| 2621 |
+
"key": "Analytic Surface Area of Openings on the Inside Face",
|
| 2622 |
+
"file_count": 1,
|
| 2623 |
+
"projects": [
|
| 2624 |
+
"sixty5"
|
| 2625 |
+
],
|
| 2626 |
+
"auto_discovered": true
|
| 2627 |
+
},
|
| 2628 |
+
{
|
| 2629 |
+
"entity": "IfcWallStandardCase",
|
| 2630 |
+
"source": "qset",
|
| 2631 |
+
"set_name": "ArchiCADQuantities",
|
| 2632 |
+
"key": "Analytic Surface Area of Openings on the Outside Face",
|
| 2633 |
+
"file_count": 1,
|
| 2634 |
+
"projects": [
|
| 2635 |
+
"sixty5"
|
| 2636 |
+
],
|
| 2637 |
+
"auto_discovered": true
|
| 2638 |
+
},
|
| 2639 |
+
{
|
| 2640 |
+
"entity": "IfcWallStandardCase",
|
| 2641 |
+
"source": "qset",
|
| 2642 |
+
"set_name": "ArchiCADQuantities",
|
| 2643 |
+
"key": "Net Surface Area of the Edges",
|
| 2644 |
+
"file_count": 1,
|
| 2645 |
+
"projects": [
|
| 2646 |
+
"sixty5"
|
| 2647 |
+
],
|
| 2648 |
+
"auto_discovered": true
|
| 2649 |
+
},
|
| 2650 |
+
{
|
| 2651 |
+
"entity": "IfcWallStandardCase",
|
| 2652 |
+
"source": "qset",
|
| 2653 |
+
"set_name": "ArchiCADQuantities",
|
| 2654 |
+
"key": "Net Surface Area on the Inside Face",
|
| 2655 |
+
"file_count": 1,
|
| 2656 |
+
"projects": [
|
| 2657 |
+
"sixty5"
|
| 2658 |
+
],
|
| 2659 |
+
"auto_discovered": true
|
| 2660 |
+
},
|
| 2661 |
+
{
|
| 2662 |
+
"entity": "IfcWallStandardCase",
|
| 2663 |
+
"source": "qset",
|
| 2664 |
+
"set_name": "ArchiCADQuantities",
|
| 2665 |
+
"key": "Net Surface Area on the Outside Face",
|
| 2666 |
+
"file_count": 1,
|
| 2667 |
+
"projects": [
|
| 2668 |
+
"sixty5"
|
| 2669 |
+
],
|
| 2670 |
+
"auto_discovered": true
|
| 2671 |
+
},
|
| 2672 |
+
{
|
| 2673 |
+
"entity": "IfcWallStandardCase",
|
| 2674 |
+
"source": "qset",
|
| 2675 |
+
"set_name": "ArchiCADQuantities",
|
| 2676 |
+
"key": "Conditional Surface Area on the Inside Face",
|
| 2677 |
+
"file_count": 1,
|
| 2678 |
+
"projects": [
|
| 2679 |
+
"sixty5"
|
| 2680 |
+
],
|
| 2681 |
+
"auto_discovered": true
|
| 2682 |
+
},
|
| 2683 |
+
{
|
| 2684 |
+
"entity": "IfcWallStandardCase",
|
| 2685 |
+
"source": "qset",
|
| 2686 |
+
"set_name": "ArchiCADQuantities",
|
| 2687 |
+
"key": "Conditional Surface Area on the Outside Face",
|
| 2688 |
+
"file_count": 1,
|
| 2689 |
+
"projects": [
|
| 2690 |
+
"sixty5"
|
| 2691 |
+
],
|
| 2692 |
+
"auto_discovered": true
|
| 2693 |
+
},
|
| 2694 |
+
{
|
| 2695 |
+
"entity": "IfcWallStandardCase",
|
| 2696 |
+
"source": "qset",
|
| 2697 |
+
"set_name": "ArchiCADQuantities",
|
| 2698 |
+
"key": "Gross Surface Area of the Wall on the Inside Face",
|
| 2699 |
+
"file_count": 1,
|
| 2700 |
+
"projects": [
|
| 2701 |
+
"sixty5"
|
| 2702 |
+
],
|
| 2703 |
+
"auto_discovered": true
|
| 2704 |
+
},
|
| 2705 |
+
{
|
| 2706 |
+
"entity": "IfcWallStandardCase",
|
| 2707 |
+
"source": "qset",
|
| 2708 |
+
"set_name": "ArchiCADQuantities",
|
| 2709 |
+
"key": "Gross Surface Area of the Wall on the Outside Face",
|
| 2710 |
+
"file_count": 1,
|
| 2711 |
+
"projects": [
|
| 2712 |
+
"sixty5"
|
| 2713 |
+
],
|
| 2714 |
+
"auto_discovered": true
|
| 2715 |
+
},
|
| 2716 |
+
{
|
| 2717 |
+
"entity": "IfcWallStandardCase",
|
| 2718 |
+
"source": "qset",
|
| 2719 |
+
"set_name": "ArchiCADQuantities",
|
| 2720 |
+
"key": "Area of the Wall",
|
| 2721 |
+
"file_count": 1,
|
| 2722 |
+
"projects": [
|
| 2723 |
+
"sixty5"
|
| 2724 |
+
],
|
| 2725 |
+
"auto_discovered": true
|
| 2726 |
+
},
|
| 2727 |
+
{
|
| 2728 |
+
"entity": "IfcWallStandardCase",
|
| 2729 |
+
"source": "qset",
|
| 2730 |
+
"set_name": "ArchiCADQuantities",
|
| 2731 |
+
"key": "Surface Area of Empty Openings in the Wall",
|
| 2732 |
+
"file_count": 1,
|
| 2733 |
+
"projects": [
|
| 2734 |
+
"sixty5"
|
| 2735 |
+
],
|
| 2736 |
+
"auto_discovered": true
|
| 2737 |
+
},
|
| 2738 |
+
{
|
| 2739 |
+
"entity": "IfcWallStandardCase",
|
| 2740 |
+
"source": "qset",
|
| 2741 |
+
"set_name": "(unnamed)",
|
| 2742 |
+
"key": "NetFootprintArea",
|
| 2743 |
+
"file_count": 1,
|
| 2744 |
+
"projects": [
|
| 2745 |
+
"hitos"
|
| 2746 |
+
],
|
| 2747 |
+
"auto_discovered": true
|
| 2748 |
+
},
|
| 2749 |
+
{
|
| 2750 |
+
"entity": "IfcWallStandardCase",
|
| 2751 |
+
"source": "qset",
|
| 2752 |
+
"set_name": "(unnamed)",
|
| 2753 |
+
"key": "NetSideAreaLeft",
|
| 2754 |
+
"file_count": 1,
|
| 2755 |
+
"projects": [
|
| 2756 |
+
"hitos"
|
| 2757 |
+
],
|
| 2758 |
+
"auto_discovered": true
|
| 2759 |
+
},
|
| 2760 |
+
{
|
| 2761 |
+
"entity": "IfcWallStandardCase",
|
| 2762 |
+
"source": "qset",
|
| 2763 |
+
"set_name": "(unnamed)",
|
| 2764 |
+
"key": "NetSideAreaRight",
|
| 2765 |
+
"file_count": 1,
|
| 2766 |
+
"projects": [
|
| 2767 |
+
"hitos"
|
| 2768 |
+
],
|
| 2769 |
+
"auto_discovered": true
|
| 2770 |
+
},
|
| 2771 |
+
{
|
| 2772 |
+
"entity": "IfcCovering",
|
| 2773 |
+
"source": "qset",
|
| 2774 |
+
"set_name": "Qto_CoveringBaseQuantities",
|
| 2775 |
+
"key": "GrossArea",
|
| 2776 |
+
"file_count": 1,
|
| 2777 |
+
"projects": [
|
| 2778 |
+
"digital_hub"
|
| 2779 |
+
],
|
| 2780 |
+
"auto_discovered": true
|
| 2781 |
+
},
|
| 2782 |
+
{
|
| 2783 |
+
"entity": "IfcCovering",
|
| 2784 |
+
"source": "qset",
|
| 2785 |
+
"set_name": "Qto_CoveringBaseQuantities",
|
| 2786 |
+
"key": "NetArea",
|
| 2787 |
+
"file_count": 1,
|
| 2788 |
+
"projects": [
|
| 2789 |
+
"digital_hub"
|
| 2790 |
+
],
|
| 2791 |
+
"auto_discovered": true
|
| 2792 |
+
},
|
| 2793 |
+
{
|
| 2794 |
+
"entity": "IfcCovering",
|
| 2795 |
+
"source": "qset",
|
| 2796 |
+
"set_name": "BaseQuantities",
|
| 2797 |
+
"key": "GrossArea",
|
| 2798 |
+
"file_count": 1,
|
| 2799 |
+
"projects": [
|
| 2800 |
+
"sixty5"
|
| 2801 |
+
],
|
| 2802 |
+
"auto_discovered": true
|
| 2803 |
+
},
|
| 2804 |
+
{
|
| 2805 |
+
"entity": "IfcCovering",
|
| 2806 |
+
"source": "qset",
|
| 2807 |
+
"set_name": "BaseQuantities",
|
| 2808 |
+
"key": "NetArea",
|
| 2809 |
+
"file_count": 1,
|
| 2810 |
+
"projects": [
|
| 2811 |
+
"sixty5"
|
| 2812 |
+
],
|
| 2813 |
+
"auto_discovered": true
|
| 2814 |
+
},
|
| 2815 |
+
{
|
| 2816 |
+
"entity": "IfcCovering",
|
| 2817 |
+
"source": "qset",
|
| 2818 |
+
"set_name": "ArchiCADQuantities",
|
| 2819 |
+
"key": "Area",
|
| 2820 |
+
"file_count": 1,
|
| 2821 |
+
"projects": [
|
| 2822 |
+
"sixty5"
|
| 2823 |
+
],
|
| 2824 |
+
"auto_discovered": true
|
| 2825 |
+
},
|
| 2826 |
+
{
|
| 2827 |
+
"entity": "IfcCovering",
|
| 2828 |
+
"source": "qset",
|
| 2829 |
+
"set_name": "ArchiCADQuantities",
|
| 2830 |
+
"key": "Surface Area",
|
| 2831 |
+
"file_count": 1,
|
| 2832 |
+
"projects": [
|
| 2833 |
+
"sixty5"
|
| 2834 |
+
],
|
| 2835 |
+
"auto_discovered": true
|
| 2836 |
+
},
|
| 2837 |
+
{
|
| 2838 |
+
"entity": "IfcCovering",
|
| 2839 |
+
"source": "qset",
|
| 2840 |
+
"set_name": "ArchiCADQuantities",
|
| 2841 |
+
"key": "Area of the Doors",
|
| 2842 |
+
"file_count": 1,
|
| 2843 |
+
"projects": [
|
| 2844 |
+
"sixty5"
|
| 2845 |
+
],
|
| 2846 |
+
"auto_discovered": true
|
| 2847 |
+
},
|
| 2848 |
+
{
|
| 2849 |
+
"entity": "IfcCovering",
|
| 2850 |
+
"source": "qset",
|
| 2851 |
+
"set_name": "ArchiCADQuantities",
|
| 2852 |
+
"key": "Area of the Windows",
|
| 2853 |
+
"file_count": 1,
|
| 2854 |
+
"projects": [
|
| 2855 |
+
"sixty5"
|
| 2856 |
+
],
|
| 2857 |
+
"auto_discovered": true
|
| 2858 |
+
},
|
| 2859 |
+
{
|
| 2860 |
+
"entity": "IfcCovering",
|
| 2861 |
+
"source": "qset",
|
| 2862 |
+
"set_name": "ArchiCADQuantities",
|
| 2863 |
+
"key": "Analytic Surface Area of Openings on the Inside Face",
|
| 2864 |
+
"file_count": 1,
|
| 2865 |
+
"projects": [
|
| 2866 |
+
"sixty5"
|
| 2867 |
+
],
|
| 2868 |
+
"auto_discovered": true
|
| 2869 |
+
},
|
| 2870 |
+
{
|
| 2871 |
+
"entity": "IfcCovering",
|
| 2872 |
+
"source": "qset",
|
| 2873 |
+
"set_name": "ArchiCADQuantities",
|
| 2874 |
+
"key": "Analytic Surface Area of Openings on the Outside Face",
|
| 2875 |
+
"file_count": 1,
|
| 2876 |
+
"projects": [
|
| 2877 |
+
"sixty5"
|
| 2878 |
+
],
|
| 2879 |
+
"auto_discovered": true
|
| 2880 |
+
},
|
| 2881 |
+
{
|
| 2882 |
+
"entity": "IfcCovering",
|
| 2883 |
+
"source": "qset",
|
| 2884 |
+
"set_name": "ArchiCADQuantities",
|
| 2885 |
+
"key": "Net Surface Area of the Edges",
|
| 2886 |
+
"file_count": 1,
|
| 2887 |
+
"projects": [
|
| 2888 |
+
"sixty5"
|
| 2889 |
+
],
|
| 2890 |
+
"auto_discovered": true
|
| 2891 |
+
},
|
| 2892 |
+
{
|
| 2893 |
+
"entity": "IfcCovering",
|
| 2894 |
+
"source": "qset",
|
| 2895 |
+
"set_name": "ArchiCADQuantities",
|
| 2896 |
+
"key": "Net Surface Area on the Inside Face",
|
| 2897 |
+
"file_count": 1,
|
| 2898 |
+
"projects": [
|
| 2899 |
+
"sixty5"
|
| 2900 |
+
],
|
| 2901 |
+
"auto_discovered": true
|
| 2902 |
+
},
|
| 2903 |
+
{
|
| 2904 |
+
"entity": "IfcCovering",
|
| 2905 |
+
"source": "qset",
|
| 2906 |
+
"set_name": "ArchiCADQuantities",
|
| 2907 |
+
"key": "Net Surface Area on the Outside Face",
|
| 2908 |
+
"file_count": 1,
|
| 2909 |
+
"projects": [
|
| 2910 |
+
"sixty5"
|
| 2911 |
+
],
|
| 2912 |
+
"auto_discovered": true
|
| 2913 |
+
},
|
| 2914 |
+
{
|
| 2915 |
+
"entity": "IfcCovering",
|
| 2916 |
+
"source": "qset",
|
| 2917 |
+
"set_name": "ArchiCADQuantities",
|
| 2918 |
+
"key": "Conditional Surface Area on the Inside Face",
|
| 2919 |
+
"file_count": 1,
|
| 2920 |
+
"projects": [
|
| 2921 |
+
"sixty5"
|
| 2922 |
+
],
|
| 2923 |
+
"auto_discovered": true
|
| 2924 |
+
},
|
| 2925 |
+
{
|
| 2926 |
+
"entity": "IfcCovering",
|
| 2927 |
+
"source": "qset",
|
| 2928 |
+
"set_name": "ArchiCADQuantities",
|
| 2929 |
+
"key": "Conditional Surface Area on the Outside Face",
|
| 2930 |
+
"file_count": 1,
|
| 2931 |
+
"projects": [
|
| 2932 |
+
"sixty5"
|
| 2933 |
+
],
|
| 2934 |
+
"auto_discovered": true
|
| 2935 |
+
},
|
| 2936 |
+
{
|
| 2937 |
+
"entity": "IfcCovering",
|
| 2938 |
+
"source": "qset",
|
| 2939 |
+
"set_name": "ArchiCADQuantities",
|
| 2940 |
+
"key": "Gross Surface Area of the Wall on the Inside Face",
|
| 2941 |
+
"file_count": 1,
|
| 2942 |
+
"projects": [
|
| 2943 |
+
"sixty5"
|
| 2944 |
+
],
|
| 2945 |
+
"auto_discovered": true
|
| 2946 |
+
},
|
| 2947 |
+
{
|
| 2948 |
+
"entity": "IfcCovering",
|
| 2949 |
+
"source": "qset",
|
| 2950 |
+
"set_name": "ArchiCADQuantities",
|
| 2951 |
+
"key": "Gross Surface Area of the Wall on the Outside Face",
|
| 2952 |
+
"file_count": 1,
|
| 2953 |
+
"projects": [
|
| 2954 |
+
"sixty5"
|
| 2955 |
+
],
|
| 2956 |
+
"auto_discovered": true
|
| 2957 |
+
},
|
| 2958 |
+
{
|
| 2959 |
+
"entity": "IfcCovering",
|
| 2960 |
+
"source": "qset",
|
| 2961 |
+
"set_name": "ArchiCADQuantities",
|
| 2962 |
+
"key": "Area of the Wall",
|
| 2963 |
+
"file_count": 1,
|
| 2964 |
+
"projects": [
|
| 2965 |
+
"sixty5"
|
| 2966 |
+
],
|
| 2967 |
+
"auto_discovered": true
|
| 2968 |
+
},
|
| 2969 |
+
{
|
| 2970 |
+
"entity": "IfcCovering",
|
| 2971 |
+
"source": "qset",
|
| 2972 |
+
"set_name": "ArchiCADQuantities",
|
| 2973 |
+
"key": "Surface Area of Empty Openings in the Wall",
|
| 2974 |
+
"file_count": 1,
|
| 2975 |
+
"projects": [
|
| 2976 |
+
"sixty5"
|
| 2977 |
+
],
|
| 2978 |
+
"auto_discovered": true
|
| 2979 |
+
},
|
| 2980 |
+
{
|
| 2981 |
+
"entity": "IfcCovering",
|
| 2982 |
+
"source": "qset",
|
| 2983 |
+
"set_name": "ArchiCADQuantities",
|
| 2984 |
+
"key": "Bottom Surface Area",
|
| 2985 |
+
"file_count": 1,
|
| 2986 |
+
"projects": [
|
| 2987 |
+
"sixty5"
|
| 2988 |
+
],
|
| 2989 |
+
"auto_discovered": true
|
| 2990 |
+
},
|
| 2991 |
+
{
|
| 2992 |
+
"entity": "IfcCovering",
|
| 2993 |
+
"source": "qset",
|
| 2994 |
+
"set_name": "ArchiCADQuantities",
|
| 2995 |
+
"key": "Conditional Surface Area of the Bottom",
|
| 2996 |
+
"file_count": 1,
|
| 2997 |
+
"projects": [
|
| 2998 |
+
"sixty5"
|
| 2999 |
+
],
|
| 3000 |
+
"auto_discovered": true
|
| 3001 |
+
},
|
| 3002 |
+
{
|
| 3003 |
+
"entity": "IfcCovering",
|
| 3004 |
+
"source": "qset",
|
| 3005 |
+
"set_name": "ArchiCADQuantities",
|
| 3006 |
+
"key": "Conditional Surface Area of the Top",
|
| 3007 |
+
"file_count": 1,
|
| 3008 |
+
"projects": [
|
| 3009 |
+
"sixty5"
|
| 3010 |
+
],
|
| 3011 |
+
"auto_discovered": true
|
| 3012 |
+
},
|
| 3013 |
+
{
|
| 3014 |
+
"entity": "IfcCovering",
|
| 3015 |
+
"source": "qset",
|
| 3016 |
+
"set_name": "ArchiCADQuantities",
|
| 3017 |
+
"key": "Edge Surface Area",
|
| 3018 |
+
"file_count": 1,
|
| 3019 |
+
"projects": [
|
| 3020 |
+
"sixty5"
|
| 3021 |
+
],
|
| 3022 |
+
"auto_discovered": true
|
| 3023 |
+
},
|
| 3024 |
+
{
|
| 3025 |
+
"entity": "IfcCovering",
|
| 3026 |
+
"source": "qset",
|
| 3027 |
+
"set_name": "ArchiCADQuantities",
|
| 3028 |
+
"key": "Gross Surface Area of the Slab Bottom",
|
| 3029 |
+
"file_count": 1,
|
| 3030 |
+
"projects": [
|
| 3031 |
+
"sixty5"
|
| 3032 |
+
],
|
| 3033 |
+
"auto_discovered": true
|
| 3034 |
+
},
|
| 3035 |
+
{
|
| 3036 |
+
"entity": "IfcCovering",
|
| 3037 |
+
"source": "qset",
|
| 3038 |
+
"set_name": "ArchiCADQuantities",
|
| 3039 |
+
"key": "Gross Surface Area of the Slab Bottom with Holes",
|
| 3040 |
+
"file_count": 1,
|
| 3041 |
+
"projects": [
|
| 3042 |
+
"sixty5"
|
| 3043 |
+
],
|
| 3044 |
+
"auto_discovered": true
|
| 3045 |
+
},
|
| 3046 |
+
{
|
| 3047 |
+
"entity": "IfcCovering",
|
| 3048 |
+
"source": "qset",
|
| 3049 |
+
"set_name": "ArchiCADQuantities",
|
| 3050 |
+
"key": "Gross Surface Area of the Slab Edges",
|
| 3051 |
+
"file_count": 1,
|
| 3052 |
+
"projects": [
|
| 3053 |
+
"sixty5"
|
| 3054 |
+
],
|
| 3055 |
+
"auto_discovered": true
|
| 3056 |
+
},
|
| 3057 |
+
{
|
| 3058 |
+
"entity": "IfcCovering",
|
| 3059 |
+
"source": "qset",
|
| 3060 |
+
"set_name": "ArchiCADQuantities",
|
| 3061 |
+
"key": "Gross Surface Area of the Slab Edges with Holes",
|
| 3062 |
+
"file_count": 1,
|
| 3063 |
+
"projects": [
|
| 3064 |
+
"sixty5"
|
| 3065 |
+
],
|
| 3066 |
+
"auto_discovered": true
|
| 3067 |
+
},
|
| 3068 |
+
{
|
| 3069 |
+
"entity": "IfcCovering",
|
| 3070 |
+
"source": "qset",
|
| 3071 |
+
"set_name": "ArchiCADQuantities",
|
| 3072 |
+
"key": "Gross Surface Area of the Slab Top",
|
| 3073 |
+
"file_count": 1,
|
| 3074 |
+
"projects": [
|
| 3075 |
+
"sixty5"
|
| 3076 |
+
],
|
| 3077 |
+
"auto_discovered": true
|
| 3078 |
+
},
|
| 3079 |
+
{
|
| 3080 |
+
"entity": "IfcCovering",
|
| 3081 |
+
"source": "qset",
|
| 3082 |
+
"set_name": "ArchiCADQuantities",
|
| 3083 |
+
"key": "Gross Surface Area of the Slab Top with Holes",
|
| 3084 |
+
"file_count": 1,
|
| 3085 |
+
"projects": [
|
| 3086 |
+
"sixty5"
|
| 3087 |
+
],
|
| 3088 |
+
"auto_discovered": true
|
| 3089 |
+
},
|
| 3090 |
+
{
|
| 3091 |
+
"entity": "IfcCovering",
|
| 3092 |
+
"source": "qset",
|
| 3093 |
+
"set_name": "ArchiCADQuantities",
|
| 3094 |
+
"key": "Holes Surface Area",
|
| 3095 |
+
"file_count": 1,
|
| 3096 |
+
"projects": [
|
| 3097 |
+
"sixty5"
|
| 3098 |
+
],
|
| 3099 |
+
"auto_discovered": true
|
| 3100 |
+
},
|
| 3101 |
+
{
|
| 3102 |
+
"entity": "IfcCovering",
|
| 3103 |
+
"source": "qset",
|
| 3104 |
+
"set_name": "ArchiCADQuantities",
|
| 3105 |
+
"key": "Top Surface Area",
|
| 3106 |
+
"file_count": 1,
|
| 3107 |
+
"projects": [
|
| 3108 |
+
"sixty5"
|
| 3109 |
+
],
|
| 3110 |
+
"auto_discovered": true
|
| 3111 |
+
},
|
| 3112 |
+
{
|
| 3113 |
+
"entity": "IfcCovering",
|
| 3114 |
+
"source": "qset",
|
| 3115 |
+
"set_name": "ArchiCADQuantities",
|
| 3116 |
+
"key": "Area of the Column",
|
| 3117 |
+
"file_count": 1,
|
| 3118 |
+
"projects": [
|
| 3119 |
+
"sixty5"
|
| 3120 |
+
],
|
| 3121 |
+
"auto_discovered": true
|
| 3122 |
+
},
|
| 3123 |
+
{
|
| 3124 |
+
"entity": "IfcCovering",
|
| 3125 |
+
"source": "qset",
|
| 3126 |
+
"set_name": "ArchiCADQuantities",
|
| 3127 |
+
"key": "Gross Surface Area of the Core (without Top/Bottom)",
|
| 3128 |
+
"file_count": 1,
|
| 3129 |
+
"projects": [
|
| 3130 |
+
"sixty5"
|
| 3131 |
+
],
|
| 3132 |
+
"auto_discovered": true
|
| 3133 |
+
},
|
| 3134 |
+
{
|
| 3135 |
+
"entity": "IfcCovering",
|
| 3136 |
+
"source": "qset",
|
| 3137 |
+
"set_name": "ArchiCADQuantities",
|
| 3138 |
+
"key": "Gross Surface Area of the Core Top (or Bottom)",
|
| 3139 |
+
"file_count": 1,
|
| 3140 |
+
"projects": [
|
| 3141 |
+
"sixty5"
|
| 3142 |
+
],
|
| 3143 |
+
"auto_discovered": true
|
| 3144 |
+
},
|
| 3145 |
+
{
|
| 3146 |
+
"entity": "IfcCovering",
|
| 3147 |
+
"source": "qset",
|
| 3148 |
+
"set_name": "ArchiCADQuantities",
|
| 3149 |
+
"key": "Gross Surface Area of the Veneer (without Top/Bottom)",
|
| 3150 |
+
"file_count": 1,
|
| 3151 |
+
"projects": [
|
| 3152 |
+
"sixty5"
|
| 3153 |
+
],
|
| 3154 |
+
"auto_discovered": true
|
| 3155 |
+
},
|
| 3156 |
+
{
|
| 3157 |
+
"entity": "IfcCovering",
|
| 3158 |
+
"source": "qset",
|
| 3159 |
+
"set_name": "ArchiCADQuantities",
|
| 3160 |
+
"key": "Gross Surface Area of the Veneer Top (or Bottom)",
|
| 3161 |
+
"file_count": 1,
|
| 3162 |
+
"projects": [
|
| 3163 |
+
"sixty5"
|
| 3164 |
+
],
|
| 3165 |
+
"auto_discovered": true
|
| 3166 |
+
},
|
| 3167 |
+
{
|
| 3168 |
+
"entity": "IfcCovering",
|
| 3169 |
+
"source": "qset",
|
| 3170 |
+
"set_name": "ArchiCADQuantities",
|
| 3171 |
+
"key": "Net Surface Area of the Core Bottom",
|
| 3172 |
+
"file_count": 1,
|
| 3173 |
+
"projects": [
|
| 3174 |
+
"sixty5"
|
| 3175 |
+
],
|
| 3176 |
+
"auto_discovered": true
|
| 3177 |
+
},
|
| 3178 |
+
{
|
| 3179 |
+
"entity": "IfcCovering",
|
| 3180 |
+
"source": "qset",
|
| 3181 |
+
"set_name": "ArchiCADQuantities",
|
| 3182 |
+
"key": "Net Surface Area of the Core (without Top/Bottom)",
|
| 3183 |
+
"file_count": 1,
|
| 3184 |
+
"projects": [
|
| 3185 |
+
"sixty5"
|
| 3186 |
+
],
|
| 3187 |
+
"auto_discovered": true
|
| 3188 |
+
},
|
| 3189 |
+
{
|
| 3190 |
+
"entity": "IfcCovering",
|
| 3191 |
+
"source": "qset",
|
| 3192 |
+
"set_name": "ArchiCADQuantities",
|
| 3193 |
+
"key": "Net Surface Area of the Core Top",
|
| 3194 |
+
"file_count": 1,
|
| 3195 |
+
"projects": [
|
| 3196 |
+
"sixty5"
|
| 3197 |
+
],
|
| 3198 |
+
"auto_discovered": true
|
| 3199 |
+
},
|
| 3200 |
+
{
|
| 3201 |
+
"entity": "IfcCovering",
|
| 3202 |
+
"source": "qset",
|
| 3203 |
+
"set_name": "ArchiCADQuantities",
|
| 3204 |
+
"key": "Net Surface Area of the Veneer Bottom",
|
| 3205 |
+
"file_count": 1,
|
| 3206 |
+
"projects": [
|
| 3207 |
+
"sixty5"
|
| 3208 |
+
],
|
| 3209 |
+
"auto_discovered": true
|
| 3210 |
+
},
|
| 3211 |
+
{
|
| 3212 |
+
"entity": "IfcCovering",
|
| 3213 |
+
"source": "qset",
|
| 3214 |
+
"set_name": "ArchiCADQuantities",
|
| 3215 |
+
"key": "Veneer Surface Area",
|
| 3216 |
+
"file_count": 1,
|
| 3217 |
+
"projects": [
|
| 3218 |
+
"sixty5"
|
| 3219 |
+
],
|
| 3220 |
+
"auto_discovered": true
|
| 3221 |
+
},
|
| 3222 |
+
{
|
| 3223 |
+
"entity": "IfcCovering",
|
| 3224 |
+
"source": "qset",
|
| 3225 |
+
"set_name": "ArchiCADQuantities",
|
| 3226 |
+
"key": "Net Surface Area of the Veneer Top",
|
| 3227 |
+
"file_count": 1,
|
| 3228 |
+
"projects": [
|
| 3229 |
+
"sixty5"
|
| 3230 |
+
],
|
| 3231 |
+
"auto_discovered": true
|
| 3232 |
+
},
|
| 3233 |
+
{
|
| 3234 |
+
"entity": "IfcCovering",
|
| 3235 |
+
"source": "qset",
|
| 3236 |
+
"set_name": "ArchiCADQuantities",
|
| 3237 |
+
"key": "End Surface Area",
|
| 3238 |
+
"file_count": 1,
|
| 3239 |
+
"projects": [
|
| 3240 |
+
"sixty5"
|
| 3241 |
+
],
|
| 3242 |
+
"auto_discovered": true
|
| 3243 |
+
},
|
| 3244 |
+
{
|
| 3245 |
+
"entity": "IfcCovering",
|
| 3246 |
+
"source": "qset",
|
| 3247 |
+
"set_name": "ArchiCADQuantities",
|
| 3248 |
+
"key": "Holes Edge Surface Area",
|
| 3249 |
+
"file_count": 1,
|
| 3250 |
+
"projects": [
|
| 3251 |
+
"sixty5"
|
| 3252 |
+
],
|
| 3253 |
+
"auto_discovered": true
|
| 3254 |
+
},
|
| 3255 |
+
{
|
| 3256 |
+
"entity": "IfcCovering",
|
| 3257 |
+
"source": "qset",
|
| 3258 |
+
"set_name": "ArchiCADQuantities",
|
| 3259 |
+
"key": "Left Side Surface Area",
|
| 3260 |
+
"file_count": 1,
|
| 3261 |
+
"projects": [
|
| 3262 |
+
"sixty5"
|
| 3263 |
+
],
|
| 3264 |
+
"auto_discovered": true
|
| 3265 |
+
},
|
| 3266 |
+
{
|
| 3267 |
+
"entity": "IfcCovering",
|
| 3268 |
+
"source": "qset",
|
| 3269 |
+
"set_name": "ArchiCADQuantities",
|
| 3270 |
+
"key": "Right Side Surface Area",
|
| 3271 |
+
"file_count": 1,
|
| 3272 |
+
"projects": [
|
| 3273 |
+
"sixty5"
|
| 3274 |
+
],
|
| 3275 |
+
"auto_discovered": true
|
| 3276 |
+
},
|
| 3277 |
+
{
|
| 3278 |
+
"entity": "IfcSite",
|
| 3279 |
+
"source": "qset",
|
| 3280 |
+
"set_name": "BaseQuantities",
|
| 3281 |
+
"key": "GrossArea",
|
| 3282 |
+
"file_count": 7,
|
| 3283 |
+
"projects": [
|
| 3284 |
+
"ac20",
|
| 3285 |
+
"fzk_house",
|
| 3286 |
+
"molio",
|
| 3287 |
+
"sixty5",
|
| 3288 |
+
"smiley_west"
|
| 3289 |
+
],
|
| 3290 |
+
"auto_discovered": true
|
| 3291 |
+
},
|
| 3292 |
+
{
|
| 3293 |
+
"entity": "IfcBuilding",
|
| 3294 |
+
"source": "qset",
|
| 3295 |
+
"set_name": "BaseQuantities",
|
| 3296 |
+
"key": "GrossFloorArea",
|
| 3297 |
+
"file_count": 7,
|
| 3298 |
+
"projects": [
|
| 3299 |
+
"ac20",
|
| 3300 |
+
"fzk_house",
|
| 3301 |
+
"molio",
|
| 3302 |
+
"sixty5",
|
| 3303 |
+
"smiley_west"
|
| 3304 |
+
],
|
| 3305 |
+
"auto_discovered": true
|
| 3306 |
+
},
|
| 3307 |
+
{
|
| 3308 |
+
"entity": "IfcBuildingStorey",
|
| 3309 |
+
"source": "qset",
|
| 3310 |
+
"set_name": "BaseQuantities",
|
| 3311 |
+
"key": "GrossFloorArea",
|
| 3312 |
+
"file_count": 7,
|
| 3313 |
+
"projects": [
|
| 3314 |
+
"ac20",
|
| 3315 |
+
"fzk_house",
|
| 3316 |
+
"molio",
|
| 3317 |
+
"sixty5",
|
| 3318 |
+
"smiley_west"
|
| 3319 |
+
],
|
| 3320 |
+
"auto_discovered": true
|
| 3321 |
+
}
|
| 3322 |
+
]
|
| 3323 |
+
}
|
teams/lux-ai/Final pipeline/run_solar_analysis.py
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
run_solar_analysis.py β Unified orchestrator.
|
| 3 |
+
|
| 4 |
+
Modes
|
| 5 |
+
-----
|
| 6 |
+
1. Single-file solar analysis (default):
|
| 7 |
+
python -m solar_pipeline.run_solar_analysis path/to/model.ifc
|
| 8 |
+
|
| 9 |
+
2. Batch metadata scan (no API calls):
|
| 10 |
+
python -m solar_pipeline.run_solar_analysis --scan-only --root "Sample projects/projects"
|
| 11 |
+
|
| 12 |
+
3. Batch solar analysis:
|
| 13 |
+
python -m solar_pipeline.run_solar_analysis --batch --root "Sample projects/projects"
|
| 14 |
+
|
| 15 |
+
Location is auto-extracted from IfcSite. Override with --lat / --lon / --name
|
| 16 |
+
when the IFC file has no geographic coordinates.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import argparse
|
| 22 |
+
import json
|
| 23 |
+
import logging
|
| 24 |
+
import os
|
| 25 |
+
import sys
|
| 26 |
+
from pathlib import Path
|
| 27 |
+
|
| 28 |
+
# ββ Ensure package is importable when run as a script βββββββββββββββββββββββββ
|
| 29 |
+
_SCRIPT_DIR = Path(__file__).resolve().parent
|
| 30 |
+
_REPO_ROOT = _SCRIPT_DIR.parent
|
| 31 |
+
if str(_REPO_ROOT) not in sys.path:
|
| 32 |
+
sys.path.insert(0, str(_REPO_ROOT))
|
| 33 |
+
|
| 34 |
+
from solar_pipeline.config import ( # noqa: E402
|
| 35 |
+
DEFAULT_CONSUMPTION_KWH_PER_M2,
|
| 36 |
+
DEFAULT_SAMPLE_ROOT,
|
| 37 |
+
FALLBACK_CONSUMPTION_KWH,
|
| 38 |
+
)
|
| 39 |
+
from solar_pipeline.ifc_metadata_extractor import ( # noqa: E402
|
| 40 |
+
Location,
|
| 41 |
+
extract_all,
|
| 42 |
+
extract_floor_area,
|
| 43 |
+
extract_location,
|
| 44 |
+
extract_true_north,
|
| 45 |
+
find_ifc_files,
|
| 46 |
+
open_model,
|
| 47 |
+
print_summary_table,
|
| 48 |
+
scan_all,
|
| 49 |
+
)
|
| 50 |
+
from solar_pipeline.ifc_roof_parser import parse_roof_segments # noqa: E402
|
| 51 |
+
from solar_pipeline.solar_production_engine import run_production_analysis # noqa: E402
|
| 52 |
+
|
| 53 |
+
log = logging.getLogger(__name__)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
# ββ Single-file solar analysis ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 57 |
+
|
| 58 |
+
def run_single(
|
| 59 |
+
ifc_path: Path,
|
| 60 |
+
lat: float | None = None,
|
| 61 |
+
lon: float | None = None,
|
| 62 |
+
name: str | None = None,
|
| 63 |
+
) -> dict | None:
|
| 64 |
+
"""
|
| 65 |
+
Full pipeline: IFC β metadata + roof geometry β PVWatts β report.
|
| 66 |
+
|
| 67 |
+
Returns the production-analysis result dict, or None on failure.
|
| 68 |
+
"""
|
| 69 |
+
if not ifc_path.is_file():
|
| 70 |
+
print(f"[Error] IFC file not found: {ifc_path}")
|
| 71 |
+
return None
|
| 72 |
+
|
| 73 |
+
project = ifc_path.parent.name
|
| 74 |
+
|
| 75 |
+
# ββ Step 1: Metadata extraction βββββββββββββββββββββββββββββββββββββββ
|
| 76 |
+
print("=" * 70)
|
| 77 |
+
print(" STEP 1 β Extracting IFC metadata")
|
| 78 |
+
print("=" * 70)
|
| 79 |
+
|
| 80 |
+
metadata = extract_all(ifc_path)
|
| 81 |
+
for key in ("window_area_m2", "floor_area_m2", "roof_area_m2",
|
| 82 |
+
"true_north_angle_deg", "latitude", "longitude"):
|
| 83 |
+
val = metadata.get(key)
|
| 84 |
+
label = key.replace("_", " ").title()
|
| 85 |
+
print(f" {label:.<30s} {val if val is not None else 'N/A'}")
|
| 86 |
+
|
| 87 |
+
# ββ Resolve location ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 88 |
+
if lat is not None and lon is not None:
|
| 89 |
+
location = Location(latitude=lat, longitude=lon, name=name or project)
|
| 90 |
+
print(f"\n Location (CLI override): {location.latitude}, {location.longitude}")
|
| 91 |
+
elif metadata.get("latitude") is not None and metadata.get("longitude") is not None:
|
| 92 |
+
location = Location(
|
| 93 |
+
latitude=metadata["latitude"],
|
| 94 |
+
longitude=metadata["longitude"],
|
| 95 |
+
name=name or project,
|
| 96 |
+
)
|
| 97 |
+
print(f"\n Location (auto from IfcSite): {location.latitude}, {location.longitude}")
|
| 98 |
+
else:
|
| 99 |
+
print("\n [Error] No location available. Use --lat / --lon or provide an IFC file with IfcSite coordinates.")
|
| 100 |
+
return None
|
| 101 |
+
|
| 102 |
+
# ββ Step 2: Roof geometry parsing βββββββββββββββββββββββββββββββββββββ
|
| 103 |
+
print()
|
| 104 |
+
print("=" * 70)
|
| 105 |
+
print(" STEP 2 β Parsing roof geometry")
|
| 106 |
+
print("=" * 70)
|
| 107 |
+
|
| 108 |
+
segments = parse_roof_segments(ifc_path)
|
| 109 |
+
if not segments:
|
| 110 |
+
print(" No roof segments found. Cannot run solar analysis.")
|
| 111 |
+
return None
|
| 112 |
+
|
| 113 |
+
for seg in segments:
|
| 114 |
+
print(f" {seg['id']} | Area: {seg['area']:>7.1f} mΒ² | "
|
| 115 |
+
f"Tilt: {seg['tilt']:>5.1f}Β° | Azimuth: {seg['azimuth']:>5.1f}Β°")
|
| 116 |
+
|
| 117 |
+
# ββ Step 3: Solar production analysis βββββββββββββββββββββββββββββββββ
|
| 118 |
+
print()
|
| 119 |
+
print("=" * 70)
|
| 120 |
+
print(" STEP 3 β Querying NREL PVWatts v8")
|
| 121 |
+
print("=" * 70)
|
| 122 |
+
|
| 123 |
+
result = run_production_analysis(segments=segments, location=location)
|
| 124 |
+
|
| 125 |
+
# ββ Step 4: Summary & LEED estimate βββββββββββββββββββββββββββββββββββ
|
| 126 |
+
print()
|
| 127 |
+
print("=" * 70)
|
| 128 |
+
print(" STEP 4 β Summary")
|
| 129 |
+
print("=" * 70)
|
| 130 |
+
|
| 131 |
+
total_kwh = result["total_kwh"]
|
| 132 |
+
total_area = sum(s["area"] for s in result["segments"])
|
| 133 |
+
print(f" IFC file : {ifc_path.name}")
|
| 134 |
+
print(f" Project : {project}")
|
| 135 |
+
print(f" Location : {location.name} ({location.latitude}, {location.longitude})")
|
| 136 |
+
print(f" Segments : {len(result['segments'])}")
|
| 137 |
+
print(f" Total roof : {total_area:,.2f} mΒ²")
|
| 138 |
+
print(f" Total prod. : {total_kwh:,.2f} kWh/yr")
|
| 139 |
+
|
| 140 |
+
# LEED estimate β use floor area if available
|
| 141 |
+
floor_area = metadata.get("floor_area_m2")
|
| 142 |
+
if floor_area and floor_area > 0:
|
| 143 |
+
consumption = floor_area * DEFAULT_CONSUMPTION_KWH_PER_M2
|
| 144 |
+
print(f"\n LEED estimate (floor area = {floor_area:,.1f} mΒ² Γ "
|
| 145 |
+
f"{DEFAULT_CONSUMPTION_KWH_PER_M2} kWh/mΒ²/yr):")
|
| 146 |
+
else:
|
| 147 |
+
consumption = FALLBACK_CONSUMPTION_KWH
|
| 148 |
+
print(f"\n LEED estimate (assumed consumption = {consumption:,} kWh/yr):")
|
| 149 |
+
|
| 150 |
+
leed_pct = (total_kwh / consumption) * 100 if consumption > 0 else 0
|
| 151 |
+
print(f" Consumption : {consumption:,.0f} kWh/yr")
|
| 152 |
+
print(f" Production : {total_kwh:,.0f} kWh/yr")
|
| 153 |
+
print(f" Score : {total_kwh:,.0f} / {consumption:,.0f} Γ 100 = {leed_pct:.1f}%")
|
| 154 |
+
print()
|
| 155 |
+
|
| 156 |
+
return result
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
# ββ Batch modes βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 160 |
+
|
| 161 |
+
def run_batch_scan(root: Path, output_csv: Path) -> list[dict]:
|
| 162 |
+
"""Scan all IFC files and write metadata CSV (no API calls)."""
|
| 163 |
+
results = scan_all(root, output_csv)
|
| 164 |
+
print_summary_table(results)
|
| 165 |
+
return results
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def run_batch_solar(root: Path, output_csv: Path) -> None:
|
| 169 |
+
"""
|
| 170 |
+
Scan all IFC files, then run solar analysis on each ``arc.ifc``
|
| 171 |
+
that has both roof elements and location data.
|
| 172 |
+
"""
|
| 173 |
+
# First do the metadata scan
|
| 174 |
+
results = scan_all(root, output_csv)
|
| 175 |
+
print_summary_table(results)
|
| 176 |
+
|
| 177 |
+
# Then run solar on architectural files with location
|
| 178 |
+
print("\n" + "=" * 70)
|
| 179 |
+
print(" BATCH SOLAR ANALYSIS β processing arc.ifc files with location data")
|
| 180 |
+
print("=" * 70 + "\n")
|
| 181 |
+
|
| 182 |
+
solar_results: list[dict] = []
|
| 183 |
+
for r in results:
|
| 184 |
+
if r.get("error"):
|
| 185 |
+
continue
|
| 186 |
+
if r.get("latitude") is None or r.get("longitude") is None:
|
| 187 |
+
continue
|
| 188 |
+
# Only process architectural files
|
| 189 |
+
if r["ifc_file"] != "arc.ifc":
|
| 190 |
+
continue
|
| 191 |
+
|
| 192 |
+
ifc_path = root / r["project_name"] / r["ifc_file"]
|
| 193 |
+
if not ifc_path.is_file():
|
| 194 |
+
continue
|
| 195 |
+
|
| 196 |
+
print(f"\n--- {r['project_name']} ---")
|
| 197 |
+
try:
|
| 198 |
+
segments = parse_roof_segments(ifc_path)
|
| 199 |
+
if not segments:
|
| 200 |
+
print(f" No roof segments β skipped")
|
| 201 |
+
continue
|
| 202 |
+
|
| 203 |
+
location = Location(
|
| 204 |
+
latitude=r["latitude"],
|
| 205 |
+
longitude=r["longitude"],
|
| 206 |
+
name=r["project_name"],
|
| 207 |
+
)
|
| 208 |
+
result = run_production_analysis(segments, location, verbose=True)
|
| 209 |
+
solar_results.append({
|
| 210 |
+
"project": r["project_name"],
|
| 211 |
+
"total_kwh": result["total_kwh"],
|
| 212 |
+
"segments": len(result["segments"]),
|
| 213 |
+
"roof_area_m2": sum(s["area"] for s in result["segments"]),
|
| 214 |
+
})
|
| 215 |
+
except Exception as exc:
|
| 216 |
+
log.warning(" Solar analysis failed for %s: %s", r["project_name"], exc)
|
| 217 |
+
|
| 218 |
+
if solar_results:
|
| 219 |
+
print("\n\n" + "=" * 70)
|
| 220 |
+
print(" BATCH SOLAR SUMMARY")
|
| 221 |
+
print("=" * 70)
|
| 222 |
+
for sr in solar_results:
|
| 223 |
+
print(f" {sr['project']:.<30s} {sr['total_kwh']:>10,.0f} kWh/yr "
|
| 224 |
+
f"({sr['segments']} segs, {sr['roof_area_m2']:,.0f} mΒ²)")
|
| 225 |
+
total = sum(sr["total_kwh"] for sr in solar_results)
|
| 226 |
+
print(f"\n Grand total: {total:,.0f} kWh/yr across {len(solar_results)} buildings")
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
# ββ CLI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 230 |
+
|
| 231 |
+
def build_parser() -> argparse.ArgumentParser:
|
| 232 |
+
parser = argparse.ArgumentParser(
|
| 233 |
+
prog="solar_pipeline",
|
| 234 |
+
description="Unified IFC metadata extraction + solar production analysis.",
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
# Modes (mutually exclusive)
|
| 238 |
+
mode = parser.add_mutually_exclusive_group()
|
| 239 |
+
mode.add_argument(
|
| 240 |
+
"--scan-only",
|
| 241 |
+
action="store_true",
|
| 242 |
+
help="Batch metadata scan only (no PVWatts API calls).",
|
| 243 |
+
)
|
| 244 |
+
mode.add_argument(
|
| 245 |
+
"--batch",
|
| 246 |
+
action="store_true",
|
| 247 |
+
help="Batch solar analysis of all arc.ifc files under --root.",
|
| 248 |
+
)
|
| 249 |
+
|
| 250 |
+
# Paths
|
| 251 |
+
parser.add_argument(
|
| 252 |
+
"ifc_file",
|
| 253 |
+
nargs="?",
|
| 254 |
+
type=Path,
|
| 255 |
+
default=None,
|
| 256 |
+
help="Path to a single IFC file (for single-file mode).",
|
| 257 |
+
)
|
| 258 |
+
parser.add_argument(
|
| 259 |
+
"--root",
|
| 260 |
+
type=Path,
|
| 261 |
+
default=DEFAULT_SAMPLE_ROOT,
|
| 262 |
+
help=f"Root directory for batch scanning (default: {DEFAULT_SAMPLE_ROOT}).",
|
| 263 |
+
)
|
| 264 |
+
parser.add_argument(
|
| 265 |
+
"--output",
|
| 266 |
+
type=Path,
|
| 267 |
+
default=_REPO_ROOT / "ifc_scan_results.csv",
|
| 268 |
+
help="Output CSV file for batch scan results.",
|
| 269 |
+
)
|
| 270 |
+
|
| 271 |
+
# Location override
|
| 272 |
+
parser.add_argument("--lat", type=float, default=None, help="Site latitude (override IfcSite).")
|
| 273 |
+
parser.add_argument("--lon", type=float, default=None, help="Site longitude (override IfcSite).")
|
| 274 |
+
parser.add_argument("--name", type=str, default=None, help="Project name for report.")
|
| 275 |
+
|
| 276 |
+
return parser
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
def main() -> None:
|
| 280 |
+
logging.basicConfig(
|
| 281 |
+
level=logging.INFO,
|
| 282 |
+
format="%(levelname)-8s %(message)s",
|
| 283 |
+
handlers=[
|
| 284 |
+
logging.StreamHandler(),
|
| 285 |
+
logging.FileHandler(
|
| 286 |
+
_REPO_ROOT / "solar_pipeline.log", mode="w", encoding="utf-8",
|
| 287 |
+
),
|
| 288 |
+
],
|
| 289 |
+
)
|
| 290 |
+
|
| 291 |
+
parser = build_parser()
|
| 292 |
+
args = parser.parse_args()
|
| 293 |
+
|
| 294 |
+
if args.scan_only:
|
| 295 |
+
# ββ Batch metadata scan βββββββββββββββββββββββββββββββββββββββββββ
|
| 296 |
+
if not args.root.exists():
|
| 297 |
+
print(f"[Error] Root directory not found: {args.root}")
|
| 298 |
+
sys.exit(1)
|
| 299 |
+
run_batch_scan(args.root, args.output)
|
| 300 |
+
|
| 301 |
+
elif args.batch:
|
| 302 |
+
# ββ Batch solar analysis ββββββββββββββββββββββββββββββββββββββββββ
|
| 303 |
+
if not args.root.exists():
|
| 304 |
+
print(f"[Error] Root directory not found: {args.root}")
|
| 305 |
+
sys.exit(1)
|
| 306 |
+
run_batch_solar(args.root, args.output)
|
| 307 |
+
|
| 308 |
+
else:
|
| 309 |
+
# ββ Single-file solar analysis ββββββββββββββββββββββββββββββββββββ
|
| 310 |
+
if args.ifc_file is None:
|
| 311 |
+
parser.print_help()
|
| 312 |
+
print("\n[Error] Provide an IFC file path or use --scan-only / --batch.")
|
| 313 |
+
sys.exit(1)
|
| 314 |
+
result = run_single(
|
| 315 |
+
args.ifc_file,
|
| 316 |
+
lat=args.lat,
|
| 317 |
+
lon=args.lon,
|
| 318 |
+
name=args.name,
|
| 319 |
+
)
|
| 320 |
+
if result is None:
|
| 321 |
+
sys.exit(1)
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
if __name__ == "__main__":
|
| 325 |
+
main()
|
teams/lux-ai/Final pipeline/solar_production_engine.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
solar_production_engine.py β NREL PVWatts v8 API client.
|
| 3 |
+
|
| 4 |
+
Calculates annual solar energy production (kWh) for roof segments.
|
| 5 |
+
Each segment has its own tilt & azimuth, yielding per-orientation accuracy
|
| 6 |
+
for LEED renewable-energy scoring.
|
| 7 |
+
|
| 8 |
+
LEED Score = (Ξ£ Segment_kWh / Consumption_total) Γ 100
|
| 9 |
+
|
| 10 |
+
Adapted from the original standalone engine to integrate with:
|
| 11 |
+
- ifc_metadata_extractor.py (auto location from IfcSite)
|
| 12 |
+
- ifc_roof_parser.py (geometry-based roof segments)
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
import logging
|
| 18 |
+
import time
|
| 19 |
+
|
| 20 |
+
import requests
|
| 21 |
+
|
| 22 |
+
from solar_pipeline.config import (
|
| 23 |
+
ARRAY_TYPE,
|
| 24 |
+
MODULE_TYPE,
|
| 25 |
+
NREL_API_KEY,
|
| 26 |
+
PANEL_EFFICIENCY,
|
| 27 |
+
PVWATTS_BASE_URL,
|
| 28 |
+
SYSTEM_LOSSES,
|
| 29 |
+
)
|
| 30 |
+
from solar_pipeline.ifc_metadata_extractor import Location
|
| 31 |
+
|
| 32 |
+
log = logging.getLogger(__name__)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
# ββ Core calculation ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 36 |
+
|
| 37 |
+
def calculate_segment_production(
|
| 38 |
+
area: float,
|
| 39 |
+
tilt: float,
|
| 40 |
+
azimuth: float,
|
| 41 |
+
location: Location,
|
| 42 |
+
) -> float:
|
| 43 |
+
"""
|
| 44 |
+
Query NREL PVWatts v8 for a single roof segment.
|
| 45 |
+
|
| 46 |
+
Parameters
|
| 47 |
+
----------
|
| 48 |
+
area : segment area in mΒ²
|
| 49 |
+
tilt : panel tilt in degrees from horizontal
|
| 50 |
+
azimuth : compass bearing (180Β° = due south)
|
| 51 |
+
location : site coordinates
|
| 52 |
+
|
| 53 |
+
Returns
|
| 54 |
+
-------
|
| 55 |
+
float β annual AC production in kWh, or 0.0 on error.
|
| 56 |
+
"""
|
| 57 |
+
system_capacity = area * PANEL_EFFICIENCY # kW
|
| 58 |
+
|
| 59 |
+
params = {
|
| 60 |
+
"api_key": NREL_API_KEY,
|
| 61 |
+
"lat": location.latitude,
|
| 62 |
+
"lon": location.longitude,
|
| 63 |
+
"system_capacity": round(system_capacity, 3),
|
| 64 |
+
"azimuth": azimuth,
|
| 65 |
+
"tilt": tilt,
|
| 66 |
+
"array_type": ARRAY_TYPE,
|
| 67 |
+
"module_type": MODULE_TYPE,
|
| 68 |
+
"losses": SYSTEM_LOSSES,
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
try:
|
| 72 |
+
response = requests.get(PVWATTS_BASE_URL, params=params, timeout=30)
|
| 73 |
+
response.raise_for_status()
|
| 74 |
+
data = response.json()
|
| 75 |
+
|
| 76 |
+
if "errors" in data and data["errors"]:
|
| 77 |
+
log.error(" PVWatts API error: %s", data["errors"])
|
| 78 |
+
return 0.0
|
| 79 |
+
|
| 80 |
+
annual_kwh = float(data["outputs"]["ac_annual"])
|
| 81 |
+
return annual_kwh
|
| 82 |
+
|
| 83 |
+
except requests.RequestException as exc:
|
| 84 |
+
log.error(" PVWatts request failed: %s", exc)
|
| 85 |
+
return 0.0
|
| 86 |
+
except (KeyError, TypeError, ValueError) as exc:
|
| 87 |
+
log.error(" Could not parse PVWatts response: %s", exc)
|
| 88 |
+
return 0.0
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
# ββ Batch analysis ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 92 |
+
|
| 93 |
+
def run_production_analysis(
|
| 94 |
+
segments: list[dict],
|
| 95 |
+
location: Location,
|
| 96 |
+
*,
|
| 97 |
+
rate_limit_sec: float = 1.0,
|
| 98 |
+
verbose: bool = True,
|
| 99 |
+
) -> dict:
|
| 100 |
+
"""
|
| 101 |
+
Iterate over roof segments, query PVWatts for each, return results.
|
| 102 |
+
|
| 103 |
+
Parameters
|
| 104 |
+
----------
|
| 105 |
+
segments : list of dicts, each with keys: id, area, tilt, azimuth
|
| 106 |
+
location : site coordinates
|
| 107 |
+
rate_limit_sec : pause between API calls (NREL rate-limit)
|
| 108 |
+
verbose : print per-segment progress to stdout
|
| 109 |
+
|
| 110 |
+
Returns
|
| 111 |
+
-------
|
| 112 |
+
dict with keys:
|
| 113 |
+
segments β list of per-segment result dicts (input + capacity_kw + annual_kwh)
|
| 114 |
+
total_kwh β total annual production (float)
|
| 115 |
+
location β the Location used
|
| 116 |
+
"""
|
| 117 |
+
total_kwh = 0.0
|
| 118 |
+
results: list[dict] = []
|
| 119 |
+
|
| 120 |
+
if verbose:
|
| 121 |
+
print(f"\n--- Analysing Roof Segments for {location.name} ---")
|
| 122 |
+
print(f" Site: ({location.latitude}, {location.longitude})")
|
| 123 |
+
print(f" Panel efficiency: {PANEL_EFFICIENCY}")
|
| 124 |
+
print()
|
| 125 |
+
|
| 126 |
+
for i, seg in enumerate(segments):
|
| 127 |
+
capacity_kw = seg["area"] * PANEL_EFFICIENCY
|
| 128 |
+
annual = calculate_segment_production(
|
| 129 |
+
seg["area"], seg["tilt"], seg["azimuth"], location,
|
| 130 |
+
)
|
| 131 |
+
total_kwh += annual
|
| 132 |
+
|
| 133 |
+
result = {
|
| 134 |
+
"id": seg["id"],
|
| 135 |
+
"area": seg["area"],
|
| 136 |
+
"tilt": seg["tilt"],
|
| 137 |
+
"azimuth": seg["azimuth"],
|
| 138 |
+
"capacity_kw": round(capacity_kw, 2),
|
| 139 |
+
"annual_kwh": round(annual, 2),
|
| 140 |
+
}
|
| 141 |
+
results.append(result)
|
| 142 |
+
|
| 143 |
+
if verbose:
|
| 144 |
+
print(
|
| 145 |
+
f" {seg['id']:>15s} | "
|
| 146 |
+
f"Area: {seg['area']:>7.1f} mΒ² | "
|
| 147 |
+
f"Tilt: {seg['tilt']:>5.1f}Β° | "
|
| 148 |
+
f"Azimuth: {seg['azimuth']:>5.1f}Β° | "
|
| 149 |
+
f"Capacity: {capacity_kw:>6.1f} kW | "
|
| 150 |
+
f"Yield: {annual:>10,.2f} kWh/yr"
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
# Respect NREL rate limits
|
| 154 |
+
if i < len(segments) - 1:
|
| 155 |
+
time.sleep(rate_limit_sec)
|
| 156 |
+
|
| 157 |
+
if verbose:
|
| 158 |
+
print()
|
| 159 |
+
print("-" * 70)
|
| 160 |
+
print(f" TOTAL BUILDING PRODUCTION: {total_kwh:>12,.2f} kWh/yr")
|
| 161 |
+
print("-" * 70)
|
| 162 |
+
|
| 163 |
+
return {
|
| 164 |
+
"segments": results,
|
| 165 |
+
"total_kwh": round(total_kwh, 2),
|
| 166 |
+
"location": location,
|
| 167 |
+
}
|
teams/lux-ai/IFC key checker/discover_ifc_keys.py
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
discover_ifc_keys.py
|
| 3 |
+
Scans all IFC files and inventories every property set and quantity set
|
| 4 |
+
name used per element type, producing:
|
| 5 |
+
- ifc_key_inventory.json (raw: all keys found, with file counts)
|
| 6 |
+
- key_aliases.json (canonical: standard key β all alias paths)
|
| 7 |
+
|
| 8 |
+
Run:
|
| 9 |
+
.venv/Scripts/python.exe discover_ifc_keys.py
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import json
|
| 15 |
+
import logging
|
| 16 |
+
from collections import defaultdict
|
| 17 |
+
from pathlib import Path
|
| 18 |
+
|
| 19 |
+
import ifcopenshell
|
| 20 |
+
from tabulate import tabulate
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
# ββ Logging βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 24 |
+
|
| 25 |
+
logging.basicConfig(
|
| 26 |
+
level=logging.INFO,
|
| 27 |
+
format="%(levelname)-8s %(message)s",
|
| 28 |
+
handlers=[logging.StreamHandler()],
|
| 29 |
+
)
|
| 30 |
+
log = logging.getLogger(__name__)
|
| 31 |
+
|
| 32 |
+
# ββ Element types to inventory ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 33 |
+
|
| 34 |
+
TARGET_TYPES = [
|
| 35 |
+
"IfcWindow",
|
| 36 |
+
"IfcDoor",
|
| 37 |
+
"IfcSpace",
|
| 38 |
+
"IfcSlab",
|
| 39 |
+
"IfcRoof",
|
| 40 |
+
"IfcWall",
|
| 41 |
+
"IfcWallStandardCase",
|
| 42 |
+
"IfcCovering",
|
| 43 |
+
"IfcSite",
|
| 44 |
+
"IfcBuilding",
|
| 45 |
+
"IfcBuildingStorey",
|
| 46 |
+
]
|
| 47 |
+
|
| 48 |
+
# Keywords that flag a property/quantity as area-related for highlighting
|
| 49 |
+
AREA_KEYWORDS = {"area", "flΓ€che", "superficie", "superficie"}
|
| 50 |
+
HEIGHT_KEYWORDS = {"height", "hΓΆhe", "width", "length", "depth", "perimeter"}
|
| 51 |
+
ORIENTATION_KEYWORDS = {"north", "latitude", "longitude", "orientation", "azimuth"}
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
# ββ Inventory data structure ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 55 |
+
#
|
| 56 |
+
# global_inventory[element_type]["quantity_sets"][qset_name][qty_name]
|
| 57 |
+
# = {"file_count": int, "projects": [str, ...]}
|
| 58 |
+
# global_inventory[element_type]["property_sets"][pset_name][prop_name]
|
| 59 |
+
# = {"file_count": int, "projects": [str, ...]}
|
| 60 |
+
|
| 61 |
+
def _empty_entry() -> dict:
|
| 62 |
+
return {"file_count": 0, "projects": []}
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def make_inventory() -> dict:
|
| 66 |
+
return {
|
| 67 |
+
t: {"quantity_sets": defaultdict(lambda: defaultdict(_empty_entry)),
|
| 68 |
+
"property_sets": defaultdict(lambda: defaultdict(_empty_entry))}
|
| 69 |
+
for t in TARGET_TYPES
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
# ββ Per-element collection ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 74 |
+
|
| 75 |
+
def collect_qsets(model: ifcopenshell.file, element_type: str) -> dict:
|
| 76 |
+
"""
|
| 77 |
+
Returns {qset_name: {qty_name: count_in_this_file}} for all
|
| 78 |
+
instances of element_type in this model.
|
| 79 |
+
"""
|
| 80 |
+
result: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
|
| 81 |
+
for elem in model.by_type(element_type):
|
| 82 |
+
for rel in getattr(elem, "IsDefinedBy", []):
|
| 83 |
+
if not rel.is_a("IfcRelDefinesByProperties"):
|
| 84 |
+
continue
|
| 85 |
+
defn = rel.RelatingPropertyDefinition
|
| 86 |
+
if not defn.is_a("IfcElementQuantity"):
|
| 87 |
+
continue
|
| 88 |
+
qset_name = defn.Name or "(unnamed)"
|
| 89 |
+
for qty in defn.Quantities:
|
| 90 |
+
qty_name = qty.Name or "(unnamed)"
|
| 91 |
+
result[qset_name][qty_name] += 1
|
| 92 |
+
return result
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def collect_psets(model: ifcopenshell.file, element_type: str) -> dict:
|
| 96 |
+
"""
|
| 97 |
+
Returns {pset_name: {prop_name: count_in_this_file}} for all
|
| 98 |
+
instances of element_type in this model.
|
| 99 |
+
"""
|
| 100 |
+
result: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
|
| 101 |
+
for elem in model.by_type(element_type):
|
| 102 |
+
for rel in getattr(elem, "IsDefinedBy", []):
|
| 103 |
+
if not rel.is_a("IfcRelDefinesByProperties"):
|
| 104 |
+
continue
|
| 105 |
+
defn = rel.RelatingPropertyDefinition
|
| 106 |
+
if not defn.is_a("IfcPropertySet"):
|
| 107 |
+
continue
|
| 108 |
+
pset_name = defn.Name or "(unnamed)"
|
| 109 |
+
for prop in getattr(defn, "HasProperties", []):
|
| 110 |
+
prop_name = getattr(prop, "Name", None) or "(unnamed)"
|
| 111 |
+
result[pset_name][prop_name] += 1
|
| 112 |
+
return result
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
# ββ Merge into global inventory βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 116 |
+
|
| 117 |
+
def merge_into(global_inv: dict, element_type: str, section: str,
|
| 118 |
+
file_data: dict, project_name: str) -> None:
|
| 119 |
+
"""
|
| 120 |
+
Merge one file's {set_name: {key_name: count}} into the global inventory,
|
| 121 |
+
incrementing file_count and appending project_name.
|
| 122 |
+
"""
|
| 123 |
+
target = global_inv[element_type][section]
|
| 124 |
+
for set_name, keys in file_data.items():
|
| 125 |
+
for key_name in keys:
|
| 126 |
+
entry = target[set_name][key_name]
|
| 127 |
+
entry["file_count"] += 1
|
| 128 |
+
if project_name not in entry["projects"]:
|
| 129 |
+
entry["projects"].append(project_name)
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
# ββ Serialisation helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 133 |
+
|
| 134 |
+
def inventory_to_plain(global_inv: dict) -> dict:
|
| 135 |
+
"""Convert defaultdict structure to plain dicts for JSON serialisation."""
|
| 136 |
+
out = {}
|
| 137 |
+
for etype, sections in global_inv.items():
|
| 138 |
+
out[etype] = {}
|
| 139 |
+
for section, sets in sections.items():
|
| 140 |
+
out[etype][section] = {}
|
| 141 |
+
for set_name, keys in sets.items():
|
| 142 |
+
out[etype][section][set_name] = {}
|
| 143 |
+
for key_name, entry in keys.items():
|
| 144 |
+
out[etype][section][set_name][key_name] = {
|
| 145 |
+
"file_count": entry["file_count"],
|
| 146 |
+
"projects": sorted(entry["projects"]),
|
| 147 |
+
}
|
| 148 |
+
return out
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
# ββ Build key_aliases.json ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 152 |
+
|
| 153 |
+
def build_aliases(inventory: dict) -> dict:
|
| 154 |
+
"""
|
| 155 |
+
Build the canonical key β alias-list mapping.
|
| 156 |
+
Each alias is tried in order; the first one that yields data wins.
|
| 157 |
+
Pre-seeds with known patterns, then extends with anything discovered
|
| 158 |
+
in the inventory that looks area-related but isn't already covered.
|
| 159 |
+
"""
|
| 160 |
+
aliases: dict[str, list[dict]] = {
|
| 161 |
+
"window_area": [
|
| 162 |
+
{"entity": "IfcWindow", "source": "qset",
|
| 163 |
+
"set_name": "Qto_WindowBaseQuantities", "key": "Area"},
|
| 164 |
+
{"entity": "IfcWindow", "source": "qset",
|
| 165 |
+
"set_name": "BaseQuantities", "key": "Area"},
|
| 166 |
+
{"entity": "IfcWindow", "source": "qset",
|
| 167 |
+
"set_name": "BaseQuantities", "key": "GrossArea"},
|
| 168 |
+
{"entity": "IfcWindow", "source": "attr",
|
| 169 |
+
"keys": ["OverallHeight", "OverallWidth"], "op": "multiply"},
|
| 170 |
+
],
|
| 171 |
+
"floor_area": [
|
| 172 |
+
{"entity": "IfcSpace", "source": "qset",
|
| 173 |
+
"set_name": "Qto_SpaceBaseQuantities", "key": "NetFloorArea"},
|
| 174 |
+
{"entity": "IfcSpace", "source": "qset",
|
| 175 |
+
"set_name": "Qto_SpaceBaseQuantities", "key": "GrossFloorArea"},
|
| 176 |
+
{"entity": "IfcSpace", "source": "qset",
|
| 177 |
+
"set_name": "BaseQuantities", "key": "NetFloorArea"},
|
| 178 |
+
{"entity": "IfcSpace", "source": "qset",
|
| 179 |
+
"set_name": "BaseQuantities", "key": "GrossFloorArea"},
|
| 180 |
+
{"entity": "IfcSpace", "source": "qset",
|
| 181 |
+
"set_name": "GSA Space Areas", "key": "GSA BIM Area"},
|
| 182 |
+
{"entity": "IfcSlab", "source": "qset",
|
| 183 |
+
"set_name": "Qto_SlabBaseQuantities", "key": "NetArea",
|
| 184 |
+
"predefined_type": "FLOOR"},
|
| 185 |
+
{"entity": "IfcSlab", "source": "qset",
|
| 186 |
+
"set_name": "BaseQuantities", "key": "NetArea",
|
| 187 |
+
"predefined_type": "FLOOR"},
|
| 188 |
+
{"entity": "IfcSlab", "source": "qset",
|
| 189 |
+
"set_name": "BaseQuantities", "key": "GrossArea",
|
| 190 |
+
"predefined_type": "FLOOR"},
|
| 191 |
+
],
|
| 192 |
+
"roof_area": [
|
| 193 |
+
{"entity": "IfcRoof", "source": "qset",
|
| 194 |
+
"set_name": "Qto_RoofBaseQuantities", "key": "NetArea"},
|
| 195 |
+
{"entity": "IfcRoof", "source": "qset",
|
| 196 |
+
"set_name": "Qto_RoofBaseQuantities", "key": "GrossArea"},
|
| 197 |
+
{"entity": "IfcRoof", "source": "qset",
|
| 198 |
+
"set_name": "BaseQuantities", "key": "NetArea"},
|
| 199 |
+
{"entity": "IfcSlab", "source": "qset",
|
| 200 |
+
"set_name": "Qto_SlabBaseQuantities", "key": "NetArea",
|
| 201 |
+
"predefined_type": "ROOF"},
|
| 202 |
+
{"entity": "IfcSlab", "source": "qset",
|
| 203 |
+
"set_name": "Qto_SlabBaseQuantities", "key": "GrossArea",
|
| 204 |
+
"predefined_type": "ROOF"},
|
| 205 |
+
{"entity": "IfcSlab", "source": "qset",
|
| 206 |
+
"set_name": "BaseQuantities", "key": "NetArea",
|
| 207 |
+
"predefined_type": "ROOF"},
|
| 208 |
+
{"entity": "IfcSlab", "source": "qset",
|
| 209 |
+
"set_name": "BaseQuantities", "key": "GrossArea",
|
| 210 |
+
"predefined_type": "ROOF"},
|
| 211 |
+
],
|
| 212 |
+
"true_north_angle": [
|
| 213 |
+
{"entity": "IfcGeometricRepresentationContext",
|
| 214 |
+
"source": "attr", "key": "TrueNorth",
|
| 215 |
+
"note": "DirectionRatios (X,Y) β atan2(x,y) β compass bearing"},
|
| 216 |
+
],
|
| 217 |
+
"latitude": [
|
| 218 |
+
{"entity": "IfcSite", "source": "attr", "key": "RefLatitude",
|
| 219 |
+
"note": "IfcCompoundPlaneAngleMeasure [deg,min,sec,microsec]"},
|
| 220 |
+
],
|
| 221 |
+
"longitude": [
|
| 222 |
+
{"entity": "IfcSite", "source": "attr", "key": "RefLongitude",
|
| 223 |
+
"note": "IfcCompoundPlaneAngleMeasure [deg,min,sec,microsec]"},
|
| 224 |
+
],
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
# Auto-extend: scan the inventory for any quantity set / property set
|
| 228 |
+
# entries that look area-related and are not already covered above.
|
| 229 |
+
already_covered: set[tuple] = set()
|
| 230 |
+
for canonical, alias_list in aliases.items():
|
| 231 |
+
for a in alias_list:
|
| 232 |
+
if a["source"] == "qset":
|
| 233 |
+
already_covered.add(
|
| 234 |
+
(a["entity"], "quantity_sets", a["set_name"], a["key"])
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
auto_discovered: list[dict] = []
|
| 238 |
+
for etype, sections in inventory.items():
|
| 239 |
+
for set_name, keys in sections.get("quantity_sets", {}).items():
|
| 240 |
+
for key_name, entry in keys.items():
|
| 241 |
+
if entry["file_count"] == 0:
|
| 242 |
+
continue
|
| 243 |
+
key_lower = key_name.lower()
|
| 244 |
+
is_area = any(kw in key_lower for kw in AREA_KEYWORDS)
|
| 245 |
+
if not is_area:
|
| 246 |
+
continue
|
| 247 |
+
tag = (etype, "quantity_sets", set_name, key_name)
|
| 248 |
+
if tag in already_covered:
|
| 249 |
+
continue
|
| 250 |
+
auto_discovered.append({
|
| 251 |
+
"entity": etype,
|
| 252 |
+
"source": "qset",
|
| 253 |
+
"set_name": set_name,
|
| 254 |
+
"key": key_name,
|
| 255 |
+
"file_count": entry["file_count"],
|
| 256 |
+
"projects": entry["projects"],
|
| 257 |
+
"auto_discovered": True,
|
| 258 |
+
})
|
| 259 |
+
|
| 260 |
+
if auto_discovered:
|
| 261 |
+
aliases["_auto_discovered_area_keys"] = auto_discovered
|
| 262 |
+
|
| 263 |
+
return aliases
|
| 264 |
+
|
| 265 |
+
|
| 266 |
+
# ββ Console reporting βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 267 |
+
|
| 268 |
+
def print_report(inventory: dict) -> None:
|
| 269 |
+
"""Print a per-element-type summary table to the console."""
|
| 270 |
+
HIGHLIGHT = {"area", "flΓ€che", "netfloorarea", "grossfloorarea",
|
| 271 |
+
"netarea", "grossarea", "grossfootprintarea"}
|
| 272 |
+
|
| 273 |
+
for etype in TARGET_TYPES:
|
| 274 |
+
data = inventory.get(etype, {})
|
| 275 |
+
qsets = data.get("quantity_sets", {})
|
| 276 |
+
psets = data.get("property_sets", {})
|
| 277 |
+
|
| 278 |
+
if not qsets and not psets:
|
| 279 |
+
continue
|
| 280 |
+
|
| 281 |
+
print(f"\n{'='*70}")
|
| 282 |
+
print(f" {etype}")
|
| 283 |
+
print(f"{'='*70}")
|
| 284 |
+
|
| 285 |
+
# Quantity sets
|
| 286 |
+
if qsets:
|
| 287 |
+
print("\n QUANTITY SETS:")
|
| 288 |
+
rows = []
|
| 289 |
+
for set_name, keys in sorted(qsets.items()):
|
| 290 |
+
for key_name, entry in sorted(
|
| 291 |
+
keys.items(),
|
| 292 |
+
key=lambda x: -x[1]["file_count"]
|
| 293 |
+
):
|
| 294 |
+
flag = "*" if key_name.lower() in HIGHLIGHT else " "
|
| 295 |
+
rows.append([
|
| 296 |
+
flag,
|
| 297 |
+
set_name,
|
| 298 |
+
key_name,
|
| 299 |
+
entry["file_count"],
|
| 300 |
+
", ".join(entry["projects"][:5]) +
|
| 301 |
+
("..." if len(entry["projects"]) > 5 else ""),
|
| 302 |
+
])
|
| 303 |
+
print(tabulate(
|
| 304 |
+
rows,
|
| 305 |
+
headers=["!", "QSet Name", "Quantity", "Files", "Projects"],
|
| 306 |
+
tablefmt="simple",
|
| 307 |
+
))
|
| 308 |
+
|
| 309 |
+
# Property sets (condensed β just set name + count of unique props)
|
| 310 |
+
if psets:
|
| 311 |
+
print("\n PROPERTY SETS (summary):")
|
| 312 |
+
rows = []
|
| 313 |
+
for set_name, keys in sorted(psets.items()):
|
| 314 |
+
max_files = max(e["file_count"] for e in keys.values())
|
| 315 |
+
rows.append([set_name, len(keys), max_files])
|
| 316 |
+
print(tabulate(
|
| 317 |
+
rows,
|
| 318 |
+
headers=["PSet Name", "# Props", "Max Files"],
|
| 319 |
+
tablefmt="simple",
|
| 320 |
+
))
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
# ββ Main ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 324 |
+
|
| 325 |
+
def main() -> None:
|
| 326 |
+
root = Path(__file__).parent / "Sample projects" / "projects"
|
| 327 |
+
ifc_files = sorted(root.rglob("*.ifc"))
|
| 328 |
+
log.info(f"Found {len(ifc_files)} IFC files")
|
| 329 |
+
|
| 330 |
+
global_inv = make_inventory()
|
| 331 |
+
|
| 332 |
+
for i, ifc_path in enumerate(ifc_files, 1):
|
| 333 |
+
project = ifc_path.parent.name
|
| 334 |
+
log.info(f"[{i}/{len(ifc_files)}] {project}/{ifc_path.name}")
|
| 335 |
+
try:
|
| 336 |
+
model = ifcopenshell.open(str(ifc_path))
|
| 337 |
+
except Exception as exc:
|
| 338 |
+
log.warning(f" Cannot open: {exc}")
|
| 339 |
+
continue
|
| 340 |
+
|
| 341 |
+
for etype in TARGET_TYPES:
|
| 342 |
+
try:
|
| 343 |
+
qdata = collect_qsets(model, etype)
|
| 344 |
+
merge_into(global_inv, etype, "quantity_sets", qdata, project)
|
| 345 |
+
except Exception as exc:
|
| 346 |
+
log.debug(f" qset {etype}: {exc}")
|
| 347 |
+
|
| 348 |
+
try:
|
| 349 |
+
pdata = collect_psets(model, etype)
|
| 350 |
+
merge_into(global_inv, etype, "property_sets", pdata, project)
|
| 351 |
+
except Exception as exc:
|
| 352 |
+
log.debug(f" pset {etype}: {exc}")
|
| 353 |
+
|
| 354 |
+
# Serialise inventory
|
| 355 |
+
plain_inv = inventory_to_plain(global_inv)
|
| 356 |
+
inv_path = Path(__file__).parent / "ifc_key_inventory.json"
|
| 357 |
+
with open(inv_path, "w", encoding="utf-8") as f:
|
| 358 |
+
json.dump(plain_inv, f, indent=2, ensure_ascii=False)
|
| 359 |
+
log.info(f"Inventory written to: {inv_path}")
|
| 360 |
+
|
| 361 |
+
# Build and write alias map
|
| 362 |
+
aliases = build_aliases(plain_inv)
|
| 363 |
+
alias_path = Path(__file__).parent / "key_aliases.json"
|
| 364 |
+
with open(alias_path, "w", encoding="utf-8") as f:
|
| 365 |
+
json.dump(aliases, f, indent=2, ensure_ascii=False)
|
| 366 |
+
log.info(f"Alias map written to: {alias_path}")
|
| 367 |
+
|
| 368 |
+
# Print console report
|
| 369 |
+
print_report(plain_inv)
|
| 370 |
+
|
| 371 |
+
# Final summary
|
| 372 |
+
print(f"\n{'='*70}")
|
| 373 |
+
print("SUMMARY")
|
| 374 |
+
print(f"{'='*70}")
|
| 375 |
+
for etype in TARGET_TYPES:
|
| 376 |
+
qsets = plain_inv.get(etype, {}).get("quantity_sets", {})
|
| 377 |
+
psets = plain_inv.get(etype, {}).get("property_sets", {})
|
| 378 |
+
if qsets or psets:
|
| 379 |
+
total_qkeys = sum(len(v) for v in qsets.values())
|
| 380 |
+
total_pkeys = sum(len(v) for v in psets.values())
|
| 381 |
+
print(f" {etype:<30} "
|
| 382 |
+
f"{len(qsets)} qsets / {total_qkeys} qty-keys | "
|
| 383 |
+
f"{len(psets)} psets / {total_pkeys} prop-keys")
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
if __name__ == "__main__":
|
| 387 |
+
main()
|
teams/lux-ai/IFC key checker/ifc_key_inventory.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
teams/lux-ai/IFC key checker/key_aliases.json
ADDED
|
@@ -0,0 +1,3275 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"window_area": [
|
| 3 |
+
{
|
| 4 |
+
"entity": "IfcWindow",
|
| 5 |
+
"source": "qset",
|
| 6 |
+
"set_name": "Qto_WindowBaseQuantities",
|
| 7 |
+
"key": "Area"
|
| 8 |
+
},
|
| 9 |
+
{
|
| 10 |
+
"entity": "IfcWindow",
|
| 11 |
+
"source": "qset",
|
| 12 |
+
"set_name": "BaseQuantities",
|
| 13 |
+
"key": "Area"
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"entity": "IfcWindow",
|
| 17 |
+
"source": "qset",
|
| 18 |
+
"set_name": "BaseQuantities",
|
| 19 |
+
"key": "GrossArea"
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"entity": "IfcWindow",
|
| 23 |
+
"source": "attr",
|
| 24 |
+
"keys": [
|
| 25 |
+
"OverallHeight",
|
| 26 |
+
"OverallWidth"
|
| 27 |
+
],
|
| 28 |
+
"op": "multiply"
|
| 29 |
+
}
|
| 30 |
+
],
|
| 31 |
+
"floor_area": [
|
| 32 |
+
{
|
| 33 |
+
"entity": "IfcSpace",
|
| 34 |
+
"source": "qset",
|
| 35 |
+
"set_name": "Qto_SpaceBaseQuantities",
|
| 36 |
+
"key": "NetFloorArea"
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"entity": "IfcSpace",
|
| 40 |
+
"source": "qset",
|
| 41 |
+
"set_name": "Qto_SpaceBaseQuantities",
|
| 42 |
+
"key": "GrossFloorArea"
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"entity": "IfcSpace",
|
| 46 |
+
"source": "qset",
|
| 47 |
+
"set_name": "BaseQuantities",
|
| 48 |
+
"key": "NetFloorArea"
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"entity": "IfcSpace",
|
| 52 |
+
"source": "qset",
|
| 53 |
+
"set_name": "BaseQuantities",
|
| 54 |
+
"key": "GrossFloorArea"
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
"entity": "IfcSpace",
|
| 58 |
+
"source": "qset",
|
| 59 |
+
"set_name": "GSA Space Areas",
|
| 60 |
+
"key": "GSA BIM Area"
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"entity": "IfcSlab",
|
| 64 |
+
"source": "qset",
|
| 65 |
+
"set_name": "Qto_SlabBaseQuantities",
|
| 66 |
+
"key": "NetArea",
|
| 67 |
+
"predefined_type": "FLOOR"
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
"entity": "IfcSlab",
|
| 71 |
+
"source": "qset",
|
| 72 |
+
"set_name": "BaseQuantities",
|
| 73 |
+
"key": "NetArea",
|
| 74 |
+
"predefined_type": "FLOOR"
|
| 75 |
+
},
|
| 76 |
+
{
|
| 77 |
+
"entity": "IfcSlab",
|
| 78 |
+
"source": "qset",
|
| 79 |
+
"set_name": "BaseQuantities",
|
| 80 |
+
"key": "GrossArea",
|
| 81 |
+
"predefined_type": "FLOOR"
|
| 82 |
+
}
|
| 83 |
+
],
|
| 84 |
+
"roof_area": [
|
| 85 |
+
{
|
| 86 |
+
"entity": "IfcRoof",
|
| 87 |
+
"source": "qset",
|
| 88 |
+
"set_name": "Qto_RoofBaseQuantities",
|
| 89 |
+
"key": "NetArea"
|
| 90 |
+
},
|
| 91 |
+
{
|
| 92 |
+
"entity": "IfcRoof",
|
| 93 |
+
"source": "qset",
|
| 94 |
+
"set_name": "Qto_RoofBaseQuantities",
|
| 95 |
+
"key": "GrossArea"
|
| 96 |
+
},
|
| 97 |
+
{
|
| 98 |
+
"entity": "IfcRoof",
|
| 99 |
+
"source": "qset",
|
| 100 |
+
"set_name": "BaseQuantities",
|
| 101 |
+
"key": "NetArea"
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
"entity": "IfcSlab",
|
| 105 |
+
"source": "qset",
|
| 106 |
+
"set_name": "Qto_SlabBaseQuantities",
|
| 107 |
+
"key": "NetArea",
|
| 108 |
+
"predefined_type": "ROOF"
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"entity": "IfcSlab",
|
| 112 |
+
"source": "qset",
|
| 113 |
+
"set_name": "Qto_SlabBaseQuantities",
|
| 114 |
+
"key": "GrossArea",
|
| 115 |
+
"predefined_type": "ROOF"
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"entity": "IfcSlab",
|
| 119 |
+
"source": "qset",
|
| 120 |
+
"set_name": "BaseQuantities",
|
| 121 |
+
"key": "NetArea",
|
| 122 |
+
"predefined_type": "ROOF"
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
"entity": "IfcSlab",
|
| 126 |
+
"source": "qset",
|
| 127 |
+
"set_name": "BaseQuantities",
|
| 128 |
+
"key": "GrossArea",
|
| 129 |
+
"predefined_type": "ROOF"
|
| 130 |
+
}
|
| 131 |
+
],
|
| 132 |
+
"true_north_angle": [
|
| 133 |
+
{
|
| 134 |
+
"entity": "IfcGeometricRepresentationContext",
|
| 135 |
+
"source": "attr",
|
| 136 |
+
"key": "TrueNorth",
|
| 137 |
+
"note": "DirectionRatios (X,Y) β atan2(x,y) β compass bearing"
|
| 138 |
+
}
|
| 139 |
+
],
|
| 140 |
+
"latitude": [
|
| 141 |
+
{
|
| 142 |
+
"entity": "IfcSite",
|
| 143 |
+
"source": "attr",
|
| 144 |
+
"key": "RefLatitude",
|
| 145 |
+
"note": "IfcCompoundPlaneAngleMeasure [deg,min,sec,microsec]"
|
| 146 |
+
}
|
| 147 |
+
],
|
| 148 |
+
"longitude": [
|
| 149 |
+
{
|
| 150 |
+
"entity": "IfcSite",
|
| 151 |
+
"source": "attr",
|
| 152 |
+
"key": "RefLongitude",
|
| 153 |
+
"note": "IfcCompoundPlaneAngleMeasure [deg,min,sec,microsec]"
|
| 154 |
+
}
|
| 155 |
+
],
|
| 156 |
+
"_auto_discovered_area_keys": [
|
| 157 |
+
{
|
| 158 |
+
"entity": "IfcWindow",
|
| 159 |
+
"source": "qset",
|
| 160 |
+
"set_name": "ArchiCADQuantities",
|
| 161 |
+
"key": "OberflΓ€chenbereich",
|
| 162 |
+
"file_count": 2,
|
| 163 |
+
"projects": [
|
| 164 |
+
"ac20",
|
| 165 |
+
"fzk_house"
|
| 166 |
+
],
|
| 167 |
+
"auto_discovered": true
|
| 168 |
+
},
|
| 169 |
+
{
|
| 170 |
+
"entity": "IfcWindow",
|
| 171 |
+
"source": "qset",
|
| 172 |
+
"set_name": "ArchiCADQuantities",
|
| 173 |
+
"key": "F/T OberflΓ€che der Γffnung auf der Anschlagseite",
|
| 174 |
+
"file_count": 2,
|
| 175 |
+
"projects": [
|
| 176 |
+
"ac20",
|
| 177 |
+
"fzk_house"
|
| 178 |
+
],
|
| 179 |
+
"auto_discovered": true
|
| 180 |
+
},
|
| 181 |
+
{
|
| 182 |
+
"entity": "IfcWindow",
|
| 183 |
+
"source": "qset",
|
| 184 |
+
"set_name": "ArchiCADQuantities",
|
| 185 |
+
"key": "F/T OberflΓ€che der Γffnung gegenΓΌber der Anschlagseite",
|
| 186 |
+
"file_count": 2,
|
| 187 |
+
"projects": [
|
| 188 |
+
"ac20",
|
| 189 |
+
"fzk_house"
|
| 190 |
+
],
|
| 191 |
+
"auto_discovered": true
|
| 192 |
+
},
|
| 193 |
+
{
|
| 194 |
+
"entity": "IfcWindow",
|
| 195 |
+
"source": "qset",
|
| 196 |
+
"set_name": "ArchiCADQuantities",
|
| 197 |
+
"key": "Nominale T/F ΓffnungsoberflΓ€che auf der Anschlagsseite",
|
| 198 |
+
"file_count": 2,
|
| 199 |
+
"projects": [
|
| 200 |
+
"ac20",
|
| 201 |
+
"fzk_house"
|
| 202 |
+
],
|
| 203 |
+
"auto_discovered": true
|
| 204 |
+
},
|
| 205 |
+
{
|
| 206 |
+
"entity": "IfcWindow",
|
| 207 |
+
"source": "qset",
|
| 208 |
+
"set_name": "ArchiCADQuantities",
|
| 209 |
+
"key": "Nominale T/F ΓffnungsoberflΓ€che auf der Seite gegenΓΌber der Anschlagsseite",
|
| 210 |
+
"file_count": 2,
|
| 211 |
+
"projects": [
|
| 212 |
+
"ac20",
|
| 213 |
+
"fzk_house"
|
| 214 |
+
],
|
| 215 |
+
"auto_discovered": true
|
| 216 |
+
},
|
| 217 |
+
{
|
| 218 |
+
"entity": "IfcWindow",
|
| 219 |
+
"source": "qset",
|
| 220 |
+
"set_name": "ArchiCADQuantities",
|
| 221 |
+
"key": "T/F Γffnung Nominaler OberflΓ€chenbereich",
|
| 222 |
+
"file_count": 2,
|
| 223 |
+
"projects": [
|
| 224 |
+
"ac20",
|
| 225 |
+
"fzk_house"
|
| 226 |
+
],
|
| 227 |
+
"auto_discovered": true
|
| 228 |
+
},
|
| 229 |
+
{
|
| 230 |
+
"entity": "IfcWindow",
|
| 231 |
+
"source": "qset",
|
| 232 |
+
"set_name": "ArchiCADQuantities",
|
| 233 |
+
"key": "Surface Area",
|
| 234 |
+
"file_count": 1,
|
| 235 |
+
"projects": [
|
| 236 |
+
"sixty5"
|
| 237 |
+
],
|
| 238 |
+
"auto_discovered": true
|
| 239 |
+
},
|
| 240 |
+
{
|
| 241 |
+
"entity": "IfcWindow",
|
| 242 |
+
"source": "qset",
|
| 243 |
+
"set_name": "ArchiCADQuantities",
|
| 244 |
+
"key": "W/D Opening Surface Area",
|
| 245 |
+
"file_count": 1,
|
| 246 |
+
"projects": [
|
| 247 |
+
"sixty5"
|
| 248 |
+
],
|
| 249 |
+
"auto_discovered": true
|
| 250 |
+
},
|
| 251 |
+
{
|
| 252 |
+
"entity": "IfcWindow",
|
| 253 |
+
"source": "qset",
|
| 254 |
+
"set_name": "ArchiCADQuantities",
|
| 255 |
+
"key": "W/D Opening Nominal Surface Area",
|
| 256 |
+
"file_count": 1,
|
| 257 |
+
"projects": [
|
| 258 |
+
"sixty5"
|
| 259 |
+
],
|
| 260 |
+
"auto_discovered": true
|
| 261 |
+
},
|
| 262 |
+
{
|
| 263 |
+
"entity": "IfcWindow",
|
| 264 |
+
"source": "qset",
|
| 265 |
+
"set_name": "ArchiCADQuantities",
|
| 266 |
+
"key": "Nominal W/D Opening Surface Area on the Reveal Side",
|
| 267 |
+
"file_count": 1,
|
| 268 |
+
"projects": [
|
| 269 |
+
"sixty5"
|
| 270 |
+
],
|
| 271 |
+
"auto_discovered": true
|
| 272 |
+
},
|
| 273 |
+
{
|
| 274 |
+
"entity": "IfcWindow",
|
| 275 |
+
"source": "qset",
|
| 276 |
+
"set_name": "ArchiCADQuantities",
|
| 277 |
+
"key": "Nominal W/D Opening Surface Area on the Side Opposite to the Reveal Side",
|
| 278 |
+
"file_count": 1,
|
| 279 |
+
"projects": [
|
| 280 |
+
"sixty5"
|
| 281 |
+
],
|
| 282 |
+
"auto_discovered": true
|
| 283 |
+
},
|
| 284 |
+
{
|
| 285 |
+
"entity": "IfcWindow",
|
| 286 |
+
"source": "qset",
|
| 287 |
+
"set_name": "ArchiCADQuantities",
|
| 288 |
+
"key": "Area",
|
| 289 |
+
"file_count": 1,
|
| 290 |
+
"projects": [
|
| 291 |
+
"sixty5"
|
| 292 |
+
],
|
| 293 |
+
"auto_discovered": true
|
| 294 |
+
},
|
| 295 |
+
{
|
| 296 |
+
"entity": "IfcDoor",
|
| 297 |
+
"source": "qset",
|
| 298 |
+
"set_name": "BaseQuantities",
|
| 299 |
+
"key": "Area",
|
| 300 |
+
"file_count": 5,
|
| 301 |
+
"projects": [
|
| 302 |
+
"ac20",
|
| 303 |
+
"fzk_house",
|
| 304 |
+
"molio",
|
| 305 |
+
"sixty5",
|
| 306 |
+
"smiley_west"
|
| 307 |
+
],
|
| 308 |
+
"auto_discovered": true
|
| 309 |
+
},
|
| 310 |
+
{
|
| 311 |
+
"entity": "IfcDoor",
|
| 312 |
+
"source": "qset",
|
| 313 |
+
"set_name": "ArchiCADQuantities",
|
| 314 |
+
"key": "OberflΓ€chenbereich",
|
| 315 |
+
"file_count": 2,
|
| 316 |
+
"projects": [
|
| 317 |
+
"ac20",
|
| 318 |
+
"fzk_house"
|
| 319 |
+
],
|
| 320 |
+
"auto_discovered": true
|
| 321 |
+
},
|
| 322 |
+
{
|
| 323 |
+
"entity": "IfcDoor",
|
| 324 |
+
"source": "qset",
|
| 325 |
+
"set_name": "ArchiCADQuantities",
|
| 326 |
+
"key": "F/T OberflΓ€che der Γffnung auf der Anschlagseite",
|
| 327 |
+
"file_count": 2,
|
| 328 |
+
"projects": [
|
| 329 |
+
"ac20",
|
| 330 |
+
"fzk_house"
|
| 331 |
+
],
|
| 332 |
+
"auto_discovered": true
|
| 333 |
+
},
|
| 334 |
+
{
|
| 335 |
+
"entity": "IfcDoor",
|
| 336 |
+
"source": "qset",
|
| 337 |
+
"set_name": "ArchiCADQuantities",
|
| 338 |
+
"key": "F/T OberflΓ€che der Γffnung gegenΓΌber der Anschlagseite",
|
| 339 |
+
"file_count": 2,
|
| 340 |
+
"projects": [
|
| 341 |
+
"ac20",
|
| 342 |
+
"fzk_house"
|
| 343 |
+
],
|
| 344 |
+
"auto_discovered": true
|
| 345 |
+
},
|
| 346 |
+
{
|
| 347 |
+
"entity": "IfcDoor",
|
| 348 |
+
"source": "qset",
|
| 349 |
+
"set_name": "ArchiCADQuantities",
|
| 350 |
+
"key": "Nominale T/F ΓffnungsoberflΓ€che auf der Anschlagsseite",
|
| 351 |
+
"file_count": 2,
|
| 352 |
+
"projects": [
|
| 353 |
+
"ac20",
|
| 354 |
+
"fzk_house"
|
| 355 |
+
],
|
| 356 |
+
"auto_discovered": true
|
| 357 |
+
},
|
| 358 |
+
{
|
| 359 |
+
"entity": "IfcDoor",
|
| 360 |
+
"source": "qset",
|
| 361 |
+
"set_name": "ArchiCADQuantities",
|
| 362 |
+
"key": "Nominale T/F ΓffnungsoberflΓ€che auf der Seite gegenΓΌber der Anschlagsseite",
|
| 363 |
+
"file_count": 2,
|
| 364 |
+
"projects": [
|
| 365 |
+
"ac20",
|
| 366 |
+
"fzk_house"
|
| 367 |
+
],
|
| 368 |
+
"auto_discovered": true
|
| 369 |
+
},
|
| 370 |
+
{
|
| 371 |
+
"entity": "IfcDoor",
|
| 372 |
+
"source": "qset",
|
| 373 |
+
"set_name": "ArchiCADQuantities",
|
| 374 |
+
"key": "T/F Γffnung Nominaler OberflΓ€chenbereich",
|
| 375 |
+
"file_count": 2,
|
| 376 |
+
"projects": [
|
| 377 |
+
"ac20",
|
| 378 |
+
"fzk_house"
|
| 379 |
+
],
|
| 380 |
+
"auto_discovered": true
|
| 381 |
+
},
|
| 382 |
+
{
|
| 383 |
+
"entity": "IfcDoor",
|
| 384 |
+
"source": "qset",
|
| 385 |
+
"set_name": "ArchiCADQuantities",
|
| 386 |
+
"key": "Surface Area",
|
| 387 |
+
"file_count": 2,
|
| 388 |
+
"projects": [
|
| 389 |
+
"molio",
|
| 390 |
+
"sixty5"
|
| 391 |
+
],
|
| 392 |
+
"auto_discovered": true
|
| 393 |
+
},
|
| 394 |
+
{
|
| 395 |
+
"entity": "IfcDoor",
|
| 396 |
+
"source": "qset",
|
| 397 |
+
"set_name": "ArchiCADQuantities",
|
| 398 |
+
"key": "Bottom Surface Area (Net)",
|
| 399 |
+
"file_count": 1,
|
| 400 |
+
"projects": [
|
| 401 |
+
"molio"
|
| 402 |
+
],
|
| 403 |
+
"auto_discovered": true
|
| 404 |
+
},
|
| 405 |
+
{
|
| 406 |
+
"entity": "IfcDoor",
|
| 407 |
+
"source": "qset",
|
| 408 |
+
"set_name": "ArchiCADQuantities",
|
| 409 |
+
"key": "W/D Opening Surface Area",
|
| 410 |
+
"file_count": 2,
|
| 411 |
+
"projects": [
|
| 412 |
+
"molio",
|
| 413 |
+
"sixty5"
|
| 414 |
+
],
|
| 415 |
+
"auto_discovered": true
|
| 416 |
+
},
|
| 417 |
+
{
|
| 418 |
+
"entity": "IfcDoor",
|
| 419 |
+
"source": "qset",
|
| 420 |
+
"set_name": "ArchiCADQuantities",
|
| 421 |
+
"key": "W/D Opening Nominal Surface Area",
|
| 422 |
+
"file_count": 2,
|
| 423 |
+
"projects": [
|
| 424 |
+
"molio",
|
| 425 |
+
"sixty5"
|
| 426 |
+
],
|
| 427 |
+
"auto_discovered": true
|
| 428 |
+
},
|
| 429 |
+
{
|
| 430 |
+
"entity": "IfcDoor",
|
| 431 |
+
"source": "qset",
|
| 432 |
+
"set_name": "ArchiCADQuantities",
|
| 433 |
+
"key": "W/D Opening Surface Area on the Reveal Side",
|
| 434 |
+
"file_count": 1,
|
| 435 |
+
"projects": [
|
| 436 |
+
"molio"
|
| 437 |
+
],
|
| 438 |
+
"auto_discovered": true
|
| 439 |
+
},
|
| 440 |
+
{
|
| 441 |
+
"entity": "IfcDoor",
|
| 442 |
+
"source": "qset",
|
| 443 |
+
"set_name": "ArchiCADQuantities",
|
| 444 |
+
"key": "W/D Opening Surface Area on the Side Opposite to the Reveal Side",
|
| 445 |
+
"file_count": 1,
|
| 446 |
+
"projects": [
|
| 447 |
+
"molio"
|
| 448 |
+
],
|
| 449 |
+
"auto_discovered": true
|
| 450 |
+
},
|
| 451 |
+
{
|
| 452 |
+
"entity": "IfcDoor",
|
| 453 |
+
"source": "qset",
|
| 454 |
+
"set_name": "ArchiCADQuantities",
|
| 455 |
+
"key": "Nominal W/D Opening Surface Area on the Reveal Side",
|
| 456 |
+
"file_count": 2,
|
| 457 |
+
"projects": [
|
| 458 |
+
"molio",
|
| 459 |
+
"sixty5"
|
| 460 |
+
],
|
| 461 |
+
"auto_discovered": true
|
| 462 |
+
},
|
| 463 |
+
{
|
| 464 |
+
"entity": "IfcDoor",
|
| 465 |
+
"source": "qset",
|
| 466 |
+
"set_name": "ArchiCADQuantities",
|
| 467 |
+
"key": "Nominal W/D Opening Surface Area on the Side Opposite to the Reveal Side",
|
| 468 |
+
"file_count": 2,
|
| 469 |
+
"projects": [
|
| 470 |
+
"molio",
|
| 471 |
+
"sixty5"
|
| 472 |
+
],
|
| 473 |
+
"auto_discovered": true
|
| 474 |
+
},
|
| 475 |
+
{
|
| 476 |
+
"entity": "IfcDoor",
|
| 477 |
+
"source": "qset",
|
| 478 |
+
"set_name": "Qto_DoorBaseQuantities",
|
| 479 |
+
"key": "Area",
|
| 480 |
+
"file_count": 3,
|
| 481 |
+
"projects": [
|
| 482 |
+
"digital_hub",
|
| 483 |
+
"fantasy_office_building_1",
|
| 484 |
+
"fantasy_office_building_2"
|
| 485 |
+
],
|
| 486 |
+
"auto_discovered": true
|
| 487 |
+
},
|
| 488 |
+
{
|
| 489 |
+
"entity": "IfcSpace",
|
| 490 |
+
"source": "qset",
|
| 491 |
+
"set_name": "GSA Space Areas",
|
| 492 |
+
"key": "GSA Space Areas",
|
| 493 |
+
"file_count": 1,
|
| 494 |
+
"projects": [
|
| 495 |
+
"samuel_macalister_sample_house"
|
| 496 |
+
],
|
| 497 |
+
"auto_discovered": true
|
| 498 |
+
},
|
| 499 |
+
{
|
| 500 |
+
"entity": "IfcSpace",
|
| 501 |
+
"source": "qset",
|
| 502 |
+
"set_name": "BaseQuantities",
|
| 503 |
+
"key": "SpaceNetFloorAreaBOMA",
|
| 504 |
+
"file_count": 5,
|
| 505 |
+
"projects": [
|
| 506 |
+
"ac20",
|
| 507 |
+
"fzk_house",
|
| 508 |
+
"molio",
|
| 509 |
+
"sixty5",
|
| 510 |
+
"smiley_west"
|
| 511 |
+
],
|
| 512 |
+
"auto_discovered": true
|
| 513 |
+
},
|
| 514 |
+
{
|
| 515 |
+
"entity": "IfcSpace",
|
| 516 |
+
"source": "qset",
|
| 517 |
+
"set_name": "BaseQuantities",
|
| 518 |
+
"key": "SpaceUsableFloorAreaBOMA",
|
| 519 |
+
"file_count": 5,
|
| 520 |
+
"projects": [
|
| 521 |
+
"ac20",
|
| 522 |
+
"fzk_house",
|
| 523 |
+
"molio",
|
| 524 |
+
"sixty5",
|
| 525 |
+
"smiley_west"
|
| 526 |
+
],
|
| 527 |
+
"auto_discovered": true
|
| 528 |
+
},
|
| 529 |
+
{
|
| 530 |
+
"entity": "IfcSpace",
|
| 531 |
+
"source": "qset",
|
| 532 |
+
"set_name": "BaseQuantities",
|
| 533 |
+
"key": "GrossCeilingArea",
|
| 534 |
+
"file_count": 5,
|
| 535 |
+
"projects": [
|
| 536 |
+
"ac20",
|
| 537 |
+
"fzk_house",
|
| 538 |
+
"molio",
|
| 539 |
+
"sixty5",
|
| 540 |
+
"smiley_west"
|
| 541 |
+
],
|
| 542 |
+
"auto_discovered": true
|
| 543 |
+
},
|
| 544 |
+
{
|
| 545 |
+
"entity": "IfcSpace",
|
| 546 |
+
"source": "qset",
|
| 547 |
+
"set_name": "BaseQuantities",
|
| 548 |
+
"key": "NetCeilingArea",
|
| 549 |
+
"file_count": 5,
|
| 550 |
+
"projects": [
|
| 551 |
+
"ac20",
|
| 552 |
+
"fzk_house",
|
| 553 |
+
"molio",
|
| 554 |
+
"sixty5",
|
| 555 |
+
"smiley_west"
|
| 556 |
+
],
|
| 557 |
+
"auto_discovered": true
|
| 558 |
+
},
|
| 559 |
+
{
|
| 560 |
+
"entity": "IfcSpace",
|
| 561 |
+
"source": "qset",
|
| 562 |
+
"set_name": "BaseQuantities",
|
| 563 |
+
"key": "GrossWallArea",
|
| 564 |
+
"file_count": 5,
|
| 565 |
+
"projects": [
|
| 566 |
+
"ac20",
|
| 567 |
+
"fzk_house",
|
| 568 |
+
"molio",
|
| 569 |
+
"sixty5",
|
| 570 |
+
"smiley_west"
|
| 571 |
+
],
|
| 572 |
+
"auto_discovered": true
|
| 573 |
+
},
|
| 574 |
+
{
|
| 575 |
+
"entity": "IfcSpace",
|
| 576 |
+
"source": "qset",
|
| 577 |
+
"set_name": "BaseQuantities",
|
| 578 |
+
"key": "NetWallArea",
|
| 579 |
+
"file_count": 5,
|
| 580 |
+
"projects": [
|
| 581 |
+
"ac20",
|
| 582 |
+
"fzk_house",
|
| 583 |
+
"molio",
|
| 584 |
+
"sixty5",
|
| 585 |
+
"smiley_west"
|
| 586 |
+
],
|
| 587 |
+
"auto_discovered": true
|
| 588 |
+
},
|
| 589 |
+
{
|
| 590 |
+
"entity": "IfcSpace",
|
| 591 |
+
"source": "qset",
|
| 592 |
+
"set_name": "ArchiCADQuantities",
|
| 593 |
+
"key": "OberflΓ€chenbereich",
|
| 594 |
+
"file_count": 2,
|
| 595 |
+
"projects": [
|
| 596 |
+
"ac20",
|
| 597 |
+
"fzk_house"
|
| 598 |
+
],
|
| 599 |
+
"auto_discovered": true
|
| 600 |
+
},
|
| 601 |
+
{
|
| 602 |
+
"entity": "IfcSpace",
|
| 603 |
+
"source": "qset",
|
| 604 |
+
"set_name": "ArchiCADQuantities",
|
| 605 |
+
"key": "FlΓ€che",
|
| 606 |
+
"file_count": 2,
|
| 607 |
+
"projects": [
|
| 608 |
+
"ac20",
|
| 609 |
+
"fzk_house"
|
| 610 |
+
],
|
| 611 |
+
"auto_discovered": true
|
| 612 |
+
},
|
| 613 |
+
{
|
| 614 |
+
"entity": "IfcSpace",
|
| 615 |
+
"source": "qset",
|
| 616 |
+
"set_name": "ArchiCADQuantities",
|
| 617 |
+
"key": "Berechnete FlΓ€che (NRF)",
|
| 618 |
+
"file_count": 2,
|
| 619 |
+
"projects": [
|
| 620 |
+
"ac20",
|
| 621 |
+
"fzk_house"
|
| 622 |
+
],
|
| 623 |
+
"auto_discovered": true
|
| 624 |
+
},
|
| 625 |
+
{
|
| 626 |
+
"entity": "IfcSpace",
|
| 627 |
+
"source": "qset",
|
| 628 |
+
"set_name": "ArchiCADQuantities",
|
| 629 |
+
"key": "TΓΌren-OberflΓ€chenbereich",
|
| 630 |
+
"file_count": 2,
|
| 631 |
+
"projects": [
|
| 632 |
+
"ac20",
|
| 633 |
+
"fzk_house"
|
| 634 |
+
],
|
| 635 |
+
"auto_discovered": true
|
| 636 |
+
},
|
| 637 |
+
{
|
| 638 |
+
"entity": "IfcSpace",
|
| 639 |
+
"source": "qset",
|
| 640 |
+
"set_name": "ArchiCADQuantities",
|
| 641 |
+
"key": "StΓΌtzenabzugsflΓ€che",
|
| 642 |
+
"file_count": 2,
|
| 643 |
+
"projects": [
|
| 644 |
+
"ac20",
|
| 645 |
+
"fzk_house"
|
| 646 |
+
],
|
| 647 |
+
"auto_discovered": true
|
| 648 |
+
},
|
| 649 |
+
{
|
| 650 |
+
"entity": "IfcSpace",
|
| 651 |
+
"source": "qset",
|
| 652 |
+
"set_name": "ArchiCADQuantities",
|
| 653 |
+
"key": "SchraffurabzugsflΓ€che",
|
| 654 |
+
"file_count": 2,
|
| 655 |
+
"projects": [
|
| 656 |
+
"ac20",
|
| 657 |
+
"fzk_house"
|
| 658 |
+
],
|
| 659 |
+
"auto_discovered": true
|
| 660 |
+
},
|
| 661 |
+
{
|
| 662 |
+
"entity": "IfcSpace",
|
| 663 |
+
"source": "qset",
|
| 664 |
+
"set_name": "ArchiCADQuantities",
|
| 665 |
+
"key": "Niedrige AbzugsflΓ€che",
|
| 666 |
+
"file_count": 2,
|
| 667 |
+
"projects": [
|
| 668 |
+
"ac20",
|
| 669 |
+
"fzk_house"
|
| 670 |
+
],
|
| 671 |
+
"auto_discovered": true
|
| 672 |
+
},
|
| 673 |
+
{
|
| 674 |
+
"entity": "IfcSpace",
|
| 675 |
+
"source": "qset",
|
| 676 |
+
"set_name": "ArchiCADQuantities",
|
| 677 |
+
"key": "WandabzugsflΓ€che",
|
| 678 |
+
"file_count": 2,
|
| 679 |
+
"projects": [
|
| 680 |
+
"ac20",
|
| 681 |
+
"fzk_house"
|
| 682 |
+
],
|
| 683 |
+
"auto_discovered": true
|
| 684 |
+
},
|
| 685 |
+
{
|
| 686 |
+
"entity": "IfcSpace",
|
| 687 |
+
"source": "qset",
|
| 688 |
+
"set_name": "ArchiCADQuantities",
|
| 689 |
+
"key": "Gemessene FlΓ€che",
|
| 690 |
+
"file_count": 2,
|
| 691 |
+
"projects": [
|
| 692 |
+
"ac20",
|
| 693 |
+
"fzk_house"
|
| 694 |
+
],
|
| 695 |
+
"auto_discovered": true
|
| 696 |
+
},
|
| 697 |
+
{
|
| 698 |
+
"entity": "IfcSpace",
|
| 699 |
+
"source": "qset",
|
| 700 |
+
"set_name": "ArchiCADQuantities",
|
| 701 |
+
"key": "NettoflΓ€che",
|
| 702 |
+
"file_count": 2,
|
| 703 |
+
"projects": [
|
| 704 |
+
"ac20",
|
| 705 |
+
"fzk_house"
|
| 706 |
+
],
|
| 707 |
+
"auto_discovered": true
|
| 708 |
+
},
|
| 709 |
+
{
|
| 710 |
+
"entity": "IfcSpace",
|
| 711 |
+
"source": "qset",
|
| 712 |
+
"set_name": "ArchiCADQuantities",
|
| 713 |
+
"key": "Reduzierte FlΓ€che",
|
| 714 |
+
"file_count": 2,
|
| 715 |
+
"projects": [
|
| 716 |
+
"ac20",
|
| 717 |
+
"fzk_house"
|
| 718 |
+
],
|
| 719 |
+
"auto_discovered": true
|
| 720 |
+
},
|
| 721 |
+
{
|
| 722 |
+
"entity": "IfcSpace",
|
| 723 |
+
"source": "qset",
|
| 724 |
+
"set_name": "ArchiCADQuantities",
|
| 725 |
+
"key": "RaumflΓ€chen Reduzierung",
|
| 726 |
+
"file_count": 2,
|
| 727 |
+
"projects": [
|
| 728 |
+
"ac20",
|
| 729 |
+
"fzk_house"
|
| 730 |
+
],
|
| 731 |
+
"auto_discovered": true
|
| 732 |
+
},
|
| 733 |
+
{
|
| 734 |
+
"entity": "IfcSpace",
|
| 735 |
+
"source": "qset",
|
| 736 |
+
"set_name": "ArchiCADQuantities",
|
| 737 |
+
"key": "GesamtabzugsflΓ€che",
|
| 738 |
+
"file_count": 2,
|
| 739 |
+
"projects": [
|
| 740 |
+
"ac20",
|
| 741 |
+
"fzk_house"
|
| 742 |
+
],
|
| 743 |
+
"auto_discovered": true
|
| 744 |
+
},
|
| 745 |
+
{
|
| 746 |
+
"entity": "IfcSpace",
|
| 747 |
+
"source": "qset",
|
| 748 |
+
"set_name": "ArchiCADQuantities",
|
| 749 |
+
"key": "HeizkΓΆrpernische OberflΓ€chenbereich hinten",
|
| 750 |
+
"file_count": 2,
|
| 751 |
+
"projects": [
|
| 752 |
+
"ac20",
|
| 753 |
+
"fzk_house"
|
| 754 |
+
],
|
| 755 |
+
"auto_discovered": true
|
| 756 |
+
},
|
| 757 |
+
{
|
| 758 |
+
"entity": "IfcSpace",
|
| 759 |
+
"source": "qset",
|
| 760 |
+
"set_name": "ArchiCADQuantities",
|
| 761 |
+
"key": "HeizkΓΆrpernische OberflΓ€chenbereich seitlich",
|
| 762 |
+
"file_count": 2,
|
| 763 |
+
"projects": [
|
| 764 |
+
"ac20",
|
| 765 |
+
"fzk_house"
|
| 766 |
+
],
|
| 767 |
+
"auto_discovered": true
|
| 768 |
+
},
|
| 769 |
+
{
|
| 770 |
+
"entity": "IfcSpace",
|
| 771 |
+
"source": "qset",
|
| 772 |
+
"set_name": "ArchiCADQuantities",
|
| 773 |
+
"key": "HeizkΓΆrpernische OberflΓ€chenbereich oben",
|
| 774 |
+
"file_count": 2,
|
| 775 |
+
"projects": [
|
| 776 |
+
"ac20",
|
| 777 |
+
"fzk_house"
|
| 778 |
+
],
|
| 779 |
+
"auto_discovered": true
|
| 780 |
+
},
|
| 781 |
+
{
|
| 782 |
+
"entity": "IfcSpace",
|
| 783 |
+
"source": "qset",
|
| 784 |
+
"set_name": "ArchiCADQuantities",
|
| 785 |
+
"key": "Wand-OberflΓ€chenbereich",
|
| 786 |
+
"file_count": 2,
|
| 787 |
+
"projects": [
|
| 788 |
+
"ac20",
|
| 789 |
+
"fzk_house"
|
| 790 |
+
],
|
| 791 |
+
"auto_discovered": true
|
| 792 |
+
},
|
| 793 |
+
{
|
| 794 |
+
"entity": "IfcSpace",
|
| 795 |
+
"source": "qset",
|
| 796 |
+
"set_name": "ArchiCADQuantities",
|
| 797 |
+
"key": "Fenster-OberflΓ€chenbereich",
|
| 798 |
+
"file_count": 2,
|
| 799 |
+
"projects": [
|
| 800 |
+
"ac20",
|
| 801 |
+
"fzk_house"
|
| 802 |
+
],
|
| 803 |
+
"auto_discovered": true
|
| 804 |
+
},
|
| 805 |
+
{
|
| 806 |
+
"entity": "IfcSpace",
|
| 807 |
+
"source": "qset",
|
| 808 |
+
"set_name": "ArchiCADQuantities",
|
| 809 |
+
"key": "Area",
|
| 810 |
+
"file_count": 2,
|
| 811 |
+
"projects": [
|
| 812 |
+
"molio",
|
| 813 |
+
"sixty5"
|
| 814 |
+
],
|
| 815 |
+
"auto_discovered": true
|
| 816 |
+
},
|
| 817 |
+
{
|
| 818 |
+
"entity": "IfcSpace",
|
| 819 |
+
"source": "qset",
|
| 820 |
+
"set_name": "ArchiCADQuantities",
|
| 821 |
+
"key": "Top Surface Area (Net)",
|
| 822 |
+
"file_count": 1,
|
| 823 |
+
"projects": [
|
| 824 |
+
"molio"
|
| 825 |
+
],
|
| 826 |
+
"auto_discovered": true
|
| 827 |
+
},
|
| 828 |
+
{
|
| 829 |
+
"entity": "IfcSpace",
|
| 830 |
+
"source": "qset",
|
| 831 |
+
"set_name": "ArchiCADQuantities",
|
| 832 |
+
"key": "Edge Surface Area (Net)",
|
| 833 |
+
"file_count": 1,
|
| 834 |
+
"projects": [
|
| 835 |
+
"molio"
|
| 836 |
+
],
|
| 837 |
+
"auto_discovered": true
|
| 838 |
+
},
|
| 839 |
+
{
|
| 840 |
+
"entity": "IfcSpace",
|
| 841 |
+
"source": "qset",
|
| 842 |
+
"set_name": "ArchiCADQuantities",
|
| 843 |
+
"key": "Bottom Surface Area (Net)",
|
| 844 |
+
"file_count": 1,
|
| 845 |
+
"projects": [
|
| 846 |
+
"molio"
|
| 847 |
+
],
|
| 848 |
+
"auto_discovered": true
|
| 849 |
+
},
|
| 850 |
+
{
|
| 851 |
+
"entity": "IfcSpace",
|
| 852 |
+
"source": "qset",
|
| 853 |
+
"set_name": "ArchiCADQuantities",
|
| 854 |
+
"key": "Calculated Area",
|
| 855 |
+
"file_count": 2,
|
| 856 |
+
"projects": [
|
| 857 |
+
"molio",
|
| 858 |
+
"sixty5"
|
| 859 |
+
],
|
| 860 |
+
"auto_discovered": true
|
| 861 |
+
},
|
| 862 |
+
{
|
| 863 |
+
"entity": "IfcSpace",
|
| 864 |
+
"source": "qset",
|
| 865 |
+
"set_name": "ArchiCADQuantities",
|
| 866 |
+
"key": "Measured Area",
|
| 867 |
+
"file_count": 2,
|
| 868 |
+
"projects": [
|
| 869 |
+
"molio",
|
| 870 |
+
"sixty5"
|
| 871 |
+
],
|
| 872 |
+
"auto_discovered": true
|
| 873 |
+
},
|
| 874 |
+
{
|
| 875 |
+
"entity": "IfcSpace",
|
| 876 |
+
"source": "qset",
|
| 877 |
+
"set_name": "ArchiCADQuantities",
|
| 878 |
+
"key": "Windows Surface Area",
|
| 879 |
+
"file_count": 2,
|
| 880 |
+
"projects": [
|
| 881 |
+
"molio",
|
| 882 |
+
"sixty5"
|
| 883 |
+
],
|
| 884 |
+
"auto_discovered": true
|
| 885 |
+
},
|
| 886 |
+
{
|
| 887 |
+
"entity": "IfcSpace",
|
| 888 |
+
"source": "qset",
|
| 889 |
+
"set_name": "ArchiCADQuantities",
|
| 890 |
+
"key": "Doors Surface Area",
|
| 891 |
+
"file_count": 2,
|
| 892 |
+
"projects": [
|
| 893 |
+
"molio",
|
| 894 |
+
"sixty5"
|
| 895 |
+
],
|
| 896 |
+
"auto_discovered": true
|
| 897 |
+
},
|
| 898 |
+
{
|
| 899 |
+
"entity": "IfcSpace",
|
| 900 |
+
"source": "qset",
|
| 901 |
+
"set_name": "ArchiCADQuantities",
|
| 902 |
+
"key": "Walls Surface Area",
|
| 903 |
+
"file_count": 2,
|
| 904 |
+
"projects": [
|
| 905 |
+
"molio",
|
| 906 |
+
"sixty5"
|
| 907 |
+
],
|
| 908 |
+
"auto_discovered": true
|
| 909 |
+
},
|
| 910 |
+
{
|
| 911 |
+
"entity": "IfcSpace",
|
| 912 |
+
"source": "qset",
|
| 913 |
+
"set_name": "ArchiCADQuantities",
|
| 914 |
+
"key": "Area Reducement",
|
| 915 |
+
"file_count": 2,
|
| 916 |
+
"projects": [
|
| 917 |
+
"molio",
|
| 918 |
+
"sixty5"
|
| 919 |
+
],
|
| 920 |
+
"auto_discovered": true
|
| 921 |
+
},
|
| 922 |
+
{
|
| 923 |
+
"entity": "IfcSpace",
|
| 924 |
+
"source": "qset",
|
| 925 |
+
"set_name": "ArchiCADQuantities",
|
| 926 |
+
"key": "Extracted Column Area",
|
| 927 |
+
"file_count": 2,
|
| 928 |
+
"projects": [
|
| 929 |
+
"molio",
|
| 930 |
+
"sixty5"
|
| 931 |
+
],
|
| 932 |
+
"auto_discovered": true
|
| 933 |
+
},
|
| 934 |
+
{
|
| 935 |
+
"entity": "IfcSpace",
|
| 936 |
+
"source": "qset",
|
| 937 |
+
"set_name": "ArchiCADQuantities",
|
| 938 |
+
"key": "Extracted Fill Area",
|
| 939 |
+
"file_count": 2,
|
| 940 |
+
"projects": [
|
| 941 |
+
"molio",
|
| 942 |
+
"sixty5"
|
| 943 |
+
],
|
| 944 |
+
"auto_discovered": true
|
| 945 |
+
},
|
| 946 |
+
{
|
| 947 |
+
"entity": "IfcSpace",
|
| 948 |
+
"source": "qset",
|
| 949 |
+
"set_name": "ArchiCADQuantities",
|
| 950 |
+
"key": "Extracted Low Area",
|
| 951 |
+
"file_count": 2,
|
| 952 |
+
"projects": [
|
| 953 |
+
"molio",
|
| 954 |
+
"sixty5"
|
| 955 |
+
],
|
| 956 |
+
"auto_discovered": true
|
| 957 |
+
},
|
| 958 |
+
{
|
| 959 |
+
"entity": "IfcSpace",
|
| 960 |
+
"source": "qset",
|
| 961 |
+
"set_name": "ArchiCADQuantities",
|
| 962 |
+
"key": "Extracted Wall Area",
|
| 963 |
+
"file_count": 2,
|
| 964 |
+
"projects": [
|
| 965 |
+
"molio",
|
| 966 |
+
"sixty5"
|
| 967 |
+
],
|
| 968 |
+
"auto_discovered": true
|
| 969 |
+
},
|
| 970 |
+
{
|
| 971 |
+
"entity": "IfcSpace",
|
| 972 |
+
"source": "qset",
|
| 973 |
+
"set_name": "ArchiCADQuantities",
|
| 974 |
+
"key": "Measured Net Area",
|
| 975 |
+
"file_count": 1,
|
| 976 |
+
"projects": [
|
| 977 |
+
"molio"
|
| 978 |
+
],
|
| 979 |
+
"auto_discovered": true
|
| 980 |
+
},
|
| 981 |
+
{
|
| 982 |
+
"entity": "IfcSpace",
|
| 983 |
+
"source": "qset",
|
| 984 |
+
"set_name": "ArchiCADQuantities",
|
| 985 |
+
"key": "Reduced Area",
|
| 986 |
+
"file_count": 1,
|
| 987 |
+
"projects": [
|
| 988 |
+
"molio"
|
| 989 |
+
],
|
| 990 |
+
"auto_discovered": true
|
| 991 |
+
},
|
| 992 |
+
{
|
| 993 |
+
"entity": "IfcSpace",
|
| 994 |
+
"source": "qset",
|
| 995 |
+
"set_name": "ArchiCADQuantities",
|
| 996 |
+
"key": "Total Extracted Area",
|
| 997 |
+
"file_count": 2,
|
| 998 |
+
"projects": [
|
| 999 |
+
"molio",
|
| 1000 |
+
"sixty5"
|
| 1001 |
+
],
|
| 1002 |
+
"auto_discovered": true
|
| 1003 |
+
},
|
| 1004 |
+
{
|
| 1005 |
+
"entity": "IfcSpace",
|
| 1006 |
+
"source": "qset",
|
| 1007 |
+
"set_name": "ArchiCADQuantities",
|
| 1008 |
+
"key": "Wall Inset Back Side Surface Area",
|
| 1009 |
+
"file_count": 2,
|
| 1010 |
+
"projects": [
|
| 1011 |
+
"molio",
|
| 1012 |
+
"sixty5"
|
| 1013 |
+
],
|
| 1014 |
+
"auto_discovered": true
|
| 1015 |
+
},
|
| 1016 |
+
{
|
| 1017 |
+
"entity": "IfcSpace",
|
| 1018 |
+
"source": "qset",
|
| 1019 |
+
"set_name": "ArchiCADQuantities",
|
| 1020 |
+
"key": "Wall Inset Side Surface Area",
|
| 1021 |
+
"file_count": 2,
|
| 1022 |
+
"projects": [
|
| 1023 |
+
"molio",
|
| 1024 |
+
"sixty5"
|
| 1025 |
+
],
|
| 1026 |
+
"auto_discovered": true
|
| 1027 |
+
},
|
| 1028 |
+
{
|
| 1029 |
+
"entity": "IfcSpace",
|
| 1030 |
+
"source": "qset",
|
| 1031 |
+
"set_name": "ArchiCADQuantities",
|
| 1032 |
+
"key": "Wall Inset Top Surface Area",
|
| 1033 |
+
"file_count": 2,
|
| 1034 |
+
"projects": [
|
| 1035 |
+
"molio",
|
| 1036 |
+
"sixty5"
|
| 1037 |
+
],
|
| 1038 |
+
"auto_discovered": true
|
| 1039 |
+
},
|
| 1040 |
+
{
|
| 1041 |
+
"entity": "IfcSpace",
|
| 1042 |
+
"source": "qset",
|
| 1043 |
+
"set_name": "ArchiCADQuantities",
|
| 1044 |
+
"key": "Extracted Curtain Wall Area",
|
| 1045 |
+
"file_count": 1,
|
| 1046 |
+
"projects": [
|
| 1047 |
+
"molio"
|
| 1048 |
+
],
|
| 1049 |
+
"auto_discovered": true
|
| 1050 |
+
},
|
| 1051 |
+
{
|
| 1052 |
+
"entity": "IfcSpace",
|
| 1053 |
+
"source": "qset",
|
| 1054 |
+
"set_name": "ArchiCADQuantities",
|
| 1055 |
+
"key": "Net Area",
|
| 1056 |
+
"file_count": 1,
|
| 1057 |
+
"projects": [
|
| 1058 |
+
"sixty5"
|
| 1059 |
+
],
|
| 1060 |
+
"auto_discovered": true
|
| 1061 |
+
},
|
| 1062 |
+
{
|
| 1063 |
+
"entity": "IfcSpace",
|
| 1064 |
+
"source": "qset",
|
| 1065 |
+
"set_name": "Qto_SpaceBaseQuantities",
|
| 1066 |
+
"key": "GrossCeilingArea",
|
| 1067 |
+
"file_count": 2,
|
| 1068 |
+
"projects": [
|
| 1069 |
+
"fantasy_office_building_1",
|
| 1070 |
+
"fantasy_office_building_2"
|
| 1071 |
+
],
|
| 1072 |
+
"auto_discovered": true
|
| 1073 |
+
},
|
| 1074 |
+
{
|
| 1075 |
+
"entity": "IfcSpace",
|
| 1076 |
+
"source": "qset",
|
| 1077 |
+
"set_name": "(unnamed)",
|
| 1078 |
+
"key": "NetFootprintArea",
|
| 1079 |
+
"file_count": 1,
|
| 1080 |
+
"projects": [
|
| 1081 |
+
"hitos"
|
| 1082 |
+
],
|
| 1083 |
+
"auto_discovered": true
|
| 1084 |
+
},
|
| 1085 |
+
{
|
| 1086 |
+
"entity": "IfcSlab",
|
| 1087 |
+
"source": "qset",
|
| 1088 |
+
"set_name": "ArchiCADQuantities",
|
| 1089 |
+
"key": "OberflΓ€chenbereich",
|
| 1090 |
+
"file_count": 2,
|
| 1091 |
+
"projects": [
|
| 1092 |
+
"ac20",
|
| 1093 |
+
"fzk_house"
|
| 1094 |
+
],
|
| 1095 |
+
"auto_discovered": true
|
| 1096 |
+
},
|
| 1097 |
+
{
|
| 1098 |
+
"entity": "IfcSlab",
|
| 1099 |
+
"source": "qset",
|
| 1100 |
+
"set_name": "ArchiCADQuantities",
|
| 1101 |
+
"key": "FlΓ€che",
|
| 1102 |
+
"file_count": 2,
|
| 1103 |
+
"projects": [
|
| 1104 |
+
"ac20",
|
| 1105 |
+
"fzk_house"
|
| 1106 |
+
],
|
| 1107 |
+
"auto_discovered": true
|
| 1108 |
+
},
|
| 1109 |
+
{
|
| 1110 |
+
"entity": "IfcSlab",
|
| 1111 |
+
"source": "qset",
|
| 1112 |
+
"set_name": "ArchiCADQuantities",
|
| 1113 |
+
"key": "OberflΓ€chenbereich Unterseite",
|
| 1114 |
+
"file_count": 2,
|
| 1115 |
+
"projects": [
|
| 1116 |
+
"ac20",
|
| 1117 |
+
"fzk_house"
|
| 1118 |
+
],
|
| 1119 |
+
"auto_discovered": true
|
| 1120 |
+
},
|
| 1121 |
+
{
|
| 1122 |
+
"entity": "IfcSlab",
|
| 1123 |
+
"source": "qset",
|
| 1124 |
+
"set_name": "ArchiCADQuantities",
|
| 1125 |
+
"key": "OberflΓ€chenbereich Oberseite",
|
| 1126 |
+
"file_count": 2,
|
| 1127 |
+
"projects": [
|
| 1128 |
+
"ac20",
|
| 1129 |
+
"fzk_house"
|
| 1130 |
+
],
|
| 1131 |
+
"auto_discovered": true
|
| 1132 |
+
},
|
| 1133 |
+
{
|
| 1134 |
+
"entity": "IfcSlab",
|
| 1135 |
+
"source": "qset",
|
| 1136 |
+
"set_name": "ArchiCADQuantities",
|
| 1137 |
+
"key": "Kante OberflΓ€chenbereich",
|
| 1138 |
+
"file_count": 2,
|
| 1139 |
+
"projects": [
|
| 1140 |
+
"ac20",
|
| 1141 |
+
"fzk_house"
|
| 1142 |
+
],
|
| 1143 |
+
"auto_discovered": true
|
| 1144 |
+
},
|
| 1145 |
+
{
|
| 1146 |
+
"entity": "IfcSlab",
|
| 1147 |
+
"source": "qset",
|
| 1148 |
+
"set_name": "ArchiCADQuantities",
|
| 1149 |
+
"key": "LΓΆcher OberflΓ€chenbereich",
|
| 1150 |
+
"file_count": 2,
|
| 1151 |
+
"projects": [
|
| 1152 |
+
"ac20",
|
| 1153 |
+
"fzk_house"
|
| 1154 |
+
],
|
| 1155 |
+
"auto_discovered": true
|
| 1156 |
+
},
|
| 1157 |
+
{
|
| 1158 |
+
"entity": "IfcSlab",
|
| 1159 |
+
"source": "qset",
|
| 1160 |
+
"set_name": "ArchiCADQuantities",
|
| 1161 |
+
"key": "Brutto-OberflΓ€che der Deckenoberseite",
|
| 1162 |
+
"file_count": 2,
|
| 1163 |
+
"projects": [
|
| 1164 |
+
"ac20",
|
| 1165 |
+
"fzk_house"
|
| 1166 |
+
],
|
| 1167 |
+
"auto_discovered": true
|
| 1168 |
+
},
|
| 1169 |
+
{
|
| 1170 |
+
"entity": "IfcSlab",
|
| 1171 |
+
"source": "qset",
|
| 1172 |
+
"set_name": "ArchiCADQuantities",
|
| 1173 |
+
"key": "Brutto-OberflΓ€chenbereich der Deckenunterseite",
|
| 1174 |
+
"file_count": 2,
|
| 1175 |
+
"projects": [
|
| 1176 |
+
"ac20",
|
| 1177 |
+
"fzk_house"
|
| 1178 |
+
],
|
| 1179 |
+
"auto_discovered": true
|
| 1180 |
+
},
|
| 1181 |
+
{
|
| 1182 |
+
"entity": "IfcSlab",
|
| 1183 |
+
"source": "qset",
|
| 1184 |
+
"set_name": "ArchiCADQuantities",
|
| 1185 |
+
"key": "Brutto-OberflΓ€chenbereich der Deckenkanten",
|
| 1186 |
+
"file_count": 2,
|
| 1187 |
+
"projects": [
|
| 1188 |
+
"ac20",
|
| 1189 |
+
"fzk_house"
|
| 1190 |
+
],
|
| 1191 |
+
"auto_discovered": true
|
| 1192 |
+
},
|
| 1193 |
+
{
|
| 1194 |
+
"entity": "IfcSlab",
|
| 1195 |
+
"source": "qset",
|
| 1196 |
+
"set_name": "ArchiCADQuantities",
|
| 1197 |
+
"key": "Konditionaler OberflΓ€chenbereich der Unterseite",
|
| 1198 |
+
"file_count": 2,
|
| 1199 |
+
"projects": [
|
| 1200 |
+
"ac20",
|
| 1201 |
+
"fzk_house"
|
| 1202 |
+
],
|
| 1203 |
+
"auto_discovered": true
|
| 1204 |
+
},
|
| 1205 |
+
{
|
| 1206 |
+
"entity": "IfcSlab",
|
| 1207 |
+
"source": "qset",
|
| 1208 |
+
"set_name": "ArchiCADQuantities",
|
| 1209 |
+
"key": "Konditionaler OberflΓ€chenbereich der Oberseite",
|
| 1210 |
+
"file_count": 2,
|
| 1211 |
+
"projects": [
|
| 1212 |
+
"ac20",
|
| 1213 |
+
"fzk_house"
|
| 1214 |
+
],
|
| 1215 |
+
"auto_discovered": true
|
| 1216 |
+
},
|
| 1217 |
+
{
|
| 1218 |
+
"entity": "IfcSlab",
|
| 1219 |
+
"source": "qset",
|
| 1220 |
+
"set_name": "ArchiCADQuantities",
|
| 1221 |
+
"key": "Brutto-OberflΓ€chenbereich der Deckenoberseite mit LΓΆchern",
|
| 1222 |
+
"file_count": 2,
|
| 1223 |
+
"projects": [
|
| 1224 |
+
"ac20",
|
| 1225 |
+
"fzk_house"
|
| 1226 |
+
],
|
| 1227 |
+
"auto_discovered": true
|
| 1228 |
+
},
|
| 1229 |
+
{
|
| 1230 |
+
"entity": "IfcSlab",
|
| 1231 |
+
"source": "qset",
|
| 1232 |
+
"set_name": "ArchiCADQuantities",
|
| 1233 |
+
"key": "Brutto-OberflΓ€chenbereich der Deckenunterseite mit LΓΆchern",
|
| 1234 |
+
"file_count": 2,
|
| 1235 |
+
"projects": [
|
| 1236 |
+
"ac20",
|
| 1237 |
+
"fzk_house"
|
| 1238 |
+
],
|
| 1239 |
+
"auto_discovered": true
|
| 1240 |
+
},
|
| 1241 |
+
{
|
| 1242 |
+
"entity": "IfcSlab",
|
| 1243 |
+
"source": "qset",
|
| 1244 |
+
"set_name": "ArchiCADQuantities",
|
| 1245 |
+
"key": "Brutto-OberflΓ€chenbereich der Deckenkanten mit LΓΆchern",
|
| 1246 |
+
"file_count": 2,
|
| 1247 |
+
"projects": [
|
| 1248 |
+
"ac20",
|
| 1249 |
+
"fzk_house"
|
| 1250 |
+
],
|
| 1251 |
+
"auto_discovered": true
|
| 1252 |
+
},
|
| 1253 |
+
{
|
| 1254 |
+
"entity": "IfcSlab",
|
| 1255 |
+
"source": "qset",
|
| 1256 |
+
"set_name": "ArchiCADQuantities",
|
| 1257 |
+
"key": "Netto-OberflΓ€chenbereich Unterseite",
|
| 1258 |
+
"file_count": 2,
|
| 1259 |
+
"projects": [
|
| 1260 |
+
"ac20",
|
| 1261 |
+
"fzk_house"
|
| 1262 |
+
],
|
| 1263 |
+
"auto_discovered": true
|
| 1264 |
+
},
|
| 1265 |
+
{
|
| 1266 |
+
"entity": "IfcSlab",
|
| 1267 |
+
"source": "qset",
|
| 1268 |
+
"set_name": "ArchiCADQuantities",
|
| 1269 |
+
"key": "Netto-OberflΓ€chenbereich Oberseite",
|
| 1270 |
+
"file_count": 2,
|
| 1271 |
+
"projects": [
|
| 1272 |
+
"ac20",
|
| 1273 |
+
"fzk_house"
|
| 1274 |
+
],
|
| 1275 |
+
"auto_discovered": true
|
| 1276 |
+
},
|
| 1277 |
+
{
|
| 1278 |
+
"entity": "IfcSlab",
|
| 1279 |
+
"source": "qset",
|
| 1280 |
+
"set_name": "ArchiCADQuantities",
|
| 1281 |
+
"key": "Netto-OberflΓ€chenbereich der Kante",
|
| 1282 |
+
"file_count": 2,
|
| 1283 |
+
"projects": [
|
| 1284 |
+
"ac20",
|
| 1285 |
+
"fzk_house"
|
| 1286 |
+
],
|
| 1287 |
+
"auto_discovered": true
|
| 1288 |
+
},
|
| 1289 |
+
{
|
| 1290 |
+
"entity": "IfcSlab",
|
| 1291 |
+
"source": "qset",
|
| 1292 |
+
"set_name": "ArchiCADQuantities",
|
| 1293 |
+
"key": "Brutto-OberflΓ€chenbereich an der Unterseite",
|
| 1294 |
+
"file_count": 2,
|
| 1295 |
+
"projects": [
|
| 1296 |
+
"ac20",
|
| 1297 |
+
"fzk_house"
|
| 1298 |
+
],
|
| 1299 |
+
"auto_discovered": true
|
| 1300 |
+
},
|
| 1301 |
+
{
|
| 1302 |
+
"entity": "IfcSlab",
|
| 1303 |
+
"source": "qset",
|
| 1304 |
+
"set_name": "ArchiCADQuantities",
|
| 1305 |
+
"key": "Brutto-OberflΓ€chenbereich an der Oberseite",
|
| 1306 |
+
"file_count": 2,
|
| 1307 |
+
"projects": [
|
| 1308 |
+
"ac20",
|
| 1309 |
+
"fzk_house"
|
| 1310 |
+
],
|
| 1311 |
+
"auto_discovered": true
|
| 1312 |
+
},
|
| 1313 |
+
{
|
| 1314 |
+
"entity": "IfcSlab",
|
| 1315 |
+
"source": "qset",
|
| 1316 |
+
"set_name": "ArchiCADQuantities",
|
| 1317 |
+
"key": "Brutto-OberflΓ€chenbereich der Dachkanten",
|
| 1318 |
+
"file_count": 2,
|
| 1319 |
+
"projects": [
|
| 1320 |
+
"ac20",
|
| 1321 |
+
"fzk_house"
|
| 1322 |
+
],
|
| 1323 |
+
"auto_discovered": true
|
| 1324 |
+
},
|
| 1325 |
+
{
|
| 1326 |
+
"entity": "IfcSlab",
|
| 1327 |
+
"source": "qset",
|
| 1328 |
+
"set_name": "ArchiCADQuantities",
|
| 1329 |
+
"key": "Konditionaler OberflΓ€chenbereich unten",
|
| 1330 |
+
"file_count": 2,
|
| 1331 |
+
"projects": [
|
| 1332 |
+
"ac20",
|
| 1333 |
+
"fzk_house"
|
| 1334 |
+
],
|
| 1335 |
+
"auto_discovered": true
|
| 1336 |
+
},
|
| 1337 |
+
{
|
| 1338 |
+
"entity": "IfcSlab",
|
| 1339 |
+
"source": "qset",
|
| 1340 |
+
"set_name": "ArchiCADQuantities",
|
| 1341 |
+
"key": "Konditionaler OberflΓ€chenbereich oben",
|
| 1342 |
+
"file_count": 2,
|
| 1343 |
+
"projects": [
|
| 1344 |
+
"ac20",
|
| 1345 |
+
"fzk_house"
|
| 1346 |
+
],
|
| 1347 |
+
"auto_discovered": true
|
| 1348 |
+
},
|
| 1349 |
+
{
|
| 1350 |
+
"entity": "IfcSlab",
|
| 1351 |
+
"source": "qset",
|
| 1352 |
+
"set_name": "ArchiCADQuantities",
|
| 1353 |
+
"key": "OberflΓ€chenbereich der Γffnungen",
|
| 1354 |
+
"file_count": 2,
|
| 1355 |
+
"projects": [
|
| 1356 |
+
"ac20",
|
| 1357 |
+
"fzk_house"
|
| 1358 |
+
],
|
| 1359 |
+
"auto_discovered": true
|
| 1360 |
+
},
|
| 1361 |
+
{
|
| 1362 |
+
"entity": "IfcSlab",
|
| 1363 |
+
"source": "qset",
|
| 1364 |
+
"set_name": "ArchiCADQuantities",
|
| 1365 |
+
"key": "Area",
|
| 1366 |
+
"file_count": 2,
|
| 1367 |
+
"projects": [
|
| 1368 |
+
"molio",
|
| 1369 |
+
"sixty5"
|
| 1370 |
+
],
|
| 1371 |
+
"auto_discovered": true
|
| 1372 |
+
},
|
| 1373 |
+
{
|
| 1374 |
+
"entity": "IfcSlab",
|
| 1375 |
+
"source": "qset",
|
| 1376 |
+
"set_name": "ArchiCADQuantities",
|
| 1377 |
+
"key": "Surface Area",
|
| 1378 |
+
"file_count": 2,
|
| 1379 |
+
"projects": [
|
| 1380 |
+
"molio",
|
| 1381 |
+
"sixty5"
|
| 1382 |
+
],
|
| 1383 |
+
"auto_discovered": true
|
| 1384 |
+
},
|
| 1385 |
+
{
|
| 1386 |
+
"entity": "IfcSlab",
|
| 1387 |
+
"source": "qset",
|
| 1388 |
+
"set_name": "ArchiCADQuantities",
|
| 1389 |
+
"key": "Top Surface Area (Net)",
|
| 1390 |
+
"file_count": 1,
|
| 1391 |
+
"projects": [
|
| 1392 |
+
"molio"
|
| 1393 |
+
],
|
| 1394 |
+
"auto_discovered": true
|
| 1395 |
+
},
|
| 1396 |
+
{
|
| 1397 |
+
"entity": "IfcSlab",
|
| 1398 |
+
"source": "qset",
|
| 1399 |
+
"set_name": "ArchiCADQuantities",
|
| 1400 |
+
"key": "Top Surface Area (Conditional)",
|
| 1401 |
+
"file_count": 1,
|
| 1402 |
+
"projects": [
|
| 1403 |
+
"molio"
|
| 1404 |
+
],
|
| 1405 |
+
"auto_discovered": true
|
| 1406 |
+
},
|
| 1407 |
+
{
|
| 1408 |
+
"entity": "IfcSlab",
|
| 1409 |
+
"source": "qset",
|
| 1410 |
+
"set_name": "ArchiCADQuantities",
|
| 1411 |
+
"key": "Edge Surface Area (Net)",
|
| 1412 |
+
"file_count": 1,
|
| 1413 |
+
"projects": [
|
| 1414 |
+
"molio"
|
| 1415 |
+
],
|
| 1416 |
+
"auto_discovered": true
|
| 1417 |
+
},
|
| 1418 |
+
{
|
| 1419 |
+
"entity": "IfcSlab",
|
| 1420 |
+
"source": "qset",
|
| 1421 |
+
"set_name": "ArchiCADQuantities",
|
| 1422 |
+
"key": "Bottom Surface Area (Net)",
|
| 1423 |
+
"file_count": 1,
|
| 1424 |
+
"projects": [
|
| 1425 |
+
"molio"
|
| 1426 |
+
],
|
| 1427 |
+
"auto_discovered": true
|
| 1428 |
+
},
|
| 1429 |
+
{
|
| 1430 |
+
"entity": "IfcSlab",
|
| 1431 |
+
"source": "qset",
|
| 1432 |
+
"set_name": "ArchiCADQuantities",
|
| 1433 |
+
"key": "Bottom Surface Area (Conditional)",
|
| 1434 |
+
"file_count": 1,
|
| 1435 |
+
"projects": [
|
| 1436 |
+
"molio"
|
| 1437 |
+
],
|
| 1438 |
+
"auto_discovered": true
|
| 1439 |
+
},
|
| 1440 |
+
{
|
| 1441 |
+
"entity": "IfcSlab",
|
| 1442 |
+
"source": "qset",
|
| 1443 |
+
"set_name": "ArchiCADQuantities",
|
| 1444 |
+
"key": "Bottom Surface Area (Gross)",
|
| 1445 |
+
"file_count": 1,
|
| 1446 |
+
"projects": [
|
| 1447 |
+
"molio"
|
| 1448 |
+
],
|
| 1449 |
+
"auto_discovered": true
|
| 1450 |
+
},
|
| 1451 |
+
{
|
| 1452 |
+
"entity": "IfcSlab",
|
| 1453 |
+
"source": "qset",
|
| 1454 |
+
"set_name": "ArchiCADQuantities",
|
| 1455 |
+
"key": "Edge Surface Area (Gross)",
|
| 1456 |
+
"file_count": 1,
|
| 1457 |
+
"projects": [
|
| 1458 |
+
"molio"
|
| 1459 |
+
],
|
| 1460 |
+
"auto_discovered": true
|
| 1461 |
+
},
|
| 1462 |
+
{
|
| 1463 |
+
"entity": "IfcSlab",
|
| 1464 |
+
"source": "qset",
|
| 1465 |
+
"set_name": "ArchiCADQuantities",
|
| 1466 |
+
"key": "Top Surface Area (Gross)",
|
| 1467 |
+
"file_count": 1,
|
| 1468 |
+
"projects": [
|
| 1469 |
+
"molio"
|
| 1470 |
+
],
|
| 1471 |
+
"auto_discovered": true
|
| 1472 |
+
},
|
| 1473 |
+
{
|
| 1474 |
+
"entity": "IfcSlab",
|
| 1475 |
+
"source": "qset",
|
| 1476 |
+
"set_name": "ArchiCADQuantities",
|
| 1477 |
+
"key": "Surface Area of the Slab Top (Conditional)",
|
| 1478 |
+
"file_count": 1,
|
| 1479 |
+
"projects": [
|
| 1480 |
+
"molio"
|
| 1481 |
+
],
|
| 1482 |
+
"auto_discovered": true
|
| 1483 |
+
},
|
| 1484 |
+
{
|
| 1485 |
+
"entity": "IfcSlab",
|
| 1486 |
+
"source": "qset",
|
| 1487 |
+
"set_name": "ArchiCADQuantities",
|
| 1488 |
+
"key": "Surface Area of the Slab Bottom (Gross)",
|
| 1489 |
+
"file_count": 1,
|
| 1490 |
+
"projects": [
|
| 1491 |
+
"molio"
|
| 1492 |
+
],
|
| 1493 |
+
"auto_discovered": true
|
| 1494 |
+
},
|
| 1495 |
+
{
|
| 1496 |
+
"entity": "IfcSlab",
|
| 1497 |
+
"source": "qset",
|
| 1498 |
+
"set_name": "ArchiCADQuantities",
|
| 1499 |
+
"key": "Surface Area of the Slab Bottom (Gross, with holes)",
|
| 1500 |
+
"file_count": 1,
|
| 1501 |
+
"projects": [
|
| 1502 |
+
"molio"
|
| 1503 |
+
],
|
| 1504 |
+
"auto_discovered": true
|
| 1505 |
+
},
|
| 1506 |
+
{
|
| 1507 |
+
"entity": "IfcSlab",
|
| 1508 |
+
"source": "qset",
|
| 1509 |
+
"set_name": "ArchiCADQuantities",
|
| 1510 |
+
"key": "Surface Area of the Slab Edges (Gross)",
|
| 1511 |
+
"file_count": 1,
|
| 1512 |
+
"projects": [
|
| 1513 |
+
"molio"
|
| 1514 |
+
],
|
| 1515 |
+
"auto_discovered": true
|
| 1516 |
+
},
|
| 1517 |
+
{
|
| 1518 |
+
"entity": "IfcSlab",
|
| 1519 |
+
"source": "qset",
|
| 1520 |
+
"set_name": "ArchiCADQuantities",
|
| 1521 |
+
"key": "Surface Area of the Slab Edges (Gross, with holes)",
|
| 1522 |
+
"file_count": 1,
|
| 1523 |
+
"projects": [
|
| 1524 |
+
"molio"
|
| 1525 |
+
],
|
| 1526 |
+
"auto_discovered": true
|
| 1527 |
+
},
|
| 1528 |
+
{
|
| 1529 |
+
"entity": "IfcSlab",
|
| 1530 |
+
"source": "qset",
|
| 1531 |
+
"set_name": "ArchiCADQuantities",
|
| 1532 |
+
"key": "Surface Area of the Slab Top (Gross)",
|
| 1533 |
+
"file_count": 1,
|
| 1534 |
+
"projects": [
|
| 1535 |
+
"molio"
|
| 1536 |
+
],
|
| 1537 |
+
"auto_discovered": true
|
| 1538 |
+
},
|
| 1539 |
+
{
|
| 1540 |
+
"entity": "IfcSlab",
|
| 1541 |
+
"source": "qset",
|
| 1542 |
+
"set_name": "ArchiCADQuantities",
|
| 1543 |
+
"key": "Surface Area of the Slab Top (Gross, with holes)",
|
| 1544 |
+
"file_count": 1,
|
| 1545 |
+
"projects": [
|
| 1546 |
+
"molio"
|
| 1547 |
+
],
|
| 1548 |
+
"auto_discovered": true
|
| 1549 |
+
},
|
| 1550 |
+
{
|
| 1551 |
+
"entity": "IfcSlab",
|
| 1552 |
+
"source": "qset",
|
| 1553 |
+
"set_name": "ArchiCADQuantities",
|
| 1554 |
+
"key": "Holes Surface Area",
|
| 1555 |
+
"file_count": 2,
|
| 1556 |
+
"projects": [
|
| 1557 |
+
"molio",
|
| 1558 |
+
"sixty5"
|
| 1559 |
+
],
|
| 1560 |
+
"auto_discovered": true
|
| 1561 |
+
},
|
| 1562 |
+
{
|
| 1563 |
+
"entity": "IfcSlab",
|
| 1564 |
+
"source": "qset",
|
| 1565 |
+
"set_name": "ArchiCADQuantities",
|
| 1566 |
+
"key": "Bottom Surface Area",
|
| 1567 |
+
"file_count": 1,
|
| 1568 |
+
"projects": [
|
| 1569 |
+
"sixty5"
|
| 1570 |
+
],
|
| 1571 |
+
"auto_discovered": true
|
| 1572 |
+
},
|
| 1573 |
+
{
|
| 1574 |
+
"entity": "IfcSlab",
|
| 1575 |
+
"source": "qset",
|
| 1576 |
+
"set_name": "ArchiCADQuantities",
|
| 1577 |
+
"key": "Conditional Surface Area of the Bottom",
|
| 1578 |
+
"file_count": 1,
|
| 1579 |
+
"projects": [
|
| 1580 |
+
"sixty5"
|
| 1581 |
+
],
|
| 1582 |
+
"auto_discovered": true
|
| 1583 |
+
},
|
| 1584 |
+
{
|
| 1585 |
+
"entity": "IfcSlab",
|
| 1586 |
+
"source": "qset",
|
| 1587 |
+
"set_name": "ArchiCADQuantities",
|
| 1588 |
+
"key": "Conditional Surface Area of the Top",
|
| 1589 |
+
"file_count": 1,
|
| 1590 |
+
"projects": [
|
| 1591 |
+
"sixty5"
|
| 1592 |
+
],
|
| 1593 |
+
"auto_discovered": true
|
| 1594 |
+
},
|
| 1595 |
+
{
|
| 1596 |
+
"entity": "IfcSlab",
|
| 1597 |
+
"source": "qset",
|
| 1598 |
+
"set_name": "ArchiCADQuantities",
|
| 1599 |
+
"key": "Edge Surface Area",
|
| 1600 |
+
"file_count": 1,
|
| 1601 |
+
"projects": [
|
| 1602 |
+
"sixty5"
|
| 1603 |
+
],
|
| 1604 |
+
"auto_discovered": true
|
| 1605 |
+
},
|
| 1606 |
+
{
|
| 1607 |
+
"entity": "IfcSlab",
|
| 1608 |
+
"source": "qset",
|
| 1609 |
+
"set_name": "ArchiCADQuantities",
|
| 1610 |
+
"key": "Gross Surface Area of the Slab Bottom",
|
| 1611 |
+
"file_count": 1,
|
| 1612 |
+
"projects": [
|
| 1613 |
+
"sixty5"
|
| 1614 |
+
],
|
| 1615 |
+
"auto_discovered": true
|
| 1616 |
+
},
|
| 1617 |
+
{
|
| 1618 |
+
"entity": "IfcSlab",
|
| 1619 |
+
"source": "qset",
|
| 1620 |
+
"set_name": "ArchiCADQuantities",
|
| 1621 |
+
"key": "Gross Surface Area of the Slab Bottom with Holes",
|
| 1622 |
+
"file_count": 1,
|
| 1623 |
+
"projects": [
|
| 1624 |
+
"sixty5"
|
| 1625 |
+
],
|
| 1626 |
+
"auto_discovered": true
|
| 1627 |
+
},
|
| 1628 |
+
{
|
| 1629 |
+
"entity": "IfcSlab",
|
| 1630 |
+
"source": "qset",
|
| 1631 |
+
"set_name": "ArchiCADQuantities",
|
| 1632 |
+
"key": "Gross Surface Area of the Slab Edges",
|
| 1633 |
+
"file_count": 1,
|
| 1634 |
+
"projects": [
|
| 1635 |
+
"sixty5"
|
| 1636 |
+
],
|
| 1637 |
+
"auto_discovered": true
|
| 1638 |
+
},
|
| 1639 |
+
{
|
| 1640 |
+
"entity": "IfcSlab",
|
| 1641 |
+
"source": "qset",
|
| 1642 |
+
"set_name": "ArchiCADQuantities",
|
| 1643 |
+
"key": "Gross Surface Area of the Slab Edges with Holes",
|
| 1644 |
+
"file_count": 1,
|
| 1645 |
+
"projects": [
|
| 1646 |
+
"sixty5"
|
| 1647 |
+
],
|
| 1648 |
+
"auto_discovered": true
|
| 1649 |
+
},
|
| 1650 |
+
{
|
| 1651 |
+
"entity": "IfcSlab",
|
| 1652 |
+
"source": "qset",
|
| 1653 |
+
"set_name": "ArchiCADQuantities",
|
| 1654 |
+
"key": "Gross Surface Area of the Slab Top",
|
| 1655 |
+
"file_count": 1,
|
| 1656 |
+
"projects": [
|
| 1657 |
+
"sixty5"
|
| 1658 |
+
],
|
| 1659 |
+
"auto_discovered": true
|
| 1660 |
+
},
|
| 1661 |
+
{
|
| 1662 |
+
"entity": "IfcSlab",
|
| 1663 |
+
"source": "qset",
|
| 1664 |
+
"set_name": "ArchiCADQuantities",
|
| 1665 |
+
"key": "Gross Surface Area of the Slab Top with Holes",
|
| 1666 |
+
"file_count": 1,
|
| 1667 |
+
"projects": [
|
| 1668 |
+
"sixty5"
|
| 1669 |
+
],
|
| 1670 |
+
"auto_discovered": true
|
| 1671 |
+
},
|
| 1672 |
+
{
|
| 1673 |
+
"entity": "IfcSlab",
|
| 1674 |
+
"source": "qset",
|
| 1675 |
+
"set_name": "ArchiCADQuantities",
|
| 1676 |
+
"key": "Top Surface Area",
|
| 1677 |
+
"file_count": 1,
|
| 1678 |
+
"projects": [
|
| 1679 |
+
"sixty5"
|
| 1680 |
+
],
|
| 1681 |
+
"auto_discovered": true
|
| 1682 |
+
},
|
| 1683 |
+
{
|
| 1684 |
+
"entity": "IfcSlab",
|
| 1685 |
+
"source": "qset",
|
| 1686 |
+
"set_name": "(unnamed)",
|
| 1687 |
+
"key": "NetFootprintArea",
|
| 1688 |
+
"file_count": 1,
|
| 1689 |
+
"projects": [
|
| 1690 |
+
"hitos"
|
| 1691 |
+
],
|
| 1692 |
+
"auto_discovered": true
|
| 1693 |
+
},
|
| 1694 |
+
{
|
| 1695 |
+
"entity": "IfcRoof",
|
| 1696 |
+
"source": "qset",
|
| 1697 |
+
"set_name": "Qto_RoofBaseQuantities",
|
| 1698 |
+
"key": "ProjectedArea",
|
| 1699 |
+
"file_count": 2,
|
| 1700 |
+
"projects": [
|
| 1701 |
+
"fantasy_office_building_1",
|
| 1702 |
+
"fantasy_office_building_2"
|
| 1703 |
+
],
|
| 1704 |
+
"auto_discovered": true
|
| 1705 |
+
},
|
| 1706 |
+
{
|
| 1707 |
+
"entity": "IfcWall",
|
| 1708 |
+
"source": "qset",
|
| 1709 |
+
"set_name": "BaseQuantities",
|
| 1710 |
+
"key": "GrossFootprintArea",
|
| 1711 |
+
"file_count": 7,
|
| 1712 |
+
"projects": [
|
| 1713 |
+
"ac20",
|
| 1714 |
+
"fzk_house",
|
| 1715 |
+
"molio",
|
| 1716 |
+
"sixty5",
|
| 1717 |
+
"smiley_west"
|
| 1718 |
+
],
|
| 1719 |
+
"auto_discovered": true
|
| 1720 |
+
},
|
| 1721 |
+
{
|
| 1722 |
+
"entity": "IfcWall",
|
| 1723 |
+
"source": "qset",
|
| 1724 |
+
"set_name": "BaseQuantities",
|
| 1725 |
+
"key": "NetFootprintArea",
|
| 1726 |
+
"file_count": 6,
|
| 1727 |
+
"projects": [
|
| 1728 |
+
"ac20",
|
| 1729 |
+
"fzk_house",
|
| 1730 |
+
"molio",
|
| 1731 |
+
"sixty5",
|
| 1732 |
+
"smiley_west"
|
| 1733 |
+
],
|
| 1734 |
+
"auto_discovered": true
|
| 1735 |
+
},
|
| 1736 |
+
{
|
| 1737 |
+
"entity": "IfcWall",
|
| 1738 |
+
"source": "qset",
|
| 1739 |
+
"set_name": "BaseQuantities",
|
| 1740 |
+
"key": "GrossSideArea",
|
| 1741 |
+
"file_count": 7,
|
| 1742 |
+
"projects": [
|
| 1743 |
+
"ac20",
|
| 1744 |
+
"fzk_house",
|
| 1745 |
+
"molio",
|
| 1746 |
+
"sixty5",
|
| 1747 |
+
"smiley_west"
|
| 1748 |
+
],
|
| 1749 |
+
"auto_discovered": true
|
| 1750 |
+
},
|
| 1751 |
+
{
|
| 1752 |
+
"entity": "IfcWall",
|
| 1753 |
+
"source": "qset",
|
| 1754 |
+
"set_name": "BaseQuantities",
|
| 1755 |
+
"key": "NetSideArea",
|
| 1756 |
+
"file_count": 7,
|
| 1757 |
+
"projects": [
|
| 1758 |
+
"ac20",
|
| 1759 |
+
"fzk_house",
|
| 1760 |
+
"molio",
|
| 1761 |
+
"sixty5",
|
| 1762 |
+
"smiley_west"
|
| 1763 |
+
],
|
| 1764 |
+
"auto_discovered": true
|
| 1765 |
+
},
|
| 1766 |
+
{
|
| 1767 |
+
"entity": "IfcWall",
|
| 1768 |
+
"source": "qset",
|
| 1769 |
+
"set_name": "ArchiCADQuantities",
|
| 1770 |
+
"key": "OberflΓ€chenbereich",
|
| 1771 |
+
"file_count": 2,
|
| 1772 |
+
"projects": [
|
| 1773 |
+
"ac20",
|
| 1774 |
+
"fzk_house"
|
| 1775 |
+
],
|
| 1776 |
+
"auto_discovered": true
|
| 1777 |
+
},
|
| 1778 |
+
{
|
| 1779 |
+
"entity": "IfcWall",
|
| 1780 |
+
"source": "qset",
|
| 1781 |
+
"set_name": "ArchiCADQuantities",
|
| 1782 |
+
"key": "FlΓ€che",
|
| 1783 |
+
"file_count": 2,
|
| 1784 |
+
"projects": [
|
| 1785 |
+
"ac20",
|
| 1786 |
+
"fzk_house"
|
| 1787 |
+
],
|
| 1788 |
+
"auto_discovered": true
|
| 1789 |
+
},
|
| 1790 |
+
{
|
| 1791 |
+
"entity": "IfcWall",
|
| 1792 |
+
"source": "qset",
|
| 1793 |
+
"set_name": "ArchiCADQuantities",
|
| 1794 |
+
"key": "Netto-OberflΓ€chenbereich an der AuΓenseite",
|
| 1795 |
+
"file_count": 2,
|
| 1796 |
+
"projects": [
|
| 1797 |
+
"ac20",
|
| 1798 |
+
"fzk_house"
|
| 1799 |
+
],
|
| 1800 |
+
"auto_discovered": true
|
| 1801 |
+
},
|
| 1802 |
+
{
|
| 1803 |
+
"entity": "IfcWall",
|
| 1804 |
+
"source": "qset",
|
| 1805 |
+
"set_name": "ArchiCADQuantities",
|
| 1806 |
+
"key": "Netto-OberflΓ€chenbereich an der Innenseite",
|
| 1807 |
+
"file_count": 2,
|
| 1808 |
+
"projects": [
|
| 1809 |
+
"ac20",
|
| 1810 |
+
"fzk_house"
|
| 1811 |
+
],
|
| 1812 |
+
"auto_discovered": true
|
| 1813 |
+
},
|
| 1814 |
+
{
|
| 1815 |
+
"entity": "IfcWall",
|
| 1816 |
+
"source": "qset",
|
| 1817 |
+
"set_name": "ArchiCADQuantities",
|
| 1818 |
+
"key": "Netto-OberflΓ€chenbereich an den Kanten",
|
| 1819 |
+
"file_count": 2,
|
| 1820 |
+
"projects": [
|
| 1821 |
+
"ac20",
|
| 1822 |
+
"fzk_house"
|
| 1823 |
+
],
|
| 1824 |
+
"auto_discovered": true
|
| 1825 |
+
},
|
| 1826 |
+
{
|
| 1827 |
+
"entity": "IfcWall",
|
| 1828 |
+
"source": "qset",
|
| 1829 |
+
"set_name": "ArchiCADQuantities",
|
| 1830 |
+
"key": "FlΓ€che der Wand",
|
| 1831 |
+
"file_count": 2,
|
| 1832 |
+
"projects": [
|
| 1833 |
+
"ac20",
|
| 1834 |
+
"fzk_house"
|
| 1835 |
+
],
|
| 1836 |
+
"auto_discovered": true
|
| 1837 |
+
},
|
| 1838 |
+
{
|
| 1839 |
+
"entity": "IfcWall",
|
| 1840 |
+
"source": "qset",
|
| 1841 |
+
"set_name": "ArchiCADQuantities",
|
| 1842 |
+
"key": "Brutto-WandoberflΓ€chenbereich an der Innenseite",
|
| 1843 |
+
"file_count": 2,
|
| 1844 |
+
"projects": [
|
| 1845 |
+
"ac20",
|
| 1846 |
+
"fzk_house"
|
| 1847 |
+
],
|
| 1848 |
+
"auto_discovered": true
|
| 1849 |
+
},
|
| 1850 |
+
{
|
| 1851 |
+
"entity": "IfcWall",
|
| 1852 |
+
"source": "qset",
|
| 1853 |
+
"set_name": "ArchiCADQuantities",
|
| 1854 |
+
"key": "Brutto-WandoberflΓ€chenbereich an der AuΓenseite",
|
| 1855 |
+
"file_count": 2,
|
| 1856 |
+
"projects": [
|
| 1857 |
+
"ac20",
|
| 1858 |
+
"fzk_house"
|
| 1859 |
+
],
|
| 1860 |
+
"auto_discovered": true
|
| 1861 |
+
},
|
| 1862 |
+
{
|
| 1863 |
+
"entity": "IfcWall",
|
| 1864 |
+
"source": "qset",
|
| 1865 |
+
"set_name": "ArchiCADQuantities",
|
| 1866 |
+
"key": "Analytische OberflΓ€che der Γffnungen an der Innenseite",
|
| 1867 |
+
"file_count": 2,
|
| 1868 |
+
"projects": [
|
| 1869 |
+
"ac20",
|
| 1870 |
+
"fzk_house"
|
| 1871 |
+
],
|
| 1872 |
+
"auto_discovered": true
|
| 1873 |
+
},
|
| 1874 |
+
{
|
| 1875 |
+
"entity": "IfcWall",
|
| 1876 |
+
"source": "qset",
|
| 1877 |
+
"set_name": "ArchiCADQuantities",
|
| 1878 |
+
"key": "Analytische OberflΓ€che der Γffnungen an der AuΓenseite",
|
| 1879 |
+
"file_count": 2,
|
| 1880 |
+
"projects": [
|
| 1881 |
+
"ac20",
|
| 1882 |
+
"fzk_house"
|
| 1883 |
+
],
|
| 1884 |
+
"auto_discovered": true
|
| 1885 |
+
},
|
| 1886 |
+
{
|
| 1887 |
+
"entity": "IfcWall",
|
| 1888 |
+
"source": "qset",
|
| 1889 |
+
"set_name": "ArchiCADQuantities",
|
| 1890 |
+
"key": "Konditionaler OberflΓ€chenbereich an der AuΓenseite",
|
| 1891 |
+
"file_count": 2,
|
| 1892 |
+
"projects": [
|
| 1893 |
+
"ac20",
|
| 1894 |
+
"fzk_house"
|
| 1895 |
+
],
|
| 1896 |
+
"auto_discovered": true
|
| 1897 |
+
},
|
| 1898 |
+
{
|
| 1899 |
+
"entity": "IfcWall",
|
| 1900 |
+
"source": "qset",
|
| 1901 |
+
"set_name": "ArchiCADQuantities",
|
| 1902 |
+
"key": "Konditionaler OberflΓ€chenbereich an der Innenseite",
|
| 1903 |
+
"file_count": 2,
|
| 1904 |
+
"projects": [
|
| 1905 |
+
"ac20",
|
| 1906 |
+
"fzk_house"
|
| 1907 |
+
],
|
| 1908 |
+
"auto_discovered": true
|
| 1909 |
+
},
|
| 1910 |
+
{
|
| 1911 |
+
"entity": "IfcWall",
|
| 1912 |
+
"source": "qset",
|
| 1913 |
+
"set_name": "ArchiCADQuantities",
|
| 1914 |
+
"key": "OberflΓ€chenbereich der Fenster in der Wand",
|
| 1915 |
+
"file_count": 2,
|
| 1916 |
+
"projects": [
|
| 1917 |
+
"ac20",
|
| 1918 |
+
"fzk_house"
|
| 1919 |
+
],
|
| 1920 |
+
"auto_discovered": true
|
| 1921 |
+
},
|
| 1922 |
+
{
|
| 1923 |
+
"entity": "IfcWall",
|
| 1924 |
+
"source": "qset",
|
| 1925 |
+
"set_name": "ArchiCADQuantities",
|
| 1926 |
+
"key": "OberflΓ€chenbereich der TΓΌren in der Wand",
|
| 1927 |
+
"file_count": 2,
|
| 1928 |
+
"projects": [
|
| 1929 |
+
"ac20",
|
| 1930 |
+
"fzk_house"
|
| 1931 |
+
],
|
| 1932 |
+
"auto_discovered": true
|
| 1933 |
+
},
|
| 1934 |
+
{
|
| 1935 |
+
"entity": "IfcWall",
|
| 1936 |
+
"source": "qset",
|
| 1937 |
+
"set_name": "ArchiCADQuantities",
|
| 1938 |
+
"key": "OberflΓ€chenbereich der leeren Γffnungen in der Wand",
|
| 1939 |
+
"file_count": 2,
|
| 1940 |
+
"projects": [
|
| 1941 |
+
"ac20",
|
| 1942 |
+
"fzk_house"
|
| 1943 |
+
],
|
| 1944 |
+
"auto_discovered": true
|
| 1945 |
+
},
|
| 1946 |
+
{
|
| 1947 |
+
"entity": "IfcWall",
|
| 1948 |
+
"source": "qset",
|
| 1949 |
+
"set_name": "ArchiCADQuantities",
|
| 1950 |
+
"key": "Area",
|
| 1951 |
+
"file_count": 3,
|
| 1952 |
+
"projects": [
|
| 1953 |
+
"molio",
|
| 1954 |
+
"sixty5"
|
| 1955 |
+
],
|
| 1956 |
+
"auto_discovered": true
|
| 1957 |
+
},
|
| 1958 |
+
{
|
| 1959 |
+
"entity": "IfcWall",
|
| 1960 |
+
"source": "qset",
|
| 1961 |
+
"set_name": "ArchiCADQuantities",
|
| 1962 |
+
"key": "Surface Area",
|
| 1963 |
+
"file_count": 3,
|
| 1964 |
+
"projects": [
|
| 1965 |
+
"molio",
|
| 1966 |
+
"sixty5"
|
| 1967 |
+
],
|
| 1968 |
+
"auto_discovered": true
|
| 1969 |
+
},
|
| 1970 |
+
{
|
| 1971 |
+
"entity": "IfcWall",
|
| 1972 |
+
"source": "qset",
|
| 1973 |
+
"set_name": "ArchiCADQuantities",
|
| 1974 |
+
"key": "Top Surface Area (Net)",
|
| 1975 |
+
"file_count": 1,
|
| 1976 |
+
"projects": [
|
| 1977 |
+
"molio"
|
| 1978 |
+
],
|
| 1979 |
+
"auto_discovered": true
|
| 1980 |
+
},
|
| 1981 |
+
{
|
| 1982 |
+
"entity": "IfcWall",
|
| 1983 |
+
"source": "qset",
|
| 1984 |
+
"set_name": "ArchiCADQuantities",
|
| 1985 |
+
"key": "Edge Surface Area (Net)",
|
| 1986 |
+
"file_count": 1,
|
| 1987 |
+
"projects": [
|
| 1988 |
+
"molio"
|
| 1989 |
+
],
|
| 1990 |
+
"auto_discovered": true
|
| 1991 |
+
},
|
| 1992 |
+
{
|
| 1993 |
+
"entity": "IfcWall",
|
| 1994 |
+
"source": "qset",
|
| 1995 |
+
"set_name": "ArchiCADQuantities",
|
| 1996 |
+
"key": "Bottom Surface Area (Net)",
|
| 1997 |
+
"file_count": 1,
|
| 1998 |
+
"projects": [
|
| 1999 |
+
"molio"
|
| 2000 |
+
],
|
| 2001 |
+
"auto_discovered": true
|
| 2002 |
+
},
|
| 2003 |
+
{
|
| 2004 |
+
"entity": "IfcWall",
|
| 2005 |
+
"source": "qset",
|
| 2006 |
+
"set_name": "ArchiCADQuantities",
|
| 2007 |
+
"key": "Area of the Doors",
|
| 2008 |
+
"file_count": 3,
|
| 2009 |
+
"projects": [
|
| 2010 |
+
"molio",
|
| 2011 |
+
"sixty5"
|
| 2012 |
+
],
|
| 2013 |
+
"auto_discovered": true
|
| 2014 |
+
},
|
| 2015 |
+
{
|
| 2016 |
+
"entity": "IfcWall",
|
| 2017 |
+
"source": "qset",
|
| 2018 |
+
"set_name": "ArchiCADQuantities",
|
| 2019 |
+
"key": "Area of the Windows",
|
| 2020 |
+
"file_count": 3,
|
| 2021 |
+
"projects": [
|
| 2022 |
+
"molio",
|
| 2023 |
+
"sixty5"
|
| 2024 |
+
],
|
| 2025 |
+
"auto_discovered": true
|
| 2026 |
+
},
|
| 2027 |
+
{
|
| 2028 |
+
"entity": "IfcWall",
|
| 2029 |
+
"source": "qset",
|
| 2030 |
+
"set_name": "ArchiCADQuantities",
|
| 2031 |
+
"key": "Analytic Surface Area of Openings on the Inside Face",
|
| 2032 |
+
"file_count": 3,
|
| 2033 |
+
"projects": [
|
| 2034 |
+
"molio",
|
| 2035 |
+
"sixty5"
|
| 2036 |
+
],
|
| 2037 |
+
"auto_discovered": true
|
| 2038 |
+
},
|
| 2039 |
+
{
|
| 2040 |
+
"entity": "IfcWall",
|
| 2041 |
+
"source": "qset",
|
| 2042 |
+
"set_name": "ArchiCADQuantities",
|
| 2043 |
+
"key": "Analytic Surface Area of Openings on the Outside Face",
|
| 2044 |
+
"file_count": 3,
|
| 2045 |
+
"projects": [
|
| 2046 |
+
"molio",
|
| 2047 |
+
"sixty5"
|
| 2048 |
+
],
|
| 2049 |
+
"auto_discovered": true
|
| 2050 |
+
},
|
| 2051 |
+
{
|
| 2052 |
+
"entity": "IfcWall",
|
| 2053 |
+
"source": "qset",
|
| 2054 |
+
"set_name": "ArchiCADQuantities",
|
| 2055 |
+
"key": "Surface Area of the Wall Inside Face (Net)",
|
| 2056 |
+
"file_count": 1,
|
| 2057 |
+
"projects": [
|
| 2058 |
+
"molio"
|
| 2059 |
+
],
|
| 2060 |
+
"auto_discovered": true
|
| 2061 |
+
},
|
| 2062 |
+
{
|
| 2063 |
+
"entity": "IfcWall",
|
| 2064 |
+
"source": "qset",
|
| 2065 |
+
"set_name": "ArchiCADQuantities",
|
| 2066 |
+
"key": "Surface Area of the Wall Outside Face (Net)",
|
| 2067 |
+
"file_count": 1,
|
| 2068 |
+
"projects": [
|
| 2069 |
+
"molio"
|
| 2070 |
+
],
|
| 2071 |
+
"auto_discovered": true
|
| 2072 |
+
},
|
| 2073 |
+
{
|
| 2074 |
+
"entity": "IfcWall",
|
| 2075 |
+
"source": "qset",
|
| 2076 |
+
"set_name": "ArchiCADQuantities",
|
| 2077 |
+
"key": "Surface Area of the Wall Inside Face (Conditional)",
|
| 2078 |
+
"file_count": 1,
|
| 2079 |
+
"projects": [
|
| 2080 |
+
"molio"
|
| 2081 |
+
],
|
| 2082 |
+
"auto_discovered": true
|
| 2083 |
+
},
|
| 2084 |
+
{
|
| 2085 |
+
"entity": "IfcWall",
|
| 2086 |
+
"source": "qset",
|
| 2087 |
+
"set_name": "ArchiCADQuantities",
|
| 2088 |
+
"key": "Surface Area of the Wall Outside Face (Conditional)",
|
| 2089 |
+
"file_count": 1,
|
| 2090 |
+
"projects": [
|
| 2091 |
+
"molio"
|
| 2092 |
+
],
|
| 2093 |
+
"auto_discovered": true
|
| 2094 |
+
},
|
| 2095 |
+
{
|
| 2096 |
+
"entity": "IfcWall",
|
| 2097 |
+
"source": "qset",
|
| 2098 |
+
"set_name": "ArchiCADQuantities",
|
| 2099 |
+
"key": "Surface Area of the Wall Inside Face (Gross)",
|
| 2100 |
+
"file_count": 1,
|
| 2101 |
+
"projects": [
|
| 2102 |
+
"molio"
|
| 2103 |
+
],
|
| 2104 |
+
"auto_discovered": true
|
| 2105 |
+
},
|
| 2106 |
+
{
|
| 2107 |
+
"entity": "IfcWall",
|
| 2108 |
+
"source": "qset",
|
| 2109 |
+
"set_name": "ArchiCADQuantities",
|
| 2110 |
+
"key": "Surface Area of the Wall Outside Face (Gross)",
|
| 2111 |
+
"file_count": 1,
|
| 2112 |
+
"projects": [
|
| 2113 |
+
"molio"
|
| 2114 |
+
],
|
| 2115 |
+
"auto_discovered": true
|
| 2116 |
+
},
|
| 2117 |
+
{
|
| 2118 |
+
"entity": "IfcWall",
|
| 2119 |
+
"source": "qset",
|
| 2120 |
+
"set_name": "ArchiCADQuantities",
|
| 2121 |
+
"key": "Surface Area of Empty Openings in the Wall",
|
| 2122 |
+
"file_count": 3,
|
| 2123 |
+
"projects": [
|
| 2124 |
+
"molio",
|
| 2125 |
+
"sixty5"
|
| 2126 |
+
],
|
| 2127 |
+
"auto_discovered": true
|
| 2128 |
+
},
|
| 2129 |
+
{
|
| 2130 |
+
"entity": "IfcWall",
|
| 2131 |
+
"source": "qset",
|
| 2132 |
+
"set_name": "ArchiCADQuantities",
|
| 2133 |
+
"key": "Net Surface Area of the Edges",
|
| 2134 |
+
"file_count": 2,
|
| 2135 |
+
"projects": [
|
| 2136 |
+
"sixty5"
|
| 2137 |
+
],
|
| 2138 |
+
"auto_discovered": true
|
| 2139 |
+
},
|
| 2140 |
+
{
|
| 2141 |
+
"entity": "IfcWall",
|
| 2142 |
+
"source": "qset",
|
| 2143 |
+
"set_name": "ArchiCADQuantities",
|
| 2144 |
+
"key": "Net Surface Area on the Inside Face",
|
| 2145 |
+
"file_count": 2,
|
| 2146 |
+
"projects": [
|
| 2147 |
+
"sixty5"
|
| 2148 |
+
],
|
| 2149 |
+
"auto_discovered": true
|
| 2150 |
+
},
|
| 2151 |
+
{
|
| 2152 |
+
"entity": "IfcWall",
|
| 2153 |
+
"source": "qset",
|
| 2154 |
+
"set_name": "ArchiCADQuantities",
|
| 2155 |
+
"key": "Net Surface Area on the Outside Face",
|
| 2156 |
+
"file_count": 2,
|
| 2157 |
+
"projects": [
|
| 2158 |
+
"sixty5"
|
| 2159 |
+
],
|
| 2160 |
+
"auto_discovered": true
|
| 2161 |
+
},
|
| 2162 |
+
{
|
| 2163 |
+
"entity": "IfcWall",
|
| 2164 |
+
"source": "qset",
|
| 2165 |
+
"set_name": "ArchiCADQuantities",
|
| 2166 |
+
"key": "Conditional Surface Area on the Inside Face",
|
| 2167 |
+
"file_count": 2,
|
| 2168 |
+
"projects": [
|
| 2169 |
+
"sixty5"
|
| 2170 |
+
],
|
| 2171 |
+
"auto_discovered": true
|
| 2172 |
+
},
|
| 2173 |
+
{
|
| 2174 |
+
"entity": "IfcWall",
|
| 2175 |
+
"source": "qset",
|
| 2176 |
+
"set_name": "ArchiCADQuantities",
|
| 2177 |
+
"key": "Conditional Surface Area on the Outside Face",
|
| 2178 |
+
"file_count": 2,
|
| 2179 |
+
"projects": [
|
| 2180 |
+
"sixty5"
|
| 2181 |
+
],
|
| 2182 |
+
"auto_discovered": true
|
| 2183 |
+
},
|
| 2184 |
+
{
|
| 2185 |
+
"entity": "IfcWall",
|
| 2186 |
+
"source": "qset",
|
| 2187 |
+
"set_name": "ArchiCADQuantities",
|
| 2188 |
+
"key": "Gross Surface Area of the Wall on the Inside Face",
|
| 2189 |
+
"file_count": 2,
|
| 2190 |
+
"projects": [
|
| 2191 |
+
"sixty5"
|
| 2192 |
+
],
|
| 2193 |
+
"auto_discovered": true
|
| 2194 |
+
},
|
| 2195 |
+
{
|
| 2196 |
+
"entity": "IfcWall",
|
| 2197 |
+
"source": "qset",
|
| 2198 |
+
"set_name": "ArchiCADQuantities",
|
| 2199 |
+
"key": "Gross Surface Area of the Wall on the Outside Face",
|
| 2200 |
+
"file_count": 2,
|
| 2201 |
+
"projects": [
|
| 2202 |
+
"sixty5"
|
| 2203 |
+
],
|
| 2204 |
+
"auto_discovered": true
|
| 2205 |
+
},
|
| 2206 |
+
{
|
| 2207 |
+
"entity": "IfcWall",
|
| 2208 |
+
"source": "qset",
|
| 2209 |
+
"set_name": "ArchiCADQuantities",
|
| 2210 |
+
"key": "Area of the Wall",
|
| 2211 |
+
"file_count": 2,
|
| 2212 |
+
"projects": [
|
| 2213 |
+
"sixty5"
|
| 2214 |
+
],
|
| 2215 |
+
"auto_discovered": true
|
| 2216 |
+
},
|
| 2217 |
+
{
|
| 2218 |
+
"entity": "IfcWall",
|
| 2219 |
+
"source": "qset",
|
| 2220 |
+
"set_name": "Qto_WallBaseQuantities",
|
| 2221 |
+
"key": "GrossFootprintArea",
|
| 2222 |
+
"file_count": 3,
|
| 2223 |
+
"projects": [
|
| 2224 |
+
"digital_hub",
|
| 2225 |
+
"fantasy_office_building_1",
|
| 2226 |
+
"fantasy_office_building_2"
|
| 2227 |
+
],
|
| 2228 |
+
"auto_discovered": true
|
| 2229 |
+
},
|
| 2230 |
+
{
|
| 2231 |
+
"entity": "IfcWall",
|
| 2232 |
+
"source": "qset",
|
| 2233 |
+
"set_name": "Qto_WallBaseQuantities",
|
| 2234 |
+
"key": "GrossSideArea",
|
| 2235 |
+
"file_count": 3,
|
| 2236 |
+
"projects": [
|
| 2237 |
+
"digital_hub",
|
| 2238 |
+
"fantasy_office_building_1",
|
| 2239 |
+
"fantasy_office_building_2"
|
| 2240 |
+
],
|
| 2241 |
+
"auto_discovered": true
|
| 2242 |
+
},
|
| 2243 |
+
{
|
| 2244 |
+
"entity": "IfcWall",
|
| 2245 |
+
"source": "qset",
|
| 2246 |
+
"set_name": "Qto_WallBaseQuantities",
|
| 2247 |
+
"key": "NetSideArea",
|
| 2248 |
+
"file_count": 3,
|
| 2249 |
+
"projects": [
|
| 2250 |
+
"digital_hub",
|
| 2251 |
+
"fantasy_office_building_1",
|
| 2252 |
+
"fantasy_office_building_2"
|
| 2253 |
+
],
|
| 2254 |
+
"auto_discovered": true
|
| 2255 |
+
},
|
| 2256 |
+
{
|
| 2257 |
+
"entity": "IfcWall",
|
| 2258 |
+
"source": "qset",
|
| 2259 |
+
"set_name": "(unnamed)",
|
| 2260 |
+
"key": "NetFootprintArea",
|
| 2261 |
+
"file_count": 1,
|
| 2262 |
+
"projects": [
|
| 2263 |
+
"hitos"
|
| 2264 |
+
],
|
| 2265 |
+
"auto_discovered": true
|
| 2266 |
+
},
|
| 2267 |
+
{
|
| 2268 |
+
"entity": "IfcWall",
|
| 2269 |
+
"source": "qset",
|
| 2270 |
+
"set_name": "(unnamed)",
|
| 2271 |
+
"key": "NetSideAreaLeft",
|
| 2272 |
+
"file_count": 1,
|
| 2273 |
+
"projects": [
|
| 2274 |
+
"hitos"
|
| 2275 |
+
],
|
| 2276 |
+
"auto_discovered": true
|
| 2277 |
+
},
|
| 2278 |
+
{
|
| 2279 |
+
"entity": "IfcWall",
|
| 2280 |
+
"source": "qset",
|
| 2281 |
+
"set_name": "(unnamed)",
|
| 2282 |
+
"key": "NetSideAreaRight",
|
| 2283 |
+
"file_count": 1,
|
| 2284 |
+
"projects": [
|
| 2285 |
+
"hitos"
|
| 2286 |
+
],
|
| 2287 |
+
"auto_discovered": true
|
| 2288 |
+
},
|
| 2289 |
+
{
|
| 2290 |
+
"entity": "IfcWallStandardCase",
|
| 2291 |
+
"source": "qset",
|
| 2292 |
+
"set_name": "BaseQuantities",
|
| 2293 |
+
"key": "GrossFootprintArea",
|
| 2294 |
+
"file_count": 5,
|
| 2295 |
+
"projects": [
|
| 2296 |
+
"ac20",
|
| 2297 |
+
"fzk_house",
|
| 2298 |
+
"sixty5",
|
| 2299 |
+
"smiley_west"
|
| 2300 |
+
],
|
| 2301 |
+
"auto_discovered": true
|
| 2302 |
+
},
|
| 2303 |
+
{
|
| 2304 |
+
"entity": "IfcWallStandardCase",
|
| 2305 |
+
"source": "qset",
|
| 2306 |
+
"set_name": "BaseQuantities",
|
| 2307 |
+
"key": "NetFootprintArea",
|
| 2308 |
+
"file_count": 4,
|
| 2309 |
+
"projects": [
|
| 2310 |
+
"ac20",
|
| 2311 |
+
"fzk_house",
|
| 2312 |
+
"sixty5",
|
| 2313 |
+
"smiley_west"
|
| 2314 |
+
],
|
| 2315 |
+
"auto_discovered": true
|
| 2316 |
+
},
|
| 2317 |
+
{
|
| 2318 |
+
"entity": "IfcWallStandardCase",
|
| 2319 |
+
"source": "qset",
|
| 2320 |
+
"set_name": "BaseQuantities",
|
| 2321 |
+
"key": "GrossSideArea",
|
| 2322 |
+
"file_count": 5,
|
| 2323 |
+
"projects": [
|
| 2324 |
+
"ac20",
|
| 2325 |
+
"fzk_house",
|
| 2326 |
+
"sixty5",
|
| 2327 |
+
"smiley_west"
|
| 2328 |
+
],
|
| 2329 |
+
"auto_discovered": true
|
| 2330 |
+
},
|
| 2331 |
+
{
|
| 2332 |
+
"entity": "IfcWallStandardCase",
|
| 2333 |
+
"source": "qset",
|
| 2334 |
+
"set_name": "BaseQuantities",
|
| 2335 |
+
"key": "NetSideArea",
|
| 2336 |
+
"file_count": 5,
|
| 2337 |
+
"projects": [
|
| 2338 |
+
"ac20",
|
| 2339 |
+
"fzk_house",
|
| 2340 |
+
"sixty5",
|
| 2341 |
+
"smiley_west"
|
| 2342 |
+
],
|
| 2343 |
+
"auto_discovered": true
|
| 2344 |
+
},
|
| 2345 |
+
{
|
| 2346 |
+
"entity": "IfcWallStandardCase",
|
| 2347 |
+
"source": "qset",
|
| 2348 |
+
"set_name": "ArchiCADQuantities",
|
| 2349 |
+
"key": "OberflΓ€chenbereich",
|
| 2350 |
+
"file_count": 2,
|
| 2351 |
+
"projects": [
|
| 2352 |
+
"ac20",
|
| 2353 |
+
"fzk_house"
|
| 2354 |
+
],
|
| 2355 |
+
"auto_discovered": true
|
| 2356 |
+
},
|
| 2357 |
+
{
|
| 2358 |
+
"entity": "IfcWallStandardCase",
|
| 2359 |
+
"source": "qset",
|
| 2360 |
+
"set_name": "ArchiCADQuantities",
|
| 2361 |
+
"key": "FlΓ€che",
|
| 2362 |
+
"file_count": 2,
|
| 2363 |
+
"projects": [
|
| 2364 |
+
"ac20",
|
| 2365 |
+
"fzk_house"
|
| 2366 |
+
],
|
| 2367 |
+
"auto_discovered": true
|
| 2368 |
+
},
|
| 2369 |
+
{
|
| 2370 |
+
"entity": "IfcWallStandardCase",
|
| 2371 |
+
"source": "qset",
|
| 2372 |
+
"set_name": "ArchiCADQuantities",
|
| 2373 |
+
"key": "Netto-OberflΓ€chenbereich an der AuΓenseite",
|
| 2374 |
+
"file_count": 2,
|
| 2375 |
+
"projects": [
|
| 2376 |
+
"ac20",
|
| 2377 |
+
"fzk_house"
|
| 2378 |
+
],
|
| 2379 |
+
"auto_discovered": true
|
| 2380 |
+
},
|
| 2381 |
+
{
|
| 2382 |
+
"entity": "IfcWallStandardCase",
|
| 2383 |
+
"source": "qset",
|
| 2384 |
+
"set_name": "ArchiCADQuantities",
|
| 2385 |
+
"key": "Netto-OberflΓ€chenbereich an der Innenseite",
|
| 2386 |
+
"file_count": 2,
|
| 2387 |
+
"projects": [
|
| 2388 |
+
"ac20",
|
| 2389 |
+
"fzk_house"
|
| 2390 |
+
],
|
| 2391 |
+
"auto_discovered": true
|
| 2392 |
+
},
|
| 2393 |
+
{
|
| 2394 |
+
"entity": "IfcWallStandardCase",
|
| 2395 |
+
"source": "qset",
|
| 2396 |
+
"set_name": "ArchiCADQuantities",
|
| 2397 |
+
"key": "Netto-OberflΓ€chenbereich an den Kanten",
|
| 2398 |
+
"file_count": 2,
|
| 2399 |
+
"projects": [
|
| 2400 |
+
"ac20",
|
| 2401 |
+
"fzk_house"
|
| 2402 |
+
],
|
| 2403 |
+
"auto_discovered": true
|
| 2404 |
+
},
|
| 2405 |
+
{
|
| 2406 |
+
"entity": "IfcWallStandardCase",
|
| 2407 |
+
"source": "qset",
|
| 2408 |
+
"set_name": "ArchiCADQuantities",
|
| 2409 |
+
"key": "FlΓ€che der Wand",
|
| 2410 |
+
"file_count": 2,
|
| 2411 |
+
"projects": [
|
| 2412 |
+
"ac20",
|
| 2413 |
+
"fzk_house"
|
| 2414 |
+
],
|
| 2415 |
+
"auto_discovered": true
|
| 2416 |
+
},
|
| 2417 |
+
{
|
| 2418 |
+
"entity": "IfcWallStandardCase",
|
| 2419 |
+
"source": "qset",
|
| 2420 |
+
"set_name": "ArchiCADQuantities",
|
| 2421 |
+
"key": "Brutto-WandoberflΓ€chenbereich an der Innenseite",
|
| 2422 |
+
"file_count": 2,
|
| 2423 |
+
"projects": [
|
| 2424 |
+
"ac20",
|
| 2425 |
+
"fzk_house"
|
| 2426 |
+
],
|
| 2427 |
+
"auto_discovered": true
|
| 2428 |
+
},
|
| 2429 |
+
{
|
| 2430 |
+
"entity": "IfcWallStandardCase",
|
| 2431 |
+
"source": "qset",
|
| 2432 |
+
"set_name": "ArchiCADQuantities",
|
| 2433 |
+
"key": "Brutto-WandoberflΓ€chenbereich an der AuΓenseite",
|
| 2434 |
+
"file_count": 2,
|
| 2435 |
+
"projects": [
|
| 2436 |
+
"ac20",
|
| 2437 |
+
"fzk_house"
|
| 2438 |
+
],
|
| 2439 |
+
"auto_discovered": true
|
| 2440 |
+
},
|
| 2441 |
+
{
|
| 2442 |
+
"entity": "IfcWallStandardCase",
|
| 2443 |
+
"source": "qset",
|
| 2444 |
+
"set_name": "ArchiCADQuantities",
|
| 2445 |
+
"key": "Analytische OberflΓ€che der Γffnungen an der Innenseite",
|
| 2446 |
+
"file_count": 2,
|
| 2447 |
+
"projects": [
|
| 2448 |
+
"ac20",
|
| 2449 |
+
"fzk_house"
|
| 2450 |
+
],
|
| 2451 |
+
"auto_discovered": true
|
| 2452 |
+
},
|
| 2453 |
+
{
|
| 2454 |
+
"entity": "IfcWallStandardCase",
|
| 2455 |
+
"source": "qset",
|
| 2456 |
+
"set_name": "ArchiCADQuantities",
|
| 2457 |
+
"key": "Analytische OberflΓ€che der Γffnungen an der AuΓenseite",
|
| 2458 |
+
"file_count": 2,
|
| 2459 |
+
"projects": [
|
| 2460 |
+
"ac20",
|
| 2461 |
+
"fzk_house"
|
| 2462 |
+
],
|
| 2463 |
+
"auto_discovered": true
|
| 2464 |
+
},
|
| 2465 |
+
{
|
| 2466 |
+
"entity": "IfcWallStandardCase",
|
| 2467 |
+
"source": "qset",
|
| 2468 |
+
"set_name": "ArchiCADQuantities",
|
| 2469 |
+
"key": "Konditionaler OberflΓ€chenbereich an der AuΓenseite",
|
| 2470 |
+
"file_count": 2,
|
| 2471 |
+
"projects": [
|
| 2472 |
+
"ac20",
|
| 2473 |
+
"fzk_house"
|
| 2474 |
+
],
|
| 2475 |
+
"auto_discovered": true
|
| 2476 |
+
},
|
| 2477 |
+
{
|
| 2478 |
+
"entity": "IfcWallStandardCase",
|
| 2479 |
+
"source": "qset",
|
| 2480 |
+
"set_name": "ArchiCADQuantities",
|
| 2481 |
+
"key": "Konditionaler OberflΓ€chenbereich an der Innenseite",
|
| 2482 |
+
"file_count": 2,
|
| 2483 |
+
"projects": [
|
| 2484 |
+
"ac20",
|
| 2485 |
+
"fzk_house"
|
| 2486 |
+
],
|
| 2487 |
+
"auto_discovered": true
|
| 2488 |
+
},
|
| 2489 |
+
{
|
| 2490 |
+
"entity": "IfcWallStandardCase",
|
| 2491 |
+
"source": "qset",
|
| 2492 |
+
"set_name": "ArchiCADQuantities",
|
| 2493 |
+
"key": "OberflΓ€chenbereich der Fenster in der Wand",
|
| 2494 |
+
"file_count": 2,
|
| 2495 |
+
"projects": [
|
| 2496 |
+
"ac20",
|
| 2497 |
+
"fzk_house"
|
| 2498 |
+
],
|
| 2499 |
+
"auto_discovered": true
|
| 2500 |
+
},
|
| 2501 |
+
{
|
| 2502 |
+
"entity": "IfcWallStandardCase",
|
| 2503 |
+
"source": "qset",
|
| 2504 |
+
"set_name": "ArchiCADQuantities",
|
| 2505 |
+
"key": "OberflΓ€chenbereich der TΓΌren in der Wand",
|
| 2506 |
+
"file_count": 2,
|
| 2507 |
+
"projects": [
|
| 2508 |
+
"ac20",
|
| 2509 |
+
"fzk_house"
|
| 2510 |
+
],
|
| 2511 |
+
"auto_discovered": true
|
| 2512 |
+
},
|
| 2513 |
+
{
|
| 2514 |
+
"entity": "IfcWallStandardCase",
|
| 2515 |
+
"source": "qset",
|
| 2516 |
+
"set_name": "ArchiCADQuantities",
|
| 2517 |
+
"key": "OberflΓ€chenbereich der leeren Γffnungen in der Wand",
|
| 2518 |
+
"file_count": 2,
|
| 2519 |
+
"projects": [
|
| 2520 |
+
"ac20",
|
| 2521 |
+
"fzk_house"
|
| 2522 |
+
],
|
| 2523 |
+
"auto_discovered": true
|
| 2524 |
+
},
|
| 2525 |
+
{
|
| 2526 |
+
"entity": "IfcWallStandardCase",
|
| 2527 |
+
"source": "qset",
|
| 2528 |
+
"set_name": "ArchiCADQuantities",
|
| 2529 |
+
"key": "Area",
|
| 2530 |
+
"file_count": 1,
|
| 2531 |
+
"projects": [
|
| 2532 |
+
"sixty5"
|
| 2533 |
+
],
|
| 2534 |
+
"auto_discovered": true
|
| 2535 |
+
},
|
| 2536 |
+
{
|
| 2537 |
+
"entity": "IfcWallStandardCase",
|
| 2538 |
+
"source": "qset",
|
| 2539 |
+
"set_name": "ArchiCADQuantities",
|
| 2540 |
+
"key": "Surface Area",
|
| 2541 |
+
"file_count": 1,
|
| 2542 |
+
"projects": [
|
| 2543 |
+
"sixty5"
|
| 2544 |
+
],
|
| 2545 |
+
"auto_discovered": true
|
| 2546 |
+
},
|
| 2547 |
+
{
|
| 2548 |
+
"entity": "IfcWallStandardCase",
|
| 2549 |
+
"source": "qset",
|
| 2550 |
+
"set_name": "ArchiCADQuantities",
|
| 2551 |
+
"key": "Area of the Doors",
|
| 2552 |
+
"file_count": 1,
|
| 2553 |
+
"projects": [
|
| 2554 |
+
"sixty5"
|
| 2555 |
+
],
|
| 2556 |
+
"auto_discovered": true
|
| 2557 |
+
},
|
| 2558 |
+
{
|
| 2559 |
+
"entity": "IfcWallStandardCase",
|
| 2560 |
+
"source": "qset",
|
| 2561 |
+
"set_name": "ArchiCADQuantities",
|
| 2562 |
+
"key": "Area of the Windows",
|
| 2563 |
+
"file_count": 1,
|
| 2564 |
+
"projects": [
|
| 2565 |
+
"sixty5"
|
| 2566 |
+
],
|
| 2567 |
+
"auto_discovered": true
|
| 2568 |
+
},
|
| 2569 |
+
{
|
| 2570 |
+
"entity": "IfcWallStandardCase",
|
| 2571 |
+
"source": "qset",
|
| 2572 |
+
"set_name": "ArchiCADQuantities",
|
| 2573 |
+
"key": "Analytic Surface Area of Openings on the Inside Face",
|
| 2574 |
+
"file_count": 1,
|
| 2575 |
+
"projects": [
|
| 2576 |
+
"sixty5"
|
| 2577 |
+
],
|
| 2578 |
+
"auto_discovered": true
|
| 2579 |
+
},
|
| 2580 |
+
{
|
| 2581 |
+
"entity": "IfcWallStandardCase",
|
| 2582 |
+
"source": "qset",
|
| 2583 |
+
"set_name": "ArchiCADQuantities",
|
| 2584 |
+
"key": "Analytic Surface Area of Openings on the Outside Face",
|
| 2585 |
+
"file_count": 1,
|
| 2586 |
+
"projects": [
|
| 2587 |
+
"sixty5"
|
| 2588 |
+
],
|
| 2589 |
+
"auto_discovered": true
|
| 2590 |
+
},
|
| 2591 |
+
{
|
| 2592 |
+
"entity": "IfcWallStandardCase",
|
| 2593 |
+
"source": "qset",
|
| 2594 |
+
"set_name": "ArchiCADQuantities",
|
| 2595 |
+
"key": "Net Surface Area of the Edges",
|
| 2596 |
+
"file_count": 1,
|
| 2597 |
+
"projects": [
|
| 2598 |
+
"sixty5"
|
| 2599 |
+
],
|
| 2600 |
+
"auto_discovered": true
|
| 2601 |
+
},
|
| 2602 |
+
{
|
| 2603 |
+
"entity": "IfcWallStandardCase",
|
| 2604 |
+
"source": "qset",
|
| 2605 |
+
"set_name": "ArchiCADQuantities",
|
| 2606 |
+
"key": "Net Surface Area on the Inside Face",
|
| 2607 |
+
"file_count": 1,
|
| 2608 |
+
"projects": [
|
| 2609 |
+
"sixty5"
|
| 2610 |
+
],
|
| 2611 |
+
"auto_discovered": true
|
| 2612 |
+
},
|
| 2613 |
+
{
|
| 2614 |
+
"entity": "IfcWallStandardCase",
|
| 2615 |
+
"source": "qset",
|
| 2616 |
+
"set_name": "ArchiCADQuantities",
|
| 2617 |
+
"key": "Net Surface Area on the Outside Face",
|
| 2618 |
+
"file_count": 1,
|
| 2619 |
+
"projects": [
|
| 2620 |
+
"sixty5"
|
| 2621 |
+
],
|
| 2622 |
+
"auto_discovered": true
|
| 2623 |
+
},
|
| 2624 |
+
{
|
| 2625 |
+
"entity": "IfcWallStandardCase",
|
| 2626 |
+
"source": "qset",
|
| 2627 |
+
"set_name": "ArchiCADQuantities",
|
| 2628 |
+
"key": "Conditional Surface Area on the Inside Face",
|
| 2629 |
+
"file_count": 1,
|
| 2630 |
+
"projects": [
|
| 2631 |
+
"sixty5"
|
| 2632 |
+
],
|
| 2633 |
+
"auto_discovered": true
|
| 2634 |
+
},
|
| 2635 |
+
{
|
| 2636 |
+
"entity": "IfcWallStandardCase",
|
| 2637 |
+
"source": "qset",
|
| 2638 |
+
"set_name": "ArchiCADQuantities",
|
| 2639 |
+
"key": "Conditional Surface Area on the Outside Face",
|
| 2640 |
+
"file_count": 1,
|
| 2641 |
+
"projects": [
|
| 2642 |
+
"sixty5"
|
| 2643 |
+
],
|
| 2644 |
+
"auto_discovered": true
|
| 2645 |
+
},
|
| 2646 |
+
{
|
| 2647 |
+
"entity": "IfcWallStandardCase",
|
| 2648 |
+
"source": "qset",
|
| 2649 |
+
"set_name": "ArchiCADQuantities",
|
| 2650 |
+
"key": "Gross Surface Area of the Wall on the Inside Face",
|
| 2651 |
+
"file_count": 1,
|
| 2652 |
+
"projects": [
|
| 2653 |
+
"sixty5"
|
| 2654 |
+
],
|
| 2655 |
+
"auto_discovered": true
|
| 2656 |
+
},
|
| 2657 |
+
{
|
| 2658 |
+
"entity": "IfcWallStandardCase",
|
| 2659 |
+
"source": "qset",
|
| 2660 |
+
"set_name": "ArchiCADQuantities",
|
| 2661 |
+
"key": "Gross Surface Area of the Wall on the Outside Face",
|
| 2662 |
+
"file_count": 1,
|
| 2663 |
+
"projects": [
|
| 2664 |
+
"sixty5"
|
| 2665 |
+
],
|
| 2666 |
+
"auto_discovered": true
|
| 2667 |
+
},
|
| 2668 |
+
{
|
| 2669 |
+
"entity": "IfcWallStandardCase",
|
| 2670 |
+
"source": "qset",
|
| 2671 |
+
"set_name": "ArchiCADQuantities",
|
| 2672 |
+
"key": "Area of the Wall",
|
| 2673 |
+
"file_count": 1,
|
| 2674 |
+
"projects": [
|
| 2675 |
+
"sixty5"
|
| 2676 |
+
],
|
| 2677 |
+
"auto_discovered": true
|
| 2678 |
+
},
|
| 2679 |
+
{
|
| 2680 |
+
"entity": "IfcWallStandardCase",
|
| 2681 |
+
"source": "qset",
|
| 2682 |
+
"set_name": "ArchiCADQuantities",
|
| 2683 |
+
"key": "Surface Area of Empty Openings in the Wall",
|
| 2684 |
+
"file_count": 1,
|
| 2685 |
+
"projects": [
|
| 2686 |
+
"sixty5"
|
| 2687 |
+
],
|
| 2688 |
+
"auto_discovered": true
|
| 2689 |
+
},
|
| 2690 |
+
{
|
| 2691 |
+
"entity": "IfcWallStandardCase",
|
| 2692 |
+
"source": "qset",
|
| 2693 |
+
"set_name": "(unnamed)",
|
| 2694 |
+
"key": "NetFootprintArea",
|
| 2695 |
+
"file_count": 1,
|
| 2696 |
+
"projects": [
|
| 2697 |
+
"hitos"
|
| 2698 |
+
],
|
| 2699 |
+
"auto_discovered": true
|
| 2700 |
+
},
|
| 2701 |
+
{
|
| 2702 |
+
"entity": "IfcWallStandardCase",
|
| 2703 |
+
"source": "qset",
|
| 2704 |
+
"set_name": "(unnamed)",
|
| 2705 |
+
"key": "NetSideAreaLeft",
|
| 2706 |
+
"file_count": 1,
|
| 2707 |
+
"projects": [
|
| 2708 |
+
"hitos"
|
| 2709 |
+
],
|
| 2710 |
+
"auto_discovered": true
|
| 2711 |
+
},
|
| 2712 |
+
{
|
| 2713 |
+
"entity": "IfcWallStandardCase",
|
| 2714 |
+
"source": "qset",
|
| 2715 |
+
"set_name": "(unnamed)",
|
| 2716 |
+
"key": "NetSideAreaRight",
|
| 2717 |
+
"file_count": 1,
|
| 2718 |
+
"projects": [
|
| 2719 |
+
"hitos"
|
| 2720 |
+
],
|
| 2721 |
+
"auto_discovered": true
|
| 2722 |
+
},
|
| 2723 |
+
{
|
| 2724 |
+
"entity": "IfcCovering",
|
| 2725 |
+
"source": "qset",
|
| 2726 |
+
"set_name": "Qto_CoveringBaseQuantities",
|
| 2727 |
+
"key": "GrossArea",
|
| 2728 |
+
"file_count": 1,
|
| 2729 |
+
"projects": [
|
| 2730 |
+
"digital_hub"
|
| 2731 |
+
],
|
| 2732 |
+
"auto_discovered": true
|
| 2733 |
+
},
|
| 2734 |
+
{
|
| 2735 |
+
"entity": "IfcCovering",
|
| 2736 |
+
"source": "qset",
|
| 2737 |
+
"set_name": "Qto_CoveringBaseQuantities",
|
| 2738 |
+
"key": "NetArea",
|
| 2739 |
+
"file_count": 1,
|
| 2740 |
+
"projects": [
|
| 2741 |
+
"digital_hub"
|
| 2742 |
+
],
|
| 2743 |
+
"auto_discovered": true
|
| 2744 |
+
},
|
| 2745 |
+
{
|
| 2746 |
+
"entity": "IfcCovering",
|
| 2747 |
+
"source": "qset",
|
| 2748 |
+
"set_name": "BaseQuantities",
|
| 2749 |
+
"key": "GrossArea",
|
| 2750 |
+
"file_count": 1,
|
| 2751 |
+
"projects": [
|
| 2752 |
+
"sixty5"
|
| 2753 |
+
],
|
| 2754 |
+
"auto_discovered": true
|
| 2755 |
+
},
|
| 2756 |
+
{
|
| 2757 |
+
"entity": "IfcCovering",
|
| 2758 |
+
"source": "qset",
|
| 2759 |
+
"set_name": "BaseQuantities",
|
| 2760 |
+
"key": "NetArea",
|
| 2761 |
+
"file_count": 1,
|
| 2762 |
+
"projects": [
|
| 2763 |
+
"sixty5"
|
| 2764 |
+
],
|
| 2765 |
+
"auto_discovered": true
|
| 2766 |
+
},
|
| 2767 |
+
{
|
| 2768 |
+
"entity": "IfcCovering",
|
| 2769 |
+
"source": "qset",
|
| 2770 |
+
"set_name": "ArchiCADQuantities",
|
| 2771 |
+
"key": "Area",
|
| 2772 |
+
"file_count": 1,
|
| 2773 |
+
"projects": [
|
| 2774 |
+
"sixty5"
|
| 2775 |
+
],
|
| 2776 |
+
"auto_discovered": true
|
| 2777 |
+
},
|
| 2778 |
+
{
|
| 2779 |
+
"entity": "IfcCovering",
|
| 2780 |
+
"source": "qset",
|
| 2781 |
+
"set_name": "ArchiCADQuantities",
|
| 2782 |
+
"key": "Surface Area",
|
| 2783 |
+
"file_count": 1,
|
| 2784 |
+
"projects": [
|
| 2785 |
+
"sixty5"
|
| 2786 |
+
],
|
| 2787 |
+
"auto_discovered": true
|
| 2788 |
+
},
|
| 2789 |
+
{
|
| 2790 |
+
"entity": "IfcCovering",
|
| 2791 |
+
"source": "qset",
|
| 2792 |
+
"set_name": "ArchiCADQuantities",
|
| 2793 |
+
"key": "Area of the Doors",
|
| 2794 |
+
"file_count": 1,
|
| 2795 |
+
"projects": [
|
| 2796 |
+
"sixty5"
|
| 2797 |
+
],
|
| 2798 |
+
"auto_discovered": true
|
| 2799 |
+
},
|
| 2800 |
+
{
|
| 2801 |
+
"entity": "IfcCovering",
|
| 2802 |
+
"source": "qset",
|
| 2803 |
+
"set_name": "ArchiCADQuantities",
|
| 2804 |
+
"key": "Area of the Windows",
|
| 2805 |
+
"file_count": 1,
|
| 2806 |
+
"projects": [
|
| 2807 |
+
"sixty5"
|
| 2808 |
+
],
|
| 2809 |
+
"auto_discovered": true
|
| 2810 |
+
},
|
| 2811 |
+
{
|
| 2812 |
+
"entity": "IfcCovering",
|
| 2813 |
+
"source": "qset",
|
| 2814 |
+
"set_name": "ArchiCADQuantities",
|
| 2815 |
+
"key": "Analytic Surface Area of Openings on the Inside Face",
|
| 2816 |
+
"file_count": 1,
|
| 2817 |
+
"projects": [
|
| 2818 |
+
"sixty5"
|
| 2819 |
+
],
|
| 2820 |
+
"auto_discovered": true
|
| 2821 |
+
},
|
| 2822 |
+
{
|
| 2823 |
+
"entity": "IfcCovering",
|
| 2824 |
+
"source": "qset",
|
| 2825 |
+
"set_name": "ArchiCADQuantities",
|
| 2826 |
+
"key": "Analytic Surface Area of Openings on the Outside Face",
|
| 2827 |
+
"file_count": 1,
|
| 2828 |
+
"projects": [
|
| 2829 |
+
"sixty5"
|
| 2830 |
+
],
|
| 2831 |
+
"auto_discovered": true
|
| 2832 |
+
},
|
| 2833 |
+
{
|
| 2834 |
+
"entity": "IfcCovering",
|
| 2835 |
+
"source": "qset",
|
| 2836 |
+
"set_name": "ArchiCADQuantities",
|
| 2837 |
+
"key": "Net Surface Area of the Edges",
|
| 2838 |
+
"file_count": 1,
|
| 2839 |
+
"projects": [
|
| 2840 |
+
"sixty5"
|
| 2841 |
+
],
|
| 2842 |
+
"auto_discovered": true
|
| 2843 |
+
},
|
| 2844 |
+
{
|
| 2845 |
+
"entity": "IfcCovering",
|
| 2846 |
+
"source": "qset",
|
| 2847 |
+
"set_name": "ArchiCADQuantities",
|
| 2848 |
+
"key": "Net Surface Area on the Inside Face",
|
| 2849 |
+
"file_count": 1,
|
| 2850 |
+
"projects": [
|
| 2851 |
+
"sixty5"
|
| 2852 |
+
],
|
| 2853 |
+
"auto_discovered": true
|
| 2854 |
+
},
|
| 2855 |
+
{
|
| 2856 |
+
"entity": "IfcCovering",
|
| 2857 |
+
"source": "qset",
|
| 2858 |
+
"set_name": "ArchiCADQuantities",
|
| 2859 |
+
"key": "Net Surface Area on the Outside Face",
|
| 2860 |
+
"file_count": 1,
|
| 2861 |
+
"projects": [
|
| 2862 |
+
"sixty5"
|
| 2863 |
+
],
|
| 2864 |
+
"auto_discovered": true
|
| 2865 |
+
},
|
| 2866 |
+
{
|
| 2867 |
+
"entity": "IfcCovering",
|
| 2868 |
+
"source": "qset",
|
| 2869 |
+
"set_name": "ArchiCADQuantities",
|
| 2870 |
+
"key": "Conditional Surface Area on the Inside Face",
|
| 2871 |
+
"file_count": 1,
|
| 2872 |
+
"projects": [
|
| 2873 |
+
"sixty5"
|
| 2874 |
+
],
|
| 2875 |
+
"auto_discovered": true
|
| 2876 |
+
},
|
| 2877 |
+
{
|
| 2878 |
+
"entity": "IfcCovering",
|
| 2879 |
+
"source": "qset",
|
| 2880 |
+
"set_name": "ArchiCADQuantities",
|
| 2881 |
+
"key": "Conditional Surface Area on the Outside Face",
|
| 2882 |
+
"file_count": 1,
|
| 2883 |
+
"projects": [
|
| 2884 |
+
"sixty5"
|
| 2885 |
+
],
|
| 2886 |
+
"auto_discovered": true
|
| 2887 |
+
},
|
| 2888 |
+
{
|
| 2889 |
+
"entity": "IfcCovering",
|
| 2890 |
+
"source": "qset",
|
| 2891 |
+
"set_name": "ArchiCADQuantities",
|
| 2892 |
+
"key": "Gross Surface Area of the Wall on the Inside Face",
|
| 2893 |
+
"file_count": 1,
|
| 2894 |
+
"projects": [
|
| 2895 |
+
"sixty5"
|
| 2896 |
+
],
|
| 2897 |
+
"auto_discovered": true
|
| 2898 |
+
},
|
| 2899 |
+
{
|
| 2900 |
+
"entity": "IfcCovering",
|
| 2901 |
+
"source": "qset",
|
| 2902 |
+
"set_name": "ArchiCADQuantities",
|
| 2903 |
+
"key": "Gross Surface Area of the Wall on the Outside Face",
|
| 2904 |
+
"file_count": 1,
|
| 2905 |
+
"projects": [
|
| 2906 |
+
"sixty5"
|
| 2907 |
+
],
|
| 2908 |
+
"auto_discovered": true
|
| 2909 |
+
},
|
| 2910 |
+
{
|
| 2911 |
+
"entity": "IfcCovering",
|
| 2912 |
+
"source": "qset",
|
| 2913 |
+
"set_name": "ArchiCADQuantities",
|
| 2914 |
+
"key": "Area of the Wall",
|
| 2915 |
+
"file_count": 1,
|
| 2916 |
+
"projects": [
|
| 2917 |
+
"sixty5"
|
| 2918 |
+
],
|
| 2919 |
+
"auto_discovered": true
|
| 2920 |
+
},
|
| 2921 |
+
{
|
| 2922 |
+
"entity": "IfcCovering",
|
| 2923 |
+
"source": "qset",
|
| 2924 |
+
"set_name": "ArchiCADQuantities",
|
| 2925 |
+
"key": "Surface Area of Empty Openings in the Wall",
|
| 2926 |
+
"file_count": 1,
|
| 2927 |
+
"projects": [
|
| 2928 |
+
"sixty5"
|
| 2929 |
+
],
|
| 2930 |
+
"auto_discovered": true
|
| 2931 |
+
},
|
| 2932 |
+
{
|
| 2933 |
+
"entity": "IfcCovering",
|
| 2934 |
+
"source": "qset",
|
| 2935 |
+
"set_name": "ArchiCADQuantities",
|
| 2936 |
+
"key": "Bottom Surface Area",
|
| 2937 |
+
"file_count": 1,
|
| 2938 |
+
"projects": [
|
| 2939 |
+
"sixty5"
|
| 2940 |
+
],
|
| 2941 |
+
"auto_discovered": true
|
| 2942 |
+
},
|
| 2943 |
+
{
|
| 2944 |
+
"entity": "IfcCovering",
|
| 2945 |
+
"source": "qset",
|
| 2946 |
+
"set_name": "ArchiCADQuantities",
|
| 2947 |
+
"key": "Conditional Surface Area of the Bottom",
|
| 2948 |
+
"file_count": 1,
|
| 2949 |
+
"projects": [
|
| 2950 |
+
"sixty5"
|
| 2951 |
+
],
|
| 2952 |
+
"auto_discovered": true
|
| 2953 |
+
},
|
| 2954 |
+
{
|
| 2955 |
+
"entity": "IfcCovering",
|
| 2956 |
+
"source": "qset",
|
| 2957 |
+
"set_name": "ArchiCADQuantities",
|
| 2958 |
+
"key": "Conditional Surface Area of the Top",
|
| 2959 |
+
"file_count": 1,
|
| 2960 |
+
"projects": [
|
| 2961 |
+
"sixty5"
|
| 2962 |
+
],
|
| 2963 |
+
"auto_discovered": true
|
| 2964 |
+
},
|
| 2965 |
+
{
|
| 2966 |
+
"entity": "IfcCovering",
|
| 2967 |
+
"source": "qset",
|
| 2968 |
+
"set_name": "ArchiCADQuantities",
|
| 2969 |
+
"key": "Edge Surface Area",
|
| 2970 |
+
"file_count": 1,
|
| 2971 |
+
"projects": [
|
| 2972 |
+
"sixty5"
|
| 2973 |
+
],
|
| 2974 |
+
"auto_discovered": true
|
| 2975 |
+
},
|
| 2976 |
+
{
|
| 2977 |
+
"entity": "IfcCovering",
|
| 2978 |
+
"source": "qset",
|
| 2979 |
+
"set_name": "ArchiCADQuantities",
|
| 2980 |
+
"key": "Gross Surface Area of the Slab Bottom",
|
| 2981 |
+
"file_count": 1,
|
| 2982 |
+
"projects": [
|
| 2983 |
+
"sixty5"
|
| 2984 |
+
],
|
| 2985 |
+
"auto_discovered": true
|
| 2986 |
+
},
|
| 2987 |
+
{
|
| 2988 |
+
"entity": "IfcCovering",
|
| 2989 |
+
"source": "qset",
|
| 2990 |
+
"set_name": "ArchiCADQuantities",
|
| 2991 |
+
"key": "Gross Surface Area of the Slab Bottom with Holes",
|
| 2992 |
+
"file_count": 1,
|
| 2993 |
+
"projects": [
|
| 2994 |
+
"sixty5"
|
| 2995 |
+
],
|
| 2996 |
+
"auto_discovered": true
|
| 2997 |
+
},
|
| 2998 |
+
{
|
| 2999 |
+
"entity": "IfcCovering",
|
| 3000 |
+
"source": "qset",
|
| 3001 |
+
"set_name": "ArchiCADQuantities",
|
| 3002 |
+
"key": "Gross Surface Area of the Slab Edges",
|
| 3003 |
+
"file_count": 1,
|
| 3004 |
+
"projects": [
|
| 3005 |
+
"sixty5"
|
| 3006 |
+
],
|
| 3007 |
+
"auto_discovered": true
|
| 3008 |
+
},
|
| 3009 |
+
{
|
| 3010 |
+
"entity": "IfcCovering",
|
| 3011 |
+
"source": "qset",
|
| 3012 |
+
"set_name": "ArchiCADQuantities",
|
| 3013 |
+
"key": "Gross Surface Area of the Slab Edges with Holes",
|
| 3014 |
+
"file_count": 1,
|
| 3015 |
+
"projects": [
|
| 3016 |
+
"sixty5"
|
| 3017 |
+
],
|
| 3018 |
+
"auto_discovered": true
|
| 3019 |
+
},
|
| 3020 |
+
{
|
| 3021 |
+
"entity": "IfcCovering",
|
| 3022 |
+
"source": "qset",
|
| 3023 |
+
"set_name": "ArchiCADQuantities",
|
| 3024 |
+
"key": "Gross Surface Area of the Slab Top",
|
| 3025 |
+
"file_count": 1,
|
| 3026 |
+
"projects": [
|
| 3027 |
+
"sixty5"
|
| 3028 |
+
],
|
| 3029 |
+
"auto_discovered": true
|
| 3030 |
+
},
|
| 3031 |
+
{
|
| 3032 |
+
"entity": "IfcCovering",
|
| 3033 |
+
"source": "qset",
|
| 3034 |
+
"set_name": "ArchiCADQuantities",
|
| 3035 |
+
"key": "Gross Surface Area of the Slab Top with Holes",
|
| 3036 |
+
"file_count": 1,
|
| 3037 |
+
"projects": [
|
| 3038 |
+
"sixty5"
|
| 3039 |
+
],
|
| 3040 |
+
"auto_discovered": true
|
| 3041 |
+
},
|
| 3042 |
+
{
|
| 3043 |
+
"entity": "IfcCovering",
|
| 3044 |
+
"source": "qset",
|
| 3045 |
+
"set_name": "ArchiCADQuantities",
|
| 3046 |
+
"key": "Holes Surface Area",
|
| 3047 |
+
"file_count": 1,
|
| 3048 |
+
"projects": [
|
| 3049 |
+
"sixty5"
|
| 3050 |
+
],
|
| 3051 |
+
"auto_discovered": true
|
| 3052 |
+
},
|
| 3053 |
+
{
|
| 3054 |
+
"entity": "IfcCovering",
|
| 3055 |
+
"source": "qset",
|
| 3056 |
+
"set_name": "ArchiCADQuantities",
|
| 3057 |
+
"key": "Top Surface Area",
|
| 3058 |
+
"file_count": 1,
|
| 3059 |
+
"projects": [
|
| 3060 |
+
"sixty5"
|
| 3061 |
+
],
|
| 3062 |
+
"auto_discovered": true
|
| 3063 |
+
},
|
| 3064 |
+
{
|
| 3065 |
+
"entity": "IfcCovering",
|
| 3066 |
+
"source": "qset",
|
| 3067 |
+
"set_name": "ArchiCADQuantities",
|
| 3068 |
+
"key": "Area of the Column",
|
| 3069 |
+
"file_count": 1,
|
| 3070 |
+
"projects": [
|
| 3071 |
+
"sixty5"
|
| 3072 |
+
],
|
| 3073 |
+
"auto_discovered": true
|
| 3074 |
+
},
|
| 3075 |
+
{
|
| 3076 |
+
"entity": "IfcCovering",
|
| 3077 |
+
"source": "qset",
|
| 3078 |
+
"set_name": "ArchiCADQuantities",
|
| 3079 |
+
"key": "Gross Surface Area of the Core (without Top/Bottom)",
|
| 3080 |
+
"file_count": 1,
|
| 3081 |
+
"projects": [
|
| 3082 |
+
"sixty5"
|
| 3083 |
+
],
|
| 3084 |
+
"auto_discovered": true
|
| 3085 |
+
},
|
| 3086 |
+
{
|
| 3087 |
+
"entity": "IfcCovering",
|
| 3088 |
+
"source": "qset",
|
| 3089 |
+
"set_name": "ArchiCADQuantities",
|
| 3090 |
+
"key": "Gross Surface Area of the Core Top (or Bottom)",
|
| 3091 |
+
"file_count": 1,
|
| 3092 |
+
"projects": [
|
| 3093 |
+
"sixty5"
|
| 3094 |
+
],
|
| 3095 |
+
"auto_discovered": true
|
| 3096 |
+
},
|
| 3097 |
+
{
|
| 3098 |
+
"entity": "IfcCovering",
|
| 3099 |
+
"source": "qset",
|
| 3100 |
+
"set_name": "ArchiCADQuantities",
|
| 3101 |
+
"key": "Gross Surface Area of the Veneer (without Top/Bottom)",
|
| 3102 |
+
"file_count": 1,
|
| 3103 |
+
"projects": [
|
| 3104 |
+
"sixty5"
|
| 3105 |
+
],
|
| 3106 |
+
"auto_discovered": true
|
| 3107 |
+
},
|
| 3108 |
+
{
|
| 3109 |
+
"entity": "IfcCovering",
|
| 3110 |
+
"source": "qset",
|
| 3111 |
+
"set_name": "ArchiCADQuantities",
|
| 3112 |
+
"key": "Gross Surface Area of the Veneer Top (or Bottom)",
|
| 3113 |
+
"file_count": 1,
|
| 3114 |
+
"projects": [
|
| 3115 |
+
"sixty5"
|
| 3116 |
+
],
|
| 3117 |
+
"auto_discovered": true
|
| 3118 |
+
},
|
| 3119 |
+
{
|
| 3120 |
+
"entity": "IfcCovering",
|
| 3121 |
+
"source": "qset",
|
| 3122 |
+
"set_name": "ArchiCADQuantities",
|
| 3123 |
+
"key": "Net Surface Area of the Core Bottom",
|
| 3124 |
+
"file_count": 1,
|
| 3125 |
+
"projects": [
|
| 3126 |
+
"sixty5"
|
| 3127 |
+
],
|
| 3128 |
+
"auto_discovered": true
|
| 3129 |
+
},
|
| 3130 |
+
{
|
| 3131 |
+
"entity": "IfcCovering",
|
| 3132 |
+
"source": "qset",
|
| 3133 |
+
"set_name": "ArchiCADQuantities",
|
| 3134 |
+
"key": "Net Surface Area of the Core (without Top/Bottom)",
|
| 3135 |
+
"file_count": 1,
|
| 3136 |
+
"projects": [
|
| 3137 |
+
"sixty5"
|
| 3138 |
+
],
|
| 3139 |
+
"auto_discovered": true
|
| 3140 |
+
},
|
| 3141 |
+
{
|
| 3142 |
+
"entity": "IfcCovering",
|
| 3143 |
+
"source": "qset",
|
| 3144 |
+
"set_name": "ArchiCADQuantities",
|
| 3145 |
+
"key": "Net Surface Area of the Core Top",
|
| 3146 |
+
"file_count": 1,
|
| 3147 |
+
"projects": [
|
| 3148 |
+
"sixty5"
|
| 3149 |
+
],
|
| 3150 |
+
"auto_discovered": true
|
| 3151 |
+
},
|
| 3152 |
+
{
|
| 3153 |
+
"entity": "IfcCovering",
|
| 3154 |
+
"source": "qset",
|
| 3155 |
+
"set_name": "ArchiCADQuantities",
|
| 3156 |
+
"key": "Net Surface Area of the Veneer Bottom",
|
| 3157 |
+
"file_count": 1,
|
| 3158 |
+
"projects": [
|
| 3159 |
+
"sixty5"
|
| 3160 |
+
],
|
| 3161 |
+
"auto_discovered": true
|
| 3162 |
+
},
|
| 3163 |
+
{
|
| 3164 |
+
"entity": "IfcCovering",
|
| 3165 |
+
"source": "qset",
|
| 3166 |
+
"set_name": "ArchiCADQuantities",
|
| 3167 |
+
"key": "Veneer Surface Area",
|
| 3168 |
+
"file_count": 1,
|
| 3169 |
+
"projects": [
|
| 3170 |
+
"sixty5"
|
| 3171 |
+
],
|
| 3172 |
+
"auto_discovered": true
|
| 3173 |
+
},
|
| 3174 |
+
{
|
| 3175 |
+
"entity": "IfcCovering",
|
| 3176 |
+
"source": "qset",
|
| 3177 |
+
"set_name": "ArchiCADQuantities",
|
| 3178 |
+
"key": "Net Surface Area of the Veneer Top",
|
| 3179 |
+
"file_count": 1,
|
| 3180 |
+
"projects": [
|
| 3181 |
+
"sixty5"
|
| 3182 |
+
],
|
| 3183 |
+
"auto_discovered": true
|
| 3184 |
+
},
|
| 3185 |
+
{
|
| 3186 |
+
"entity": "IfcCovering",
|
| 3187 |
+
"source": "qset",
|
| 3188 |
+
"set_name": "ArchiCADQuantities",
|
| 3189 |
+
"key": "End Surface Area",
|
| 3190 |
+
"file_count": 1,
|
| 3191 |
+
"projects": [
|
| 3192 |
+
"sixty5"
|
| 3193 |
+
],
|
| 3194 |
+
"auto_discovered": true
|
| 3195 |
+
},
|
| 3196 |
+
{
|
| 3197 |
+
"entity": "IfcCovering",
|
| 3198 |
+
"source": "qset",
|
| 3199 |
+
"set_name": "ArchiCADQuantities",
|
| 3200 |
+
"key": "Holes Edge Surface Area",
|
| 3201 |
+
"file_count": 1,
|
| 3202 |
+
"projects": [
|
| 3203 |
+
"sixty5"
|
| 3204 |
+
],
|
| 3205 |
+
"auto_discovered": true
|
| 3206 |
+
},
|
| 3207 |
+
{
|
| 3208 |
+
"entity": "IfcCovering",
|
| 3209 |
+
"source": "qset",
|
| 3210 |
+
"set_name": "ArchiCADQuantities",
|
| 3211 |
+
"key": "Left Side Surface Area",
|
| 3212 |
+
"file_count": 1,
|
| 3213 |
+
"projects": [
|
| 3214 |
+
"sixty5"
|
| 3215 |
+
],
|
| 3216 |
+
"auto_discovered": true
|
| 3217 |
+
},
|
| 3218 |
+
{
|
| 3219 |
+
"entity": "IfcCovering",
|
| 3220 |
+
"source": "qset",
|
| 3221 |
+
"set_name": "ArchiCADQuantities",
|
| 3222 |
+
"key": "Right Side Surface Area",
|
| 3223 |
+
"file_count": 1,
|
| 3224 |
+
"projects": [
|
| 3225 |
+
"sixty5"
|
| 3226 |
+
],
|
| 3227 |
+
"auto_discovered": true
|
| 3228 |
+
},
|
| 3229 |
+
{
|
| 3230 |
+
"entity": "IfcSite",
|
| 3231 |
+
"source": "qset",
|
| 3232 |
+
"set_name": "BaseQuantities",
|
| 3233 |
+
"key": "GrossArea",
|
| 3234 |
+
"file_count": 7,
|
| 3235 |
+
"projects": [
|
| 3236 |
+
"ac20",
|
| 3237 |
+
"fzk_house",
|
| 3238 |
+
"molio",
|
| 3239 |
+
"sixty5",
|
| 3240 |
+
"smiley_west"
|
| 3241 |
+
],
|
| 3242 |
+
"auto_discovered": true
|
| 3243 |
+
},
|
| 3244 |
+
{
|
| 3245 |
+
"entity": "IfcBuilding",
|
| 3246 |
+
"source": "qset",
|
| 3247 |
+
"set_name": "BaseQuantities",
|
| 3248 |
+
"key": "GrossFloorArea",
|
| 3249 |
+
"file_count": 7,
|
| 3250 |
+
"projects": [
|
| 3251 |
+
"ac20",
|
| 3252 |
+
"fzk_house",
|
| 3253 |
+
"molio",
|
| 3254 |
+
"sixty5",
|
| 3255 |
+
"smiley_west"
|
| 3256 |
+
],
|
| 3257 |
+
"auto_discovered": true
|
| 3258 |
+
},
|
| 3259 |
+
{
|
| 3260 |
+
"entity": "IfcBuildingStorey",
|
| 3261 |
+
"source": "qset",
|
| 3262 |
+
"set_name": "BaseQuantities",
|
| 3263 |
+
"key": "GrossFloorArea",
|
| 3264 |
+
"file_count": 7,
|
| 3265 |
+
"projects": [
|
| 3266 |
+
"ac20",
|
| 3267 |
+
"fzk_house",
|
| 3268 |
+
"molio",
|
| 3269 |
+
"sixty5",
|
| 3270 |
+
"smiley_west"
|
| 3271 |
+
],
|
| 3272 |
+
"auto_discovered": true
|
| 3273 |
+
}
|
| 3274 |
+
]
|
| 3275 |
+
}
|
teams/lux-ai/Logs/ifc_scan.log
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
INFO Found 36 IFC file(s) under: C:\Users\LEGION\Documents\GitHub\Lux.Ai\Sample projects\projects
|
| 2 |
+
INFO [1/36] 4351/arc.ifc
|
| 3 |
+
INFO Schema: IFC2X3
|
| 4 |
+
INFO [2/36] ac20/arc.ifc
|
| 5 |
+
INFO Schema: IFC4
|
| 6 |
+
INFO [3/36] city_house_munich/arc.ifc
|
| 7 |
+
INFO Schema: IFC4X3
|
| 8 |
+
INFO [4/36] dental_clinic/arc.ifc
|
| 9 |
+
INFO Schema: IFC2X3
|
| 10 |
+
INFO [5/36] dental_clinic/mep.ifc
|
| 11 |
+
INFO Schema: IFC2X3
|
| 12 |
+
INFO [6/36] dental_clinic/str.ifc
|
| 13 |
+
INFO Schema: IFC2X3
|
| 14 |
+
INFO [7/36] digital_hub/arc.ifc
|
| 15 |
+
INFO Schema: IFC4
|
| 16 |
+
INFO [8/36] digital_hub/heating.ifc
|
| 17 |
+
INFO Schema: IFC4
|
| 18 |
+
INFO [9/36] digital_hub/plumbing.ifc
|
| 19 |
+
INFO Schema: IFC4
|
| 20 |
+
INFO [10/36] digital_hub/ventilation.ifc
|
| 21 |
+
INFO Schema: IFC4
|
| 22 |
+
INFO [11/36] duplex/arc.ifc
|
| 23 |
+
INFO Schema: IFC2X3
|
| 24 |
+
INFO [12/36] duplex/mep.ifc
|
| 25 |
+
INFO Schema: IFC2X3
|
| 26 |
+
INFO [13/36] ettenheim_gis/city.ifc
|
| 27 |
+
INFO Schema: IFC2X3
|
| 28 |
+
INFO [14/36] fantasy_hotel_1/arc.ifc
|
| 29 |
+
INFO Schema: IFC4
|
| 30 |
+
INFO [15/36] fantasy_hotel_2/arc.ifc
|
| 31 |
+
INFO Schema: IFC4
|
| 32 |
+
INFO [16/36] fantasy_office_building_1/arc.ifc
|
| 33 |
+
INFO Schema: IFC4
|
| 34 |
+
INFO [17/36] fantasy_office_building_2/arc.ifc
|
| 35 |
+
INFO Schema: IFC4
|
| 36 |
+
INFO [18/36] fantasy_office_building_3/arc.ifc
|
| 37 |
+
INFO Schema: IFC4
|
| 38 |
+
INFO [19/36] fantasy_residential_building_1/arc.ifc
|
| 39 |
+
INFO Schema: IFC4
|
| 40 |
+
INFO [20/36] fzk_house/arc.ifc
|
| 41 |
+
INFO Schema: IFC4
|
| 42 |
+
INFO [21/36] hitos/arc.ifc
|
| 43 |
+
INFO Schema: IFC2X3
|
| 44 |
+
INFO [22/36] molio/arc.ifc
|
| 45 |
+
INFO Schema: IFC2X3
|
| 46 |
+
INFO [23/36] samuel_macalister_sample_house/arc.ifc
|
| 47 |
+
INFO Schema: IFC4
|
| 48 |
+
INFO [24/36] samuel_macalister_sample_house/mep.ifc
|
| 49 |
+
INFO Schema: IFC4
|
| 50 |
+
INFO [25/36] schependomlaan/arc.ifc
|
| 51 |
+
INFO Schema: IFC2X3
|
| 52 |
+
INFO [26/36] sixty5/arc.ifc
|
| 53 |
+
INFO Schema: IFC2X3
|
| 54 |
+
INFO [27/36] sixty5/electrical.ifc
|
| 55 |
+
INFO Schema: IFC2X3
|
| 56 |
+
INFO [28/36] sixty5/facade.ifc
|
| 57 |
+
INFO Schema: IFC2X3
|
| 58 |
+
INFO [29/36] sixty5/kitchen.ifc
|
| 59 |
+
INFO Schema: IFC2X3
|
| 60 |
+
INFO [30/36] sixty5/plumbing.ifc
|
| 61 |
+
INFO Schema: IFC2X3
|
| 62 |
+
INFO [31/36] sixty5/str.ifc
|
| 63 |
+
INFO Schema: IFC2X3
|
| 64 |
+
INFO [32/36] sixty5/ventilation.ifc
|
| 65 |
+
INFO Schema: IFC2X3
|
| 66 |
+
INFO [33/36] smiley_west/arc.ifc
|
| 67 |
+
INFO Schema: IFC4
|
| 68 |
+
INFO [34/36] wbdg_office/arc.ifc
|
| 69 |
+
INFO Schema: IFC2X3
|
| 70 |
+
INFO [35/36] wbdg_office/mep.ifc
|
| 71 |
+
INFO Schema: IFC2X3
|
| 72 |
+
INFO [36/36] wbdg_office/str.ifc
|
| 73 |
+
INFO Schema: IFC2X3
|
| 74 |
+
INFO CSV written to: C:\Users\LEGION\Documents\GitHub\Lux.Ai\ifc_scan_results.csv
|
teams/lux-ai/Logs/ifc_scan_results.csv
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
project_name,ifc_file,window_area_m2,floor_area_m2,roof_area_m2,true_north_angle_deg,latitude,longitude,error
|
| 2 |
+
4351,arc.ifc,10.8697,,,,,,
|
| 3 |
+
ac20,arc.ifc,309.5,1939.3839,688.7667,0.0,49.15,8.716667,
|
| 4 |
+
city_house_munich,arc.ifc,29.5085,,,32.56,48.187378,11.47271,
|
| 5 |
+
dental_clinic,arc.ifc,100.63,,,,42.358429,-71.059776,
|
| 6 |
+
dental_clinic,mep.ifc,,,,,,,
|
| 7 |
+
dental_clinic,str.ifc,,,,,42.358429,-71.059776,
|
| 8 |
+
digital_hub,arc.ifc,375.0,2842.1215,,360.0,48.1376,11.57994,
|
| 9 |
+
digital_hub,heating.ifc,,,,360.0,48.1376,11.57994,
|
| 10 |
+
digital_hub,plumbing.ifc,,,,360.0,48.1376,11.57994,
|
| 11 |
+
digital_hub,ventilation.ifc,,,,360.0,48.1376,11.57994,
|
| 12 |
+
duplex,arc.ifc,65.9415,,,,41.8744,-87.6394,
|
| 13 |
+
duplex,mep.ifc,,,,,41.8744,-87.6394,
|
| 14 |
+
ettenheim_gis,city.ifc,725.9732,,,,,,
|
| 15 |
+
fantasy_hotel_1,arc.ifc,58.7815,,,360.0,42.358723,-71.056763,
|
| 16 |
+
fantasy_hotel_2,arc.ifc,400.305,,,360.0,42.358723,-71.056763,
|
| 17 |
+
fantasy_office_building_1,arc.ifc,156.816,1257.0688,339.9228,360.0,48.139126,11.580186,
|
| 18 |
+
fantasy_office_building_2,arc.ifc,142.91,716.0568,419.5723,360.0,48.1376,11.57994,
|
| 19 |
+
fantasy_office_building_3,arc.ifc,984.96,,,360.0,48.148853,11.567998,
|
| 20 |
+
fantasy_residential_building_1,arc.ifc,19.563,,,360.0,48.139126,11.580186,
|
| 21 |
+
fzk_house,arc.ifc,23.1702,173.3424,165.1222,310.0,49.100435,8.436539,
|
| 22 |
+
hitos,arc.ifc,1531.3183,,,360.0,69.666667,18.833333,
|
| 23 |
+
molio,arc.ifc,,140.7827,,0.0,55.667574,12.619383,
|
| 24 |
+
samuel_macalister_sample_house,arc.ifc,61.6407,,,323.0,42.213,-71.0335,
|
| 25 |
+
samuel_macalister_sample_house,mep.ifc,,,,26.62,51.45,5.483333,
|
| 26 |
+
schependomlaan,arc.ifc,,,,,,,
|
| 27 |
+
sixty5,arc.ifc,4901.1499,0.0,,0.0,51.45,5.483333,
|
| 28 |
+
sixty5,electrical.ifc,,,,360.0,42.387,-71.242,
|
| 29 |
+
sixty5,facade.ifc,,,,0.0,51.45,5.483333,
|
| 30 |
+
sixty5,kitchen.ifc,,,,270.0,0.0,0.0,
|
| 31 |
+
sixty5,plumbing.ifc,,,,360.0,42.387,-71.242,
|
| 32 |
+
sixty5,str.ifc,,34199.084,,360.0,42.387,-71.242,
|
| 33 |
+
sixty5,ventilation.ifc,,,,360.0,42.387,-71.242,
|
| 34 |
+
smiley_west,arc.ifc,95.9195,1821.1689,614.4088,147.5,49.033245,8.39098,
|
| 35 |
+
wbdg_office,arc.ifc,124.5,,,,42.358429,-71.059776,
|
| 36 |
+
wbdg_office,mep.ifc,,,,,42.358429,-71.059776,
|
| 37 |
+
wbdg_office,str.ifc,,,,,42.358429,-71.059776,
|
teams/lux-ai/Logs/solar_pipeline.log
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
INFO Schema: IFC4
|
| 2 |
+
INFO Parsing roof geometry: arc.ifc
|
| 3 |
+
INFO Found 2 roof element(s) for geometry extraction
|
| 4 |
+
INFO Applying true-north correction: 310.00Β°
|
| 5 |
+
INFO Roof area validated: geometry=165.1 mΒ², property-set=165.1 mΒ² (0% diff)
|
| 6 |
+
INFO Extracted 2 roof segment(s) from arc.ifc
|
teams/lux-ai/Logs/solar_pipeline_scan.csv
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
project_name,ifc_file,window_area_m2,floor_area_m2,roof_area_m2,true_north_angle_deg,latitude,longitude,error
|
| 2 |
+
4351,arc.ifc,10.8697,7517.2622,,,,,
|
| 3 |
+
ac20,arc.ifc,309.5,1939.3839,688.7667,0.0,49.15,8.716667,
|
| 4 |
+
city_house_munich,arc.ifc,29.5085,,,32.56,48.187378,11.47271,
|
| 5 |
+
dental_clinic,arc.ifc,100.63,6935.7986,,,42.358429,-71.059776,
|
| 6 |
+
dental_clinic,mep.ifc,,,,,,,
|
| 7 |
+
dental_clinic,str.ifc,,,,,42.358429,-71.059776,
|
| 8 |
+
digital_hub,arc.ifc,375.0,2842.1215,,360.0,48.1376,11.57994,
|
| 9 |
+
digital_hub,heating.ifc,,,,360.0,48.1376,11.57994,
|
| 10 |
+
digital_hub,plumbing.ifc,,,,360.0,48.1376,11.57994,
|
| 11 |
+
digital_hub,ventilation.ifc,,,,360.0,48.1376,11.57994,
|
| 12 |
+
duplex,arc.ifc,65.9415,422.0466,,,41.8744,-87.6394,
|
| 13 |
+
duplex,mep.ifc,,797.7923,,,41.8744,-87.6394,
|
| 14 |
+
ettenheim_gis,city.ifc,725.9732,,,,,,
|
| 15 |
+
fantasy_hotel_1,arc.ifc,58.7815,,,360.0,42.358723,-71.056763,
|
| 16 |
+
fantasy_hotel_2,arc.ifc,400.305,,,360.0,42.358723,-71.056763,
|
| 17 |
+
fantasy_office_building_1,arc.ifc,156.816,1257.0688,339.9228,360.0,48.139126,11.580186,
|
| 18 |
+
fantasy_office_building_2,arc.ifc,142.91,716.0568,419.5723,360.0,48.1376,11.57994,
|
| 19 |
+
fantasy_office_building_3,arc.ifc,984.96,,,360.0,48.148853,11.567998,
|
| 20 |
+
fantasy_residential_building_1,arc.ifc,19.563,,,360.0,48.139126,11.580186,
|
| 21 |
+
fzk_house,arc.ifc,23.1702,173.3424,165.1222,310.0,49.100435,8.436539,
|
| 22 |
+
hitos,arc.ifc,1531.3183,,,360.0,69.666667,18.833333,
|
| 23 |
+
molio,arc.ifc,,140.7827,,0.0,55.667574,12.619383,
|
| 24 |
+
samuel_macalister_sample_house,arc.ifc,61.6407,,,323.0,42.213,-71.0335,
|
| 25 |
+
samuel_macalister_sample_house,mep.ifc,,,,26.62,51.45,5.483333,
|
| 26 |
+
schependomlaan,arc.ifc,,,,,,,
|
| 27 |
+
sixty5,arc.ifc,,,,,,,<built-in function open> returned a result with an exception set
|
| 28 |
+
sixty5,electrical.ifc,,,,,,,<built-in function open> returned a result with an exception set
|
| 29 |
+
sixty5,facade.ifc,,,,0.0,51.45,5.483333,
|
| 30 |
+
sixty5,kitchen.ifc,,,,270.0,0.0,0.0,
|
| 31 |
+
sixty5,plumbing.ifc,,,,360.0,42.387,-71.242,
|
| 32 |
+
sixty5,str.ifc,,33915.088,,360.0,42.387,-71.242,
|
| 33 |
+
sixty5,ventilation.ifc,,,,,,,<built-in function open> returned a result with an exception set
|
| 34 |
+
smiley_west,arc.ifc,95.9195,1821.1689,614.4088,147.5,49.033245,8.39098,
|
| 35 |
+
wbdg_office,arc.ifc,124.5,3582.0077,,,42.358429,-71.059776,
|
| 36 |
+
wbdg_office,mep.ifc,,7009.6301,,,42.358429,-71.059776,
|
| 37 |
+
wbdg_office,str.ifc,,,,,42.358429,-71.059776,
|
teams/lux-ai/Lux ai tool/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Lux.Ai β IFCore Platform Tool
|
| 2 |
+
|
| 3 |
+
> Solar energy compliance checks for the IFCore platform.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## Repo Structure (IFCore Contract)
|
| 8 |
+
|
| 9 |
+
```
|
| 10 |
+
Lux ai tool/
|
| 11 |
+
βββ tools/
|
| 12 |
+
β βββ checker_solar.py β 5 check_* functions (platform-scanned)
|
| 13 |
+
βββ final_pipeline/ β bundled IFC parsing + solar engine
|
| 14 |
+
β βββ config.py
|
| 15 |
+
β βββ ifc_metadata_extractor.py
|
| 16 |
+
β βββ ifc_roof_parser.py
|
| 17 |
+
β βββ solar_production_engine.py
|
| 18 |
+
β βββ key_aliases.json
|
| 19 |
+
βββ run.py β CLI entry-point
|
| 20 |
+
βββ requirements.txt β team dependencies
|
| 21 |
+
βββ test_contract.py β contract validation tests
|
| 22 |
+
βββ README.md β this file
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
The platform auto-discovers all `check_*` functions inside `tools/checker_*.py`.
|
| 26 |
+
|
| 27 |
+
---
|
| 28 |
+
|
| 29 |
+
## Quick Start
|
| 30 |
+
|
| 31 |
+
```bash
|
| 32 |
+
cd "Lux ai tool"
|
| 33 |
+
pip install -r requirements.txt
|
| 34 |
+
|
| 35 |
+
# Run all 5 checks on an IFC file
|
| 36 |
+
python run.py path/to/model.ifc
|
| 37 |
+
|
| 38 |
+
# Run specific checks only
|
| 39 |
+
python run.py path/to/model.ifc --checks location building_areas roof_geometry
|
| 40 |
+
|
| 41 |
+
# Override coordinates for solar / LEED checks
|
| 42 |
+
python run.py path/to/model.ifc --lat 48.13 --lon 11.58
|
| 43 |
+
|
| 44 |
+
# Export results to JSON
|
| 45 |
+
python run.py path/to/model.ifc --output results.json
|
| 46 |
+
|
| 47 |
+
# List available checks
|
| 48 |
+
python run.py --list-checks
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
| Function | What it checks | Needs internet? |
|
| 52 |
+
|----------|---------------|-----------------|
|
| 53 |
+
| `check_location` | IfcSite has latitude + longitude | No |
|
| 54 |
+
| `check_building_areas` | Window, floor, and roof area present | No |
|
| 55 |
+
| `check_roof_geometry` | 3D roof segments extractable (tilt, azimuth, area) | No |
|
| 56 |
+
| `check_solar_production` | PVWatts returns kWh/yr > 0 for each segment | **Yes** |
|
| 57 |
+
| `check_leed_score` | LEED renewable-energy score β₯ 50% | **Yes** |
|
| 58 |
+
|
| 59 |
+
---
|
| 60 |
+
|
| 61 |
+
## Usage
|
| 62 |
+
|
| 63 |
+
### Local testing
|
| 64 |
+
|
| 65 |
+
```python
|
| 66 |
+
import ifcopenshell
|
| 67 |
+
from tools.checker_solar import check_location
|
| 68 |
+
|
| 69 |
+
model = ifcopenshell.open("path/to/model.ifc")
|
| 70 |
+
results = check_location(model)
|
| 71 |
+
for r in results:
|
| 72 |
+
print(f"[{r['check_status'].upper()}] {r['element_name']}: {r['actual_value']} (req: {r['required_value']})")
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
### Contract validation
|
| 76 |
+
|
| 77 |
+
```bash
|
| 78 |
+
cd "Lux ai tool"
|
| 79 |
+
python test_contract.py
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
---
|
| 83 |
+
|
| 84 |
+
## Contract Compliance
|
| 85 |
+
|
| 86 |
+
Every `check_*` function:
|
| 87 |
+
|
| 88 |
+
- **First arg:** `model` (`ifcopenshell.file` object)
|
| 89 |
+
- **Returns:** `list[dict]` β one dict per element
|
| 90 |
+
- **Dict keys:** `element_id`, `element_type`, `element_name`, `element_name_long`, `check_status`, `actual_value`, `required_value`, `comment`, `log`
|
| 91 |
+
- **Status values:** `pass`, `fail`, `warning`, `blocked`, `log`
|
| 92 |
+
|
| 93 |
+
---
|
| 94 |
+
|
| 95 |
+
## Dependencies
|
| 96 |
+
|
| 97 |
+
This tool is fully self-contained. All `final_pipeline/` modules are bundled. Install with:
|
| 98 |
+
|
| 99 |
+
```bash
|
| 100 |
+
pip install ifcopenshell numpy requests
|
| 101 |
+
```
|
teams/lux-ai/Lux ai tool/final_pipeline/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
final_pipeline β Unified IFC metadata extraction + solar production analysis.
|
| 3 |
+
|
| 4 |
+
Merges:
|
| 5 |
+
- IFC property-set/quantity-set metadata scanner (window, floor, roof area,
|
| 6 |
+
orientation, lat/lon) with multi-exporter alias fallback
|
| 7 |
+
- Geometry-based roof segment parser (per-face tilt & azimuth via 3D mesh)
|
| 8 |
+
- NREL PVWatts v8 solar production engine
|
| 9 |
+
|
| 10 |
+
Supports single-file solar analysis and batch scanning of project directories.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from final_pipeline.config import __version__
|
| 14 |
+
|
| 15 |
+
__all__ = ["__version__"]
|