v0.4.1: UI opgeschoond — tabbladen, iconen, uniforme labels, T-cyclus
- core/ui-kit.js: gedeelde velden/schakelaars/tabs/specificatiekaarten - Bootstrap Icons lokaal (css/vendor, font ingebed) - Woning- en unit-instellingen in tabbladen, labels gestandaardiseerd - Specificaties onder 3D als kaarten - T: standaard -> 3D groot -> alleen render -> standaard Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c690e72d13
commit
3591fad220
@@ -151,6 +151,14 @@ assets/ logo (preloader) + bestemmingsmap voor toekomstige
|
|||||||
t.o.v. het bovenaanzicht (boven = noord): voor = noord, achter = zuid,
|
t.o.v. het bovenaanzicht (boven = noord): voor = noord, achter = zuid,
|
||||||
links = west, rechts = oost (intern blijven de oude sleutels). Oude unit-JSON's kunnen in
|
links = west, rechts = oost (intern blijven de oude sleutels). Oude unit-JSON's kunnen in
|
||||||
een woning worden geïmporteerd. Units kunnen (nog) niet gedraaid worden.
|
een woning worden geïmporteerd. Units kunnen (nog) niet gedraaid worden.
|
||||||
|
- **UI-opschoning (v0.4.1).** Gedeelde UI-kit (js/core/ui-kit.js, window.UI):
|
||||||
|
velden, schakelaars, secties, tabbladen, specificatie-kaarten en iconen
|
||||||
|
(Bootstrap Icons 1.11.3, MIT, lokaal in css/vendor met ingebed lettertype,
|
||||||
|
werkt via file://). Woning-instellingen staan in tabbladen (Algemeen, Gevel,
|
||||||
|
Regelwerk, Dakrand, Verlichting); unit-eigenschappen ook (Afmetingen,
|
||||||
|
Koppeling, Dak). Labels: zelfstandig naamwoord, geen dubbele punt, uitleg
|
||||||
|
in een hint (i-icoon) i.p.v. tussen haakjes. Sneltoets T wisselt in drie
|
||||||
|
stappen: standaard -> 3D groot -> alleen de render -> standaard.
|
||||||
- **Mijlpaal v0.4 — gevelopbouw.** Samenvatting van de v0.3.11–0.3.19-iteraties:
|
- **Mijlpaal v0.4 — gevelopbouw.** Samenvatting van de v0.3.11–0.3.19-iteraties:
|
||||||
kruislat-latwerk op vochtdoek (richting gevelbekleding globaal), lampen op de
|
kruislat-latwerk op vochtdoek (richting gevelbekleding globaal), lampen op de
|
||||||
afgewerkte gevel (+52 mm), logo-watermerk op het doek, trasraam (cement-
|
afgewerkte gevel (+52 mm), logo-watermerk op het doek, trasraam (cement-
|
||||||
|
|||||||
+92
@@ -544,3 +544,95 @@ html, body { height: 100%; }
|
|||||||
.vf-menu.open { display: block; }
|
.vf-menu.open { display: block; }
|
||||||
.vf-item { display: flex; align-items: center; gap: 8px; color: #eee; font-size: 13px; padding: 4px 0; cursor: pointer; user-select: none; }
|
.vf-item { display: flex; align-items: center; gap: 8px; color: #eee; font-size: 13px; padding: 4px 0; cursor: pointer; user-select: none; }
|
||||||
.vf-item input { accent-color: var(--primary-on-dark); width: 15px; height: 15px; cursor: pointer; }
|
.vf-item input { accent-color: var(--primary-on-dark); width: 15px; height: 15px; cursor: pointer; }
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
UI-KIT (core/ui-kit.js): velden, schakelaars, secties, tabbladen,
|
||||||
|
specificatie-kaarten. Iconen: Bootstrap Icons (css/vendor).
|
||||||
|
============================================================ */
|
||||||
|
.bi { line-height: 1; vertical-align: -0.125em; }
|
||||||
|
.ui-section { margin-bottom: 14px; }
|
||||||
|
.ui-section h2, .ui-spec-card h2 {
|
||||||
|
display: flex; align-items: center; gap: 7px; margin: 0 0 8px; font-size: 12px; font-weight: 600;
|
||||||
|
color: var(--primary); text-transform: uppercase; letter-spacing: .6px;
|
||||||
|
}
|
||||||
|
.ui-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 28px; }
|
||||||
|
.ui-field { display: flex; justify-content: space-between; align-items: center; gap: 10px; min-height: 28px; }
|
||||||
|
.ui-field label { font-size: 12.5px; color: var(--text); display: flex; align-items: center; gap: 5px; }
|
||||||
|
.ui-field.is-disabled label { color: var(--text-faint); }
|
||||||
|
.ui-control { display: flex; align-items: center; flex: none; }
|
||||||
|
.ui-control input[type=number], .ui-control select {
|
||||||
|
width: 118px; background: var(--surface); border: 1px solid var(--border); color: var(--text);
|
||||||
|
padding: 4px 6px; border-radius: 4px; font-size: 12.5px; font-family: inherit;
|
||||||
|
}
|
||||||
|
.ui-control input[type=number] { text-align: right; }
|
||||||
|
.ui-control input:focus, .ui-control select:focus { border-color: var(--primary); outline: none; box-shadow: 0 0 0 2px var(--primary-soft); }
|
||||||
|
.ui-control input:disabled { opacity: .55; background: var(--border-soft); cursor: not-allowed; }
|
||||||
|
.ui-unit { width: 26px; margin-left: 5px; font-size: 11px; color: var(--text-faint); }
|
||||||
|
.ui-hint { color: var(--text-faint); font-size: 11px; cursor: help; }
|
||||||
|
.ui-hint:hover { color: var(--primary); }
|
||||||
|
.ui-switch { position: relative; display: inline-block; width: 36px; height: 20px; margin-right: 82px; }
|
||||||
|
.ui-switch input { opacity: 0; width: 0; height: 0; }
|
||||||
|
.ui-switch span { position: absolute; inset: 0; background: #c9d1db; border-radius: 20px; cursor: pointer; transition: background .15s; }
|
||||||
|
.ui-switch span::before { content: ''; position: absolute; width: 14px; height: 14px; left: 3px; top: 3px; background: #fff; border-radius: 50%; transition: transform .15s; }
|
||||||
|
.ui-switch input:checked + span { background: var(--primary); }
|
||||||
|
.ui-switch input:checked + span::before { transform: translateX(16px); }
|
||||||
|
.ui-switch input:focus-visible + span { box-shadow: 0 0 0 2px var(--primary-soft); }
|
||||||
|
.ui-info, .ui-warn { display: flex; gap: 8px; align-items: flex-start; font-size: 12px; line-height: 1.5; border-radius: 4px; padding: 7px 10px; margin: 6px 0 10px; }
|
||||||
|
.ui-info { color: var(--text-muted); background: var(--surface); border: 1px solid var(--border-soft); }
|
||||||
|
.ui-warn { color: var(--danger); background: var(--danger-soft); border: 1px solid var(--danger); }
|
||||||
|
.ui-info .bi, .ui-warn .bi { margin-top: 2px; }
|
||||||
|
.ui-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
|
||||||
|
.ui-btn {
|
||||||
|
display: inline-flex; align-items: center; gap: 6px; background: var(--surface); border: 1px solid var(--border);
|
||||||
|
color: var(--text); padding: 5px 11px; border-radius: 4px; font-size: 12px; cursor: pointer; font-family: inherit;
|
||||||
|
}
|
||||||
|
.ui-btn:hover { border-color: var(--primary); color: var(--primary); }
|
||||||
|
.ui-btn.ui-btn-danger { color: var(--danger); border-color: #e3b4b0; }
|
||||||
|
.ui-btn.ui-btn-danger:hover { background: var(--danger-soft); border-color: var(--danger); }
|
||||||
|
.ui-btn.ui-btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; }
|
||||||
|
.ui-panel-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
|
||||||
|
.ui-panel-head h1 { display: flex; align-items: center; gap: 8px; margin: 0; font-size: 14px; }
|
||||||
|
.ui-panel-head .ui-sub { font-size: 12px; color: var(--text-muted); }
|
||||||
|
|
||||||
|
.ui-tab-bar { display: flex; gap: 2px; border-bottom: 1px solid var(--border); margin: 0 0 12px; }
|
||||||
|
.ui-tab {
|
||||||
|
display: inline-flex; align-items: center; gap: 6px; background: none; border: none; border-bottom: 2px solid transparent;
|
||||||
|
margin-bottom: -1px; padding: 7px 12px; font-size: 12.5px; color: var(--text-muted); cursor: pointer; font-family: inherit;
|
||||||
|
}
|
||||||
|
.ui-tab:hover { color: var(--text); }
|
||||||
|
.ui-tab.active { color: var(--primary); border-bottom-color: var(--primary); font-weight: 600; }
|
||||||
|
.ui-tab-page { display: none; }
|
||||||
|
.ui-tab-page.active { display: block; }
|
||||||
|
|
||||||
|
.ui-spec-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }
|
||||||
|
.ui-spec-card { background: var(--surface); border: 1px solid var(--border-soft); border-radius: 6px; padding: 10px 12px; }
|
||||||
|
.ui-spec-row { display: flex; justify-content: space-between; gap: 10px; font-size: 12px; padding: 3px 0; border-bottom: 1px dashed var(--border-soft); }
|
||||||
|
.ui-spec-row:last-child { border-bottom: none; }
|
||||||
|
.ui-spec-val { font-weight: 600; color: var(--text); text-align: right; font-variant-numeric: tabular-nums; }
|
||||||
|
.ui-spec-val small { font-weight: normal; color: var(--text-faint); margin-left: 3px; }
|
||||||
|
.ui-spec-total { border-top: 1px solid var(--border); margin-top: 4px; padding-top: 2px; }
|
||||||
|
.ui-spec-total .ui-spec-val { color: var(--primary); }
|
||||||
|
|
||||||
|
.btn-3d-toggle { display: inline-flex; align-items: center; gap: 6px; }
|
||||||
|
.hn-item .bi { margin-right: 6px; color: var(--text-muted); }
|
||||||
|
.hn-item.active .bi, .hn-item:hover .bi { color: var(--primary); }
|
||||||
|
.hn-label { display: inline-flex; align-items: center; }
|
||||||
|
|
||||||
|
/* Weergave-cyclus (T): render-only = ook het info-deel onder de 3D weg. */
|
||||||
|
.render-only #ho-specs, .render-only #specs-panel-3d, .render-only #floor-specs-panel-3d { display: none; }
|
||||||
|
|
||||||
|
/* Bestaande editor-panelen (wand/vloer) in dezelfde stijl als de UI-kit. */
|
||||||
|
.control-group { min-height: 28px; margin-bottom: 3px; gap: 10px; }
|
||||||
|
.control-group label { font-size: 12.5px; color: var(--text); }
|
||||||
|
.control-group input, .control-group select { width: 118px; font-size: 12.5px; padding: 4px 6px; font-family: inherit; }
|
||||||
|
h1 { display: flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 600; letter-spacing: .6px; }
|
||||||
|
.tab-btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; text-transform: none; font-size: 12.5px; letter-spacing: 0; }
|
||||||
|
.result-row { padding: 3px 0; border-bottom: 1px dashed var(--border-soft); }
|
||||||
|
.result-row:last-child { border-bottom: none; }
|
||||||
|
.result-row .highlight { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
|
||||||
|
#specs-panel-3d .spec-section, #floor-specs-panel-3d .spec-section {
|
||||||
|
background: var(--surface); border: 1px solid var(--border-soft); border-radius: 6px; padding: 10px 12px; margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.add-opening-btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; text-transform: none; font-size: 12.5px; }
|
||||||
|
.hn-actions .ui-btn { flex: 1; justify-content: center; }
|
||||||
|
.hn-title { display: flex; align-items: center; gap: 6px; }
|
||||||
|
|||||||
Vendored
+21
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2019-2024 The Bootstrap Authors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
Vendored
+5
File diff suppressed because one or more lines are too long
+4
-2
@@ -10,6 +10,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Tectum — Van Emous Mantelzorgwoningen</title>
|
<title>Tectum — Van Emous Mantelzorgwoningen</title>
|
||||||
|
<link rel="stylesheet" href="css/vendor/bootstrap-icons.min.css">
|
||||||
<link rel="stylesheet" href="css/app.css">
|
<link rel="stylesheet" href="css/app.css">
|
||||||
<script src="js/vendor/three.min.js"></script>
|
<script src="js/vendor/three.min.js"></script>
|
||||||
<script src="js/vendor/OrbitControls.js"></script>
|
<script src="js/vendor/OrbitControls.js"></script>
|
||||||
@@ -52,8 +53,8 @@
|
|||||||
<button id="menu-beeld-btn" class="menu-btn">Beeld</button>
|
<button id="menu-beeld-btn" class="menu-btn">Beeld</button>
|
||||||
<div id="menu-beeld-dropdown" class="menu-dropdown">
|
<div id="menu-beeld-dropdown" class="menu-dropdown">
|
||||||
<div class="menu-item" id="menu-fullscreen"><span>Volledig scherm</span><span class="menu-hotkey">F</span></div>
|
<div class="menu-item" id="menu-fullscreen"><span>Volledig scherm</span><span class="menu-hotkey">F</span></div>
|
||||||
<div class="menu-item" id="menu-3d-fullscreen"><span>3D volledig scherm</span><span class="menu-hotkey">T</span></div>
|
<div class="menu-item" id="menu-3d-fullscreen"><span>3D groot / alleen render</span><span class="menu-hotkey">T</span></div>
|
||||||
<div class="menu-item" id="menu-2d-fullscreen"><span>2D volledig scherm</span><span class="menu-hotkey">R</span></div>
|
<div class="menu-item" id="menu-2d-fullscreen"><span>2D groot</span><span class="menu-hotkey">R</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -116,6 +117,7 @@
|
|||||||
<script src="js/core/canvas-labels.js"></script>
|
<script src="js/core/canvas-labels.js"></script>
|
||||||
<script src="js/core/camera-memory.js"></script>
|
<script src="js/core/camera-memory.js"></script>
|
||||||
<script src="js/core/view-filter.js"></script>
|
<script src="js/core/view-filter.js"></script>
|
||||||
|
<script src="js/core/ui-kit.js"></script>
|
||||||
<script src="js/vendor/jspdf.umd.min.js"></script>
|
<script src="js/vendor/jspdf.umd.min.js"></script>
|
||||||
<script src="js/core/pdf-sheet.js"></script>
|
<script src="js/core/pdf-sheet.js"></script>
|
||||||
<script src="js/wall/wall-model.js"></script>
|
<script src="js/wall/wall-model.js"></script>
|
||||||
|
|||||||
@@ -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.4.0';
|
const APP_VERSION = '0.4.1';
|
||||||
// 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 = [];
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*!
|
||||||
|
* Tectum
|
||||||
|
* Copyright © 2026 Machiel Heinen.
|
||||||
|
* Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming.
|
||||||
|
* Zie LICENTIE.md.
|
||||||
|
*/
|
||||||
|
// ============================================================
|
||||||
|
// core/ui-kit.js — gedeelde UI-bouwstenen (window.UI): iconen (Bootstrap
|
||||||
|
// Icons, css/vendor), invoervelden, schakelaars, secties, tabbladen,
|
||||||
|
// specificatie-kaarten en de 3-staps weergave-cyclus (sneltoets T).
|
||||||
|
// Eén plek voor opmaak en woordgebruik, zodat alle panelen gelijk ogen.
|
||||||
|
//
|
||||||
|
// Woordgebruik labels: zelfstandig naamwoord met hoofdletter, geen dubbele
|
||||||
|
// punt, geen uitleg tussen haakjes (die gaat in een hint), eenheid rechts
|
||||||
|
// van het veld. Bijvoorbeeld "Afstand min." i.p.v. "Lampen min. h.o.h.".
|
||||||
|
// Plain script (geen ES-module).
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
window.UI = (() => {
|
||||||
|
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||||
|
const icon = (name, cls) => `<i class="bi bi-${name}${cls ? ' ' + cls : ''}" aria-hidden="true"></i>`;
|
||||||
|
const hint = (text) => text ? `<span class="ui-hint" title="${esc(text)}">${icon('info-circle')}</span>` : '';
|
||||||
|
|
||||||
|
function field(id, label, control, o) {
|
||||||
|
return `<div class="ui-field${o && o.disabled ? ' is-disabled' : ''}"><label for="${id}">${label}${hint(o && o.hint)}</label><div class="ui-control">${control}</div></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getal. o: { step, unit ('mm' standaard, '' = geen), hint, min, disabled }
|
||||||
|
function num(id, label, value, o) {
|
||||||
|
o = o || {};
|
||||||
|
const unit = o.unit === undefined ? 'mm' : o.unit;
|
||||||
|
return field(id, label,
|
||||||
|
`<input type="number" id="${id}" value="${value}" step="${o.step || 50}"${o.min !== undefined ? ` min="${o.min}"` : ''}${o.disabled ? ' disabled' : ''}>` +
|
||||||
|
`<span class="ui-unit">${unit}</span>`, o);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keuzelijst. options: { waarde: 'Label' }.
|
||||||
|
function sel(id, label, options, value, o) {
|
||||||
|
const opts = Object.entries(options).map(([k, v]) => `<option value="${k}"${k === value ? ' selected' : ''}>${v}</option>`).join('');
|
||||||
|
return field(id, label, `<select id="${id}">${opts}</select><span class="ui-unit"></span>`, o);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aan/uit-schakelaar.
|
||||||
|
function toggle(id, label, checked, o) {
|
||||||
|
return field(id, label,
|
||||||
|
`<label class="ui-switch"><input type="checkbox" id="${id}"${checked ? ' checked' : ''}><span></span></label><span class="ui-unit"></span>`, o);
|
||||||
|
}
|
||||||
|
|
||||||
|
const section = (title, iconName, body) =>
|
||||||
|
`<section class="ui-section"><h2>${iconName ? icon(iconName) : ''}${title}</h2>${body}</section>`;
|
||||||
|
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 grid = (...cols) => `<div class="ui-grid">${cols.map(c => `<div>${c}</div>`).join('')}</div>`;
|
||||||
|
const button = (label, iconName, attrs, cls) =>
|
||||||
|
`<button type="button" class="ui-btn${cls ? ' ' + cls : ''}" ${attrs || ''}>${iconName ? icon(iconName) : ''}<span>${label}</span></button>`;
|
||||||
|
const actions = (...btns) => `<div class="ui-actions">${btns.join('')}</div>`;
|
||||||
|
|
||||||
|
// Tabbladen. group = sleutel waaronder het actieve tabblad onthouden wordt
|
||||||
|
// (bijv. 'woning', 'unit'); tabs = [{ key, label, icon, html }].
|
||||||
|
const activeTabs = {};
|
||||||
|
function tabs(group, list) {
|
||||||
|
const active = list.some(t => t.key === activeTabs[group]) ? activeTabs[group] : list[0].key;
|
||||||
|
return `<div class="ui-tabs" data-tab-group="${group}">
|
||||||
|
<div class="ui-tab-bar">${list.map(t => `<button type="button" class="ui-tab${t.key === active ? ' active' : ''}" data-tab="${t.key}">${t.icon ? icon(t.icon) : ''}<span>${t.label}</span></button>`).join('')}</div>
|
||||||
|
${list.map(t => `<div class="ui-tab-page${t.key === active ? ' active' : ''}" data-tab-page="${t.key}">${t.html}</div>`).join('')}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
// Eenmalig per container: klik op een tab wisselt de pagina.
|
||||||
|
function wireTabs(container) {
|
||||||
|
if (container._uiTabsWired) return;
|
||||||
|
container._uiTabsWired = true;
|
||||||
|
container.addEventListener('click', (e) => {
|
||||||
|
const btn = e.target.closest('.ui-tab');
|
||||||
|
if (!btn) return;
|
||||||
|
const wrap = btn.closest('.ui-tabs');
|
||||||
|
activeTabs[wrap.dataset.tabGroup] = btn.dataset.tab;
|
||||||
|
wrap.querySelectorAll('.ui-tab').forEach(b => b.classList.toggle('active', b === btn));
|
||||||
|
wrap.querySelectorAll('.ui-tab-page').forEach(p => p.classList.toggle('active', p.dataset.tabPage === btn.dataset.tab));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specificaties: kaart met regels [label, waarde, eenheid].
|
||||||
|
function specCard(title, iconName, rows, total) {
|
||||||
|
const row = ([label, value, unit]) =>
|
||||||
|
`<div class="ui-spec-row"><span>${label}</span><span class="ui-spec-val">${value}${unit ? `<small>${unit}</small>` : ''}</span></div>`;
|
||||||
|
return `<div class="ui-spec-card"><h2>${icon(iconName)}${title}</h2>${rows.map(row).join('')}${total ? `<div class="ui-spec-total">${row(total)}</div>` : ''}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoer koppelen: onNum/onSel/onToggle(root, id, fn, after).
|
||||||
|
function onNum(id, fn, after, o) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (!el) return;
|
||||||
|
el.addEventListener('input', (e) => {
|
||||||
|
const v = parseFloat(e.target.value);
|
||||||
|
if (isFinite(v) && (!(o && o.positive) || v > 0)) { fn(v); after && after(); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function onSel(id, fn, after) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.addEventListener('input', (e) => { fn(e.target.value); after && after(); });
|
||||||
|
}
|
||||||
|
function onToggle(id, fn, after) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.addEventListener('change', (e) => { fn(e.target.checked); after && after(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Weergave-cyclus 3D (sneltoets T): standaard -> 3D groot -> alleen de
|
||||||
|
// render (ook het info-/specificatiedeel eronder weg) -> standaard.
|
||||||
|
function cycle3D(el) {
|
||||||
|
el.classList.remove('fullscreen-2d');
|
||||||
|
if (!el.classList.contains('fullscreen-3d')) el.classList.add('fullscreen-3d');
|
||||||
|
else if (!el.classList.contains('render-only')) el.classList.add('render-only');
|
||||||
|
else el.classList.remove('fullscreen-3d', 'render-only');
|
||||||
|
}
|
||||||
|
function toggle2D(el) {
|
||||||
|
el.classList.remove('fullscreen-3d', 'render-only');
|
||||||
|
el.classList.toggle('fullscreen-2d');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { esc, icon, hint, num, sel, toggle, section, info, warn, grid, button, actions, tabs, wireTabs, specCard, onNum, onSel, onToggle, cycle3D, toggle2D };
|
||||||
|
})();
|
||||||
@@ -21,7 +21,7 @@ function createViewFilter(toolbar, items, onChange) {
|
|||||||
const wrap = document.createElement('div');
|
const wrap = document.createElement('div');
|
||||||
wrap.className = 'view-filter';
|
wrap.className = 'view-filter';
|
||||||
wrap.innerHTML = `
|
wrap.innerHTML = `
|
||||||
<button type="button" class="btn-3d-toggle vf-btn">Weergave ▾</button>
|
<button type="button" class="btn-3d-toggle vf-btn"><i class="bi bi-eye"></i>Weergave<i class="bi bi-chevron-down"></i></button>
|
||||||
<div class="vf-menu">
|
<div class="vf-menu">
|
||||||
${items.map(it => `<label class="vf-item"><input type="checkbox" data-key="${it.key}" ${state[it.key] ? 'checked' : ''}> ${it.label}</label>`).join('')}
|
${items.map(it => `<label class="vf-item"><input type="checkbox" data-key="${it.key}" ${state[it.key] ? 'checked' : ''}> ${it.label}</label>`).join('')}
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|||||||
+67
-73
@@ -32,11 +32,11 @@ const FLOOR_TEMPLATE = `
|
|||||||
<div id="floor-left-column">
|
<div id="floor-left-column">
|
||||||
<div id="floor-pane-2d-container">
|
<div id="floor-pane-2d-container">
|
||||||
<div class="pane-2d-sub" id="floor-pane-top">
|
<div class="pane-2d-sub" id="floor-pane-top">
|
||||||
<div class="pane-label">2D BOVENAANZICHT</div>
|
<div class="pane-label">BOVENAANZICHT</div>
|
||||||
<canvas id="floor-canvas-top"></canvas>
|
<canvas id="floor-canvas-top"></canvas>
|
||||||
</div>
|
</div>
|
||||||
<div class="pane-2d-sub" id="floor-pane-section">
|
<div class="pane-2d-sub" id="floor-pane-section">
|
||||||
<div class="pane-label">DOORSNEDE (BALKPROFIEL)</div>
|
<div class="pane-label">DOORSNEDE</div>
|
||||||
<canvas id="floor-canvas-section"></canvas>
|
<canvas id="floor-canvas-section"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -46,59 +46,59 @@ const FLOOR_TEMPLATE = `
|
|||||||
<span id="f-io-status" class="io-status"></span>
|
<span id="f-io-status" class="io-status"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-bar">
|
<div class="tab-bar">
|
||||||
<button class="tab-btn active" data-tab="floor-tab-main">1. Vloer Opbouw</button>
|
<button class="tab-btn active" data-tab="floor-tab-main"><i class="bi bi-rulers"></i> Opbouw</button>
|
||||||
<button class="tab-btn" data-tab="floor-tab-openings">2. Doorvoeren</button>
|
<button class="tab-btn" data-tab="floor-tab-openings"><i class="bi bi-bullseye"></i> Doorvoeren</button>
|
||||||
<button class="tab-btn" data-tab="floor-tab-chassis">3. Chassis</button>
|
<button class="tab-btn" data-tab="floor-tab-chassis"><i class="bi bi-truck"></i> Chassis</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="floor-tab-main" class="tab-content active">
|
<div id="floor-tab-main" class="tab-content active">
|
||||||
<div class="controls-grid">
|
<div class="controls-grid">
|
||||||
<div>
|
<div>
|
||||||
<h1>Vloer Dimensies (mm)</h1>
|
<h1><i class="bi bi-rulers"></i> Afmetingen</h1>
|
||||||
<div class="control-group"><label>Breedte:</label>
|
<div class="control-group"><label>Breedte</label>
|
||||||
<div><input type="number" id="f-inp-width" value="2000" step="50"><span class="unit">mm</span></div></div>
|
<div><input type="number" id="f-inp-width" value="2000" step="50"><span class="unit">mm</span></div></div>
|
||||||
<div class="control-group"><label>Diepte:</label>
|
<div class="control-group"><label>Diepte</label>
|
||||||
<div><input type="number" id="f-inp-depth" value="2000" step="50"><span class="unit">mm</span></div></div>
|
<div><input type="number" id="f-inp-depth" value="2000" step="50"><span class="unit">mm</span></div></div>
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<h1>Vloerbalken</h1>
|
<h1><i class="bi bi-columns-gap"></i> Vloerbalken</h1>
|
||||||
<div class="control-group"><label>Balk Breedte:</label>
|
<div class="control-group"><label>Breedte</label>
|
||||||
<div><input type="number" id="f-inp-joist-w" value="70" step="5"><span class="unit">mm</span></div></div>
|
<div><input type="number" id="f-inp-joist-w" value="70" step="5"><span class="unit">mm</span></div></div>
|
||||||
<div class="control-group"><label>Balk Hoogte:</label>
|
<div class="control-group"><label>Hoogte</label>
|
||||||
<div><input type="number" id="f-inp-joist-h" value="220" step="5"><span class="unit">mm</span></div></div>
|
<div><input type="number" id="f-inp-joist-h" value="220" step="5"><span class="unit">mm</span></div></div>
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<h1>Onderlaag (OSB-plaat)</h1>
|
<h1><i class="bi bi-layers"></i> Onderlaag OSB</h1>
|
||||||
<div class="control-group"><label>Dikte:</label>
|
<div class="control-group"><label>Dikte</label>
|
||||||
<div><input type="number" id="f-inp-underlay-t" value="22" step="1" min="3" max="40"><span class="unit">mm</span></div></div>
|
<div><input type="number" id="f-inp-underlay-t" value="22" step="1" min="3" max="40"><span class="unit">mm</span></div></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1>Regelwerk Afstand (h.o.h.)</h1>
|
<h1><i class="bi bi-arrows"></i> Regelwerk</h1>
|
||||||
<div class="control-group"><label>Min. h.o.h.:</label>
|
<div class="control-group"><label>Afstand min.</label>
|
||||||
<div><input type="number" id="f-inp-min-sp" value="300" step="10"><span class="unit">mm</span></div></div>
|
<div><input type="number" id="f-inp-min-sp" value="300" step="10"><span class="unit">mm</span></div></div>
|
||||||
<div class="control-group"><label>Max. h.o.h.:</label>
|
<div class="control-group"><label>Afstand max.</label>
|
||||||
<div><input type="number" id="f-inp-max-sp" value="600" step="10"><span class="unit">mm</span></div></div>
|
<div><input type="number" id="f-inp-max-sp" value="600" step="10"><span class="unit">mm</span></div></div>
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<div class="result-row"><span>Totaal vloerbalken:</span><span data-spec="joist-count" class="highlight">-</span></div>
|
<div class="result-row"><span>Vloerbalken</span><span data-spec="joist-count" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Vloeroppervlak:</span><span data-spec="floor-area" class="highlight">-</span></div>
|
<div class="result-row"><span>Vloeroppervlak</span><span data-spec="floor-area" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Volume balkhout:</span><span data-spec="joist-m3" class="highlight">-</span></div>
|
<div class="result-row"><span>Volume balkhout</span><span data-spec="joist-m3" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Gewicht balkhout:</span><span data-spec="joist-kg" class="highlight">-</span></div>
|
<div class="result-row"><span>Gewicht balkhout</span><span data-spec="joist-kg" class="highlight">-</span></div>
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<div class="result-row"><span>OSB-plaat oppervlak:</span><span data-spec="osb-m2" class="highlight">-</span></div>
|
<div class="result-row"><span>OSB oppervlak</span><span data-spec="osb-m2" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>OSB-plaat volume:</span><span data-spec="osb-m3" class="highlight">-</span></div>
|
<div class="result-row"><span>OSB volume</span><span data-spec="osb-m3" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>OSB-plaat gewicht:</span><span data-spec="osb-kg" class="highlight">-</span></div>
|
<div class="result-row"><span>OSB gewicht</span><span data-spec="osb-kg" class="highlight">-</span></div>
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<div class="result-row"><span>Isolatie-inhoud (leegte):</span><span data-spec="void-m3" class="highlight">-</span></div>
|
<div class="result-row"><span>Isolatie</span><span data-spec="void-m3" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Totaal gewicht vloer:</span><span data-spec="total-kg" class="highlight">-</span></div>
|
<div class="result-row"><span>Totaal gewicht</span><span data-spec="total-kg" class="highlight">-</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="floor-tab-openings" class="tab-content">
|
<div id="floor-tab-openings" class="tab-content">
|
||||||
<button id="f-btn-add-opening" class="add-opening-btn">+ Doorvoer Toevoegen</button>
|
<button id="f-btn-add-opening" class="add-opening-btn"><i class="bi bi-plus-lg"></i> Doorvoer toevoegen</button>
|
||||||
<div id="f-openings-global-warn" class="global-warning" style="display:none;"></div>
|
<div id="f-openings-global-warn" class="global-warning" style="display:none;"></div>
|
||||||
<div id="f-openings-list"></div>
|
<div id="f-openings-list"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -109,13 +109,13 @@ const FLOOR_TEMPLATE = `
|
|||||||
</div>
|
</div>
|
||||||
<div class="controls-grid">
|
<div class="controls-grid">
|
||||||
<div>
|
<div>
|
||||||
<div class="control-group"><label>Wieldiameter:</label>
|
<div class="control-group"><label>Wieldiameter</label>
|
||||||
<div><input type="number" id="f-inp-wheel-d" value="400" step="10" min="50"><span class="unit">mm</span></div></div>
|
<div><input type="number" id="f-inp-wheel-d" value="400" step="10" min="50"><span class="unit">mm</span></div></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="control-group"><label>Spoorbreedte (X):</label>
|
<div class="control-group"><label>Spoorbreedte</label>
|
||||||
<div><input type="number" id="f-inp-track" value="1200" step="50" min="100"><span class="unit">mm</span></div></div>
|
<div><input type="number" id="f-inp-track" value="1200" step="50" min="100"><span class="unit">mm</span></div></div>
|
||||||
<div class="control-group"><label>Wielbasis (Y):</label>
|
<div class="control-group"><label>Wielbasis</label>
|
||||||
<div><input type="number" id="f-inp-wheelbase" value="1200" step="50" min="100"><span class="unit">mm</span></div></div>
|
<div><input type="number" id="f-inp-wheelbase" value="1200" step="50" min="100"><span class="unit">mm</span></div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -126,7 +126,7 @@ const FLOOR_TEMPLATE = `
|
|||||||
|
|
||||||
<div id="floor-right-column">
|
<div id="floor-right-column">
|
||||||
<div id="floor-pane-3d">
|
<div id="floor-pane-3d">
|
||||||
<div class="pane-label">3D PERSPECTIEF</div>
|
<div class="pane-label">3D</div>
|
||||||
<div class="toolbar-3d" id="floor-toolbar-3d"></div>
|
<div class="toolbar-3d" id="floor-toolbar-3d"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -134,27 +134,27 @@ const FLOOR_TEMPLATE = `
|
|||||||
<div class="controls-grid">
|
<div class="controls-grid">
|
||||||
<div>
|
<div>
|
||||||
<div class="spec-section">
|
<div class="spec-section">
|
||||||
<h1>Balken</h1>
|
<h1><i class="bi bi-columns-gap"></i> Balken</h1>
|
||||||
<div class="result-row"><span>Vloerbalken:</span><span data-spec="joist-count" class="highlight">-</span></div>
|
<div class="result-row"><span>Vloerbalken</span><span data-spec="joist-count" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Vloeroppervlak:</span><span data-spec="floor-area" class="highlight">-</span></div>
|
<div class="result-row"><span>Vloeroppervlak</span><span data-spec="floor-area" class="highlight">-</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="spec-section">
|
<div class="spec-section">
|
||||||
<h1>Houtwerk</h1>
|
<h1><i class="bi bi-tree"></i> Houtwerk</h1>
|
||||||
<div class="result-row"><span>Volume balkhout:</span><span data-spec="joist-m3" class="highlight">-</span></div>
|
<div class="result-row"><span>Volume balkhout</span><span data-spec="joist-m3" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Gewicht balkhout:</span><span data-spec="joist-kg" class="highlight">-</span></div>
|
<div class="result-row"><span>Gewicht balkhout</span><span data-spec="joist-kg" class="highlight">-</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="spec-section">
|
<div class="spec-section">
|
||||||
<h1>OSB-plaat (onderlaag, 22mm)</h1>
|
<h1><i class="bi bi-layers"></i> OSB onderlaag</h1>
|
||||||
<div class="result-row"><span>Oppervlak:</span><span data-spec="osb-m2" class="highlight">-</span></div>
|
<div class="result-row"><span>Oppervlak</span><span data-spec="osb-m2" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Volume:</span><span data-spec="osb-m3" class="highlight">-</span></div>
|
<div class="result-row"><span>Volume</span><span data-spec="osb-m3" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Gewicht:</span><span data-spec="osb-kg" class="highlight">-</span></div>
|
<div class="result-row"><span>Gewicht</span><span data-spec="osb-kg" class="highlight">-</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="spec-section">
|
<div class="spec-section">
|
||||||
<h1>Isolatie & totaal</h1>
|
<h1><i class="bi bi-speedometer2"></i> Isolatie en gewicht</h1>
|
||||||
<div class="result-row"><span>Inhoud leegtes:</span><span data-spec="void-m3" class="highlight">-</span></div>
|
<div class="result-row"><span>Isolatie</span><span data-spec="void-m3" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Totaal gewicht vloer:</span><span data-spec="total-kg" class="highlight">-</span></div>
|
<div class="result-row"><span>Totaal gewicht</span><span data-spec="total-kg" class="highlight">-</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -170,23 +170,23 @@ const FLOOR_TEMPLATE = `
|
|||||||
</div>
|
</div>
|
||||||
<div class="controls-grid">
|
<div class="controls-grid">
|
||||||
<div>
|
<div>
|
||||||
<div class="control-group"><label for="inp-proj-naam">Projectnaam:</label>
|
<div class="control-group"><label for="inp-proj-naam">Projectnaam</label>
|
||||||
<div><input type="text" id="inp-proj-naam" value=""></div></div>
|
<div><input type="text" id="inp-proj-naam" value=""></div></div>
|
||||||
<div class="control-group"><label for="inp-proj-plaats">Plaats:</label>
|
<div class="control-group"><label for="inp-proj-plaats">Plaats</label>
|
||||||
<div><input type="text" id="inp-proj-plaats" value=""></div></div>
|
<div><input type="text" id="inp-proj-plaats" value=""></div></div>
|
||||||
<div class="control-group"><label for="inp-proj-straat">Straat:</label>
|
<div class="control-group"><label for="inp-proj-straat">Straat</label>
|
||||||
<div><input type="text" id="inp-proj-straat" value=""></div></div>
|
<div><input type="text" id="inp-proj-straat" value=""></div></div>
|
||||||
<div class="control-group"><label for="inp-proj-nummer">Projectnummer:</label>
|
<div class="control-group"><label for="inp-proj-nummer">Projectnummer</label>
|
||||||
<div><input type="text" id="inp-proj-nummer" value=""></div></div>
|
<div><input type="text" id="inp-proj-nummer" value=""></div></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="control-group"><label for="inp-proj-tekenaar">Tekenaar:</label>
|
<div class="control-group"><label for="inp-proj-tekenaar">Tekenaar</label>
|
||||||
<div><input type="text" id="inp-proj-tekenaar" value=""></div></div>
|
<div><input type="text" id="inp-proj-tekenaar" value=""></div></div>
|
||||||
<div class="control-group"><label for="inp-proj-titel">Titel:</label>
|
<div class="control-group"><label for="inp-proj-titel">Titel</label>
|
||||||
<div><input type="text" id="inp-proj-titel" value="Vloer"></div></div>
|
<div><input type="text" id="inp-proj-titel" value="Vloer"></div></div>
|
||||||
<div class="control-group"><label for="inp-proj-element">Element nummer:</label>
|
<div class="control-group"><label for="inp-proj-element">Elementnummer</label>
|
||||||
<div><input type="text" id="inp-proj-element" value=""></div></div>
|
<div><input type="text" id="inp-proj-element" value=""></div></div>
|
||||||
<div class="control-group"><label for="inp-proj-opmerking">Opmerking:</label>
|
<div class="control-group"><label for="inp-proj-opmerking">Opmerking</label>
|
||||||
<div><input type="text" id="inp-proj-opmerking" value=""></div></div>
|
<div><input type="text" id="inp-proj-opmerking" value=""></div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -203,18 +203,16 @@ const FLOOR_TEMPLATE = `
|
|||||||
// (RoofModel). Zelfde editor, andere teksten + gespiegelde doorsnede/3D.
|
// (RoofModel). Zelfde editor, andere teksten + gespiegelde doorsnede/3D.
|
||||||
function floorTemplateForRoof() {
|
function floorTemplateForRoof() {
|
||||||
const r = [
|
const r = [
|
||||||
['1. Vloer Opbouw', '1. Dak Opbouw'],
|
[' Vloerbalken</h1>', ' Dakbalken</h1>'],
|
||||||
['Vloer Dimensies (mm)', 'Dak Dimensies (mm)'],
|
['<span>Vloerbalken</span>', '<span>Dakbalken</span>'],
|
||||||
['<h1>Vloerbalken</h1>', '<h1>Dakbalken</h1>'],
|
[' Onderlaag OSB</h1>', ' Plafondplaat OSB</h1>'],
|
||||||
['Onderlaag (OSB-plaat)', 'Plafondplaat (OSB, onderzijde)'],
|
[' OSB onderlaag</h1>', ' OSB plafond</h1>'],
|
||||||
['Totaal vloerbalken:', 'Totaal dakbalken:'],
|
['<button class="tab-btn" data-tab="floor-tab-chassis"><i class="bi bi-truck"></i> Chassis</button>', ''],
|
||||||
['OSB-plaat (onderlaag, 22mm)', 'OSB-plaat (plafond)'],
|
[' Isolatie en gewicht</h1>', ' Dakleer, isolatie en gewicht</h1>\n <div class="result-row"><span>Dakleer oppervlak</span><span data-spec="dakleer-m2" class="highlight">-</span></div>\n <div class="result-row"><span>Dakleer gewicht</span><span data-spec="dakleer-kg" class="highlight">-</span></div>']
|
||||||
['<button class="tab-btn" data-tab="floor-tab-chassis">3. Chassis</button>', ''],
|
|
||||||
['<h1>Isolatie & totaal</h1>', '<h1>Dakleer, isolatie & totaal</h1>\n <div class="result-row"><span>Dakleer oppervlak:</span><span data-spec="dakleer-m2" class="highlight">-</span></div>\n <div class="result-row"><span>Dakleer gewicht:</span><span data-spec="dakleer-kg" class="highlight">-</span></div>']
|
|
||||||
];
|
];
|
||||||
let t = FLOOR_TEMPLATE;
|
let t = FLOOR_TEMPLATE;
|
||||||
r.forEach(([a, b]) => { t = t.split(a).join(b); });
|
r.forEach(([a, b]) => { t = t.split(a).join(b); });
|
||||||
return t.split('Vloeroppervlak:').join('Dakoppervlak:').split('Totaal gewicht vloer:').join('Totaal gewicht dak:');
|
return t.split('Vloeroppervlak').join('Dakoppervlak');
|
||||||
}
|
}
|
||||||
|
|
||||||
function mountFloorEditor(container, callbacks) {
|
function mountFloorEditor(container, callbacks) {
|
||||||
@@ -262,9 +260,9 @@ function mountFloorEditor(container, callbacks) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function anchorLabel(a) {
|
function anchorLabel(a) {
|
||||||
if (a === 'right') return 'Afstand vanaf oostzijde (X)';
|
if (a === 'right') return 'Afstand tot oostzijde';
|
||||||
if (a === 'middle') return 'Verschuiving t.o.v. midden (X)';
|
if (a === 'middle') return 'Verschuiving t.o.v. midden';
|
||||||
return 'Afstand vanaf westzijde (X)';
|
return 'Afstand tot westzijde';
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderOpeningsList() {
|
function renderOpeningsList() {
|
||||||
@@ -277,13 +275,13 @@ function mountFloorEditor(container, callbacks) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="controls-grid">
|
<div class="controls-grid">
|
||||||
<div>
|
<div>
|
||||||
<div class="control-group"><label>Diameter:</label>
|
<div class="control-group"><label>Diameter</label>
|
||||||
<div><input type="number" data-field="diameter" data-id="${op.id}" value="${op.diameter}" step="5" min="10"><span class="unit">mm</span></div></div>
|
<div><input type="number" data-field="diameter" data-id="${op.id}" value="${op.diameter}" step="5" min="10"><span class="unit">mm</span></div></div>
|
||||||
<div class="control-group"><label>Afstand vanaf noordzijde (Y):</label>
|
<div class="control-group"><label>Afstand tot noordzijde</label>
|
||||||
<div><input type="number" data-field="offsetY" data-id="${op.id}" value="${op.offsetY}" step="50"><span class="unit">mm</span></div></div>
|
<div><input type="number" data-field="offsetY" data-id="${op.id}" value="${op.offsetY}" step="50"><span class="unit">mm</span></div></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="control-group"><label>Anker (X):</label>
|
<div class="control-group"><label>Anker</label>
|
||||||
<div>
|
<div>
|
||||||
<select data-field="anchorX" data-id="${op.id}">
|
<select data-field="anchorX" data-id="${op.id}">
|
||||||
<option value="left" ${op.anchorX === 'left' ? 'selected' : ''}>West</option>
|
<option value="left" ${op.anchorX === 'left' ? 'selected' : ''}>West</option>
|
||||||
@@ -292,7 +290,7 @@ function mountFloorEditor(container, callbacks) {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group"><label>${anchorLabel(op.anchorX)}:</label>
|
<div class="control-group"><label>${anchorLabel(op.anchorX)}</label>
|
||||||
<div><input type="number" data-field="offsetX" data-id="${op.id}" value="${op.offsetX}" step="50"><span class="unit">mm</span></div></div>
|
<div><input type="number" data-field="offsetX" data-id="${op.id}" value="${op.offsetX}" step="50"><span class="unit">mm</span></div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -778,15 +776,11 @@ function mountFloorEditor(container, callbacks) {
|
|||||||
window.addEventListener('resize', onWindowResize);
|
window.addEventListener('resize', onWindowResize);
|
||||||
|
|
||||||
function toggleFullscreen3D() {
|
function toggleFullscreen3D() {
|
||||||
const el = document.getElementById('floor-app');
|
UI.cycle3D(document.getElementById('floor-app')); // T: standaard -> 3D groot -> alleen render
|
||||||
el.classList.remove('fullscreen-2d');
|
|
||||||
el.classList.toggle('fullscreen-3d');
|
|
||||||
requestAnimationFrame(onWindowResize);
|
requestAnimationFrame(onWindowResize);
|
||||||
}
|
}
|
||||||
function toggleFullscreen2D() {
|
function toggleFullscreen2D() {
|
||||||
const el = document.getElementById('floor-app');
|
UI.toggle2D(document.getElementById('floor-app'));
|
||||||
el.classList.remove('fullscreen-3d');
|
|
||||||
el.classList.toggle('fullscreen-2d');
|
|
||||||
requestAnimationFrame(onWindowResize);
|
requestAnimationFrame(onWindowResize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+203
-268
@@ -23,11 +23,11 @@
|
|||||||
const HOUSE_TEMPLATE = `
|
const HOUSE_TEMPLATE = `
|
||||||
<div id="house-app">
|
<div id="house-app">
|
||||||
<aside id="house-nav">
|
<aside id="house-nav">
|
||||||
<div class="hn-title">Woning</div>
|
<div class="hn-title"><i class="bi bi-diagram-3"></i> Woning</div>
|
||||||
<div id="house-nav-tree"></div>
|
<div id="house-nav-tree"></div>
|
||||||
<div class="hn-actions">
|
<div class="hn-actions">
|
||||||
<button type="button" id="hn-add-unit" class="io-btn">+ Unit</button>
|
<button type="button" id="hn-add-unit" class="ui-btn"><i class="bi bi-plus-lg"></i><span>Unit</span></button>
|
||||||
<button type="button" id="hn-add-iw" class="io-btn">+ Binnenwand</button>
|
<button type="button" id="hn-add-iw" class="ui-btn"><i class="bi bi-plus-lg"></i><span>Binnenwand</span></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="hn-soon">
|
<div class="hn-soon">
|
||||||
<div class="hn-item disabled" title="Nog niet beschikbaar">Schuine daken</div>
|
<div class="hn-item disabled" title="Nog niet beschikbaar">Schuine daken</div>
|
||||||
@@ -47,14 +47,14 @@ const HOUSE_TEMPLATE = `
|
|||||||
</div>
|
</div>
|
||||||
<div class="controls-grid">
|
<div class="controls-grid">
|
||||||
<div>
|
<div>
|
||||||
<div class="control-group"><label for="h-proj-naam">Projectnaam:</label><div><input type="text" id="h-proj-naam"></div></div>
|
<div class="control-group"><label for="h-proj-naam">Projectnaam</label><div><input type="text" id="h-proj-naam"></div></div>
|
||||||
<div class="control-group"><label for="h-proj-plaats">Plaats:</label><div><input type="text" id="h-proj-plaats"></div></div>
|
<div class="control-group"><label for="h-proj-plaats">Plaats</label><div><input type="text" id="h-proj-plaats"></div></div>
|
||||||
<div class="control-group"><label for="h-proj-straat">Straat:</label><div><input type="text" id="h-proj-straat"></div></div>
|
<div class="control-group"><label for="h-proj-straat">Straat</label><div><input type="text" id="h-proj-straat"></div></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="control-group"><label for="h-proj-nummer">Projectnummer:</label><div><input type="text" id="h-proj-nummer"></div></div>
|
<div class="control-group"><label for="h-proj-nummer">Projectnummer</label><div><input type="text" id="h-proj-nummer"></div></div>
|
||||||
<div class="control-group"><label for="h-proj-tekenaar">Tekenaar:</label><div><input type="text" id="h-proj-tekenaar"></div></div>
|
<div class="control-group"><label for="h-proj-tekenaar">Tekenaar</label><div><input type="text" id="h-proj-tekenaar"></div></div>
|
||||||
<div class="control-group"><label for="h-proj-opmerking">Opmerking:</label><div><input type="text" id="h-proj-opmerking"></div></div>
|
<div class="control-group"><label for="h-proj-opmerking">Opmerking</label><div><input type="text" id="h-proj-opmerking"></div></div>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
@@ -70,10 +70,10 @@ const HOUSE_OVERVIEW_TEMPLATE = `
|
|||||||
<div id="house-overview">
|
<div id="house-overview">
|
||||||
<div class="ho-col ho-left">
|
<div class="ho-col ho-left">
|
||||||
<div id="ho-pane-plan" class="ho-pane">
|
<div id="ho-pane-plan" class="ho-pane">
|
||||||
<div class="pane-label">2D PLATTEGROND</div>
|
<div class="pane-label">PLATTEGROND</div>
|
||||||
<div class="toolbar-3d">
|
<div class="toolbar-3d">
|
||||||
<button type="button" id="ho-tool-select" class="btn-3d-toggle active">Selecteren</button>
|
<button type="button" id="ho-tool-select" class="btn-3d-toggle active" title="Selecteren"><i class="bi bi-cursor"></i>Selecteren</button>
|
||||||
<button type="button" id="ho-tool-iw" class="btn-3d-toggle">Binnenwand tekenen</button>
|
<button type="button" id="ho-tool-iw" class="btn-3d-toggle" title="Binnenwand tekenen"><i class="bi bi-pencil"></i>Binnenwand</button>
|
||||||
</div>
|
</div>
|
||||||
<canvas id="ho-canvas"></canvas>
|
<canvas id="ho-canvas"></canvas>
|
||||||
<div id="ho-hint" class="ho-hint"></div>
|
<div id="ho-hint" class="ho-hint"></div>
|
||||||
@@ -82,9 +82,9 @@ const HOUSE_OVERVIEW_TEMPLATE = `
|
|||||||
</div>
|
</div>
|
||||||
<div class="ho-col ho-right">
|
<div class="ho-col ho-right">
|
||||||
<div id="ho-pane-3d" class="ho-pane">
|
<div id="ho-pane-3d" class="ho-pane">
|
||||||
<div class="pane-label">3D PERSPECTIEF</div>
|
<div class="pane-label">3D</div>
|
||||||
<div class="toolbar-3d" id="ho-toolbar-3d">
|
<div class="toolbar-3d" id="ho-toolbar-3d">
|
||||||
<button type="button" id="ho-btn-render" class="btn-3d-toggle" title="Avondscène: elke lamp en spot is een echte lichtbron">Volle render</button>
|
<button type="button" id="ho-btn-render" class="btn-3d-toggle" title="Avondscène: elke lamp en spot is een echte lichtbron"><i class="bi bi-moon-stars"></i>Render</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="ho-specs"></div>
|
<div id="ho-specs"></div>
|
||||||
@@ -187,25 +187,25 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
function renderNav() {
|
function renderNav() {
|
||||||
const parts = HouseModel.computeParts(state, { skip3D: true });
|
const parts = HouseModel.computeParts(state, { skip3D: true });
|
||||||
const item = (part, label, meta, cls) =>
|
const item = (part, label, meta, cls, iconName) =>
|
||||||
`<div class="hn-item ${cls || ''} ${activePart === part ? 'active' : ''}" data-part="${part}">
|
`<div class="hn-item ${cls || ''} ${activePart === part ? 'active' : ''}" data-part="${part}">
|
||||||
<span>${label}</span>${meta ? `<span class="hn-meta">${meta}</span>` : ''}</div>`;
|
<span class="hn-label">${iconName ? UI.icon(iconName) : ''}${label}</span>${meta ? `<span class="hn-meta">${meta}</span>` : ''}</div>`;
|
||||||
let html = item('overzicht', 'Overzicht', '', 'hn-top');
|
let html = item('overzicht', 'Overzicht', '', 'hn-top', 'grid-1x2');
|
||||||
parts.units.forEach(u => {
|
parts.units.forEach(u => {
|
||||||
const sel = selection && selection.type === 'unit' && selection.id === u.id && activePart === 'overzicht';
|
const sel = selection && selection.type === 'unit' && selection.id === u.id && activePart === 'overzicht';
|
||||||
html += `<div class="hn-group">`;
|
html += `<div class="hn-group">`;
|
||||||
html += `<div class="hn-item hn-unit ${sel ? 'active' : ''}" data-select-unit="${u.id}"><span>Unit ${u.id}</span><span class="hn-meta">${u.rect.W} × ${u.rect.D}</span></div>`;
|
html += `<div class="hn-item hn-unit ${sel ? 'active' : ''}" data-select-unit="${u.id}"><span class="hn-label">${UI.icon('box')}Unit ${u.id}</span><span class="hn-meta">${u.rect.W} × ${u.rect.D}</span></div>`;
|
||||||
html += item(`floor:${u.id}`, 'Vloer', u.floorElement, 'hn-sub');
|
html += item(`floor:${u.id}`, 'Vloer', u.floorElement, 'hn-sub', 'grid-3x3');
|
||||||
html += item(`roof:${u.id}`, 'Dak (plat)', u.roof.element, 'hn-sub');
|
html += item(`roof:${u.id}`, 'Dak', u.roof.element, 'hn-sub', 'house');
|
||||||
u.walls.forEach(w => {
|
u.walls.forEach(w => {
|
||||||
const meta = w.removed ? 'vervalt (naad)' : w.cutsU.length ? `deels naad · ${w.pieces.map(p => p.element).join(', ')}` : w.pieces[0].element;
|
const meta = w.removed ? 'vervalt' : w.pieces.map(p => p.element).join(', ');
|
||||||
html += item(`wall:${u.id}:${w.side}`, w.label, meta, 'hn-sub' + (w.removed ? ' hn-removed' : ''));
|
html += item(`wall:${u.id}:${w.side}`, w.label, meta, 'hn-sub' + (w.removed ? ' hn-removed' : ''), 'bricks');
|
||||||
});
|
});
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
});
|
});
|
||||||
if (parts.interior.length) {
|
if (parts.interior.length) {
|
||||||
html += `<div class="hn-group"><div class="hn-item hn-unit hn-static"><span>Binnenwanden</span></div>`;
|
html += `<div class="hn-group"><div class="hn-item hn-unit hn-static"><span class="hn-label">${UI.icon('distribute-horizontal')}Binnenwanden</span></div>`;
|
||||||
parts.interior.forEach(p => { html += item(`iw:${p.id}`, p.element, `${p.place.width} mm`, 'hn-sub'); });
|
parts.interior.forEach(p => { html += item(`iw:${p.id}`, p.element, `${p.place.width} mm`, 'hn-sub', 'distribute-horizontal'); });
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
}
|
}
|
||||||
document.getElementById('house-nav-tree').innerHTML = html;
|
document.getElementById('house-nav-tree').innerHTML = html;
|
||||||
@@ -617,227 +617,172 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Eigenschappenpaneel (contextafhankelijk) ---
|
// --- Eigenschappenpaneel (contextafhankelijk, opgebouwd met UI-kit) ---
|
||||||
const num = (id, label, value, step) =>
|
const { num, sel, toggle, section, info, button, actions } = UI;
|
||||||
`<div class="control-group"><label for="${id}">${label}:</label><div><input type="number" id="${id}" value="${value}" step="${step || 50}"><span class="unit">mm</span></div></div>`;
|
const cols = UI.grid; // (grid = de 3D-rasterhulp)
|
||||||
const sel = (id, label, options, value) =>
|
UI.wireTabs(propsEl);
|
||||||
`<div class="control-group"><label for="${id}">${label}:</label><div><select id="${id}">${Object.entries(options).map(([k, v]) => `<option value="${k}" ${k === value ? 'selected' : ''}>${v}</option>`).join('')}</select></div></div>`;
|
const head = (iconName, title, sub) =>
|
||||||
|
`<div class="ui-panel-head"><h1>${UI.icon(iconName)}${title}</h1>${sub ? `<span class="ui-sub">${sub}</span>` : ''}</div>`;
|
||||||
|
const after = () => refresh({ keepProps: true }); // getal: panelen blijven staan
|
||||||
|
const rerender = () => refresh(); // keuze: velden kunnen wijzigen
|
||||||
|
|
||||||
function renderProps() {
|
function renderProps() {
|
||||||
const warn = parts.overlaps.length
|
const warn = (parts.overlaps.length ? UI.warn(`Units overlappen elkaar: ${parts.overlaps.map(p => p.join(' + ')).join(', ')}.`) : '') +
|
||||||
? `<div class="global-warning">Let op: units overlappen elkaar (${parts.overlaps.map(p => p.join(' + ')).join(', ')}).</div>` : '';
|
parts.rects.filter(r => r.error).map(r => UI.warn(`Unit ${r.id}: ${r.error}`)).join('');
|
||||||
const errs = parts.rects.filter(r => r.error).map(r => `<div class="global-warning">Unit ${r.id}: ${r.error}</div>`).join('');
|
|
||||||
|
|
||||||
if (selection && selection.type === 'unit' && unitById(selection.id)) {
|
if (selection && selection.type === 'unit' && unitById(selection.id)) renderUnitProps(unitById(selection.id), warn);
|
||||||
const u = unitById(selection.id);
|
else if (selection && selection.type === 'wall') renderWallProps(warn);
|
||||||
|
else if (selection && selection.type === 'iw' && iwById(selection.id)) renderIwProps(iwById(selection.id), warn);
|
||||||
|
else renderHouseProps(warn);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderUnitProps(u, warn) {
|
||||||
const isRoot = state.units[0].id === u.id;
|
const isRoot = state.units[0].id === u.id;
|
||||||
const a = u.attach;
|
const a = u.attach;
|
||||||
const axis = a && (a.side === 'voor' || a.side === 'achter') ? 'x' : 'z';
|
const axis = a && (a.side === 'voor' || a.side === 'achter') ? 'x' : 'z';
|
||||||
propsEl.innerHTML = `${warn}${errs}
|
const f = u.unit.floor.floor;
|
||||||
<div class="controls-grid">
|
const koppeling = isRoot
|
||||||
<div>
|
? info('Basis-unit: de noordwesthoek ligt op (0, 0). Andere units koppelen hieraan.')
|
||||||
<h1>Unit ${u.id}</h1>
|
: sel('hp-att-to', 'Gekoppeld aan', Object.fromEntries(state.units.filter(o => o.id !== u.id).map(o => [o.id, 'Unit ' + o.id])), a.to) +
|
||||||
${num('hp-width', 'Breedte (west-oost)', u.unit.floor.floor.width)}
|
sel('hp-att-side', 'Zijde', HouseModel.SIDE_LABELS, a.side) +
|
||||||
${num('hp-depth', 'Diepte (noord-zuid)', u.unit.floor.floor.depth)}
|
sel('hp-att-align', 'Uitlijning', HouseModel.ALIGN_LABELS[axis], a.align) +
|
||||||
${sel('hp-corners', 'Hoeken', UnitModel.CORNER_MODES, u.unit.layout.corners)}
|
num('hp-att-offset', 'Verschuiving', a.offset || 0);
|
||||||
</div>
|
const dak = toggle('hp-roof-follow', 'Maat volgt vloer', u.roof.followFloor) +
|
||||||
<div>
|
(u.roof.followFloor ? '' :
|
||||||
<h1>Koppeling</h1>
|
num('hp-roof-w', 'Breedte', u.roof.deck.floor.width, { hint: 'West–oost' }) +
|
||||||
${isRoot ? '<div class="material-info">Basis-unit: noordwest-hoek op (0, 0). Andere units koppelen hieraan.</div>' : `
|
num('hp-roof-d', 'Diepte', u.roof.deck.floor.depth, { hint: 'Noord–zuid' }) +
|
||||||
${sel('hp-att-to', 'Gekoppeld aan', Object.fromEntries(state.units.filter(o => o.id !== u.id).map(o => [o.id, 'Unit ' + o.id])), a.to)}
|
num('hp-roof-ox', 'Verschuiving oost', u.roof.offsetX || 0) +
|
||||||
${sel('hp-att-side', 'Aan zijde', HouseModel.SIDE_LABELS, a.side)}
|
num('hp-roof-oz', 'Verschuiving zuid', u.roof.offsetZ || 0)) +
|
||||||
${sel('hp-att-align', 'Uitlijning', HouseModel.ALIGN_LABELS[axis], a.align)}
|
toggle('hp-fascia', 'Boeidelen', u.roof.fascia !== false, { hint: 'Uit = daktrim van 80 mm' });
|
||||||
${num('hp-att-offset', 'Verschuiving', a.offset || 0)}`}
|
|
||||||
</div>
|
propsEl.innerHTML = warn + head('box', `Unit ${u.id}`, `${f.width} × ${f.depth} mm`) +
|
||||||
</div>
|
UI.tabs('unit', [
|
||||||
<div class="divider"></div>
|
{ key: 'maat', label: 'Afmetingen', icon: 'rulers', html: cols(
|
||||||
<div class="controls-grid">
|
num('hp-width', 'Breedte', f.width, { hint: 'West–oost' }) + num('hp-depth', 'Diepte', f.depth, { hint: 'Noord–zuid' }),
|
||||||
<div>
|
sel('hp-corners', 'Hoekaansluiting', UnitModel.CORNER_MODES, u.unit.layout.corners)) },
|
||||||
<h1>Dak (plat)</h1>
|
{ key: 'koppeling', label: 'Koppeling', icon: 'link-45deg', html: cols(koppeling, '') },
|
||||||
${sel('hp-roof-follow', 'Dak volgt vloer', { ja: 'Ja (standaard)', nee: 'Nee, eigen maat' }, u.roof.followFloor ? 'ja' : 'nee')}
|
{ key: 'dak', label: 'Dak', icon: 'house', html: cols(dak, u.roof.followFloor ? info('Het dak heeft de maat van de vloer. Zet "Maat volgt vloer" uit voor een eigen maat of verschuiving.') : '') }
|
||||||
${u.roof.followFloor ? '' : `
|
]) +
|
||||||
${num('hp-roof-w', 'Dakbreedte (west-oost)', u.roof.deck.floor.width)}
|
actions(
|
||||||
${num('hp-roof-d', 'Dakdiepte (noord-zuid)', u.roof.deck.floor.depth)}`}
|
button('Vloer bewerken', 'grid-3x3', `data-go="floor:${u.id}"`),
|
||||||
</div>
|
button('Dak bewerken', 'house', `data-go="roof:${u.id}"`),
|
||||||
<div>
|
isRoot ? '' : button('Unit verwijderen', 'trash', `data-remove-unit="${u.id}"`, 'ui-btn-danger'));
|
||||||
${u.roof.followFloor ? '<div class="material-info">Het dak heeft dezelfde maat als de vloer. Kies "Nee, eigen maat" om het dak langer/breder te maken of te verschuiven.</div>' : `
|
|
||||||
<h1>Positie dak</h1>
|
UI.onNum('hp-width', v => { if (v >= 1000) f.width = v; }, after);
|
||||||
${num('hp-roof-ox', 'Verschuiving oost (+)', u.roof.offsetX || 0)}
|
UI.onNum('hp-depth', v => { if (v >= 1000) f.depth = v; }, after);
|
||||||
${num('hp-roof-oz', 'Verschuiving zuid (+)', u.roof.offsetZ || 0)}`}
|
UI.onNum('hp-att-offset', v => { u.attach.offset = v; }, after);
|
||||||
</div>
|
UI.onSel('hp-corners', v => { u.unit.layout.corners = v; }, rerender);
|
||||||
</div>
|
UI.onSel('hp-att-to', v => { u.attach.to = v; }, rerender);
|
||||||
<div class="divider"></div>
|
UI.onSel('hp-att-side', v => { u.attach.side = v; }, rerender);
|
||||||
<div class="controls-grid">
|
UI.onSel('hp-att-align', v => { u.attach.align = v; }, rerender);
|
||||||
<div>
|
UI.onToggle('hp-roof-follow', v => { u.roof.followFloor = v; }, rerender);
|
||||||
<h1>Dakrand</h1>
|
UI.onToggle('hp-fascia', v => { u.roof.fascia = v; }, rerender);
|
||||||
${sel('hp-fascia', 'Boeidelen', { aan: 'Aan', uit: 'Uit (daktrim 80 mm)' }, u.roof.fascia !== false ? 'aan' : 'uit')}
|
UI.onNum('hp-roof-w', v => { if (v >= 500) u.roof.deck.floor.width = v; }, after);
|
||||||
</div>
|
UI.onNum('hp-roof-d', v => { if (v >= 500) u.roof.deck.floor.depth = v; }, after);
|
||||||
<div>
|
UI.onNum('hp-roof-ox', v => { u.roof.offsetX = v; }, after);
|
||||||
<div class="material-info">Buitenverlichting (type, richting, afstanden) is woning-breed: Overzicht, niets geselecteerd.</div>
|
UI.onNum('hp-roof-oz', v => { u.roof.offsetZ = v; }, after);
|
||||||
</div>
|
}
|
||||||
</div>
|
|
||||||
<div class="ho-prop-actions">
|
function renderWallProps(warn) {
|
||||||
<button type="button" class="io-btn" data-go="floor:${u.id}">Vloer bewerken</button>
|
|
||||||
<button type="button" class="io-btn" data-go="roof:${u.id}">Dak bewerken</button>
|
|
||||||
${isRoot ? '' : `<button type="button" class="io-btn io-btn-danger" data-remove-unit="${u.id}">Unit verwijderen</button>`}
|
|
||||||
</div>`;
|
|
||||||
const onNum = (id, fn) => document.getElementById(id) && document.getElementById(id).addEventListener('input', (e) => {
|
|
||||||
const v = parseFloat(e.target.value);
|
|
||||||
if (isFinite(v)) { fn(v); refresh({ keepProps: true }); }
|
|
||||||
});
|
|
||||||
onNum('hp-width', v => { if (v >= 1000) u.unit.floor.floor.width = v; });
|
|
||||||
onNum('hp-depth', v => { if (v >= 1000) u.unit.floor.floor.depth = v; });
|
|
||||||
onNum('hp-att-offset', v => { u.attach.offset = v; });
|
|
||||||
const onSel = (id, fn) => document.getElementById(id) && document.getElementById(id).addEventListener('input', (e) => { fn(e.target.value); refresh(); });
|
|
||||||
onSel('hp-corners', v => { u.unit.layout.corners = v; });
|
|
||||||
onSel('hp-att-to', v => { u.attach.to = v; });
|
|
||||||
onSel('hp-att-side', v => { u.attach.side = v; });
|
|
||||||
onSel('hp-att-align', v => { u.attach.align = v; });
|
|
||||||
onSel('hp-roof-follow', v => { u.roof.followFloor = v === 'ja'; });
|
|
||||||
onSel('hp-fascia', v => { u.roof.fascia = v === 'aan'; });
|
|
||||||
onNum('hp-roof-w', v => { if (v >= 500) u.roof.deck.floor.width = v; });
|
|
||||||
onNum('hp-roof-d', v => { if (v >= 500) u.roof.deck.floor.depth = v; });
|
|
||||||
onNum('hp-roof-ox', v => { u.roof.offsetX = v; });
|
|
||||||
onNum('hp-roof-oz', v => { u.roof.offsetZ = v; });
|
|
||||||
} else if (selection && selection.type === 'wall') {
|
|
||||||
const pu = parts.units.find(x => x.id === selection.unit);
|
const pu = parts.units.find(x => x.id === selection.unit);
|
||||||
const wl = pu && pu.walls.find(x => x.side === selection.side);
|
const wl = pu && pu.walls.find(x => x.side === selection.side);
|
||||||
const piece = wl && wl.pieces.find(p => p.element === selection.element);
|
const piece = wl && wl.pieces.find(p => p.element === selection.element);
|
||||||
if (!piece) { selection = null; return renderProps(); }
|
if (!piece) { selection = null; return renderProps(); }
|
||||||
propsEl.innerHTML = `${warn}
|
const lt = state.settings.lighting;
|
||||||
<h1>${piece.element} — ${piece.label}</h1>
|
const rows = [
|
||||||
<div class="result-row"><span>Lengte volle zijde:</span><span class="highlight">${wl.width} mm</span></div>
|
['Lengte zijde', wl.width, 'mm'],
|
||||||
<div class="result-row"><span>Dit wandstuk:</span><span class="highlight">${piece.place.width} mm (${Math.round(piece.u0)}–${Math.round(piece.u1)} vanaf links, van buitenaf gezien)</span></div>
|
['Lengte wandstuk', piece.place.width, 'mm'],
|
||||||
<div class="result-row"><span>Naad met andere unit:</span><span class="highlight">${wl.cutsU.length ? wl.cutsU.map(([a, b]) => `${Math.round(a)}–${Math.round(b)}`).join(', ') + ' mm vervalt' : 'nee'}</span></div>
|
['Positie', `${Math.round(piece.u0)} – ${Math.round(piece.u1)}`, 'mm'],
|
||||||
<div class="result-row"><span>Openingen in dit stuk:</span><span class="highlight">${piece.config.openings.length}</span></div>
|
['Naad', wl.cutsU.length ? wl.cutsU.map(([a, b]) => `${Math.round(a)} – ${Math.round(b)}`).join(', ') : 'geen', wl.cutsU.length ? 'mm' : ''],
|
||||||
${state.settings.lighting.mode === 'gevel' ? `
|
['Openingen', piece.config.openings.length, '']
|
||||||
<div class="divider"></div>
|
];
|
||||||
<h1>Gevellampen op deze wand</h1>
|
const lampen = lt.mode === 'gevel'
|
||||||
${sel('hw-light', 'Lampen', LightingModel.WALL_OVERRIDES, unitById(pu.id).lighting.walls[wl.side] || 'auto')}
|
? sel('hw-light', 'Lampen', LightingModel.WALL_OVERRIDES, unitById(pu.id).lighting.walls[wl.side] || 'auto',
|
||||||
<div class="result-row"><span>Lampen op dit wandstuk:</span><span class="highlight">${piece.lamps.length}</span></div>
|
{ hint: `Automatisch: ${lt.endOffset} mm uit de hoek, alleen op vrije gevel van min. ${lt.minWallWidth} mm. Naast openingen: ${lt.openingOffset} mm naast elke opening.` }) +
|
||||||
<div class="material-info">Automatisch: vanaf ${state.settings.lighting.endOffset} mm van hoek/uiteinde, alleen op vrije stukken gevel ≥ ${state.settings.lighting.minWallWidth} mm op ${state.settings.lighting.lampHeight} mm hoogte (smallere stukken krijgen geen lampen). Override: ${state.settings.lighting.openingOffset} mm naast elke opening.</div>`
|
UI.specCard('Op dit wandstuk', 'lightbulb', [['Gevellampen', piece.lamps.length, 'st.']])
|
||||||
: '<div class="material-info">Gevellampen: kies in de woning-instellingen "Buitenverlichting: Gevellampen".</div>'}
|
: info('Gevellampen staan uit. Kies bij Woning › Verlichting het type "Gevellampen".');
|
||||||
${wl.dropped.length ? `<div class="global-warning">${wl.dropped.length} opening(en) van deze zijde vallen in een naad en vervallen.</div>` : ''}
|
propsEl.innerHTML = warn + head('bricks', piece.element, piece.label) +
|
||||||
<div class="ho-prop-actions"><button type="button" class="io-btn" data-go="wall:${pu.id}:${wl.side}">Wand bewerken</button></div>`;
|
cols(UI.specCard('Wandstuk', 'rulers', rows), section('Gevellampen', 'lightbulb', lampen)) +
|
||||||
const hw = document.getElementById('hw-light');
|
(wl.dropped.length ? UI.warn(`${wl.dropped.length} opening(en) van deze zijde vallen in een naad en vervallen.`) : '') +
|
||||||
if (hw) hw.addEventListener('input', (e) => { unitById(pu.id).lighting.walls[wl.side] = e.target.value; refresh(); });
|
actions(button('Wand bewerken', 'pencil-square', `data-go="wall:${pu.id}:${wl.side}"`));
|
||||||
} else if (selection && selection.type === 'iw' && iwById(selection.id)) {
|
UI.onSel('hw-light', v => { unitById(pu.id).lighting.walls[wl.side] = v; }, rerender);
|
||||||
const iw = iwById(selection.id);
|
|
||||||
const p = parts.interior.find(x => x.id === iw.id);
|
|
||||||
propsEl.innerHTML = `${warn}
|
|
||||||
<div class="controls-grid">
|
|
||||||
<div>
|
|
||||||
<h1>${p.element} — binnenwand</h1>
|
|
||||||
${num('hi-x1', 'Begin X', iw.x1)}${num('hi-z1', 'Begin Y', iw.z1)}
|
|
||||||
${num('hi-x2', 'Eind X', iw.x2)}${num('hi-z2', 'Eind Y', iw.z2)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h1>Opbouw</h1>
|
|
||||||
${num('hi-thick', 'Dikte', HouseModel.thicknessOf(iw.wall), 5)}
|
|
||||||
${num('hi-height', 'Hoogte', iw.wall.wall.heightAll)}
|
|
||||||
<div class="result-row"><span>Lengte:</span><span class="highlight">${p.place.width} mm</span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ho-prop-actions">
|
|
||||||
<button type="button" class="io-btn" data-go="iw:${iw.id}">Wand bewerken (openingen)</button>
|
|
||||||
<button type="button" class="io-btn io-btn-danger" data-remove-iw="${iw.id}">Verwijderen</button>
|
|
||||||
</div>`;
|
|
||||||
const onNum = (id, fn) => document.getElementById(id).addEventListener('input', (e) => {
|
|
||||||
const v = parseFloat(e.target.value);
|
|
||||||
if (isFinite(v)) { fn(v); refresh({ keepProps: true }); }
|
|
||||||
});
|
|
||||||
['x1', 'z1', 'x2', 'z2'].forEach(f => onNum('hi-' + f, v => { iw[f] = v; }));
|
|
||||||
onNum('hi-thick', v => { if (v >= 40) HouseModel.setThickness(iw.wall, v); });
|
|
||||||
onNum('hi-height', v => { if (v >= 500) iw.wall.wall.heightAll = iw.wall.wall.heightLeft = iw.wall.wall.heightRight = v; });
|
|
||||||
} else {
|
|
||||||
propsEl.innerHTML = `${warn}${errs}
|
|
||||||
<h1>Woning</h1>
|
|
||||||
<div class="material-info">${state.units.length} unit(s), ${state.interiorWalls.length} binnenwand(en).
|
|
||||||
Klik in de plattegrond op een unit, wand of binnenwand om die te selecteren; dubbelklik om te bewerken.
|
|
||||||
Waar units tegen elkaar staan (oranje gestippeld) vervalt de wand.</div>
|
|
||||||
<div class="ho-prop-actions">
|
|
||||||
<button type="button" class="io-btn" data-action="add-unit">+ Unit toevoegen</button>
|
|
||||||
<button type="button" class="io-btn" data-action="draw-iw">Binnenwand tekenen</button>
|
|
||||||
<button type="button" class="io-btn" data-action="project">Project eigenschappen</button>
|
|
||||||
</div>
|
|
||||||
<div class="divider"></div>
|
|
||||||
${renderSettingsHtml()}`;
|
|
||||||
wireSettings();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Woning-instellingen: h.o.h. voor ALLE buiten-/binnenwanden + dakrand.
|
function renderIwProps(iw, warn) {
|
||||||
function renderSettingsHtml() {
|
const p = parts.interior.find(x => x.id === iw.id);
|
||||||
const sp = state.settings.spacing, r = state.settings.roof, lt = state.settings.lighting;
|
propsEl.innerHTML = warn + head('distribute-horizontal', p.element, `Binnenwand · ${p.place.width} mm`) +
|
||||||
return `
|
cols(
|
||||||
<div class="controls-grid">
|
section('Positie', 'crosshair', num('hi-x1', 'Begin X', iw.x1) + num('hi-z1', 'Begin Y', iw.z1) + num('hi-x2', 'Eind X', iw.x2) + num('hi-z2', 'Eind Y', iw.z2)),
|
||||||
<div>
|
section('Opbouw', 'layers', num('hi-thick', 'Dikte', HouseModel.thicknessOf(iw.wall), { step: 5 }) + num('hi-height', 'Hoogte', iw.wall.wall.heightAll))) +
|
||||||
<h1>Gevelbekleding (test, alle buitenwanden)</h1>
|
actions(
|
||||||
${sel('hs-cladding', 'Richting WEO/Novalit', WallModel.CLADDING_DIRECTIONS, state.settings.cladding)}
|
button('Openingen bewerken', 'pencil-square', `data-go="iw:${iw.id}"`),
|
||||||
${sel('hs-trasraam', 'Trasraam (cement-gebonden plaat)', { aan: 'Aan', uit: 'Uit' }, state.settings.trasraam.enabled ? 'aan' : 'uit')}
|
button('Verwijderen', 'trash', `data-remove-iw="${iw.id}"`, 'ui-btn-danger'));
|
||||||
${state.settings.trasraam.enabled ? num('hs-trasraam-h', 'Hoogte trasraam (vanaf bovenkant vloer)', state.settings.trasraam.height, 10) : ''}
|
['x1', 'z1', 'x2', 'z2'].forEach(k => UI.onNum('hi-' + k, v => { iw[k] = v; }, after));
|
||||||
${sel('hs-sandwich', `Sandwichpanelen rondom (${SandwichModel.DEPTH_MM} mm de grond in)`, { aan: 'Aan', uit: 'Uit' }, state.settings.sandwich ? 'aan' : 'uit')}
|
UI.onNum('hi-thick', v => { if (v >= 40) HouseModel.setThickness(iw.wall, v); }, after);
|
||||||
<div class="divider"></div>
|
UI.onNum('hi-height', v => { if (v >= 500) iw.wall.wall.heightAll = iw.wall.wall.heightLeft = iw.wall.wall.heightRight = v; }, after);
|
||||||
<h1>Regelwerk h.o.h. (alle wanden)</h1>
|
|
||||||
${num('hs-ext-min', 'Buitenwanden min.', sp.exterior.min, 10)}
|
|
||||||
${num('hs-ext-max', 'Buitenwanden max.', sp.exterior.max, 10)}
|
|
||||||
${num('hs-int-min', 'Binnenwanden min.', sp.interior.min, 10)}
|
|
||||||
${num('hs-int-max', 'Binnenwanden max.', sp.interior.max, 10)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h1>Dakrand (maten, hele woning)</h1>
|
|
||||||
${num('hs-overhang', `Overstek boeideel (${RoofModel.OVERHANG_MIN}–${RoofModel.OVERHANG_MAX})`, r.overhang, 10)}
|
|
||||||
${num('hs-fascia-h', 'Hoogte boeideel', r.fasciaHeight, 10)}
|
|
||||||
${num('hs-trim-h', 'Hoogte daktrim', r.trimHeight, 5)}
|
|
||||||
${num('hs-spot-min', 'Spots min. h.o.h.', r.spotMin, 50)}
|
|
||||||
${num('hs-spot-max', 'Spots max. h.o.h.', r.spotMax, 50)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="divider"></div>
|
|
||||||
<div class="controls-grid">
|
|
||||||
<div>
|
|
||||||
<h1>Buitenverlichting (hele woning)</h1>
|
|
||||||
${sel('hs-light-mode', 'Type', LightingModel.MODES, lt.mode)}
|
|
||||||
${lt.mode === 'gevel' ? sel('hs-light-dir', 'Lichtrichting', LightingModel.DIRECTIONS, lt.direction) : ''}
|
|
||||||
${lt.mode === 'spots' ? '<div class="material-info">Boeideel-spots komen alleen bij units met boeidelen.</div>' : ''}
|
|
||||||
${num('hs-lamp-h', 'Lamphoogte', lt.lampHeight, 50)}
|
|
||||||
${num('hs-lamp-min', 'Lampen min. h.o.h.', lt.lampMin, 50)}
|
|
||||||
${num('hs-lamp-max', 'Lampen max. h.o.h.', lt.lampMax, 50)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h1> </h1>
|
|
||||||
${num('hs-lamp-w', 'Min. vrije gevelbreedte', lt.minWallWidth, 50)}
|
|
||||||
${num('hs-lamp-end', 'Vanaf hoek/uiteinde', lt.endOffset, 50)}
|
|
||||||
${num('hs-lamp-open', 'Override: naast opening', lt.openingOffset, 50)}
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
}
|
}
|
||||||
function wireSettings() {
|
|
||||||
const sp = state.settings.spacing, r = state.settings.roof;
|
// Woning-instellingen (niets geselecteerd): alles wat voor de hele woning
|
||||||
const onNum = (id, fn) => { const el = document.getElementById(id); if (el) el.addEventListener('input', (e) => { const v = parseFloat(e.target.value); if (isFinite(v) && v > 0) { fn(v); refresh({ keepProps: true }); } }); };
|
// geldt, in tabbladen.
|
||||||
const onSel = (id, fn) => { const el = document.getElementById(id); if (el) el.addEventListener('input', (e) => { fn(e.target.value); refresh(); }); };
|
function renderHouseProps(warn) {
|
||||||
onSel('hs-cladding', v => { state.settings.cladding = v; });
|
const st = state.settings, sp = st.spacing, r = st.roof, lt = st.lighting;
|
||||||
onSel('hs-trasraam', v => { state.settings.trasraam.enabled = v === 'aan'; });
|
const pos = { positive: true };
|
||||||
onSel('hs-sandwich', v => { state.settings.sandwich = v === 'aan'; });
|
const verlichting = lt.mode === 'gevel'
|
||||||
onNum('hs-trasraam-h', v => { state.settings.trasraam.height = v; });
|
? cols(
|
||||||
onNum('hs-ext-min', v => { sp.exterior.min = v; });
|
section('Gevellampen', 'lightbulb', sel('hs-light-dir', 'Lichtrichting', LightingModel.DIRECTIONS, lt.direction) +
|
||||||
onNum('hs-ext-max', v => { sp.exterior.max = v; });
|
num('hs-lamp-h', 'Hoogte', lt.lampHeight, { hint: 'Vanaf bovenkant vloer' }) +
|
||||||
onNum('hs-int-min', v => { sp.interior.min = v; });
|
num('hs-lamp-min', 'Afstand min.', lt.lampMin, { hint: 'Hart-op-hart' }) + num('hs-lamp-max', 'Afstand max.', lt.lampMax, { hint: 'Hart-op-hart' })),
|
||||||
onNum('hs-int-max', v => { sp.interior.max = v; });
|
section('Plaatsing', 'distribute-horizontal', num('hs-lamp-w', 'Min. gevelbreedte', lt.minWallWidth, { hint: 'Smallere vrije stukken gevel krijgen geen lamp' }) +
|
||||||
const lt = state.settings.lighting;
|
num('hs-lamp-end', 'Afstand tot hoek', lt.endOffset) +
|
||||||
onNum('hs-overhang', v => { r.overhang = Math.min(RoofModel.OVERHANG_MAX, Math.max(RoofModel.OVERHANG_MIN, v)); });
|
num('hs-lamp-open', 'Afstand tot opening', lt.openingOffset, { hint: 'Voor wanden met de instelling "Naast openingen"' })))
|
||||||
onNum('hs-fascia-h', v => { r.fasciaHeight = v; });
|
: lt.mode === 'spots'
|
||||||
onNum('hs-spot-min', v => { r.spotMin = v; });
|
? cols(section('Boeideel-spots', 'lightbulb', num('hs-spot-min', 'Afstand min.', r.spotMin, { hint: 'Hart-op-hart' }) + num('hs-spot-max', 'Afstand max.', r.spotMax, { hint: 'Hart-op-hart' })),
|
||||||
onSel('hs-light-mode', v => { lt.mode = v; });
|
info('Spots komen alleen onder units met boeidelen.'))
|
||||||
onSel('hs-light-dir', v => { lt.direction = v; });
|
: '';
|
||||||
onNum('hs-lamp-h', v => { lt.lampHeight = v; });
|
|
||||||
onNum('hs-lamp-min', v => { lt.lampMin = v; });
|
propsEl.innerHTML = warn + head('house-door', 'Woning', `${state.units.length} unit(s) · ${state.interiorWalls.length} binnenwand(en)`) +
|
||||||
onNum('hs-lamp-max', v => { lt.lampMax = v; });
|
UI.tabs('woning', [
|
||||||
onNum('hs-lamp-w', v => { lt.minWallWidth = v; });
|
{ key: 'algemeen', label: 'Algemeen', icon: 'house-door', html:
|
||||||
onNum('hs-lamp-end', v => { lt.endOffset = v; });
|
info('Klik in de plattegrond op een unit, wand of binnenwand om te selecteren; dubbelklik om te bewerken. Waar units tegen elkaar staan (oranje gestippeld) vervalt de wand.') +
|
||||||
onNum('hs-lamp-open', v => { lt.openingOffset = v; });
|
actions(button('Unit toevoegen', 'plus-lg', 'data-action="add-unit"'), button('Binnenwand tekenen', 'pencil', 'data-action="draw-iw"'),
|
||||||
onNum('hs-spot-max', v => { r.spotMax = v; });
|
button('Projectgegevens', 'card-text', 'data-action="project"')) },
|
||||||
onNum('hs-trim-h', v => { r.trimHeight = v; });
|
{ key: 'gevel', label: 'Gevel', icon: 'bricks', html: cols(
|
||||||
|
section('Gevelbekleding', 'grip-horizontal', sel('hs-cladding', 'Richting', WallModel.CLADDING_DIRECTIONS, st.cladding, { hint: 'WEO/Novalit, voorlopig voor alle buitenwanden' })) +
|
||||||
|
section('Trasraam', 'bricks', toggle('hs-trasraam', 'Trasraam', st.trasraam.enabled, { hint: 'Cement-gebonden plaat onderaan de gevel' }) +
|
||||||
|
(st.trasraam.enabled ? num('hs-trasraam-h', 'Hoogte', st.trasraam.height, { step: 10, hint: 'Vanaf bovenkant vloer' }) : '')),
|
||||||
|
section('Sandwichpanelen', 'bounding-box', toggle('hs-sandwich', 'Rondom woning', st.sandwich,
|
||||||
|
{ hint: `${SandwichModel.PANEL_WIDTH_MM} × ${SandwichModel.PANEL_LENGTH_MM} × ${SandwichModel.THICK_MM} mm, vanaf bovenkant vloer ${SandwichModel.DEPTH_MM} mm de grond in` }))) },
|
||||||
|
{ key: 'constructie', label: 'Regelwerk', icon: 'columns-gap', html: cols(
|
||||||
|
section('Buitenwanden', 'bricks', num('hs-ext-min', 'Afstand min.', sp.exterior.min, { step: 10, hint: 'Hart-op-hart stijlen' }) + num('hs-ext-max', 'Afstand max.', sp.exterior.max, { step: 10, hint: 'Hart-op-hart stijlen' })),
|
||||||
|
section('Binnenwanden', 'distribute-horizontal', num('hs-int-min', 'Afstand min.', sp.interior.min, { step: 10, hint: 'Hart-op-hart stijlen' }) + num('hs-int-max', 'Afstand max.', sp.interior.max, { step: 10, hint: 'Hart-op-hart stijlen' }))) },
|
||||||
|
{ key: 'dak', label: 'Dakrand', icon: 'house', html: cols(
|
||||||
|
section('Boeideel', 'border-outer', num('hs-overhang', 'Overstek', r.overhang, { step: 10, hint: `${RoofModel.OVERHANG_MIN} – ${RoofModel.OVERHANG_MAX} mm` }) + num('hs-fascia-h', 'Hoogte', r.fasciaHeight, { step: 10 })),
|
||||||
|
section('Daktrim', 'border-top', num('hs-trim-h', 'Hoogte', r.trimHeight, { step: 5, hint: 'Bij units zonder boeidelen' }))) },
|
||||||
|
{ key: 'verlichting', label: 'Verlichting', icon: 'lightbulb', html:
|
||||||
|
cols(sel('hs-light-mode', 'Type', LightingModel.MODES, lt.mode), '') + verlichting }
|
||||||
|
]);
|
||||||
|
|
||||||
|
UI.onSel('hs-cladding', v => { st.cladding = v; }, rerender);
|
||||||
|
UI.onToggle('hs-trasraam', v => { st.trasraam.enabled = v; }, rerender);
|
||||||
|
UI.onToggle('hs-sandwich', v => { st.sandwich = v; }, rerender);
|
||||||
|
UI.onNum('hs-trasraam-h', v => { st.trasraam.height = v; }, after, pos);
|
||||||
|
UI.onNum('hs-ext-min', v => { sp.exterior.min = v; }, after, pos);
|
||||||
|
UI.onNum('hs-ext-max', v => { sp.exterior.max = v; }, after, pos);
|
||||||
|
UI.onNum('hs-int-min', v => { sp.interior.min = v; }, after, pos);
|
||||||
|
UI.onNum('hs-int-max', v => { sp.interior.max = v; }, after, pos);
|
||||||
|
UI.onNum('hs-overhang', v => { r.overhang = Math.min(RoofModel.OVERHANG_MAX, Math.max(RoofModel.OVERHANG_MIN, v)); }, after, pos);
|
||||||
|
UI.onNum('hs-fascia-h', v => { r.fasciaHeight = v; }, after, pos);
|
||||||
|
UI.onNum('hs-trim-h', v => { r.trimHeight = v; }, after, pos);
|
||||||
|
UI.onNum('hs-spot-min', v => { r.spotMin = v; }, after, pos);
|
||||||
|
UI.onNum('hs-spot-max', v => { r.spotMax = v; }, after, pos);
|
||||||
|
UI.onSel('hs-light-mode', v => { lt.mode = v; }, rerender);
|
||||||
|
UI.onSel('hs-light-dir', v => { lt.direction = v; }, rerender);
|
||||||
|
UI.onNum('hs-lamp-h', v => { lt.lampHeight = v; }, after, pos);
|
||||||
|
UI.onNum('hs-lamp-min', v => { lt.lampMin = v; }, after, pos);
|
||||||
|
UI.onNum('hs-lamp-max', v => { lt.lampMax = v; }, after, pos);
|
||||||
|
UI.onNum('hs-lamp-w', v => { lt.minWallWidth = v; }, after, pos);
|
||||||
|
UI.onNum('hs-lamp-end', v => { lt.endOffset = v; }, after, pos);
|
||||||
|
UI.onNum('hs-lamp-open', v => { lt.openingOffset = v; }, after, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
propsEl.addEventListener('click', (e) => {
|
propsEl.addEventListener('click', (e) => {
|
||||||
@@ -859,37 +804,29 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
else if (act.dataset.action === 'project') openProjectProperties();
|
else if (act.dataset.action === 'project') openProjectProperties();
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Specificaties/totalen ---
|
// --- Specificaties/totalen (onder de 3D) ---
|
||||||
function renderSpecs(totals) {
|
function renderSpecs(totals) {
|
||||||
const row = (label, value) => `<div class="result-row"><span>${label}:</span><span class="highlight">${value}</span></div>`;
|
const f = houseFmt;
|
||||||
document.getElementById('ho-specs').innerHTML = `
|
const cards = [
|
||||||
<div class="controls-grid">
|
UI.specCard('Gewicht', 'speedometer2', totals.weights.map(r => [r.label, f(r.kg), 'kg']), ['Totaal woning', f(totals.totalKg), 'kg']),
|
||||||
<div>
|
UI.specCard('Hout & isolatie', 'tree', [
|
||||||
<div class="spec-section"><h1>Gewicht</h1>
|
['Balken', totals.beamCount, 'st.'],
|
||||||
${totals.weights.map(r => row(r.label, houseFmt(r.kg) + ' kg')).join('')}
|
['Volume hout', f(totals.woodM3, 3), 'm³'],
|
||||||
<div class="divider"></div>${row('Totaal woning', houseFmt(totals.totalKg) + ' kg')}
|
['Inkoop wandbalken', WallModel.purchaseText(totals.purchase), ''],
|
||||||
</div>
|
['Snijverlies', `${f(totals.purchaseWasteMM / 1000, 2)} m · ${f(totals.purchaseStockMM ? totals.purchaseWasteMM / totals.purchaseStockMM * 100 : 0)}`, '%'],
|
||||||
</div>
|
[`Latwerk ${WallModel.BATTEN.thick}×${WallModel.BATTEN.width}`, `${totals.battens.count} × ${WallModel.BATTEN.length}`, 'mm'],
|
||||||
<div>
|
['Glaswol', f(totals.isolatieM3, 3), 'm³']
|
||||||
<div class="spec-section"><h1>Houtwerk & isolatie</h1>
|
]),
|
||||||
${row('Aantal balken', totals.beamCount + ' stuks')}
|
UI.specCard('Plaatwerk', 'layers', totals.plaatwerk.filter(p => p.m2 > 0).map(p => [p.naam, f(p.m2, 2), 'm²']))
|
||||||
${row('Volume hout', houseFmt(totals.woodM3, 3) + ' m3')}
|
];
|
||||||
${row('Latwerk 20x30 (kruislat)', totals.battens.count + ' st. à ' + WallModel.BATTEN.length + ' mm')}
|
if (totals.roofEdge) {
|
||||||
${row('Inkoop wandbalken', WallModel.purchaseText(totals.purchase))}
|
const e = totals.roofEdge, rows = [];
|
||||||
${row('Snijverlies', houseFmt(totals.purchaseWasteMM / 1000, 2) + ' m (' + houseFmt(totals.purchaseStockMM ? totals.purchaseWasteMM / totals.purchaseStockMM * 100 : 0) + '%)')}
|
if (e.fasciaLengthM > 0) rows.push([`Boeidelen · ${e.fasciaHeight} mm`, f(e.fasciaLengthM, 2), 'm'], ['Onderplaat boeideel', f(e.soffitM2, 2), 'm²']);
|
||||||
${row('Glaswol (isolatie)', houseFmt(totals.isolatieM3, 3) + ' m3')}
|
if (e.trimLengthM > 0) rows.push([`Daktrim · ${e.trimHeight} mm`, f(e.trimLengthM, 2), 'm']);
|
||||||
</div>
|
rows.push(['Boeideel-spots', e.spots, 'st.'], ['Gevellampen', totals.lamps.total, 'st.']);
|
||||||
<div class="spec-section"><h1>Plaatwerk</h1>
|
cards.push(UI.specCard('Dakrand & verlichting', 'lightbulb', rows));
|
||||||
${totals.plaatwerk.filter(p => p.m2 > 0).map(p => row(p.naam, houseFmt(p.m2, 2) + ' m2')).join('')}
|
}
|
||||||
</div>
|
document.getElementById('ho-specs').innerHTML = `<div class="ui-spec-grid">${cards.join('')}</div>`;
|
||||||
${totals.roofEdge ? `<div class="spec-section"><h1>Dakrand & buitenverlichting</h1>
|
|
||||||
${totals.roofEdge.fasciaLengthM > 0 ? row('Boeidelen', houseFmt(totals.roofEdge.fasciaLengthM, 2) + ' m · ' + totals.roofEdge.fasciaHeight + ' mm hoog') + row('Onderplaat boeideel', houseFmt(totals.roofEdge.soffitM2, 2) + ' m2') : ''}
|
|
||||||
${totals.roofEdge.trimLengthM > 0 ? row('Daktrim', houseFmt(totals.roofEdge.trimLengthM, 2) + ' m · ' + totals.roofEdge.trimHeight + ' mm hoog') : ''}
|
|
||||||
${row('Boeideel-spots', totals.roofEdge.spots + ' stuks')}
|
|
||||||
${row('Gevellampen', totals.lamps.total + ' stuks')}
|
|
||||||
</div>` : ''}
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Alles opnieuw doorrekenen ---
|
// --- Alles opnieuw doorrekenen ---
|
||||||
@@ -952,10 +889,8 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
}
|
}
|
||||||
window.addEventListener('resize', onResize);
|
window.addEventListener('resize', onResize);
|
||||||
|
|
||||||
function toggleFullscreen(cls, other) {
|
function toggleView(fn) {
|
||||||
const el = document.getElementById('house-overview');
|
fn(document.getElementById('house-overview'));
|
||||||
el.classList.remove(other);
|
|
||||||
el.classList.toggle(cls);
|
|
||||||
requestAnimationFrame(onResize);
|
requestAnimationFrame(onResize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -974,8 +909,8 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
},
|
},
|
||||||
refresh,
|
refresh,
|
||||||
setTool,
|
setTool,
|
||||||
toggleFullscreen3D: () => toggleFullscreen('fullscreen-3d', 'fullscreen-2d'),
|
toggleFullscreen3D: () => toggleView(UI.cycle3D),
|
||||||
toggleFullscreen2D: () => toggleFullscreen('fullscreen-2d', 'fullscreen-3d')
|
toggleFullscreen2D: () => toggleView(UI.toggle2D)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+68
-75
@@ -25,16 +25,16 @@ const WALL_TEMPLATE = `
|
|||||||
<div id="left-column">
|
<div id="left-column">
|
||||||
<div id="pane-2d-container">
|
<div id="pane-2d-container">
|
||||||
<div class="pane-2d-sub pane-2d-primary" id="pane-2d-front">
|
<div class="pane-2d-sub pane-2d-primary" id="pane-2d-front">
|
||||||
<div class="pane-label">2D BUITENAANZICHT</div>
|
<div class="pane-label">BUITENAANZICHT</div>
|
||||||
<canvas id="canvas-2d-front"></canvas>
|
<canvas id="canvas-2d-front"></canvas>
|
||||||
</div>
|
</div>
|
||||||
<div class="pane-2d-sub pane-2d-row" id="pane-2d-bottom-row">
|
<div class="pane-2d-sub pane-2d-row" id="pane-2d-bottom-row">
|
||||||
<div class="pane-2d-half" id="pane-2d-top">
|
<div class="pane-2d-half" id="pane-2d-top">
|
||||||
<div class="pane-label">2D BOVENAANZICHT</div>
|
<div class="pane-label">BOVENAANZICHT</div>
|
||||||
<canvas id="canvas-2d-top"></canvas>
|
<canvas id="canvas-2d-top"></canvas>
|
||||||
</div>
|
</div>
|
||||||
<div class="pane-2d-half" id="pane-2d-side">
|
<div class="pane-2d-half" id="pane-2d-side">
|
||||||
<div class="pane-label">2D ZIJAANZICHT</div>
|
<div class="pane-label">ZIJAANZICHT</div>
|
||||||
<canvas id="canvas-2d-side"></canvas>
|
<canvas id="canvas-2d-side"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -46,100 +46,97 @@ const WALL_TEMPLATE = `
|
|||||||
<span id="io-status" class="io-status"></span>
|
<span id="io-status" class="io-status"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-bar">
|
<div class="tab-bar">
|
||||||
<button class="tab-btn active" data-tab="tab-wall">1. Wand Opbouw</button>
|
<button class="tab-btn active" data-tab="tab-wall"><i class="bi bi-rulers"></i> Opbouw</button>
|
||||||
<button class="tab-btn" data-tab="tab-opening">2. Openingen</button>
|
<button class="tab-btn" data-tab="tab-opening"><i class="bi bi-door-open"></i> Openingen</button>
|
||||||
<button class="tab-btn" data-tab="tab-materials">3. Materiaalkeuze</button>
|
<button class="tab-btn" data-tab="tab-materials"><i class="bi bi-layers"></i> Materiaal</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- TAB 1: WAND -->
|
<!-- TAB 1: WAND -->
|
||||||
<div id="tab-wall" class="tab-content active">
|
<div id="tab-wall" class="tab-content active">
|
||||||
<div class="controls-grid">
|
<div class="controls-grid">
|
||||||
<div>
|
<div>
|
||||||
<h1>Wand Dimensies (mm)</h1>
|
<h1><i class="bi bi-rulers"></i> Afmetingen</h1>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="inp-wall-w">Wand Breedte:</label>
|
<label for="inp-wall-w">Breedte</label>
|
||||||
<div><input type="number" id="inp-wall-w" value="6000" step="50" min="800" max="10000"><span class="unit">mm</span></div>
|
<div><input type="number" id="inp-wall-w" value="6000" step="50" min="800" max="10000"><span class="unit">mm</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="inp-wall-h-all">Totale Hoogte:</label>
|
<label for="inp-wall-h-all">Hoogte</label>
|
||||||
<div><input type="number" id="inp-wall-h-all" value="2400" step="50" min="500" max="5000"><span class="unit">mm</span></div>
|
<div><input type="number" id="inp-wall-h-all" value="2400" step="50" min="500" max="5000"><span class="unit">mm</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="inp-wall-h-left">Hoogte Links ($Y_1$):</label>
|
<label for="inp-wall-h-left">Hoogte links</label>
|
||||||
<div><input type="number" id="inp-wall-h-left" value="2400" step="50" min="500" max="5000"><span class="unit">mm</span></div>
|
<div><input type="number" id="inp-wall-h-left" value="2400" step="50" min="500" max="5000"><span class="unit">mm</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="inp-wall-h-right">Hoogte Rechts ($Y_2$):</label>
|
<label for="inp-wall-h-right">Hoogte rechts</label>
|
||||||
<div><input type="number" id="inp-wall-h-right" value="2400" step="50" min="500" max="5000"><span class="unit">mm</span></div>
|
<div><input type="number" id="inp-wall-h-right" value="2400" step="50" min="500" max="5000"><span class="unit">mm</span></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<h1>Verticale Balken & Dorpels</h1>
|
<h1><i class="bi bi-columns-gap"></i> Stijlen</h1>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="inp-stud-w">Verticale Balk Dikte/Breedte:</label>
|
<label for="inp-stud-w">Dikte</label>
|
||||||
<div><input type="number" id="inp-stud-w" value="38" step="5" min="20" max="300"><span class="unit">mm</span></div>
|
<div><input type="number" id="inp-stud-w" value="38" step="5" min="20" max="300"><span class="unit">mm</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="inp-stud-d">Verticale Balk Diepte:</label>
|
<label for="inp-stud-d">Diepte</label>
|
||||||
<div><input type="number" id="inp-stud-d" value="235" step="5" min="20" max="400"><span class="unit">mm</span></div>
|
<div><input type="number" id="inp-stud-d" value="235" step="5" min="20" max="400"><span class="unit">mm</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h1>Muur Liggers (Top & Onder)</h1>
|
<h1><i class="bi bi-distribute-vertical"></i> Regels (boven en onder)</h1>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="inp-plate-w">Ligger Dikte:</label>
|
<label for="inp-plate-w">Dikte</label>
|
||||||
<div><input type="number" id="inp-plate-w" value="38" step="5" min="20" max="200"><span class="unit">mm</span></div>
|
<div><input type="number" id="inp-plate-w" value="38" step="5" min="20" max="200"><span class="unit">mm</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="inp-plate-d">Ligger Diepte:</label>
|
<label for="inp-plate-d">Diepte</label>
|
||||||
<div><input type="number" id="inp-plate-d" value="235" step="5" min="20" max="400"><span class="unit">mm</span></div>
|
<div><input type="number" id="inp-plate-d" value="235" step="5" min="20" max="400"><span class="unit">mm</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="inp-sheathing-t">Beplating Dikte:</label>
|
<label for="inp-sheathing-t">Dikte beplating</label>
|
||||||
<div><input type="number" id="inp-sheathing-t" value="10" step="1" min="3" max="30"><span class="unit">mm</span></div>
|
<div><input type="number" id="inp-sheathing-t" value="10" step="1" min="3" max="30"><span class="unit">mm</span></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<h1>Regelwerk Afstand (h.o.h.)</h1>
|
<h1><i class="bi bi-arrows"></i> Regelwerk</h1>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="inp-min-sp">Min. h.o.h.:</label>
|
<label for="inp-min-sp">Afstand min.</label>
|
||||||
<div><input type="number" id="inp-min-sp" value="240" step="10" min="100" max="1000"><span class="unit">mm</span></div>
|
<div><input type="number" id="inp-min-sp" value="240" step="10" min="100" max="1000"><span class="unit">mm</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="inp-max-sp">Max. h.o.h.:</label>
|
<label for="inp-max-sp">Afstand max.</label>
|
||||||
<div><input type="number" id="inp-max-sp" value="480" step="10" min="150" max="1500"><span class="unit">mm</span></div>
|
<div><input type="number" id="inp-max-sp" value="480" step="10" min="150" max="1500"><span class="unit">mm</span></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<div class="result-row"><span>Totaal verticale balken:</span><span data-spec="vert-beams" class="highlight">-</span></div>
|
<div class="result-row"><span>Verticale balken</span><span data-spec="vert-beams" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Totaal horizontale balken:</span><span data-spec="horiz-beams" class="highlight">-</span></div>
|
<div class="result-row"><span>Horizontale balken</span><span data-spec="horiz-beams" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Dakhelling:</span><span data-spec="roof-angle" class="highlight">-</span></div>
|
<div class="result-row"><span>Dakhelling</span><span data-spec="roof-angle" class="highlight">-</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- TAB 2: OPENINGEN (meerdere, ankerbaar) -->
|
<!-- TAB 2: OPENINGEN (meerdere, ankerbaar) -->
|
||||||
<div id="tab-opening" class="tab-content">
|
<div id="tab-opening" class="tab-content">
|
||||||
<button id="btn-add-opening" class="add-opening-btn">+ Opening Toevoegen</button>
|
<button id="btn-add-opening" class="add-opening-btn"><i class="bi bi-plus-lg"></i> Opening toevoegen</button>
|
||||||
<div id="openings-global-warning" class="global-warning" style="display:none;"></div>
|
<div id="openings-global-warning" class="global-warning" style="display:none;"></div>
|
||||||
<div id="openings-list"></div>
|
<div id="openings-list"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- TAB 3: MATERIAALKEUZE -->
|
<!-- TAB 3: MATERIAALKEUZE -->
|
||||||
<div id="tab-materials" class="tab-content">
|
<div id="tab-materials" class="tab-content">
|
||||||
<h1>Binnenzijde</h1>
|
<h1><i class="bi bi-layers"></i> Buitenzijde</h1>
|
||||||
|
|
||||||
<div class="divider"></div>
|
|
||||||
<h1>Buitenzijde</h1>
|
|
||||||
<div class="material-toggle-group">
|
<div class="material-toggle-group">
|
||||||
<button type="button" id="btn-mat-vochtdoek" class="material-toggle-btn active" data-material="vochtdoek">Vochtdoek</button>
|
<button type="button" id="btn-mat-vochtdoek" class="material-toggle-btn active" data-material="vochtdoek">Vochtdoek</button>
|
||||||
<button type="button" id="btn-mat-cementplaat" class="material-toggle-btn" data-material="cementplaat">Cement-gebonden platen</button>
|
<button type="button" id="btn-mat-cementplaat" class="material-toggle-btn" data-material="cementplaat">Cementplaat</button>
|
||||||
<button type="button" id="btn-mat-spaanplaat" class="material-toggle-btn" data-material="spaanplaat">Spaanplaat</button>
|
<button type="button" id="btn-mat-spaanplaat" class="material-toggle-btn" data-material="spaanplaat">Spaanplaat</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="cladding-section">
|
<div id="cladding-section">
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<h1>Gevelbekleding (WEO/Novalit) — richting</h1>
|
<h1><i class="bi bi-grip-horizontal"></i> Richting gevelbekleding</h1>
|
||||||
<div class="material-toggle-group">
|
<div class="material-toggle-group">
|
||||||
<button type="button" class="material-toggle-btn active" data-cladding="horizontaal">Horizontaal</button>
|
<button type="button" class="material-toggle-btn active" data-cladding="horizontaal">Horizontaal</button>
|
||||||
<button type="button" class="material-toggle-btn" data-cladding="verticaal">Verticaal</button>
|
<button type="button" class="material-toggle-btn" data-cladding="verticaal">Verticaal</button>
|
||||||
@@ -153,7 +150,7 @@ const WALL_TEMPLATE = `
|
|||||||
|
|
||||||
<div id="right-column">
|
<div id="right-column">
|
||||||
<div id="pane-3d">
|
<div id="pane-3d">
|
||||||
<div class="pane-label">3D PERSPECTIEF</div>
|
<div class="pane-label">3D</div>
|
||||||
<div class="toolbar-3d" id="wall-toolbar-3d"></div>
|
<div class="toolbar-3d" id="wall-toolbar-3d"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -161,37 +158,37 @@ const WALL_TEMPLATE = `
|
|||||||
<div class="controls-grid">
|
<div class="controls-grid">
|
||||||
<div>
|
<div>
|
||||||
<div class="spec-section">
|
<div class="spec-section">
|
||||||
<h1>Balken & Dak</h1>
|
<h1><i class="bi bi-columns-gap"></i> Balken</h1>
|
||||||
<div class="result-row"><span>Verticale balken:</span><span data-spec="vert-beams" class="highlight">-</span></div>
|
<div class="result-row"><span>Verticale balken</span><span data-spec="vert-beams" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Horizontale balken:</span><span data-spec="horiz-beams" class="highlight">-</span></div>
|
<div class="result-row"><span>Horizontale balken</span><span data-spec="horiz-beams" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Dakhelling:</span><span data-spec="roof-angle" class="highlight">-</span></div>
|
<div class="result-row"><span>Dakhelling</span><span data-spec="roof-angle" class="highlight">-</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="spec-section">
|
<div class="spec-section">
|
||||||
<h1>Houtwerk</h1>
|
<h1><i class="bi bi-tree"></i> Houtwerk</h1>
|
||||||
<div class="result-row"><span>Volume hout:</span><span data-spec="wood-m3" class="highlight">-</span></div>
|
<div class="result-row"><span>Volume hout</span><span data-spec="wood-m3" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Gewicht hout:</span><span data-spec="wood-kg" class="highlight">-</span></div>
|
<div class="result-row"><span>Gewicht hout</span><span data-spec="wood-kg" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Inkoop balken:</span><span data-spec="stock-list" class="highlight">-</span></div>
|
<div class="result-row"><span>Inkoop balken</span><span data-spec="stock-list" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Snijverlies:</span><span data-spec="stock-waste" class="highlight">-</span></div>
|
<div class="result-row"><span>Snijverlies</span><span data-spec="stock-waste" class="highlight">-</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="spec-section">
|
<div class="spec-section">
|
||||||
<h1>Spaanplaat (binnenzijde)</h1>
|
<h1><i class="bi bi-layers"></i> Binnenzijde: spaanplaat</h1>
|
||||||
<div class="result-row"><span>Oppervlak:</span><span data-spec="spaanplaat-m2" class="highlight">-</span></div>
|
<div class="result-row"><span>Oppervlak</span><span data-spec="spaanplaat-m2" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Volume:</span><span data-spec="spaanplaat-m3" class="highlight">-</span></div>
|
<div class="result-row"><span>Volume</span><span data-spec="spaanplaat-m3" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Gewicht:</span><span data-spec="spaanplaat-kg" class="highlight">-</span></div>
|
<div class="result-row"><span>Gewicht</span><span data-spec="spaanplaat-kg" class="highlight">-</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="spec-section">
|
<div class="spec-section">
|
||||||
<h1>Buitenzijde (<span data-spec="buiten-materiaalnaam">Vochtdoek</span>)</h1>
|
<h1><i class="bi bi-layers-half"></i> Buitenzijde: <span data-spec="buiten-materiaalnaam">Vochtdoek</span></h1>
|
||||||
<div class="result-row"><span>Oppervlak:</span><span data-spec="buiten-m2" class="highlight">-</span></div>
|
<div class="result-row"><span>Oppervlak</span><span data-spec="buiten-m2" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Volume:</span><span data-spec="buiten-m3" class="highlight">-</span></div>
|
<div class="result-row"><span>Volume</span><span data-spec="buiten-m3" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Gewicht:</span><span data-spec="buiten-kg" class="highlight">-</span></div>
|
<div class="result-row"><span>Gewicht</span><span data-spec="buiten-kg" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Latwerk 20x30 (kruislat):</span><span data-spec="latten" class="highlight">-</span></div>
|
<div class="result-row"><span>Latwerk 20×30</span><span data-spec="latten" class="highlight">-</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="spec-section">
|
<div class="spec-section">
|
||||||
<h1>Isolatie & totaal</h1>
|
<h1><i class="bi bi-speedometer2"></i> Isolatie en gewicht</h1>
|
||||||
<div class="result-row"><span>Inhoud leegtes:</span><span data-spec="void-m3" class="highlight">-</span></div>
|
<div class="result-row"><span>Isolatie</span><span data-spec="void-m3" class="highlight">-</span></div>
|
||||||
<div class="result-row"><span>Totaal gewicht wand:</span><span data-spec="total-kg" class="highlight">-</span></div>
|
<div class="result-row"><span>Totaal gewicht</span><span data-spec="total-kg" class="highlight">-</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -207,23 +204,23 @@ const WALL_TEMPLATE = `
|
|||||||
</div>
|
</div>
|
||||||
<div class="controls-grid">
|
<div class="controls-grid">
|
||||||
<div>
|
<div>
|
||||||
<div class="control-group"><label for="inp-proj-naam">Projectnaam:</label>
|
<div class="control-group"><label for="inp-proj-naam">Projectnaam</label>
|
||||||
<div><input type="text" id="inp-proj-naam" value=""></div></div>
|
<div><input type="text" id="inp-proj-naam" value=""></div></div>
|
||||||
<div class="control-group"><label for="inp-proj-plaats">Plaats:</label>
|
<div class="control-group"><label for="inp-proj-plaats">Plaats</label>
|
||||||
<div><input type="text" id="inp-proj-plaats" value=""></div></div>
|
<div><input type="text" id="inp-proj-plaats" value=""></div></div>
|
||||||
<div class="control-group"><label for="inp-proj-straat">Straat:</label>
|
<div class="control-group"><label for="inp-proj-straat">Straat</label>
|
||||||
<div><input type="text" id="inp-proj-straat" value=""></div></div>
|
<div><input type="text" id="inp-proj-straat" value=""></div></div>
|
||||||
<div class="control-group"><label for="inp-proj-nummer">Projectnummer:</label>
|
<div class="control-group"><label for="inp-proj-nummer">Projectnummer</label>
|
||||||
<div><input type="text" id="inp-proj-nummer" value=""></div></div>
|
<div><input type="text" id="inp-proj-nummer" value=""></div></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="control-group"><label for="inp-proj-tekenaar">Tekenaar:</label>
|
<div class="control-group"><label for="inp-proj-tekenaar">Tekenaar</label>
|
||||||
<div><input type="text" id="inp-proj-tekenaar" value=""></div></div>
|
<div><input type="text" id="inp-proj-tekenaar" value=""></div></div>
|
||||||
<div class="control-group"><label for="inp-proj-titel">Titel:</label>
|
<div class="control-group"><label for="inp-proj-titel">Titel</label>
|
||||||
<div><input type="text" id="inp-proj-titel" value="BuitenWand"></div></div>
|
<div><input type="text" id="inp-proj-titel" value="BuitenWand"></div></div>
|
||||||
<div class="control-group"><label for="inp-proj-element">Element nummer:</label>
|
<div class="control-group"><label for="inp-proj-element">Elementnummer</label>
|
||||||
<div><input type="text" id="inp-proj-element" value=""></div></div>
|
<div><input type="text" id="inp-proj-element" value=""></div></div>
|
||||||
<div class="control-group"><label for="inp-proj-opmerking">Opmerking:</label>
|
<div class="control-group"><label for="inp-proj-opmerking">Opmerking</label>
|
||||||
<div><input type="text" id="inp-proj-opmerking" value=""></div></div>
|
<div><input type="text" id="inp-proj-opmerking" value=""></div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -338,9 +335,9 @@ function mountWallEditor(container, callbacks) {
|
|||||||
// OPENINGEN: UI (lijst met kaarten, ankerkeuze, toevoegen/verwijderen)
|
// OPENINGEN: UI (lijst met kaarten, ankerkeuze, toevoegen/verwijderen)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
function anchorOffsetLabel(anchor) {
|
function anchorOffsetLabel(anchor) {
|
||||||
if (anchor === 'right') return 'Afstand vanaf rechts:';
|
if (anchor === 'right') return 'Afstand tot rechts';
|
||||||
if (anchor === 'middle') return 'Verschuiving t.o.v. midden:';
|
if (anchor === 'middle') return 'Verschuiving t.o.v. midden';
|
||||||
return 'Afstand vanaf links:';
|
return 'Afstand tot links';
|
||||||
}
|
}
|
||||||
|
|
||||||
function addOpening() {
|
function addOpening() {
|
||||||
@@ -360,16 +357,12 @@ function mountWallEditor(container, callbacks) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggleFullscreen3D() {
|
function toggleFullscreen3D() {
|
||||||
const el = document.getElementById('app-container');
|
UI.cycle3D(document.getElementById('app-container')); // T: standaard -> 3D groot -> alleen render
|
||||||
el.classList.remove('fullscreen-2d');
|
|
||||||
el.classList.toggle('fullscreen-3d');
|
|
||||||
requestAnimationFrame(onWindowResize);
|
requestAnimationFrame(onWindowResize);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleFullscreen2D() {
|
function toggleFullscreen2D() {
|
||||||
const el = document.getElementById('app-container');
|
UI.toggle2D(document.getElementById('app-container'));
|
||||||
el.classList.remove('fullscreen-3d');
|
|
||||||
el.classList.toggle('fullscreen-2d');
|
|
||||||
requestAnimationFrame(onWindowResize);
|
requestAnimationFrame(onWindowResize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,7 +441,7 @@ function mountWallEditor(container, callbacks) {
|
|||||||
<div class="controls-grid">
|
<div class="controls-grid">
|
||||||
<div>
|
<div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label>Type:</label>
|
<label>Type</label>
|
||||||
<div>
|
<div>
|
||||||
<select data-field="type" data-id="${op.id}">
|
<select data-field="type" data-id="${op.id}">
|
||||||
${Object.entries(WallModel.OPENING_TYPES).map(([k, t]) => `<option value="${k}" ${(op.type || 'raam') === k ? 'selected' : ''}>${t.label}</option>`).join('')}
|
${Object.entries(WallModel.OPENING_TYPES).map(([k, t]) => `<option value="${k}" ${(op.type || 'raam') === k ? 'selected' : ''}>${t.label}</option>`).join('')}
|
||||||
@@ -456,21 +449,21 @@ function mountWallEditor(container, callbacks) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label>Breedte:</label>
|
<label>Breedte</label>
|
||||||
<div><input type="number" data-field="w" data-id="${op.id}" value="${op.w}" step="50" min="400" max="2850"><span class="unit">mm</span></div>
|
<div><input type="number" data-field="w" data-id="${op.id}" value="${op.w}" step="50" min="400" max="2850"><span class="unit">mm</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label>Hoogte:</label>
|
<label>Hoogte</label>
|
||||||
<div><input type="number" data-field="h" data-id="${op.id}" value="${op.h}" step="50" min="400"><span class="unit">mm</span></div>
|
<div><input type="number" data-field="h" data-id="${op.id}" value="${op.h}" step="50" min="400"><span class="unit">mm</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label>Hoogte vloer (Y):</label>
|
<label>Afstand tot vloer</label>
|
||||||
<div><input type="number" data-field="y" data-id="${op.id}" value="${op.y}" step="50" min="0"><span class="unit">mm</span></div>
|
<div><input type="number" data-field="y" data-id="${op.id}" value="${op.y}" step="50" min="0"><span class="unit">mm</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label>Ankerpunt:</label>
|
<label>Ankerpunt</label>
|
||||||
<div>
|
<div>
|
||||||
<select data-field="anchor" data-id="${op.id}">
|
<select data-field="anchor" data-id="${op.id}">
|
||||||
<option value="left" ${op.anchor === 'left' ? 'selected' : ''}>Links</option>
|
<option value="left" ${op.anchor === 'left' ? 'selected' : ''}>Links</option>
|
||||||
|
|||||||
Reference in New Issue
Block a user