v0.7.2: fixes uit de code-review van v0.7.1: elektraleidingen nemen niet langer het valse plafond van de buurruimte door de wand heen over (gebied zonder functie = geen vals plafond) en meten begin/eind 50 mm de ruimte in, zodat de zigzag bij een overgang vals plafond/dak verdwijnt (Amstel 249,9 → 246,8 m, 16 van 68 leidingen strakker); elektra en schoorsteen snijden mee met de doorsnede (geen zwevende buizen); PDF en de plaatfase van de IFC bouwen geen buizen meer; schoorsteen in 2D en 3D op de laag Gevelbekleding; Esc sluit elke dialoog via Annuleren en sneltoetsen gaan niet meer naar de editor erachter; toets 3 sluit het rapport; menu en popovers sluiten elkaar (één uitklapper tegelijk); paneelkop ge-escaped; na slepen/pannen buiten de plattegrond wordt de volgende klik niet meer ingeslikt en blijft de cursor van het gereedschap; "Alles" in Lagen zet ook beplating binnen/buiten terug; laag uit met een selectie erin werkt het paneel bij; opgeruimd: gedeelde werkbalk (UI.editorBar) voor woning, wand en vloer, één knopbouwer (UI.btnOf), dichte paneelsecties pas opgebouwd bij openen, dode knopacties weg, binnenwand verwijderen op één plek, doorgestuurde acties uit één lijst, render-export "alleen afgewerkt" volledig in HouseModel (finishedOnly), structuurboom springt alleen bij een andere selectie
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ab0dca9bb5
commit
89cadbebf7
+1
-1
@@ -50,7 +50,7 @@
|
|||||||
<div class="license-box" id="about-license"></div>
|
<div class="license-box" id="about-license"></div>
|
||||||
<div class="about-footer">
|
<div class="about-footer">
|
||||||
<span>© 2026 Machiel Heinen · ontwikkeld in opdracht van Van Emous Mantelzorgwoningen</span>
|
<span>© 2026 Machiel Heinen · ontwikkeld in opdracht van Van Emous Mantelzorgwoningen</span>
|
||||||
<button id="about-close" class="about-close">Sluiten</button>
|
<button id="about-close" class="about-close" data-esc>Sluiten</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming.
|
* Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming.
|
||||||
* Zie LICENTIE.md.
|
* Zie LICENTIE.md.
|
||||||
*/
|
*/
|
||||||
const APP_VERSION = '0.7.1';
|
const APP_VERSION = '0.7.2';
|
||||||
// 0.0.X per kleine iteratie, 0.X.0 per mijlpaal, X.0.0 bij live-gang.
|
// 0.0.X per kleine iteratie, 0.X.0 per mijlpaal, X.0.0 bij live-gang.
|
||||||
|
|
||||||
let tabs = [];
|
let tabs = [];
|
||||||
@@ -243,9 +243,15 @@ function toggleBrowserFullscreen() {
|
|||||||
else document.documentElement.requestFullscreen();
|
else document.documentElement.requestFullscreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alle dialogen; Esc gebruikt hun Annuleren-knop (data-esc), zodat bijv. projectgegevens worden teruggezet.
|
||||||
|
const OVERLAYS = '.dlg-overlay.open, #about-overlay.open, #new-overlay.open, #house-project-overlay.open, #wall-project-overlay.open, #cost-settings-overlay.open';
|
||||||
function closeOverlays() {
|
function closeOverlays() {
|
||||||
const open = document.querySelector('.dlg-overlay.open, #about-overlay.open, #new-overlay.open');
|
const open = document.querySelector(OVERLAYS);
|
||||||
if (open) { open.classList.remove('open'); return true; }
|
if (open) {
|
||||||
|
const cancel = open.querySelector('[data-esc]');
|
||||||
|
if (cancel) cancel.click(); else open.classList.remove('open');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return Menubar.close() || UI.closePopovers();
|
return Menubar.close() || UI.closePopovers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,6 +259,12 @@ function wireHotkeys() {
|
|||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
const inField = ['INPUT', 'SELECT', 'TEXTAREA'].includes(document.activeElement.tagName);
|
const inField = ['INPUT', 'SELECT', 'TEXTAREA'].includes(document.activeElement.tagName);
|
||||||
const k = e.key.toLowerCase();
|
const k = e.key.toLowerCase();
|
||||||
|
// Dialoog open: alleen Esc (sluiten); geen sneltoetsen naar de editor erachter. Typen in de dialoog blijft werken.
|
||||||
|
if (document.querySelector(OVERLAYS)) {
|
||||||
|
if (e.key === 'Escape') closeOverlays();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (e.ctrlKey || e.metaKey) {
|
if (e.ctrlKey || e.metaKey) {
|
||||||
const act = { s: () => editorCall('exportDesign'), n: openNewModal, o: () => editorCall('triggerImport'), '\\': togglePanels }[k];
|
const act = { s: () => editorCall('exportDesign'), n: openNewModal, o: () => editorCall('triggerImport'), '\\': togglePanels }[k];
|
||||||
if (act && !e.altKey && !e.shiftKey) { e.preventDefault(); act(); }
|
if (act && !e.altKey && !e.shiftKey) { e.preventDefault(); act(); }
|
||||||
|
|||||||
+17
-10
@@ -9,11 +9,9 @@
|
|||||||
// zodat wire() alleen de eigen knoppen koppelt.
|
// zodat wire() alleen de eigen knoppen koppelt.
|
||||||
|
|
||||||
window.Inspector = (() => {
|
window.Inspector = (() => {
|
||||||
const icon = (n) => `<i class="bi bi-${n}" aria-hidden="true"></i>`;
|
const { icon, esc } = UI;
|
||||||
const big = (label, iconName, attrs, cls) =>
|
const big = UI.btnOf('ins-btn ins-big');
|
||||||
`<button type="button" class="ins-btn ins-big${cls ? ' ' + cls : ''}" ${attrs || ''}>${icon(iconName)}<span>${label}</span></button>`;
|
const small = UI.btnOf('ins-btn');
|
||||||
const small = (label, iconName, attrs, cls) =>
|
|
||||||
`<button type="button" class="ins-btn${cls ? ' ' + cls : ''}" ${attrs || ''}>${icon(iconName)}<span>${label}</span></button>`;
|
|
||||||
const kv = (label, value, unit) =>
|
const kv = (label, value, unit) =>
|
||||||
`<div class="ins-kv"><span>${label}</span><b>${value}${unit ? `<small>${unit}</small>` : ''}</b></div>`;
|
`<div class="ins-kv"><span>${label}</span><b>${value}${unit ? `<small>${unit}</small>` : ''}</b></div>`;
|
||||||
|
|
||||||
@@ -32,9 +30,9 @@ window.Inspector = (() => {
|
|||||||
const keepScroll = el._insKey === scrollKey ? el.scrollTop : 0;
|
const keepScroll = el._insKey === scrollKey ? el.scrollTop : 0;
|
||||||
el._insKey = scrollKey;
|
el._insKey = scrollKey;
|
||||||
el.innerHTML = `<div class="ins-head">
|
el.innerHTML = `<div class="ins-head">
|
||||||
${view.crumbs && view.crumbs.length ? `<div class="ins-crumbs">${view.crumbs.map(c => `<button type="button" class="ins-crumb" ${c.attrs || ''}>${c.label}</button>`).join(icon('chevron-right'))}${icon('chevron-right')}</div>` : ''}
|
${view.crumbs && view.crumbs.length ? `<div class="ins-crumbs">${view.crumbs.map(c => `<button type="button" class="ins-crumb" ${c.attrs || ''}>${esc(c.label)}</button>`).join(icon('chevron-right'))}${icon('chevron-right')}</div>` : ''}
|
||||||
<h2>${view.icon ? icon(view.icon) : ''}<span>${view.title}</span></h2>
|
<h2>${view.icon ? icon(view.icon) : ''}<span>${esc(view.title)}</span></h2>
|
||||||
${view.sub ? `<div class="ins-sub">${view.sub}</div>` : ''}
|
${view.sub ? `<div class="ins-sub">${esc(view.sub)}</div>` : ''}
|
||||||
</div>
|
</div>
|
||||||
${view.top ? `<div class="ins-top">${view.top}</div>` : ''}
|
${view.top ? `<div class="ins-top">${view.top}</div>` : ''}
|
||||||
<div class="ins-sections"></div>`;
|
<div class="ins-sections"></div>`;
|
||||||
@@ -45,16 +43,25 @@ window.Inspector = (() => {
|
|||||||
const isOpen = !collapsible || open.has(sec.key);
|
const isOpen = !collapsible || open.has(sec.key);
|
||||||
node.className = 'ins-section' + (collapsible ? ' ins-collapsible' : '') + (isOpen ? ' open' : '');
|
node.className = 'ins-section' + (collapsible ? ' ins-collapsible' : '') + (isOpen ? ' open' : '');
|
||||||
node.innerHTML = (collapsible ? `<button type="button" class="ins-section-head" aria-expanded="${isOpen}">${sec.icon ? icon(sec.icon) : ''}<span>${sec.label}</span>${icon('chevron-down')}</button>` : '') +
|
node.innerHTML = (collapsible ? `<button type="button" class="ins-section-head" aria-expanded="${isOpen}">${sec.icon ? icon(sec.icon) : ''}<span>${sec.label}</span>${icon('chevron-down')}</button>` : '') +
|
||||||
`<div class="ins-section-body">${groupsHtml(sec.groups ? sec.groups() : [], sec.label)}</div>`;
|
'<div class="ins-section-body"></div>';
|
||||||
host.appendChild(node);
|
host.appendChild(node);
|
||||||
|
// Een dichte sectie wordt pas opgebouwd (en gekoppeld) als hij opengaat.
|
||||||
|
let built = false;
|
||||||
|
const build = () => {
|
||||||
|
if (built) return;
|
||||||
|
built = true;
|
||||||
|
node.querySelector('.ins-section-body').innerHTML = groupsHtml(sec.groups ? sec.groups() : [], sec.label);
|
||||||
|
if (sec.wire) sec.wire(node);
|
||||||
|
};
|
||||||
|
if (isOpen) build();
|
||||||
if (collapsible) node.querySelector('.ins-section-head').addEventListener('click', () => {
|
if (collapsible) node.querySelector('.ins-section-head').addEventListener('click', () => {
|
||||||
const now = !node.classList.contains('open');
|
const now = !node.classList.contains('open');
|
||||||
|
if (now) build();
|
||||||
node.classList.toggle('open', now);
|
node.classList.toggle('open', now);
|
||||||
node.querySelector('.ins-section-head').setAttribute('aria-expanded', now);
|
node.querySelector('.ins-section-head').setAttribute('aria-expanded', now);
|
||||||
if (now) open.add(sec.key); else open.delete(sec.key);
|
if (now) open.add(sec.key); else open.delete(sec.key);
|
||||||
saveOpen();
|
saveOpen();
|
||||||
});
|
});
|
||||||
if (sec.wire) sec.wire(node);
|
|
||||||
});
|
});
|
||||||
el.scrollTop = keepScroll;
|
el.scrollTop = keepScroll;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -23,13 +23,15 @@ window.Menubar = (() => {
|
|||||||
function close() {
|
function close() {
|
||||||
if (openIdx < 0) return false;
|
if (openIdx < 0) return false;
|
||||||
openIdx = -1;
|
openIdx = -1;
|
||||||
|
UI.release(close);
|
||||||
root.querySelectorAll('.mb-menu').forEach(m => m.classList.remove('open'));
|
root.querySelectorAll('.mb-menu').forEach(m => m.classList.remove('open'));
|
||||||
root.querySelectorAll('.mb-drop').forEach(d => { d.innerHTML = ''; });
|
root.querySelectorAll('.mb-drop').forEach(d => { d.innerHTML = ''; });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function openMenu(i) {
|
function openMenu(i) {
|
||||||
close();
|
if (openIdx < 0) UI.claim(close);
|
||||||
|
else { root.querySelectorAll('.mb-menu').forEach(m => m.classList.remove('open')); root.querySelectorAll('.mb-drop').forEach(d => { d.innerHTML = ''; }); }
|
||||||
openIdx = i;
|
openIdx = i;
|
||||||
const wrap = root.querySelectorAll('.mb-menu')[i];
|
const wrap = root.querySelectorAll('.mb-menu')[i];
|
||||||
wrap.classList.add('open');
|
wrap.classList.add('open');
|
||||||
@@ -54,7 +56,6 @@ window.Menubar = (() => {
|
|||||||
const top = e.target.closest('[data-menu]');
|
const top = e.target.closest('[data-menu]');
|
||||||
if (top && openIdx >= 0 && Number(top.dataset.menu) !== openIdx) openMenu(Number(top.dataset.menu));
|
if (top && openIdx >= 0 && Number(top.dataset.menu) !== openIdx) openMenu(Number(top.dataset.menu));
|
||||||
});
|
});
|
||||||
document.addEventListener('click', close);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { init, close };
|
return { init, close };
|
||||||
|
|||||||
+38
-13
@@ -80,8 +80,10 @@ window.UI = (() => {
|
|||||||
const info = (text) => `<div class="ui-info">${icon('info-circle')}<span>${text}</span></div>`;
|
const info = (text) => `<div class="ui-info">${icon('info-circle')}<span>${text}</span></div>`;
|
||||||
const warn = (text) => `<div class="ui-warn">${icon('exclamation-triangle')}<span>${text}</span></div>`;
|
const warn = (text) => `<div class="ui-warn">${icon('exclamation-triangle')}<span>${text}</span></div>`;
|
||||||
const grid = (...cols) => `<div class="ui-grid">${cols.map(c => `<div>${c}</div>`).join('')}</div>`;
|
const grid = (...cols) => `<div class="ui-grid">${cols.map(c => `<div>${c}</div>`).join('')}</div>`;
|
||||||
const button = (label, iconName, attrs, cls) =>
|
// Eén knopbouwer; base = de knopsoort (ui-btn, tb-btn, ins-btn …), cls = extra klassen.
|
||||||
`<button type="button" class="ui-btn${cls ? ' ' + cls : ''}" ${attrs || ''}>${iconName ? icon(iconName) : ''}<span>${label}</span></button>`;
|
const btnOf = (base) => (label, iconName, attrs, cls) =>
|
||||||
|
`<button type="button" class="${base}${cls ? ' ' + cls : ''}" ${attrs || ''}>${iconName ? icon(iconName) : ''}${label ? `<span>${label}</span>` : ''}</button>`;
|
||||||
|
const button = btnOf('ui-btn');
|
||||||
const actions = (...btns) => `<div class="ui-actions">${btns.join('')}</div>`;
|
const actions = (...btns) => `<div class="ui-actions">${btns.join('')}</div>`;
|
||||||
|
|
||||||
const activeTabs = {};
|
const activeTabs = {};
|
||||||
@@ -142,27 +144,50 @@ window.UI = (() => {
|
|||||||
el.querySelectorAll('[data-view]').forEach(b => b.classList.toggle('active', b.dataset.view === mode));
|
el.querySelectorAll('[data-view]').forEach(b => b.classList.toggle('active', b.dataset.view === mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
const tb = (label, iconName, attrs, cls) =>
|
const tb = btnOf('tb-btn');
|
||||||
`<button type="button" class="tb-btn${cls ? ' ' + cls : ''}" ${attrs || ''}>${iconName ? icon(iconName) : ''}${label ? `<span>${label}</span>` : ''}</button>`;
|
|
||||||
const tbSep = () => '<span class="tb-sep"></span>';
|
const tbSep = () => '<span class="tb-sep"></span>';
|
||||||
const viewSeg = (mode) => `<div class="tb-seg" role="group" aria-label="Weergave">${VIEWS.map(v =>
|
const viewSeg = (mode) => `<div class="tb-seg" role="group" aria-label="Weergave">${VIEWS.map(v =>
|
||||||
tb('', v.icon, `data-view="${v.key}" title="${v.label} (${v.key1})"`, v.key === mode ? 'active' : '')).join('')}</div>`;
|
tb('', v.icon, `data-view="${v.key}" title="${v.label} (${v.key1})"`, v.key === mode ? 'active' : '')).join('')}</div>`;
|
||||||
|
|
||||||
// Uitklappaneel onder een knop; sluit bij klik buiten, Esc of een andere popover.
|
// Werkbalk van een editor: [left] … [Lagen, Omgeving] [after] | [2D + 3D, 2D, 3D]. Gedeeld door woning, wand en vloer.
|
||||||
|
// o: left/after (html), onResize, layers (opties voor ViewLayers.createMenu), onSky, beforeView(mode).
|
||||||
|
function editorBar(toolbar, root, o) {
|
||||||
|
toolbar.innerHTML = `${o.left || ''}<div class="tb-spacer"></div><div class="tb-group tb-view"></div>${o.after || ''}${tbSep()}${viewSeg(viewOf(root))}`;
|
||||||
|
const group = toolbar.querySelector('.tb-view');
|
||||||
|
if (o.layers) ViewLayers.createMenu(group, o.layers);
|
||||||
|
if (o.onSky) Skybox.createToggle(group, o.onSky);
|
||||||
|
const bar = {
|
||||||
|
setView(mode) {
|
||||||
|
if (o.beforeView) o.beforeView(mode);
|
||||||
|
setView(root, mode);
|
||||||
|
requestAnimationFrame(o.onResize);
|
||||||
|
},
|
||||||
|
viewState: () => ({ view: viewOf(root) })
|
||||||
|
};
|
||||||
|
toolbar.querySelectorAll('[data-view]').forEach(b => b.addEventListener('click', () => bar.setView(b.dataset.view)));
|
||||||
|
return bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eén uitklapper tegelijk (popover of menu): wie opent, sluit de vorige. claim bij openen, release bij sluiten.
|
||||||
let openPop = null;
|
let openPop = null;
|
||||||
|
function claim(close) { if (openPop && openPop !== close) openPop(); openPop = close; }
|
||||||
|
function release(close) { if (openPop === close) openPop = null; }
|
||||||
|
const closePopovers = () => { if (openPop) { openPop(); return true; } return false; };
|
||||||
|
document.addEventListener('click', () => closePopovers());
|
||||||
|
|
||||||
|
// Uitklappaneel onder een knop; sluit bij klik buiten, Esc of een andere uitklapper.
|
||||||
function popover(btn, panel) {
|
function popover(btn, panel) {
|
||||||
const close = () => { panel.classList.remove('open'); btn.classList.remove('open'); if (openPop === close) openPop = null; };
|
const close = () => { panel.classList.remove('open'); btn.classList.remove('open'); release(close); };
|
||||||
btn.addEventListener('click', (e) => {
|
btn.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const was = panel.classList.contains('open');
|
if (panel.classList.contains('open')) { close(); return; }
|
||||||
if (openPop) openPop();
|
claim(close);
|
||||||
if (!was) { panel.classList.add('open'); btn.classList.add('open'); openPop = close; }
|
panel.classList.add('open');
|
||||||
|
btn.classList.add('open');
|
||||||
});
|
});
|
||||||
panel.addEventListener('click', (e) => e.stopPropagation());
|
panel.addEventListener('click', (e) => e.stopPropagation());
|
||||||
return close;
|
return close;
|
||||||
}
|
}
|
||||||
const closePopovers = () => { if (openPop) { openPop(); return true; } return false; };
|
|
||||||
document.addEventListener('click', () => closePopovers());
|
|
||||||
|
|
||||||
// Kaarten verdelen over kolommen van min. minW breed; telkens in de kortste kolom. Opnieuw aanroepen bij een andere breedte.
|
// Kaarten verdelen over kolommen van min. minW breed; telkens in de kortste kolom. Opnieuw aanroepen bij een andere breedte.
|
||||||
function masonry(el, minW, gap) {
|
function masonry(el, minW, gap) {
|
||||||
@@ -178,6 +203,6 @@ window.UI = (() => {
|
|||||||
cards.forEach(card => cols.reduce((a, b) => b.offsetHeight < a.offsetHeight ? b : a).appendChild(card));
|
cards.forEach(card => cols.reduce((a, b) => b.offsetHeight < a.offsetHeight ? b : a).appendChild(card));
|
||||||
}
|
}
|
||||||
|
|
||||||
return { esc, icon, hint, num, range, onRange, setRange, sel, toggle, color, onColor, text, onText, section, info, warn, grid, button, actions, tabs, wireTabs, specCard, onNum, onSel, onToggle,
|
return { esc, icon, hint, num, range, onRange, setRange, sel, toggle, color, onColor, text, onText, section, info, warn, grid, button, actions, tabs, wireTabs, specCard, onNum, onSel, onToggle, btnOf,
|
||||||
VIEWS, viewOf, setView, tb, tbSep, viewSeg, popover, closePopovers, masonry };
|
VIEWS, viewOf, setView, tb, tbSep, viewSeg, editorBar, popover, claim, release, closePopovers, masonry };
|
||||||
})();
|
})();
|
||||||
|
|||||||
+10
-3
@@ -20,7 +20,7 @@ window.ViewLayers = (() => {
|
|||||||
{ key: 'lampen', label: 'Verlichting', icon: 'lightbulb', phase: 6, hint: 'Gevellampen, spots en binnenverlichting' },
|
{ 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: '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: '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, palen en schoorsteen; uit om van boven in de woning te kijken' },
|
{ 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' }
|
{ key: 'maten', label: 'Maten', icon: 'rulers', hint: 'Maatvoering in de plattegrond' }
|
||||||
];
|
];
|
||||||
const KEYS = LIST.map(l => l.key);
|
const KEYS = LIST.map(l => l.key);
|
||||||
@@ -98,10 +98,17 @@ window.ViewLayers = (() => {
|
|||||||
emit();
|
emit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Alles = ook de editor-eigen schakelaars weer aan.
|
||||||
const all = panel.querySelector('[data-all]');
|
const all = panel.querySelector('[data-all]');
|
||||||
if (all) all.addEventListener('click', () => { cur = ViewLayers.all(); emit(); });
|
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();
|
sync();
|
||||||
return { el: wrap, set: (l) => { cur = normalize(l); sync(); }, toggle: (key) => { cur = Object.assign({}, cur, { [key]: !cur[key] }); emit(); } };
|
return { el: wrap, set: (l) => { cur = normalize(l); sync(); } };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { LIST, KEYS, PHASES, GROUPS, all, normalize, shows, upTo, phaseOf, hideByMaterial, createMenu };
|
return { LIST, KEYS, PHASES, GROUPS, all, normalize, shows, upTo, phaseOf, hideByMaterial, createMenu };
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ window.CostSettings = (() => {
|
|||||||
<button type="button" class="ui-btn" data-cs="export-changes" title="Alleen de velden die afwijken van intern.csv">Wijzigingen downloaden</button>
|
<button type="button" class="ui-btn" data-cs="export-changes" title="Alleen de velden die afwijken van intern.csv">Wijzigingen downloaden</button>
|
||||||
<button type="button" class="ui-btn" data-cs="import">Importeren…</button>
|
<button type="button" class="ui-btn" data-cs="import">Importeren…</button>
|
||||||
<span style="flex:1"></span>
|
<span style="flex:1"></span>
|
||||||
<button type="button" class="ui-btn" data-cs="cancel">Annuleren</button>
|
<button type="button" class="ui-btn" data-cs="cancel" data-esc>Annuleren</button>
|
||||||
<button type="button" class="ui-btn ui-btn-primary" data-cs="save">Toepassen</button>
|
<button type="button" class="ui-btn ui-btn-primary" data-cs="save">Toepassen</button>
|
||||||
</div>
|
</div>
|
||||||
<input type="file" data-file="import" accept=".csv,.json,text/csv,application/json,text/plain" style="display:none">
|
<input type="file" data-file="import" accept=".csv,.json,text/csv,application/json,text/plain" style="display:none">
|
||||||
|
|||||||
@@ -146,8 +146,14 @@ window.ElektraModel = (() => {
|
|||||||
// rooms: leidingen lopen boven een vals plafond, niet tegen de onderkant van een hoog dak.
|
// rooms: leidingen lopen boven een vals plafond, niet tegen de onderkant van een hoog dak.
|
||||||
function makeRouter(units, rooms) {
|
function makeRouter(units, rooms) {
|
||||||
const unitAt = (x, z) => units.find(u => x >= u.rect.x0 - 1 && x <= u.rect.x1 + 1 && z >= u.rect.z0 - 1 && z <= u.rect.z1 + 1) || null;
|
const unitAt = (x, z) => units.find(u => x >= u.rect.x0 - 1 && x <= u.rect.x1 + 1 && z >= u.rect.z0 - 1 && z <= u.rect.z1 + 1) || null;
|
||||||
// Vals plafond (mm boven vloer) van de ruimte op (x,z): undefined = geen ruimte (binnenwand, naad), null = geen vals plafond.
|
// Vals plafond (mm boven vloer) van de ruimte op (x,z): undefined = in een wand of naad (geen gebied), null = geen
|
||||||
const roomPlenum = (x, z) => { const r = rooms && rooms.roomAt ? rooms.roomAt(x, z) : null; return r ? (r.ceilingActive ? r.ceiling : null) : undefined; };
|
// vals plafond (ook een gebied zonder functie: niet het plafond van de buurruimte door de wand heen overnemen).
|
||||||
|
const roomPlenum = (x, z) => {
|
||||||
|
if (!rooms || !rooms.roomAt) return undefined;
|
||||||
|
const r = rooms.roomAt(x, z);
|
||||||
|
if (r) return r.ceilingActive ? r.ceiling : null;
|
||||||
|
return rooms.regionAt && rooms.regionAt(x, z) ? null : undefined;
|
||||||
|
};
|
||||||
// Absoluut; in een binnenwand het laagste valse plafond ernaast: de leiding gaat daar door de wand, niet omhoog.
|
// Absoluut; in een binnenwand het laagste valse plafond ernaast: de leiding gaat daar door de wand, niet omhoog.
|
||||||
const plenumAt = (x, z) => {
|
const plenumAt = (x, z) => {
|
||||||
const u = unitAt(x, z);
|
const u = unitAt(x, z);
|
||||||
@@ -192,7 +198,12 @@ window.ElektraModel = (() => {
|
|||||||
const hOf = (pt, st) => st !== null ? st : roofCeilAt(pt.x, pt.z);
|
const hOf = (pt, st) => st !== null ? st : roofCeilAt(pt.x, pt.z);
|
||||||
const mk = (pt, y) => Object.assign({}, p, pt, { y, wall: null, ceil: true, floor: false, plenum: inPlenum(pt.x, pt.z, y) });
|
const mk = (pt, y) => Object.assign({}, p, pt, { y, wall: null, ceil: true, floor: false, plenum: inPlenum(pt.x, pt.z, y) });
|
||||||
const n = Math.max(2, Math.ceil(len / 100));
|
const n = Math.max(2, Math.ceil(len / 100));
|
||||||
const S = Array.from({ length: n + 1 }, (_, k) => { const pt = at(k / n), st = plenumOn(pt.x, pt.z); return { t: k / n, pt, st, y: hOf(pt, st) }; });
|
// Begin/eind op een wandvlak 50 mm de ruimte in meten, net als top(): anders telt de buurruimte achter de wand mee.
|
||||||
|
const probe = (pt, e) => e.wall && e.wall.n ? { x: pt.x + e.wall.n.x * 50, z: pt.z + e.wall.n.z * 50 } : pt;
|
||||||
|
const S = Array.from({ length: n + 1 }, (_, k) => {
|
||||||
|
const pt = at(k / n), pr = k === 0 ? probe(pt, p) : k === n ? probe(pt, q) : pt, st = plenumOn(pr.x, pr.z);
|
||||||
|
return { t: k / n, pt, st, y: hOf(pt, st) };
|
||||||
|
});
|
||||||
for (let k = 1; k <= n; k++) {
|
for (let k = 1; k <= n; k++) {
|
||||||
const A = S[k - 1], B = S[k];
|
const A = S[k - 1], B = S[k];
|
||||||
if (!same(A.st, B.st)) {
|
if (!same(A.st, B.st)) {
|
||||||
|
|||||||
+12
-17
@@ -159,7 +159,7 @@ const FLOOR_TEMPLATE = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" id="wall-project-cancel" class="ui-btn">Annuleren</button>
|
<button type="button" id="wall-project-cancel" class="ui-btn" data-esc>Annuleren</button>
|
||||||
<button type="button" id="wall-project-save" class="ui-btn ui-btn-primary">Opslaan</button>
|
<button type="button" id="wall-project-save" class="ui-btn ui-btn-primary">Opslaan</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -620,22 +620,21 @@ function mountFloorEditor(container, callbacks) {
|
|||||||
let lastResolvedOpenings = [];
|
let lastResolvedOpenings = [];
|
||||||
let lastChassisData = { wheels: [], outOfBounds: false };
|
let lastChassisData = { wheels: [], outOfBounds: false };
|
||||||
|
|
||||||
const toolbar = document.getElementById('floor-toolbar');
|
|
||||||
toolbar.innerHTML = `<div class="tb-spacer"></div><div class="tb-group tb-view"></div>${UI.tbSep()}${UI.viewSeg('split')}`;
|
|
||||||
toolbar.querySelectorAll('[data-view]').forEach(b => b.addEventListener('click', () => setView(b.dataset.view)));
|
|
||||||
const viewGroup = toolbar.querySelector('.tb-view');
|
|
||||||
ViewLayers.createMenu(viewGroup, {
|
|
||||||
layers: {}, keys: [],
|
|
||||||
extras: [{ key: 'beplating', label: roofMode ? 'Beplating (plafond + dakleer)' : 'Beplating (OSB-plaat)' }],
|
|
||||||
onExtra: (v) => { osbVisible = v.beplating; update3DScene(); }
|
|
||||||
});
|
|
||||||
let skyMode = Skybox.getMode();
|
let skyMode = Skybox.getMode();
|
||||||
const skyEnv = Skybox.createEnvironment(scene);
|
const skyEnv = Skybox.createEnvironment(scene);
|
||||||
const applySky = () => {
|
const applySky = () => {
|
||||||
skyEnv.fitTo(floorGroup3D, 0);
|
skyEnv.fitTo(floorGroup3D, 0);
|
||||||
Skybox.apply(skyMode, { env: skyEnv, grid, controls, sun: dirLight, ambient: floorAmbient, base: { ambient: 0.6, sun: 0.8 }, shadowRoot: floorGroup3D });
|
Skybox.apply(skyMode, { env: skyEnv, grid, controls, sun: dirLight, ambient: floorAmbient, base: { ambient: 0.6, sun: 0.8 }, shadowRoot: floorGroup3D });
|
||||||
};
|
};
|
||||||
Skybox.createToggle(viewGroup, (m) => { skyMode = m; applySky(); });
|
const bar = UI.editorBar(document.getElementById('floor-toolbar'), document.getElementById('floor-app'), {
|
||||||
|
onResize: () => onWindowResize(),
|
||||||
|
layers: {
|
||||||
|
layers: {}, keys: [],
|
||||||
|
extras: [{ key: 'beplating', label: roofMode ? 'Beplating (plafond + dakleer)' : 'Beplating (OSB-plaat)' }],
|
||||||
|
onExtra: (v) => { osbVisible = v.beplating; update3DScene(); }
|
||||||
|
},
|
||||||
|
onSky: (m) => { skyMode = m; applySky(); }
|
||||||
|
});
|
||||||
|
|
||||||
function update3DScene() {
|
function update3DScene() {
|
||||||
if (floorGroup3D) scene.remove(floorGroup3D);
|
if (floorGroup3D) scene.remove(floorGroup3D);
|
||||||
@@ -682,10 +681,6 @@ function mountFloorEditor(container, callbacks) {
|
|||||||
}
|
}
|
||||||
window.addEventListener('resize', onWindowResize);
|
window.addEventListener('resize', onWindowResize);
|
||||||
|
|
||||||
function setView(mode) {
|
|
||||||
UI.setView(document.getElementById('floor-app'), mode);
|
|
||||||
requestAnimationFrame(onWindowResize);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPureState() {
|
function getPureState() {
|
||||||
return {
|
return {
|
||||||
@@ -735,8 +730,8 @@ function mountFloorEditor(container, callbacks) {
|
|||||||
},
|
},
|
||||||
exportDesign,
|
exportDesign,
|
||||||
triggerImport,
|
triggerImport,
|
||||||
setView,
|
setView: bar.setView,
|
||||||
viewState: () => ({ view: UI.viewOf(document.getElementById('floor-app')) }),
|
viewState: bar.viewState,
|
||||||
openProjectProperties,
|
openProjectProperties,
|
||||||
getStateJson: getPureState,
|
getStateJson: getPureState,
|
||||||
loadStateJson: applyPureState
|
loadStateJson: applyPureState
|
||||||
|
|||||||
@@ -49,19 +49,20 @@ window.HouseIfc = (() => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function build(state, layers) {
|
// rough: buizen en dozen bouwen; de plaatfase verzamelt alleen platen en heeft ze niet nodig.
|
||||||
|
function build(state, layers, rough) {
|
||||||
const M = materialsSet();
|
const M = materialsSet();
|
||||||
const parts = HouseModel.computeParts(state, {
|
const parts = HouseModel.computeParts(state, {
|
||||||
wallMaterials: M.wall, floorMaterials: M.floor, roofMaterials: M.roof, elektraMaterials: M.elektra, postMaterials: M.post,
|
wallMaterials: M.wall, floorMaterials: M.floor, roofMaterials: M.roof, elektraMaterials: M.elektra, postMaterials: M.post,
|
||||||
layers: Object.assign(layers, { lampen: false })
|
layers, elektraRough: rough
|
||||||
});
|
});
|
||||||
HouseModel.placeGroups(parts);
|
HouseModel.placeGroups(parts);
|
||||||
return { parts, classify: classifier(M) };
|
return { parts, classify: classifier(M) };
|
||||||
}
|
}
|
||||||
|
|
||||||
function generate(state) {
|
function generate(state) {
|
||||||
const frame = build(state, ViewLayers.upTo(1));
|
const frame = build(state, ViewLayers.upTo(1), true);
|
||||||
const sheet = build(state, ViewLayers.upTo(2));
|
const sheet = build(state, ViewLayers.upTo(2), false);
|
||||||
const parts = frame.parts;
|
const parts = frame.parts;
|
||||||
if (!parts.units.length) throw new Error('geen units in het ontwerp');
|
if (!parts.units.length) throw new Error('geen units in het ontwerp');
|
||||||
const bx0 = Math.min(...parts.rects.map(r => r.x0)), bz1 = Math.max(...parts.rects.map(r => r.z1));
|
const bx0 = Math.min(...parts.rects.map(r => r.x0)), bz1 = Math.max(...parts.rects.map(r => r.z1));
|
||||||
|
|||||||
+13
-2
@@ -1700,7 +1700,7 @@ window.HouseModel = (() => {
|
|||||||
const lightingGroup = build ? LightingModel.build3D(lighting.lamps, o.lightingMaterials || LightingModel.createMaterials(), lighting.indoor) : null;
|
const lightingGroup = build ? LightingModel.build3D(lighting.lamps, o.lightingMaterials || LightingModel.createMaterials(), lighting.indoor) : null;
|
||||||
if (lightingGroup) lightingGroup.visible = shows('lampen');
|
if (lightingGroup) lightingGroup.visible = shows('lampen');
|
||||||
const elektra = ElektraModel.compute(state.settings.elektra, units, interior, lighting, roofEdge, rooms);
|
const elektra = ElektraModel.compute(state.settings.elektra, units, interior, lighting, roofEdge, rooms);
|
||||||
const elektraGroup = build ? ElektraModel.build3D(elektra, o.elektraMaterials, layers, o.elektraRough) : null;
|
const elektraGroup = build ? ElektraModel.build3D(elektra, o.elektraMaterials, layers, o.finishedOnly ? false : o.elektraRough) : null;
|
||||||
if (elektraGroup) elektraGroup.visible = shows('elektra');
|
if (elektraGroup) elektraGroup.visible = shows('elektra');
|
||||||
|
|
||||||
const posts = PostModel.compute(state.settings.roof.posts, units, groundY);
|
const posts = PostModel.compute(state.settings.roof.posts, units, groundY);
|
||||||
@@ -1722,7 +1722,18 @@ window.HouseModel = (() => {
|
|||||||
const fixturesGroup = build ? FixtureModel.build3D(fixtures) : null;
|
const fixturesGroup = build ? FixtureModel.build3D(fixtures) : null;
|
||||||
if (fixturesGroup) fixturesGroup.visible = shows('inrichting');
|
if (fixturesGroup) fixturesGroup.visible = shows('inrichting');
|
||||||
|
|
||||||
return { rects, seams, overlaps: overlappingPairs(rects), units, interior, roofEdge, roofEdgeGroup, lighting, lightingGroup, elektra, elektraGroup, sandwich, sandwichGroup, posts, postsGroup, chimneys, chimneysGroup, rooms, roomsGroup, fixtures, fixturesGroup };
|
const parts = { rects, seams, overlaps: overlappingPairs(rects), units, interior, roofEdge, roofEdgeGroup, lighting, lightingGroup, elektra, elektraGroup, sandwich, sandwichGroup, posts, postsGroup, chimneys, chimneysGroup, rooms, roomsGroup, fixtures, fixturesGroup };
|
||||||
|
if (build && o.finishedOnly) keepFinished(parts, o.wallMaterials);
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// finishedOnly (render-export): alleen wat in de afgewerkte woning te zien is. Naast de buitenste vloerbalken en geen
|
||||||
|
// elektraleidingen (hierboven): geen isolatie, latten en vochtdoek achter de gevel, geen lichtkegels, valse plafonds altijd.
|
||||||
|
function keepFinished(parts, wallMaterials) {
|
||||||
|
if (wallMaterials) allWallPieces(parts).forEach(p => ViewLayers.hideByMaterial(p.group,
|
||||||
|
[wallMaterials.isolatieMaterial, wallMaterials.battenMaterial, wallMaterials.doekMaterial]));
|
||||||
|
if (parts.lightingGroup) parts.lightingGroup.traverse(x => { if (x.userData.beam) x.visible = false; });
|
||||||
|
if (parts.roomsGroup && parts.roomsGroup.userData.ceilings) parts.roomsGroup.userData.ceilings.visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3D-groepen op hun plek in de woning (m); elektra, palen en dakrand staan al in woningcoördinaten.
|
// 3D-groepen op hun plek in de woning (m); elektra, palen en dakrand staan al in woningcoördinaten.
|
||||||
|
|||||||
@@ -762,7 +762,8 @@ window.HousePdf = (() => {
|
|||||||
|
|
||||||
function generate(state) {
|
function generate(state) {
|
||||||
const parts = HouseModel.computeParts(state, {
|
const parts = HouseModel.computeParts(state, {
|
||||||
wallMaterials: WallModel.createMaterials(), floorMaterials: FloorModel.createMaterials(), roofMaterials: RoofModel.createMaterials()
|
wallMaterials: WallModel.createMaterials(), floorMaterials: FloorModel.createMaterials(), roofMaterials: RoofModel.createMaterials(),
|
||||||
|
elektraRough: false
|
||||||
});
|
});
|
||||||
const totals = HouseModel.computeTotals(parts);
|
const totals = HouseModel.computeTotals(parts);
|
||||||
const sheets = sheetList(parts);
|
const sheets = sheetList(parts);
|
||||||
|
|||||||
+70
-94
@@ -41,7 +41,7 @@ const HOUSE_TEMPLATE = `
|
|||||||
</div>
|
</div>
|
||||||
<div class="material-info">Geldt voor elk blad van de woning-PDF. Titel en elementnummer worden per blad automatisch ingevuld.</div>
|
<div class="material-info">Geldt voor elk blad van de woning-PDF. Titel en elementnummer worden per blad automatisch ingevuld.</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" id="house-project-cancel" class="ui-btn">Annuleren</button>
|
<button type="button" id="house-project-cancel" class="ui-btn" data-esc>Annuleren</button>
|
||||||
<button type="button" id="house-project-save" class="ui-btn ui-btn-primary">Opslaan</button>
|
<button type="button" id="house-project-save" class="ui-btn ui-btn-primary">Opslaan</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -198,6 +198,7 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
|
|
||||||
// Structuur: groepen in-/uitklapbaar (onthouden); een groep met het actieve onderdeel of de selectie klapt vanzelf open.
|
// Structuur: groepen in-/uitklapbaar (onthouden); een groep met het actieve onderdeel of de selectie klapt vanzelf open.
|
||||||
const NAV_PREF = 'tectum.nav.closed';
|
const NAV_PREF = 'tectum.nav.closed';
|
||||||
|
let navActiveKey = null;
|
||||||
let navClosed;
|
let navClosed;
|
||||||
try { navClosed = new Set(JSON.parse(localStorage.getItem(NAV_PREF) || 'null') || ['iw', 'rooms', 'fixtures']); } catch (e) { navClosed = new Set(['iw', 'rooms', 'fixtures']); }
|
try { navClosed = new Set(JSON.parse(localStorage.getItem(NAV_PREF) || 'null') || ['iw', 'rooms', 'fixtures']); } catch (e) { navClosed = new Set(['iw', 'rooms', 'fixtures']); }
|
||||||
function renderNav(parts) {
|
function renderNav(parts) {
|
||||||
@@ -262,8 +263,11 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
}
|
}
|
||||||
const tree = document.getElementById('house-nav-tree');
|
const tree = document.getElementById('house-nav-tree');
|
||||||
tree.innerHTML = html;
|
tree.innerHTML = html;
|
||||||
|
// Alleen in beeld schuiven als de selectie of het onderdeel verandert, niet bij elke herberekening.
|
||||||
|
const key = activePart + '|' + JSON.stringify(selection);
|
||||||
const act = tree.querySelector('.hn-item.active');
|
const act = tree.querySelector('.hn-item.active');
|
||||||
if (act) act.scrollIntoView({ block: 'nearest' });
|
if (act && key !== navActiveKey) act.scrollIntoView({ block: 'nearest' });
|
||||||
|
navActiveKey = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('house-nav-tree').addEventListener('click', (e) => {
|
document.getElementById('house-nav-tree').addEventListener('click', (e) => {
|
||||||
@@ -418,23 +422,25 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
['room', 'Ruimte', 'paint-bucket', 'R', 'Functie kiezen en in een ruimte klikken; de ruimte loopt tot de wanden'],
|
['room', 'Ruimte', 'paint-bucket', 'R', 'Functie kiezen en in een ruimte klikken; de ruimte loopt tot de wanden'],
|
||||||
['fixture', 'Inrichting', 'lamp', 'I', 'Stuk kiezen en in de plattegrond klikken (D = draaien)']
|
['fixture', 'Inrichting', 'lamp', 'I', 'Stuk kiezen en in de plattegrond klikken (D = draaien)']
|
||||||
];
|
];
|
||||||
toolbar.innerHTML = `<div class="tb-group tb-tools" role="group" aria-label="Gereedschap">${TOOLS.map(([k, label, ic, key, hint]) =>
|
const bar = UI.editorBar(toolbar, document.getElementById('house-overview'), {
|
||||||
UI.tb(label, ic, `data-ho-tool="${k}" title="${label} (${key}): ${hint}"`)).join('')}</div>
|
left: `<div class="tb-group tb-tools" role="group" aria-label="Gereedschap">${TOOLS.map(([k, label, ic, key, hint]) =>
|
||||||
${UI.tbSep()}${UI.tb('Unit', 'plus-square', 'data-tb="add-unit" title="Unit toevoegen aan de geselecteerde (of laatste) unit"')}
|
UI.tb(label, ic, `data-ho-tool="${k}" title="${label} (${key}): ${hint}"`)).join('')}</div>
|
||||||
<div class="tb-group tb-options" id="ho-tool-options"></div>
|
${UI.tbSep()}${UI.tb('Unit', 'plus-square', 'data-tb="add-unit" title="Unit toevoegen aan de geselecteerde (of laatste) unit"')}
|
||||||
<div class="tb-spacer"></div>
|
<div class="tb-group tb-options" id="ho-tool-options"></div>`,
|
||||||
<div class="tb-group tb-view" id="ho-view-group"></div>
|
after: `<div class="tb-group tb-section" id="ho-section-group">
|
||||||
<div class="tb-group tb-section" id="ho-section-group">
|
${UI.tb('Doorsnede', 'scissors', 'data-tb="section" title="Horizontale doorsnede (X)"')}
|
||||||
${UI.tb('Doorsnede', 'scissors', 'data-tb="section" title="Horizontale doorsnede (X)"')}
|
<span class="tb-range"><input type="range" id="ho-section" min="100" max="4000" step="50" value="${view3D.sectionMM}" aria-label="Hoogte doorsnede"><span id="ho-section-val"></span></span>
|
||||||
<span class="tb-range"><input type="range" id="ho-section" min="100" max="4000" step="50" value="${view3D.sectionMM}" aria-label="Hoogte doorsnede"><span id="ho-section-val"></span></span>
|
</div>
|
||||||
</div>
|
${UI.tb('', 'camera', 'data-tb="render" title="Render: elke lamp en spot als echte lichtbron"')}
|
||||||
${UI.tb('', 'camera', 'data-tb="render" title="Render: elke lamp en spot als echte lichtbron"')}
|
${UI.tb('', 'person-walking', 'data-tb="walk" title="Lopen (L): W/S vooruit/achteruit, A/D opzij, slepen = rondkijken, E = deur/schakelaar"')}
|
||||||
${UI.tb('', 'person-walking', 'data-tb="walk" title="Lopen (L): W/S vooruit/achteruit, A/D opzij, slepen = rondkijken, E = deur/schakelaar"')}
|
${UI.tbSep()}
|
||||||
${UI.tbSep()}
|
${UI.tb('Rapport', 'clipboard-data', 'data-tb="report" title="Rapport in plaats van de plattegrond (4): transport, gewicht, kosten, hout en plaatwerk"')}`,
|
||||||
${UI.tb('Rapport', 'clipboard-data', 'data-tb="report" title="Rapport in plaats van de plattegrond (4): transport, gewicht, kosten, hout en plaatwerk"')}
|
onResize,
|
||||||
${UI.viewSeg(UI.viewOf(document.getElementById('house-overview')))}`;
|
layers: { layers: view3D.layers, onChange: setLayers },
|
||||||
|
onSky: (m) => { skyMode = m; applyRenderMode(); },
|
||||||
|
beforeView: (mode) => { setWalk(false); if (mode === '3d') toggleReport(false); view3D.mode = mode; }
|
||||||
|
});
|
||||||
toolbar.querySelectorAll('[data-ho-tool]').forEach(b => b.addEventListener('click', () => setTool(b.dataset.hoTool)));
|
toolbar.querySelectorAll('[data-ho-tool]').forEach(b => b.addEventListener('click', () => setTool(b.dataset.hoTool)));
|
||||||
toolbar.querySelectorAll('[data-view]').forEach(b => b.addEventListener('click', () => setView(b.dataset.view)));
|
|
||||||
toolbar.addEventListener('click', (e) => {
|
toolbar.addEventListener('click', (e) => {
|
||||||
const b = e.target.closest('[data-tb]');
|
const b = e.target.closest('[data-tb]');
|
||||||
if (!b) return;
|
if (!b) return;
|
||||||
@@ -446,9 +452,6 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
else if (a === 'report') toggleReport();
|
else if (a === 'report') toggleReport();
|
||||||
b.blur();
|
b.blur();
|
||||||
});
|
});
|
||||||
const viewGroup = document.getElementById('ho-view-group');
|
|
||||||
const layerMenu = ViewLayers.createMenu(viewGroup, { layers: view3D.layers, onChange: setLayers });
|
|
||||||
Skybox.createToggle(viewGroup, (m) => { skyMode = m; applyRenderMode(); });
|
|
||||||
function syncToolbar() {
|
function syncToolbar() {
|
||||||
const q = (a) => toolbar.querySelector(`[data-tb="${a}"]`);
|
const q = (a) => toolbar.querySelector(`[data-tb="${a}"]`);
|
||||||
q('section').classList.toggle('active', view3D.section);
|
q('section').classList.toggle('active', view3D.section);
|
||||||
@@ -464,10 +467,10 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
function setLayers(l) {
|
function setLayers(l) {
|
||||||
view3D.layers = ViewLayers.normalize(l);
|
view3D.layers = ViewLayers.normalize(l);
|
||||||
const L = view3D.layers;
|
const L = view3D.layers;
|
||||||
if (selection && ((!L.ruimtes && (selection.type === 'room' || selection.type === 'region')) ||
|
const hidden = selection && ((!L.ruimtes && (selection.type === 'room' || selection.type === 'region')) ||
|
||||||
(!L.inrichting && selection.type === 'fixture') || (!L.elektra && selection.type === 'elektra'))) selection = null;
|
(!L.inrichting && selection.type === 'fixture') || (!L.elektra && selection.type === 'elektra'));
|
||||||
layerMenu.set(L);
|
if (hidden) selection = null;
|
||||||
scheduleRefresh({ keepProps: true });
|
scheduleRefresh({ keepProps: !hidden }); // vervalt de selectie, dan ook het paneel bijwerken
|
||||||
}
|
}
|
||||||
const sectionInput = document.getElementById('ho-section');
|
const sectionInput = document.getElementById('ho-section');
|
||||||
function syncSectionRow() {
|
function syncSectionRow() {
|
||||||
@@ -667,7 +670,7 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
tool = t;
|
tool = t;
|
||||||
drawStart = null;
|
drawStart = null;
|
||||||
document.querySelectorAll('[data-ho-tool]').forEach(b => b.classList.toggle('active', b.dataset.hoTool === t));
|
document.querySelectorAll('[data-ho-tool]').forEach(b => b.classList.toggle('active', b.dataset.hoTool === t));
|
||||||
canvas.style.cursor = t === 'iw' || t === 'fixture' ? 'crosshair' : t === 'room' ? 'cell' : 'default';
|
canvas.style.cursor = toolCursor();
|
||||||
roomHover = null;
|
roomHover = null;
|
||||||
fxHover = null;
|
fxHover = null;
|
||||||
renderToolOptions();
|
renderToolOptions();
|
||||||
@@ -675,6 +678,7 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
drawPlan();
|
drawPlan();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toolCursor = () => tool === 'iw' || tool === 'fixture' ? 'crosshair' : tool === 'room' ? 'cell' : 'default';
|
||||||
function updateHint() {
|
function updateHint() {
|
||||||
document.getElementById('ho-hint').textContent = tool === 'iw'
|
document.getElementById('ho-hint').textContent = tool === 'iw'
|
||||||
? (drawStart ? 'Klik het eindpunt (Esc = annuleren). Snapt op 50 mm, haaks, en op wandvlakken.' : 'Klik het beginpunt van de binnenwand.')
|
? (drawStart ? 'Klik het eindpunt (Esc = annuleren). Snapt op 50 mm, haaks, en op wandvlakken.' : 'Klik het beginpunt van de binnenwand.')
|
||||||
@@ -728,13 +732,13 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
: ElektraModel.snapToWall(parts.elektra, w.x, w.z);
|
: ElektraModel.snapToWall(parts.elektra, w.x, w.z);
|
||||||
drawPlan();
|
drawPlan();
|
||||||
});
|
});
|
||||||
function onElektraDrop() {
|
function onElektraDrop(e) {
|
||||||
if (!elDrag) return;
|
if (!elDrag) return;
|
||||||
const d = elDrag;
|
const d = elDrag;
|
||||||
elDrag = null;
|
elDrag = null;
|
||||||
canvas.style.cursor = tool === 'iw' ? 'crosshair' : tool === 'room' ? 'cell' : 'default';
|
canvas.style.cursor = toolCursor();
|
||||||
if (!d.moved || !d.snap) return;
|
if (!d.moved || !d.snap) return;
|
||||||
suppressClick = true;
|
if (e.target === canvas) suppressClick = true;
|
||||||
if (d.snap.floor) {
|
if (d.snap.floor) {
|
||||||
const rc = parts.rects.find(q => d.snap.x > q.x0 && d.snap.x < q.x1 && d.snap.z > q.z0 && d.snap.z < q.z1);
|
const rc = parts.rects.find(q => d.snap.x > q.x0 && d.snap.x < q.x1 && d.snap.z > q.z0 && d.snap.z < q.z1);
|
||||||
const vw = state.settings.elektra.vloerWcd.find(o => 'vwcd:' + o.id === d.key);
|
const vw = state.settings.elektra.vloerWcd.find(o => 'vwcd:' + o.id === d.key);
|
||||||
@@ -769,13 +773,13 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
if (it) fxDrag.pose = fxPose(it, w.x + fxDrag.dx, w.z + fxDrag.dz, e.altKey);
|
if (it) fxDrag.pose = fxPose(it, w.x + fxDrag.dx, w.z + fxDrag.dz, e.altKey);
|
||||||
drawPlan();
|
drawPlan();
|
||||||
});
|
});
|
||||||
function onFixtureDrop() {
|
function onFixtureDrop(e) {
|
||||||
if (!fxDrag) return;
|
if (!fxDrag) return;
|
||||||
const d = fxDrag;
|
const d = fxDrag;
|
||||||
fxDrag = null;
|
fxDrag = null;
|
||||||
canvas.style.cursor = 'default';
|
canvas.style.cursor = toolCursor();
|
||||||
if (!d.moved || !d.pose) return;
|
if (!d.moved || !d.pose) return;
|
||||||
suppressClick = true;
|
if (e.target === canvas) suppressClick = true;
|
||||||
const fs = fixtureState(d.id);
|
const fs = fixtureState(d.id);
|
||||||
if (fs) placeFixture(fs, d.pose.x, d.pose.z, d.pose.rot);
|
if (fs) placeFixture(fs, d.pose.x, d.pose.z, d.pose.rot);
|
||||||
selection = { type: 'fixture', id: d.id };
|
selection = { type: 'fixture', id: d.id };
|
||||||
@@ -801,13 +805,13 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
if (ch) chDrag.snap = ChimneyModel.snapToFacade(parts.rects, w.x, w.z, ch.w);
|
if (ch) chDrag.snap = ChimneyModel.snapToFacade(parts.rects, w.x, w.z, ch.w);
|
||||||
drawPlan();
|
drawPlan();
|
||||||
});
|
});
|
||||||
window.addEventListener('mouseup', () => {
|
window.addEventListener('mouseup', (e) => {
|
||||||
if (!chDrag) return;
|
if (!chDrag) return;
|
||||||
const d = chDrag;
|
const d = chDrag;
|
||||||
chDrag = null;
|
chDrag = null;
|
||||||
canvas.style.cursor = 'default';
|
canvas.style.cursor = toolCursor();
|
||||||
if (!d.moved || !d.snap) return;
|
if (!d.moved || !d.snap) return;
|
||||||
suppressClick = true;
|
if (e.target === canvas) suppressClick = true;
|
||||||
const cs = (state.chimneys || []).find(c => c.id === d.id);
|
const cs = (state.chimneys || []).find(c => c.id === d.id);
|
||||||
if (cs) Object.assign(cs, { unitId: d.snap.unitId, side: d.snap.side, u: d.snap.u });
|
if (cs) Object.assign(cs, { unitId: d.snap.unitId, side: d.snap.side, u: d.snap.u });
|
||||||
const u = parts.units.find(q => q.id === d.snap.unitId), wl = u && u.walls.find(q => q.side === d.snap.side), pc = wl && wl.pieces.find(p => !p.baseY);
|
const u = parts.units.find(q => q.id === d.snap.unitId), wl = u && u.walls.find(q => q.side === d.snap.side), pc = wl && wl.pieces.find(p => !p.baseY);
|
||||||
@@ -887,11 +891,12 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
if (selection.type === 'fixture') removeFixture(selection.id);
|
if (selection.type === 'fixture') removeFixture(selection.id);
|
||||||
else if (selection.type === 'room') removeRoom(selection.id);
|
else if (selection.type === 'room') removeRoom(selection.id);
|
||||||
else if (selection.type === 'elektra' && selection.id !== 'mk') removeSelectedElektra();
|
else if (selection.type === 'elektra' && selection.id !== 'mk') removeSelectedElektra();
|
||||||
else if (selection.type === 'iw') {
|
else if (selection.type === 'iw') removeInteriorWall(selection.id);
|
||||||
state.interiorWalls = state.interiorWalls.filter(w => w.id !== selection.id);
|
}
|
||||||
selection = null;
|
function removeInteriorWall(id) {
|
||||||
refresh();
|
state.interiorWalls = state.interiorWalls.filter(w => w.id !== id);
|
||||||
}
|
selection = null;
|
||||||
|
refresh();
|
||||||
}
|
}
|
||||||
function onKey(e) {
|
function onKey(e) {
|
||||||
if (['INPUT', 'SELECT', 'TEXTAREA'].includes(document.activeElement.tagName)) return;
|
if (['INPUT', 'SELECT', 'TEXTAREA'].includes(document.activeElement.tagName)) return;
|
||||||
@@ -962,9 +967,10 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
plan2D.cz = (canvas.height / 2 - (pan.oy + e.clientY - pan.sy)) / view.scale;
|
plan2D.cz = (canvas.height / 2 - (pan.oy + e.clientY - pan.sy)) / view.scale;
|
||||||
drawPlan();
|
drawPlan();
|
||||||
}
|
}
|
||||||
function onPanEnd() {
|
// Na een sleep de klik op de plattegrond overslaan, maar alleen als die klik er ook komt (loslaten op de plattegrond).
|
||||||
|
function onPanEnd(e) {
|
||||||
if (!pan) return;
|
if (!pan) return;
|
||||||
if (pan.moved) { canvas.style.cursor = 'default'; if (!pan.mid) suppressClick = true; }
|
if (pan.moved) { canvas.style.cursor = toolCursor(); if (!pan.mid && e.target === canvas) suppressClick = true; }
|
||||||
pan = null;
|
pan = null;
|
||||||
}
|
}
|
||||||
window.addEventListener('mousemove', onPanMove);
|
window.addEventListener('mousemove', onPanMove);
|
||||||
@@ -1339,24 +1345,12 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
const stucColors = Object.fromEntries(Object.entries(WallModel.STUC_COLORS).map(([k, c]) => [k, c.label]));
|
const stucColors = Object.fromEntries(Object.entries(WallModel.STUC_COLORS).map(([k, c]) => [k, c.label]));
|
||||||
const swatchOf = (hex) => `<div class="ins-swatch" style="background:${hex}"></div>`;
|
const swatchOf = (hex) => `<div class="ins-swatch" style="background:${hex}"></div>`;
|
||||||
function wireButtons(body) {
|
function wireButtons(body) {
|
||||||
body.querySelectorAll('[data-ho-tool]').forEach(b => b.addEventListener('click', () => setTool(b.dataset.hoTool)));
|
|
||||||
body.querySelectorAll('[data-go]').forEach(b => b.addEventListener('click', () => showPart(b.dataset.go)));
|
body.querySelectorAll('[data-go]').forEach(b => b.addEventListener('click', () => showPart(b.dataset.go)));
|
||||||
body.querySelectorAll('[data-ho-act]').forEach(b => b.addEventListener('click', () => {
|
body.querySelectorAll('[data-ho-act]').forEach(b => b.addEventListener('click', () => {
|
||||||
const a = b.dataset.hoAct;
|
const a = b.dataset.hoAct;
|
||||||
if (a === 'add-unit') addUnit();
|
if (a === 'add-seams') { HouseModel.ensureSeamWalls(state, b.dataset.unit); refresh(); }
|
||||||
else if (a === 'project') openProjectProperties();
|
|
||||||
else if (a === 'save') exportDesign();
|
|
||||||
else if (a === 'dxf') exportDxf();
|
|
||||||
else if (a === 'ifc') exportIfc();
|
|
||||||
else if (a === 'obj') exportObj();
|
|
||||||
else if (a === 'costs') CostSettings.open();
|
|
||||||
else if (a === 'add-seams') { HouseModel.ensureSeamWalls(state, b.dataset.unit); refresh(); }
|
|
||||||
else if (a === 'remove-unit') removeUnit(b.dataset.unit);
|
else if (a === 'remove-unit') removeUnit(b.dataset.unit);
|
||||||
else if (a === 'remove-iw') {
|
else if (a === 'remove-iw') removeInteriorWall(Number(b.dataset.iw));
|
||||||
state.interiorWalls = state.interiorWalls.filter(w => w.id !== Number(b.dataset.iw));
|
|
||||||
selection = null;
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2295,12 +2289,6 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
syncToolbar();
|
syncToolbar();
|
||||||
requestAnimationFrame(onResize);
|
requestAnimationFrame(onResize);
|
||||||
}
|
}
|
||||||
function setView(mode) {
|
|
||||||
setWalk(false);
|
|
||||||
UI.setView(overviewEl, view3D.mode = mode);
|
|
||||||
requestAnimationFrame(onResize);
|
|
||||||
}
|
|
||||||
|
|
||||||
const esc = UI.esc;
|
const esc = UI.esc;
|
||||||
function stackedBars(title, iconName, series, rows, fmtVal, note) {
|
function stackedBars(title, iconName, series, rows, fmtVal, note) {
|
||||||
const max = Math.max(1, ...rows.map(r => r.total));
|
const max = Math.max(1, ...rows.map(r => r.total));
|
||||||
@@ -2442,28 +2430,24 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
parts.units.forEach(u => houseGroup.add(u.roof.group));
|
parts.units.forEach(u => houseGroup.add(u.roof.group));
|
||||||
if (parts.roofEdgeGroup) houseGroup.add(parts.roofEdgeGroup);
|
if (parts.roofEdgeGroup) houseGroup.add(parts.roofEdgeGroup);
|
||||||
if (parts.postsGroup) houseGroup.add(parts.postsGroup);
|
if (parts.postsGroup) houseGroup.add(parts.postsGroup);
|
||||||
if (parts.chimneysGroup) houseGroup.add(parts.chimneysGroup);
|
|
||||||
}
|
}
|
||||||
|
// Alles wat binnen de wanden of onder het plafond zit, snijdt mee met de doorsnede (anders zweeft het boven de snede).
|
||||||
|
const clipGroup = (g) => {
|
||||||
|
if (!g) return;
|
||||||
|
g.traverse(o => {
|
||||||
|
if (!o.material || (o.material.clippingPlanes || []).length === clip.length) return;
|
||||||
|
o.material.clippingPlanes = clip;
|
||||||
|
o.material.needsUpdate = true;
|
||||||
|
});
|
||||||
|
houseGroup.add(g);
|
||||||
|
};
|
||||||
if (parts.sandwichGroup) houseGroup.add(parts.sandwichGroup);
|
if (parts.sandwichGroup) houseGroup.add(parts.sandwichGroup);
|
||||||
if (parts.lightingGroup && !section) houseGroup.add(parts.lightingGroup);
|
if (parts.lightingGroup && !section) houseGroup.add(parts.lightingGroup);
|
||||||
if (parts.elektraGroup) houseGroup.add(parts.elektraGroup);
|
clipGroup(parts.elektraGroup);
|
||||||
if (parts.roomsGroup) {
|
clipGroup(parts.chimneysGroup); // laag Gevelbekleding, net als in de plattegrond
|
||||||
parts.roomsGroup.userData.ceilings.visible = !section;
|
if (parts.roomsGroup) parts.roomsGroup.userData.ceilings.visible = !section;
|
||||||
parts.roomsGroup.traverse(o => {
|
clipGroup(parts.roomsGroup);
|
||||||
if (!o.material || (o.material.clippingPlanes || []).length === clip.length) return;
|
clipGroup(parts.fixturesGroup);
|
||||||
o.material.clippingPlanes = clip;
|
|
||||||
o.material.needsUpdate = true;
|
|
||||||
});
|
|
||||||
houseGroup.add(parts.roomsGroup);
|
|
||||||
}
|
|
||||||
if (parts.fixturesGroup) {
|
|
||||||
parts.fixturesGroup.traverse(o => {
|
|
||||||
if (!o.material || (o.material.clippingPlanes || []).length === clip.length) return;
|
|
||||||
o.material.clippingPlanes = clip;
|
|
||||||
o.material.needsUpdate = true;
|
|
||||||
});
|
|
||||||
houseGroup.add(parts.fixturesGroup);
|
|
||||||
}
|
|
||||||
scene.add(houseGroup);
|
scene.add(houseGroup);
|
||||||
|
|
||||||
const bx0 = Math.min(...parts.rects.map(r => r.x0)), bx1 = Math.max(...parts.rects.map(r => r.x1));
|
const bx0 = Math.min(...parts.rects.map(r => r.x0)), bx1 = Math.max(...parts.rects.map(r => r.x1));
|
||||||
@@ -2777,7 +2761,7 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
refresh,
|
refresh,
|
||||||
select,
|
select,
|
||||||
setTool,
|
setTool,
|
||||||
setView,
|
setView: bar.setView,
|
||||||
toggleReport: () => toggleReport(),
|
toggleReport: () => toggleReport(),
|
||||||
toggleSection,
|
toggleSection,
|
||||||
fitView: () => fitPlan(),
|
fitView: () => fitPlan(),
|
||||||
@@ -2863,7 +2847,7 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
const exportIfc = () => exportAs('ifc', 'BIM-export (IFC)', (f) => HouseIfc.generate(state).save(f), 'IFC4: balken, platen en elektra');
|
const exportIfc = () => exportAs('ifc', 'BIM-export (IFC)', (f) => HouseIfc.generate(state).save(f), 'IFC4: balken, platen en elektra');
|
||||||
|
|
||||||
// Render-export (sfeerimpressie): alleen wat in de afgewerkte woning te zien is, los van de gekozen lagen, doorsnede
|
// Render-export (sfeerimpressie): alleen wat in de afgewerkte woning te zien is, los van de gekozen lagen, doorsnede
|
||||||
// en weergave-opties; geen buizen, dozen, binnenste vloerbalken, latten en isolatie achter de gevel, lichtkegels.
|
// en weergave-opties (HouseModel.computeParts met finishedOnly).
|
||||||
// Lampen als echte lichtbronnen via een Blender-script in de ZIP.
|
// Lampen als echte lichtbronnen via een Blender-script in de ZIP.
|
||||||
// Eigen materiaalsets (steen, riet, rabat en tegels komen uit gedeelde caches); maaiveld onder het midden = oorsprong.
|
// Eigen materiaalsets (steen, riet, rabat en tegels komen uit gedeelde caches); maaiveld onder het midden = oorsprong.
|
||||||
const exportObj = () => exportAs('zip', 'render-export (OBJ + texturen + lampen, ZIP)', (filename) => {
|
const exportObj = () => exportAs('zip', 'render-export (OBJ + texturen + lampen, ZIP)', (filename) => {
|
||||||
@@ -2873,13 +2857,9 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
const full = HouseModel.computeParts(state, {
|
const full = HouseModel.computeParts(state, {
|
||||||
wallMaterials, floorMaterials: FloorModel.createMaterials(), roofMaterials, lightingMaterials: LightingModel.createMaterials(),
|
wallMaterials, floorMaterials: FloorModel.createMaterials(), roofMaterials, lightingMaterials: LightingModel.createMaterials(),
|
||||||
sandwichMaterials: SandwichModel.createMaterials(), postMaterials: PostModel.createMaterials(), chimneyMaterials: ChimneyModel.createMaterials(),
|
sandwichMaterials: SandwichModel.createMaterials(), postMaterials: PostModel.createMaterials(), chimneyMaterials: ChimneyModel.createMaterials(),
|
||||||
elektraMaterials: ElektraModel.createMaterials(), layers: ViewLayers.all(), elektraRough: false, finishedOnly: true
|
elektraMaterials: ElektraModel.createMaterials(), layers: ViewLayers.all(), finishedOnly: true
|
||||||
});
|
});
|
||||||
HouseModel.placeGroups(full);
|
HouseModel.placeGroups(full);
|
||||||
HouseModel.allWallPieces(full).forEach(p => ViewLayers.hideByMaterial(p.group,
|
|
||||||
[wallMaterials.isolatieMaterial, wallMaterials.battenMaterial, wallMaterials.doekMaterial]));
|
|
||||||
if (full.lightingGroup) full.lightingGroup.traverse(o => { if (o.userData.beam) o.visible = false; });
|
|
||||||
if (full.roomsGroup && full.roomsGroup.userData.ceilings) full.roomsGroup.userData.ceilings.visible = true;
|
|
||||||
const roots = full.units.map(u => u.floorGroup).concat(HouseModel.allWallPieces(full).map(p => p.group), full.units.map(u => u.roof.group),
|
const roots = full.units.map(u => u.floorGroup).concat(HouseModel.allWallPieces(full).map(p => p.group), full.units.map(u => u.roof.group),
|
||||||
[full.roofEdgeGroup, full.postsGroup, full.chimneysGroup, full.sandwichGroup, full.lightingGroup, full.elektraGroup, full.roomsGroup, full.fixturesGroup]).filter(Boolean);
|
[full.roofEdgeGroup, full.postsGroup, full.chimneysGroup, full.sandwichGroup, full.lightingGroup, full.elektraGroup, full.roomsGroup, full.fixturesGroup]).filter(Boolean);
|
||||||
const bx0 = Math.min(...full.rects.map(r => r.x0)), bx1 = Math.max(...full.rects.map(r => r.x1));
|
const bx0 = Math.min(...full.rects.map(r => r.x0)), bx1 = Math.max(...full.rects.map(r => r.x1));
|
||||||
@@ -2932,6 +2912,7 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
return (...args) => { if (activeEditor && typeof activeEditor[fn] === 'function') return activeEditor[fn](...args); };
|
return (...args) => { if (activeEditor && typeof activeEditor[fn] === 'function') return activeEditor[fn](...args); };
|
||||||
}
|
}
|
||||||
const has = (fn) => !!(activeEditor && typeof activeEditor[fn] === 'function');
|
const has = (fn) => !!(activeEditor && typeof activeEditor[fn] === 'function');
|
||||||
|
// Acties die naar de actieve onderdeel-editor gaan; de API hieronder wordt uit deze lijst opgebouwd.
|
||||||
const FORWARDED = ['mirrorHorizontal', 'setView', 'toggleReport', 'toggleSection', 'fitView', 'toggleWalk'];
|
const FORWARDED = ['mirrorHorizontal', 'setView', 'toggleReport', 'toggleSection', 'fitView', 'toggleWalk'];
|
||||||
|
|
||||||
function onPartKey(e) {
|
function onPartKey(e) {
|
||||||
@@ -2953,12 +2934,7 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
exportIfc,
|
exportIfc,
|
||||||
exportObj,
|
exportObj,
|
||||||
triggerImport,
|
triggerImport,
|
||||||
mirrorHorizontal: forward('mirrorHorizontal'),
|
...Object.fromEntries(FORWARDED.map(fn => [fn, forward(fn)])),
|
||||||
setView: forward('setView'),
|
|
||||||
toggleReport: forward('toggleReport'),
|
|
||||||
toggleSection: forward('toggleSection'),
|
|
||||||
fitView: forward('fitView'),
|
|
||||||
toggleWalk: forward('toggleWalk'),
|
|
||||||
viewState: () => forward('viewState')() || {},
|
viewState: () => forward('viewState')() || {},
|
||||||
// Doorgestuurde acties bestaan alleen als de actieve onderdeel-editor ze kent (bijv. Rapport alleen in de woning).
|
// Doorgestuurde acties bestaan alleen als de actieve onderdeel-editor ze kent (bijv. Rapport alleen in de woning).
|
||||||
can: (fn) => !FORWARDED.includes(fn) || has(fn),
|
can: (fn) => !FORWARDED.includes(fn) || has(fn),
|
||||||
|
|||||||
+16
-21
@@ -220,7 +220,7 @@ const WALL_TEMPLATE = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" id="wall-project-cancel" class="ui-btn">Annuleren</button>
|
<button type="button" id="wall-project-cancel" class="ui-btn" data-esc>Annuleren</button>
|
||||||
<button type="button" id="wall-project-save" class="ui-btn ui-btn-primary">Opslaan</button>
|
<button type="button" id="wall-project-save" class="ui-btn ui-btn-primary">Opslaan</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -335,11 +335,6 @@ function mountWallEditor(container, callbacks) {
|
|||||||
updateAll();
|
updateAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setView(mode) {
|
|
||||||
UI.setView(document.getElementById('app-container'), mode);
|
|
||||||
requestAnimationFrame(onWindowResize);
|
|
||||||
}
|
|
||||||
|
|
||||||
const PROJECT_PROP_IDS = ['inp-proj-naam', 'inp-proj-plaats', 'inp-proj-straat', 'inp-proj-nummer',
|
const PROJECT_PROP_IDS = ['inp-proj-naam', 'inp-proj-plaats', 'inp-proj-straat', 'inp-proj-nummer',
|
||||||
'inp-proj-tekenaar', 'inp-proj-titel', 'inp-proj-element', 'inp-proj-opmerking'];
|
'inp-proj-tekenaar', 'inp-proj-titel', 'inp-proj-element', 'inp-proj-opmerking'];
|
||||||
let projectPropsSnapshot = null;
|
let projectPropsSnapshot = null;
|
||||||
@@ -1299,25 +1294,25 @@ function mountWallEditor(container, callbacks) {
|
|||||||
|
|
||||||
let spaanplaatVisible = true;
|
let spaanplaatVisible = true;
|
||||||
let buitenVisible = true;
|
let buitenVisible = true;
|
||||||
const toolbar = document.getElementById('wall-toolbar');
|
|
||||||
toolbar.innerHTML = `<div class="tb-group tb-tools">${UI.tb('Spiegelen', 'symmetry-vertical', 'data-wt="mirror" title="Horizontaal spiegelen (Shift+H)"')}</div>
|
|
||||||
<div class="tb-spacer"></div><div class="tb-group tb-view"></div>${UI.tbSep()}${UI.viewSeg('split')}`;
|
|
||||||
toolbar.querySelector('[data-wt="mirror"]').addEventListener('click', () => mirrorHorizontal());
|
|
||||||
toolbar.querySelectorAll('[data-view]').forEach(b => b.addEventListener('click', () => setView(b.dataset.view)));
|
|
||||||
const viewGroup = toolbar.querySelector('.tb-view');
|
|
||||||
ViewLayers.createMenu(viewGroup, {
|
|
||||||
layers, keys: ['regelwerk', 'beplating', 'doek', 'latwerk', 'gevel', 'kozijnen', 'afwerking'],
|
|
||||||
onChange: (l) => { layers = l; update3DScene(); },
|
|
||||||
extras: [{ key: 'binnen', label: 'Beplating binnenzijde' }, { key: 'buiten', label: 'Beplating buitenzijde' }],
|
|
||||||
onExtra: (v) => { spaanplaatVisible = v.binnen; buitenVisible = v.buiten; update3DScene(); }
|
|
||||||
});
|
|
||||||
let skyMode = Skybox.getMode();
|
let skyMode = Skybox.getMode();
|
||||||
const skyEnv = Skybox.createEnvironment(scene);
|
const skyEnv = Skybox.createEnvironment(scene);
|
||||||
const applySky = () => {
|
const applySky = () => {
|
||||||
skyEnv.fitTo(wallGroup3D, 0);
|
skyEnv.fitTo(wallGroup3D, 0);
|
||||||
Skybox.apply(skyMode, { env: skyEnv, grid, controls, sun: dirLight, ambient, base: { ambient: 0.6, sun: 0.8 }, shadowRoot: wallGroup3D });
|
Skybox.apply(skyMode, { env: skyEnv, grid, controls, sun: dirLight, ambient, base: { ambient: 0.6, sun: 0.8 }, shadowRoot: wallGroup3D });
|
||||||
};
|
};
|
||||||
Skybox.createToggle(viewGroup, (m) => { skyMode = m; applySky(); });
|
const toolbar = document.getElementById('wall-toolbar');
|
||||||
|
const bar = UI.editorBar(toolbar, document.getElementById('app-container'), {
|
||||||
|
left: `<div class="tb-group tb-tools">${UI.tb('Spiegelen', 'symmetry-vertical', 'data-wt="mirror" title="Horizontaal spiegelen (Shift+H)"')}</div>`,
|
||||||
|
onResize: onWindowResize,
|
||||||
|
layers: {
|
||||||
|
layers, keys: ['regelwerk', 'beplating', 'doek', 'latwerk', 'gevel', 'kozijnen', 'afwerking'],
|
||||||
|
onChange: (l) => { layers = l; update3DScene(); },
|
||||||
|
extras: [{ key: 'binnen', label: 'Beplating binnenzijde' }, { key: 'buiten', label: 'Beplating buitenzijde' }],
|
||||||
|
onExtra: (v) => { spaanplaatVisible = v.binnen; buitenVisible = v.buiten; update3DScene(); }
|
||||||
|
},
|
||||||
|
onSky: (m) => { skyMode = m; applySky(); }
|
||||||
|
});
|
||||||
|
toolbar.querySelector('[data-wt="mirror"]').addEventListener('click', () => mirrorHorizontal());
|
||||||
|
|
||||||
let lastWallStats = null;
|
let lastWallStats = null;
|
||||||
|
|
||||||
@@ -1418,8 +1413,8 @@ function mountWallEditor(container, callbacks) {
|
|||||||
exportDesign,
|
exportDesign,
|
||||||
triggerImport,
|
triggerImport,
|
||||||
mirrorHorizontal,
|
mirrorHorizontal,
|
||||||
setView,
|
setView: bar.setView,
|
||||||
viewState: () => ({ view: UI.viewOf(document.getElementById('app-container')) }),
|
viewState: bar.viewState,
|
||||||
openProjectProperties,
|
openProjectProperties,
|
||||||
getStateJson: getPureState,
|
getStateJson: getPureState,
|
||||||
loadStateJson: applyPureState
|
loadStateJson: applyPureState
|
||||||
|
|||||||
Reference in New Issue
Block a user