export {} // module boundary (stripped by transpiler) // ── Building Envelope & Thermal Check ─────────────────────────────────── // Stakeholder: Architect / Energy Consultant // // Identifies all external-facing elements (walls, slabs, roofs, windows, // doors, curtain walls) by reading Pset_*Common.IsExternal and checks // whether they carry thermal transmittance values. Missing thermal data // on external elements is a common issue in energy models. The script // isolates the envelope, color-codes by thermal status, and exports // the findings — a workflow that requires cross-referencing two property // values across hundreds of elements. // ───────────────────────────────────────────────────────────────────────── bim.viewer.resetColors() bim.viewer.resetVisibility() // ── 1. Gather envelope candidates ─────────────────────────────────────── const envelopeTypes = [ 'IfcWall', 'IfcWallStandardCase', 'IfcCurtainWall', 'IfcSlab', 'IfcRoof', 'IfcDoor', 'IfcDoorStandardCase', 'IfcWindow', 'IfcPlate', ] const candidates = bim.query.byType(...envelopeTypes) if (candidates.length === 0) { console.error('No envelope element types found') throw new Error('no elements') } // ── 2. Classify each element ──────────────────────────────────────────── interface EnvelopeResult { entity: BimEntity isExternal: boolean | null thermalTransmittance: number | null thermalSource: string } const results: EnvelopeResult[] = [] // Collect property paths for CSV export const envelopePropPaths = new Set() for (const entity of candidates) { const result: EnvelopeResult = { entity, isExternal: null, thermalTransmittance: null, thermalSource: '' } const psets = bim.query.properties(entity) for (const pset of psets) { for (const p of pset.properties) { const lower = p.name.toLowerCase() if (lower === 'isexternal' && p.value !== null) { result.isExternal = p.value === true || p.value === 'TRUE' || p.value === '.T.' envelopePropPaths.add(pset.name + '.IsExternal') } if (lower === 'thermaltransmittance' && p.value !== null && p.value !== '') { result.thermalTransmittance = typeof p.value === 'number' ? p.value : parseFloat(String(p.value)) result.thermalSource = pset.name envelopePropPaths.add(pset.name + '.ThermalTransmittance') } // Also capture other thermal-related properties if ((lower === 'firerating' || lower === 'loadbearing' || lower === 'acousticrating') && p.value !== null && p.value !== '') { envelopePropPaths.add(pset.name + '.' + p.name) } } } results.push(result) } // ── 3. Separate external vs internal ──────────────────────────────────── const external = results.filter(r => r.isExternal === true) const internal = results.filter(r => r.isExternal === false) const unknown = results.filter(r => r.isExternal === null) const extWithThermal = external.filter(r => r.thermalTransmittance !== null) const extNoThermal = external.filter(r => r.thermalTransmittance === null) // ── 4. Color-code ─────────────────────────────────────────────────────── const batches: Array<{ entities: BimEntity[]; color: string }> = [] if (extWithThermal.length > 0) batches.push({ entities: extWithThermal.map(r => r.entity), color: '#27ae60' }) // green: external + thermal if (extNoThermal.length > 0) batches.push({ entities: extNoThermal.map(r => r.entity), color: '#e74c3c' }) // red: external, missing thermal if (internal.length > 0) batches.push({ entities: internal.map(r => r.entity), color: '#95a5a6' }) // grey: internal if (unknown.length > 0) batches.push({ entities: unknown.map(r => r.entity), color: '#f39c12' }) // orange: unknown if (batches.length > 0) bim.viewer.colorizeAll(batches) // Isolate envelope if we found external elements if (external.length > 0) { bim.viewer.isolate(external.map(r => r.entity)) } // ── 5. Report ─────────────────────────────────────────────────────────── console.log('═══════════════════════════════════════') console.log(' BUILDING ENVELOPE & THERMAL CHECK') console.log('═══════════════════════════════════════') console.log('') console.log('Scanned ' + candidates.length + ' envelope-type elements') console.log('') console.log(' External: ' + external.length + ' (isolated in view)') console.log(' Internal: ' + internal.length + ' ● grey') console.log(' Undefined: ' + unknown.length + ' ● orange — IsExternal not set') console.log('') console.log(' External with thermal data: ' + extWithThermal.length + ' ● green') console.log(' External without thermal data: ' + extNoThermal.length + ' ● red — NEEDS ATTENTION') // ── 6. Thermal value distribution ─────────────────────────────────────── if (extWithThermal.length > 0) { console.log('') console.log('── Thermal Transmittance (U-value) Distribution ──') // Group by type const byType: Record = {} for (const r of extWithThermal) { if (!byType[r.entity.Type]) byType[r.entity.Type] = [] if (r.thermalTransmittance !== null) byType[r.entity.Type].push(r.thermalTransmittance) } for (const [type, values] of Object.entries(byType).sort((a, b) => b[1].length - a[1].length)) { const min = Math.min(...values) const max = Math.max(...values) const avg = values.reduce((s, v) => s + v, 0) / values.length console.log(' ' + type + ' (' + values.length + ')') console.log(' min=' + min.toFixed(3) + ' avg=' + avg.toFixed(3) + ' max=' + max.toFixed(3) + ' W/(m²·K)') } } // ── 7. Elements missing IsExternal property ───────────────────────────── if (unknown.length > 0) { console.log('') console.warn('── Missing IsExternal Property ──') const byType: Record = {} for (const r of unknown) { byType[r.entity.Type] = (byType[r.entity.Type] || 0) + 1 } for (const [type, count] of Object.entries(byType).sort((a, b) => b[1] - a[1])) { console.warn(' ' + type + ': ' + count + ' elements') } } // ── 8. External elements missing thermal data ─────────────────────────── if (extNoThermal.length > 0) { console.log('') console.warn('── External Elements Without Thermal Data ──') for (const r of extNoThermal.slice(0, 15)) { console.warn(' ' + (r.entity.Name || '') + ' [' + r.entity.Type + ']') } if (extNoThermal.length > 15) console.warn(' ... and ' + (extNoThermal.length - 15) + ' more') const envPropCols = Array.from(envelopePropPaths).sort() bim.export.csv(extNoThermal.map(r => r.entity), { columns: ['Name', 'Type', 'GlobalId', 'ObjectType', ...envPropCols], filename: 'envelope-missing-thermal.csv' }) console.log('') console.log('Exported ' + extNoThermal.length + ' elements to envelope-missing-thermal.csv') } // Export full envelope dataset with all discovered properties if (external.length > 0) { const envPropCols = Array.from(envelopePropPaths).sort() bim.export.csv(external.map(r => r.entity), { columns: ['Name', 'Type', 'GlobalId', 'ObjectType', ...envPropCols], filename: 'envelope-analysis.csv' }) console.log('Exported ' + external.length + ' external elements (' + (4 + envPropCols.length) + ' columns) to envelope-analysis.csv') }