/*! * Tectum * Copyright © 2026 Machiel Heinen. * Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming. * Zie LICENTIE.md. */ window.ViewLayers = (() => { // phase: bouwvolgorde voor de snelkeuze "Fase" (en IFC); lagen zonder phase blijven daarbij aan. const LIST = [ { key: 'regelwerk', label: 'Regelwerk', icon: 'grid-3x3', phase: 0, hint: 'Vloer-, wand- en dakbalken, stalen nokbalk' }, { key: 'elektra', label: 'Elektra', icon: 'lightning-charge', phase: 1, hint: 'Meterkast, buizen, dozen, schakelaars en wcd\'s' }, { key: 'beplating', label: 'Beplating', icon: 'square', phase: 2, hint: 'Spaanplaat binnen, OSB vloer, plafond en dakbeschot, cementplaat buiten' }, { key: 'doek', label: 'Vochtdoek', icon: 'droplet', phase: 3, hint: 'Vochtdoek op de buitenzijde' }, { key: 'latwerk', label: 'Latwerk', icon: 'distribute-vertical', phase: 4, hint: 'Kruislat en trasraamplaat' }, { key: 'gevel', label: 'Gevelbekleding', icon: 'bricks', phase: 5, hint: 'Rabat, steenstrips, stuc, schoorsteen, sandwichpanelen' }, { key: 'kozijnen', label: 'Kozijnen', icon: 'window', phase: 5, hint: 'Kozijnen, deuren en glas' }, { key: 'dakbedekking', label: 'Dakbedekking', icon: 'house-up', phase: 5, hint: 'Dakleer of riet' }, { key: 'afwerking', label: 'Afwerking', icon: 'brush', phase: 6, hint: 'Vloerafwerking, verf, plafond, dakrand, dagkanten, logobord' }, { key: 'lampen', label: 'Verlichting', icon: 'lightbulb', phase: 6, hint: 'Gevellampen, spots en binnenverlichting' }, { key: 'inrichting', label: 'Inrichting', icon: 'lamp', phase: 6, hint: 'Sanitair, keuken, kasten, bed, zithoek' }, { key: 'ruimtes', label: 'Ruimtes', icon: 'bounding-box', phase: 6, hint: 'Ruimtes met functie, vloer en wandpanelen' }, { key: 'dak', label: 'Dak', icon: 'house', hint: 'Het hele dak met dakrand, overkapping en palen; uit om van boven in de woning te kijken' }, { key: 'maten', label: 'Maten', icon: 'rulers', hint: 'Maatvoering in de plattegrond' } ]; const KEYS = LIST.map(l => l.key); const PHASES = ['Regelwerk', 'Elektra', 'Beplating', 'Vochtdoek', 'Latwerk', 'Gevel & dak', 'Afgewerkt']; const all = () => Object.fromEntries(KEYS.map(k => [k, true])); const normalize = (layers) => Object.fromEntries(KEYS.map(k => [k, !layers || layers[k] !== false])); const shows = (layers, key) => !layers || layers[key] !== false; // Alles tot en met een bouwfase (0..6), zoals de vroegere bouwfase-schuif. const upTo = (phase) => Object.fromEntries(LIST.map(l => [l.key, l.phase === undefined || l.phase <= phase])); const phaseOf = (layers) => PHASES.findIndex((_, p) => LIST.every(l => l.phase === undefined || (l.phase <= p) === shows(layers, l.key))); // Verbergt meshes met een van de materialen (ook afgeleide met userData.base), bijv. het regelwerk. function hideByMaterial(group, materials) { if (!group) return; const set = new Set(materials.filter(Boolean)); group.traverse(o => { if (!o.isMesh) return; const m0 = Array.isArray(o.material) ? o.material[0] : o.material; if (m0 && (set.has(m0) || set.has(m0.userData && m0.userData.base))) o.visible = false; }); } const GROUPS = [ ['Casco', ['regelwerk', 'elektra', 'beplating', 'doek', 'latwerk']], ['Buiten', ['gevel', 'kozijnen', 'dakbedekking', 'dak']], ['Binnen', ['afwerking', 'lampen', 'inrichting', 'ruimtes']], ['Plattegrond', ['maten']] ]; // De enige plek voor zichtbaarheid: knop "Lagen" in de werkbalk met een uitklappaneel. // keys beperkt de lagen (wand-editor); extras zijn editor-eigen schakelaars, bijv. beplating binnen/buiten. function createMenu(toolbar, o) { const items = o.keys ? LIST.filter(l => o.keys.includes(l.key)) : LIST; const extras = o.extras || []; const extra = Object.fromEntries(extras.map(x => [x.key, x.checked !== false])); const check = (attr, key, label, hint, ic) => ``; const wrap = document.createElement('div'); wrap.className = 'tb-pop-wrap'; wrap.innerHTML = `
${items.length ? `
${GROUPS.map(([label, keys]) => { const g = items.filter(l => keys.includes(l.key)); return g.length ? `
${label}
${g.map(l => check('data-layer', l.key, l.label, l.hint, l.icon)).join('')}
` : ''; }).join('')}
` : ''} ${extras.length ? `
${o.extrasLabel || 'Weergave'}
${extras.map(x => check('data-extra', x.key, x.label, x.hint)).join('')}
` : ''}
`; toolbar.appendChild(wrap); const btn = wrap.querySelector('.tb-btn'), panel = wrap.querySelector('.tb-pop'); UI.popover(btn, panel); let cur = normalize(o.layers); const sel = panel.querySelector('.pop-phase'); const sync = () => { panel.querySelectorAll('[data-layer]').forEach(c => { c.checked = cur[c.dataset.layer]; }); panel.querySelectorAll('[data-extra]').forEach(c => { c.checked = extra[c.dataset.extra]; }); if (sel) { const p = phaseOf(cur); sel.value = p >= 0 ? String(p) : ''; } const hidden = items.some(l => !cur[l.key]) || extras.some(x => !extra[x.key]); btn.classList.toggle('flag', hidden); }; const emit = () => { sync(); o.onChange && o.onChange(Object.assign({}, cur)); }; panel.addEventListener('change', (e) => { const t = e.target; if (t.dataset.layer) { cur = Object.assign({}, cur, { [t.dataset.layer]: t.checked }); emit(); } else if (t.dataset.extra) { extra[t.dataset.extra] = t.checked; sync(); o.onExtra && o.onExtra(Object.assign({}, extra)); } else if (t === sel && sel.value !== '') { const keep = Object.fromEntries(LIST.filter(l => l.phase === undefined).map(l => [l.key, cur[l.key]])); cur = Object.assign(upTo(Number(sel.value)), keep); emit(); } }); // Alles = ook de editor-eigen schakelaars weer aan. const all = panel.querySelector('[data-all]'); if (all) all.addEventListener('click', () => { const extraOff = extras.some(x => !extra[x.key]); extras.forEach(x => { extra[x.key] = true; }); cur = ViewLayers.all(); emit(); if (extraOff && o.onExtra) o.onExtra(Object.assign({}, extra)); }); sync(); return { el: wrap, set: (l) => { cur = normalize(l); sync(); } }; } return { LIST, KEYS, PHASES, GROUPS, all, normalize, shows, upTo, phaseOf, hideByMaterial, createMenu }; })();