v0.4.9: naadwanden (2 delen, deur), kopbalken op naden, isolatie niet meer getekend

- HouseModel: naadwand per naad (ensureSeamWalls bij nieuwe woning en unit toevoegen),
  twee delen met beplating alleen aan de kamerzijde, openingen gespiegeld
- WallModel: buitenzijde 'geen', thicknessOf eenzijdig
- FloorModel: kopbalken (rims) voor vloer en dak op naden, ook in PDF en totalen
- isolatie: 3D-lagen en filter verwijderd, berekening blijft

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Machiel
2026-09-30 13:13:07 +02:00
co-authored by Claude Opus 5.5
parent f163f4deb1
commit 9bd7faeaa6
11 changed files with 271 additions and 124 deletions
+40 -7
View File
@@ -135,7 +135,11 @@ function mountHouseEditor(container, callbacks) {
if (kind === 'floor' && unitById(a)) unitById(a).unit.floor = activeEditor.getStateJson();
else if (kind === 'wall' && unitById(a)) unitById(a).unit.walls[b] = activeEditor.getStateJson();
else if (kind === 'roof' && unitById(a)) unitById(a).roof.deck = activeEditor.getStateJson();
else if (kind === 'iw' && iwById(Number(a))) iwById(Number(a)).wall = activeEditor.getStateJson();
else if (kind === 'iw' && iwById(Number(a))) {
const iw = iwById(Number(a));
iw.wall = activeEditor.getStateJson();
if (iw.seam) iw.wall.materials = Object.assign({}, iw.wall.materials, { exterior: 'geen' }); // naadzijde blijft zonder plaat
}
}
function showPart(part) {
@@ -172,7 +176,8 @@ function mountHouseEditor(container, callbacks) {
activeEditor.loadStateJson(HouseModel.clone(u.roof.deck));
} else if (kind === 'iw' && iwById(Number(a))) {
HouseModel.applyGlobalSettings(state);
HouseModel.interiorLayout(iwById(Number(a))); // breedte bijwerken
if (iwById(Number(a)).seam) HouseModel.computeParts(state, { skip3D: true }); // breedte naadwand-deel
else HouseModel.interiorLayout(iwById(Number(a))); // breedte bijwerken
activeEditor = WallEditor.mount(host, { cameraKey: camKey(part), lockWidth: 'De lengte van een binnenwand volgt uit de tekening (overzicht).', lockSpacing: SPACING_LOCK_MSG });
activeEditor.loadStateJson(HouseModel.clone(iwById(Number(a)).wall));
} else {
@@ -205,7 +210,15 @@ function mountHouseEditor(container, callbacks) {
});
if (parts.interior.length) {
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', 'distribute-horizontal'); });
const seen = new Set();
parts.interior.forEach(p => {
if (seen.has(p.id)) return; // naadwand: twee delen, één item
seen.add(p.id);
if (p.seam) {
const els = parts.interior.filter(x => x.id === p.id).map(x => x.element).join(', ');
html += item(`iw:${p.id}`, `Naadwand ${p.seam.a}|${p.seam.b}`, els, 'hn-sub', 'layout-split');
} else html += item(`iw:${p.id}`, p.element, `${p.place.width} mm`, 'hn-sub', 'distribute-horizontal');
});
html += `</div>`;
}
document.getElementById('house-nav-tree').innerHTML = html;
@@ -234,6 +247,7 @@ function mountHouseEditor(container, callbacks) {
const side = ['rechts', 'achter', 'links', 'voor'].find(s => !taken.has(s)) || 'rechts';
const id = HouseModel.nextUnitId(state);
state.units.push(HouseModel.createUnitEntry(id, { to: parent.id, side, align: 'start', offset: 0 }, { width: 3000, depth: 3000, windowOn: [] }));
HouseModel.ensureSeamWalls(state, id); // tussenwand met deur op de nieuwe naad
selection = { type: 'unit', id };
showPart('overzicht');
}
@@ -299,7 +313,7 @@ function mountHouseEditor(container, callbacks) {
let dakVisible = true;
// Opgeslagen camerastand gaat voor; anders eenmalig passend maken.
let cameraFitted = CameraMemory.restore(overviewCamKey, camera, controls);
let beplatingVisible = true, isolatieVisible = true, lampenVisible = true;
let beplatingVisible = true, lampenVisible = true;
let fullRender = false;
let skyMode = Skybox.getMode();
const skyEnv = Skybox.createEnvironment(scene);
@@ -308,12 +322,10 @@ function mountHouseEditor(container, callbacks) {
// en plafondplaten, dakleer en boeideel/daktrim.
createViewFilter(document.getElementById('ho-toolbar-3d'), [
{ key: 'beplating', label: 'Beplating' },
{ key: 'isolatie', label: 'Isolatie' },
{ key: 'dak', label: 'Dak' },
{ key: 'lampen', label: 'Lampen' }
], (v) => {
beplatingVisible = v.beplating;
isolatieVisible = v.isolatie;
dakVisible = v.dak;
lampenVisible = v.lampen;
refresh({ keepProps: true });
@@ -725,6 +737,8 @@ function mountHouseEditor(container, callbacks) {
function renderIwProps(iw, warn) {
const p = parts.interior.find(x => x.id === iw.id);
if (iw.seam) return renderSeamProps(iw, warn);
if (!p) { selection = null; return renderProps(); }
propsEl.innerHTML = warn + head('distribute-horizontal', p.element, `Binnenwand &middot; ${p.place.width} mm`) +
cols(
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)),
@@ -737,6 +751,25 @@ function mountHouseEditor(container, callbacks) {
UI.onNum('hi-height', v => { if (v >= 500) iw.wall.wall.heightAll = iw.wall.wall.heightLeft = iw.wall.wall.heightRight = v; }, after);
}
// Naadwand: twee delen (één per unit), één set openingen en maten.
function renderSeamProps(iw, warn) {
const leaves = parts.interior.filter(x => x.id === iw.id);
const title = `Naadwand ${iw.seam.a} | ${iw.seam.b}`;
propsEl.innerHTML = warn + head('layout-split', title, leaves.length ? `${leaves.length} delen` : 'vervalt') +
(leaves.length ? '' : UI.warn(`Unit ${iw.seam.a} en ${iw.seam.b} staan niet (meer) tegen elkaar; deze naadwand wordt niet gebouwd.`)) +
cols(
section('Opbouw', 'layers',
num('hi-thick', 'Dikte per deel', HouseModel.thicknessOf(iw.wall), { step: 5, hint: 'Regelwerk + beplating aan de kamerzijde; naadzijde zonder plaat' }) +
num('hi-height', 'Hoogte', iw.wall.wall.heightAll)),
UI.specCard('Delen', 'layout-split', leaves.map(l => [`${l.element} (unit ${l.seamUnit})`, l.place.width, 'mm']))) +
info('Tussenwand op de naad, per unit een eigen deel dat tegen het andere aan staat. Openingen gelden voor beide delen (de deur loopt door).') +
actions(
button('Openingen bewerken', 'pencil-square', `data-go="iw:${iw.id}"`),
button('Verwijderen', 'trash', `data-remove-iw="${iw.id}"`, 'ui-btn-danger'));
UI.onNum('hi-thick', v => { if (v >= 40) HouseModel.setThickness(iw.wall, v); }, after);
UI.onNum('hi-height', v => { if (v >= 500) iw.wall.wall.heightAll = iw.wall.wall.heightLeft = iw.wall.wall.heightRight = v; }, after);
}
// Woning-instellingen (niets geselecteerd): alles wat voor de hele woning
// geldt, in tabbladen.
function renderHouseProps(warn) {
@@ -857,7 +890,7 @@ function mountHouseEditor(container, callbacks) {
// --- Alles opnieuw doorrekenen ---
function refresh(opts) {
parts = HouseModel.computeParts(state, { wallMaterials, floorMaterials, roofMaterials, lightingMaterials, sandwichMaterials, beplatingVisible, isolatieVisible, lampenVisible });
parts = HouseModel.computeParts(state, { wallMaterials, floorMaterials, roofMaterials, lightingMaterials, sandwichMaterials, beplatingVisible, lampenVisible });
const totals = HouseModel.computeTotals(parts);
if (houseGroup) scene.remove(houseGroup);