export {} // module boundary (stripped by transpiler) // ── Multi-Model Federation Comparison ─────────────────────────────────── // Stakeholder: Project Manager / BIM Coordinator // // When multiple IFC models are loaded (architectural + structural + MEP), // this script compares them side by side: entity counts, type coverage, // naming consistency, and property completeness per model. The UI shows // one tree at a time — this script cross-references everything and // highlights discrepancies that indicate coordination issues. // ───────────────────────────────────────────────────────────────────────── const models = bim.model.list() if (models.length === 0) { console.error('No models loaded') throw new Error('no models') } console.log('═══════════════════════════════════════') console.log(' FEDERATION COMPARISON') console.log('═══════════════════════════════════════') console.log('') // ── 1. Model overview ─────────────────────────────────────────────────── console.log('── Loaded Models (' + models.length + ') ──') let totalEntities = 0 for (const m of models) { const sizeMB = (m.fileSize / 1024 / 1024).toFixed(1) console.log(' [' + m.id.slice(0, 8) + '] ' + m.name) console.log(' Schema: ' + m.schemaVersion + ' | Entities: ' + m.entityCount + ' | Size: ' + sizeMB + ' MB') totalEntities += m.entityCount } console.log('') console.log('Total: ' + totalEntities + ' entities across ' + models.length + ' models') // ── 2. Type distribution per model ────────────────────────────────────── const all = bim.query.all() // Build type→model distribution const typeByModel: Record> = {} const modelEntityMap: Record = {} for (const e of all) { const mid = e.ref.modelId if (!modelEntityMap[mid]) modelEntityMap[mid] = [] modelEntityMap[mid].push(e) if (!typeByModel[e.Type]) typeByModel[e.Type] = {} typeByModel[e.Type][mid] = (typeByModel[e.Type][mid] || 0) + 1 } // Find types that exist in multiple models (potential overlaps/conflicts) const sharedTypes: string[] = [] const exclusiveTypes: Record = {} // modelId → types only in that model for (const [type, distribution] of Object.entries(typeByModel)) { const modelIds = Object.keys(distribution) if (modelIds.length > 1) { sharedTypes.push(type) } else { const mid = modelIds[0] if (!exclusiveTypes[mid]) exclusiveTypes[mid] = [] exclusiveTypes[mid].push(type) } } console.log('') console.log('── Type Distribution ──') console.log('') // Types present in multiple models if (sharedTypes.length > 0) { console.log('Types shared across models (' + sharedTypes.length + '):') const header = ' Type ' + models.map(m => ('| ' + m.name.slice(0, 10) + ' ').slice(0, 13)).join('') console.log(header) console.log(' ' + '-'.repeat(header.length - 2)) for (const type of sharedTypes.sort((a, b) => { const totalA = Object.values(typeByModel[a]).reduce((s, v) => s + v, 0) const totalB = Object.values(typeByModel[b]).reduce((s, v) => s + v, 0) return totalB - totalA }).slice(0, 20)) { const typeStr = (type + ' ').slice(0, 30) const counts = models.map(m => { const c = typeByModel[type][m.id] || 0 return ('| ' + (c > 0 ? String(c) : '-') + ' ').slice(0, 13) }).join('') console.log(' ' + typeStr + counts) } } // Types exclusive to one model console.log('') console.log('Types exclusive to one model:') for (const m of models) { const exclusive = exclusiveTypes[m.id] || [] if (exclusive.length > 0) { console.log(' ' + m.name + ': ' + exclusive.slice(0, 8).join(', ') + (exclusive.length > 8 ? ' (+' + (exclusive.length - 8) + ' more)' : '')) } } // ── 3. Naming consistency check ───────────────────────────────────────── console.log('') console.log('── Naming Consistency ──') for (const m of models) { const entities = modelEntityMap[m.id] || [] const named = entities.filter(e => e.Name && e.Name !== '') const described = entities.filter(e => e.Description && e.Description !== '') const typed = entities.filter(e => e.ObjectType && e.ObjectType !== '') const namePct = entities.length > 0 ? (named.length / entities.length * 100).toFixed(1) : '0' const descPct = entities.length > 0 ? (described.length / entities.length * 100).toFixed(1) : '0' const typePct = entities.length > 0 ? (typed.length / entities.length * 100).toFixed(1) : '0' console.log(' ' + m.name + ':') console.log(' Named: ' + namePct + '% | Described: ' + descPct + '% | ObjectType: ' + typePct + '%') } // ── 4. Property coverage comparison ───────────────────────────────────── console.log('') console.log('── Property Coverage (sample of 100 per model) ──') for (const m of models) { const entities = modelEntityMap[m.id] || [] const sample = entities.slice(0, 100) let withPsets = 0 let withQsets = 0 let totalPsets = 0 for (const e of sample) { const psets = bim.query.properties(e) const qsets = bim.query.quantities(e) if (psets.length > 0) withPsets++ if (qsets.length > 0) withQsets++ totalPsets += psets.length } const psetPct = sample.length > 0 ? (withPsets / sample.length * 100).toFixed(0) : '0' const qsetPct = sample.length > 0 ? (withQsets / sample.length * 100).toFixed(0) : '0' const avgPsets = sample.length > 0 ? (totalPsets / sample.length).toFixed(1) : '0' console.log(' ' + m.name + ':') console.log(' With properties: ' + psetPct + '% | With quantities: ' + qsetPct + '% | Avg psets/entity: ' + avgPsets) } // ── 5. Potential coordination issues ──────────────────────────────────── if (models.length > 1) { console.log('') console.log('── Coordination Notes ──') // Check for spatial types in non-architectural models const spatialTypes = ['IfcBuilding', 'IfcBuildingStorey', 'IfcSite', 'IfcSpace'] for (const m of models) { const hasSpatial: string[] = [] for (const st of spatialTypes) { if (typeByModel[st] && typeByModel[st][m.id]) { hasSpatial.push(st + '(' + typeByModel[st][m.id] + ')') } } if (hasSpatial.length > 0) { console.log(' ' + m.name + ' defines: ' + hasSpatial.join(', ')) } } // Check entity count ratios (large disparities may indicate issues) const counts = models.map(m => modelEntityMap[m.id]?.length || 0) const maxCount = Math.max(...counts) const minCount = Math.min(...counts) if (maxCount > 0 && minCount > 0 && maxCount / minCount > 10) { console.warn('') console.warn(' Large entity count disparity (' + maxCount + ' vs ' + minCount + ')') console.warn(' This may indicate models at different levels of detail') } } // ── 6. Color-code by model origin ─────────────────────────────────────── const modelColors = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c'] const colorBatches: Array<{ entities: BimEntity[]; color: string }> = [] let idx = 0 for (const m of models) { const entities = modelEntityMap[m.id] || [] if (entities.length > 0) { colorBatches.push({ entities, color: modelColors[idx % modelColors.length] }) idx++ } } if (colorBatches.length > 0) bim.viewer.colorizeAll(colorBatches) console.log('') console.log('── Model Colors ──') idx = 0 for (const m of models) { console.log(' ' + m.name + ' ● ' + modelColors[idx % modelColors.length]) idx++ }