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
+30 -27
View File
@@ -114,8 +114,12 @@ window.FloorModel = (() => {
// ----------------------------------------------------------
// Balkverdeling: zelfde algoritme (calculateSegmentPositions) als de wand.
// params = state.floor (of de ruwe invoerwaarden uit de editor).
// rims (optioneel): { front, back } — kopbalk langs de voor-/achterrand
// (haaks op de balken), bijv. op een naad tussen twee units waar de
// modules weer aan elkaar gemonteerd worden. De langsbalken worden daar
// met één balkbreedte ingekort. Langs de zijranden ligt altijd al een balk.
// ----------------------------------------------------------
function computeFrame(params) {
function computeFrame(params, rims) {
const width = params.width || 2000;
const depth = params.depth || 2000;
const joistW = params.joistWidth || 70;
@@ -131,7 +135,9 @@ window.FloorModel = (() => {
calculateSegmentPositions(width - joistW, minSp, maxSp).forEach(p => joists.push(leftJoistX + p));
joists = [...new Set(joists.map(x => Math.round(x)))].sort((a, b) => a - b);
return { joists, width, depth, joistW, joistH, underlayThick };
const r = { front: !!(rims && rims.front), back: !!(rims && rims.back) };
const joistLen = depth - (r.front ? joistW : 0) - (r.back ? joistW : 0);
return { joists, width, depth, joistW, joistH, underlayThick, rims: r, joistLen };
}
// Bovenkant vloer (mm) = waar de wanden van een unit op staan.
@@ -151,8 +157,10 @@ window.FloorModel = (() => {
const { width, depth, joists, joistW, joistH, underlayThick } = calcData;
const widthM = width * toM, depthM = depth * toM;
const joistWM = joistW * toM, joistHM = joistH * toM, underlayM = underlayThick * toM;
const rimCount = calcData.rims ? (calcData.rims.front ? 1 : 0) + (calcData.rims.back ? 1 : 0) : 0;
const joistLenM = (calcData.joistLen || depth) * toM;
const joistWoodM3 = joists.length * joistWM * joistHM * depthM;
const joistWoodM3 = joists.length * joistWM * joistHM * joistLenM + rimCount * joistWM * joistHM * widthM;
const joistWoodKg = joistWoodM3 * DENSITY_WOOD_KGM3;
const grossAreaM2 = widthM * depthM;
@@ -197,8 +205,9 @@ window.FloorModel = (() => {
}
// Alles in één keer vanuit een vloer-state (handig voor de unit).
function computeFromState(state) {
const calcData = computeFrame(state.floor);
// rims: zie computeFrame.
function computeFromState(state, rims) {
const calcData = computeFrame(state.floor, rims);
const resolvedOpenings = resolveFloorOpenings(state.openings || [], calcData.width, calcData.depth, calcData.joists, calcData.joistW);
const chassisData = computeChassisWheels(state.chassis, calcData.width, calcData.depth);
return { calcData, resolvedOpenings, chassisData };
@@ -208,9 +217,6 @@ window.FloorModel = (() => {
return {
woodMaterial: new THREE.MeshStandardMaterial({ color: 0xc29b62, roughness: 0.6 }),
osbMaterial: new THREE.MeshStandardMaterial({ color: 0xcda86a, roughness: 0.75, side: THREE.DoubleSide }),
isolatieMaterial: new THREE.MeshStandardMaterial({
color: 0xffe08c, roughness: 0.9, transparent: true, opacity: 0.5, side: THREE.DoubleSide
}),
wheelMaterial: new THREE.MeshStandardMaterial({ color: 0x151515, roughness: 0.5 })
};
}
@@ -220,12 +226,11 @@ window.FloorModel = (() => {
// Z = -diepte/2), Y = dikte (balkhoogte + OSB). Balken en isolatie zijn
// simpele dozen; de OSB-onderlaag is een geëxtrudeerd Shape met ronde
// uitsparingen voor de doorvoeren.
// opts: { materials, osbVisible, isolatieVisible }
// opts: { materials, osbVisible }
// ----------------------------------------------------------
function build3D(calcData, resolvedOpenings, chassisData, opts) {
const { woodMaterial, osbMaterial, isolatieMaterial, wheelMaterial } = opts.materials;
const { woodMaterial, osbMaterial, wheelMaterial } = opts.materials;
const osbVisible = opts.osbVisible !== false;
const isolatieVisible = opts.isolatieVisible !== false;
const floorGroup3D = new THREE.Group();
const toM = 0.001;
@@ -233,28 +238,26 @@ window.FloorModel = (() => {
const widthM = width * toM, depthM = depth * toM;
const joistWM = joistW * toM, joistHM = joistH * toM, underlayM = underlayThick * toM;
// A. VLOERBALKEN — dozen, lopen over de volle diepte door.
// A. VLOERBALKEN — dozen over de diepte (ingekort bij een kopbalk), plus
// de kopbalken (rims) over de volle breedte.
const rims = calcData.rims || {};
const joistLenM = (calcData.joistLen || depth) * toM;
const joistZ = ((rims.front ? joistW : 0) - (rims.back ? joistW : 0)) / 2 * toM;
joists.forEach(xMM => {
const geo = new THREE.BoxGeometry(joistWM, joistHM, depthM);
const geo = new THREE.BoxGeometry(joistWM, joistHM, joistLenM);
const mesh = new THREE.Mesh(geo, woodMaterial);
mesh.position.set(xMM * toM, joistHM / 2, 0);
mesh.position.set(xMM * toM, joistHM / 2, joistZ);
mesh.castShadow = true;
floorGroup3D.add(mesh);
});
[['front', -1], ['back', 1]].filter(([k]) => rims[k]).forEach(([, sign]) => {
const mesh = new THREE.Mesh(new THREE.BoxGeometry(widthM, joistHM, joistWM), woodMaterial);
mesh.position.set(0, joistHM / 2, sign * (depthM - joistWM) / 2);
mesh.castShadow = true;
floorGroup3D.add(mesh);
});
// B. ISOLATIE — vult elk balkvak (tussen twee opeenvolgende balken) over
// de volle balkhoogte/diepte. Zelfde "leegte tussen het regelwerk"-
// gedachte als bij wall.js, hier ook echt gevisualiseerd als laag.
const bays = computeBays(joists, joistW);
bays.forEach(bay => {
const bayWidthM = (bay.right - bay.left) * toM;
if (bayWidthM <= 0) return;
const geo = new THREE.BoxGeometry(bayWidthM, joistHM, depthM);
const mesh = new THREE.Mesh(geo, isolatieMaterial);
mesh.position.set(((bay.left + bay.right) / 2) * toM, joistHM / 2, 0);
mesh.visible = isolatieVisible;
floorGroup3D.add(mesh);
});
// (Isolatie wordt niet getekend; de leegte-inhoud zit in computeSpecs.)
// C. OSB-ONDERLAAG — één geëxtrudeerd vlak over de volle vloer, met
// ronde uitsparingen voor de doorvoeren. Shape wordt opgebouwd in
+14 -2
View File
@@ -46,8 +46,15 @@ window.FloorPdf = (() => {
doc.setLineWidth(0.15);
doc.line(tblX[0], ty, tblX[4] + 12, ty);
ty += 4;
[String(1), String(joistW), String(joistH), String(Math.round(depth)), String(joists.length)]
const rims = calcData.rims || {}, joistLen = calcData.joistLen || depth;
const rimCount = (rims.front ? 1 : 0) + (rims.back ? 1 : 0);
[String(1), String(joistW), String(joistH), String(Math.round(joistLen)), String(joists.length)]
.forEach((v, i) => doc.text(v, tblX[i], ty));
if (rimCount) {
ty += 4.3; // kopbalk(en) op een naad (FloorModel.computeFrame)
[String(2), String(joistW), String(joistH), String(Math.round(width)), String(rimCount)]
.forEach((v, i) => doc.text(v, tblX[i], ty));
}
// --- Plaatmateriaal + isolatie ---
let my = 240;
@@ -89,11 +96,16 @@ window.FloorPdf = (() => {
doc.setLineWidth(0.3);
doc.setTextColor(...BLACK);
doc.setFontSize(5);
const jz0 = rims.front ? joistW : 0;
joists.forEach(x => {
const xLeft = x + width / 2 - joistW / 2;
doc.rect(px(xLeft), py(0), joistW * scale, depth * scale, 'S');
doc.rect(px(xLeft), py(jz0), joistW * scale, joistLen * scale, 'S');
doc.text('1', px(xLeft + joistW / 2), py(depth / 2), { align: 'center' });
});
[['front', 0], ['back', depth - joistW]].filter(([k]) => rims[k]).forEach(([, z]) => {
doc.rect(px(0), py(z), width * scale, joistW * scale, 'S');
doc.text('2', px(width / 2), py(z + joistW / 2) + 1.2, { align: 'center' });
});
doc.setDrawColor(...RED);
doc.setLineWidth(0.2);
+3 -6
View File
@@ -713,7 +713,6 @@ function mountFloorEditor(container, callbacks) {
const floorMaterials = roofMode ? RoofModel.createMaterials() : FloorModel.createMaterials();
let osbVisible = true;
let isolatieVisible = true;
let floorGroup3D = null;
let lastResolvedOpenings = [];
let lastChassisData = { wheels: [], outOfBounds: false };
@@ -721,11 +720,9 @@ function mountFloorEditor(container, callbacks) {
// Weergave-filter (core/view-filter.js). Beplating = OSB-plaat (bij een
// dak: plafondplaat + dakleer).
createViewFilter(document.getElementById('floor-toolbar-3d'), [
{ key: 'beplating', label: roofMode ? 'Beplating (plafond + dakleer)' : 'Beplating (OSB-plaat)' },
{ key: 'isolatie', label: 'Isolatie' }
{ key: 'beplating', label: roofMode ? 'Beplating (plafond + dakleer)' : 'Beplating (OSB-plaat)' }
], (v) => {
osbVisible = v.beplating;
isolatieVisible = v.isolatie;
update3DScene();
});
// Omgeving (core/skybox.js: geen/dag/nacht); de vloer staat op de tegelvloer.
@@ -740,8 +737,8 @@ function mountFloorEditor(container, callbacks) {
function update3DScene() {
if (floorGroup3D) scene.remove(floorGroup3D);
floorGroup3D = roofMode
? RoofModel.build3D(calcData, lastResolvedOpenings, { materials: floorMaterials, osbVisible, isolatieVisible, dakleerVisible: osbVisible })
: FloorModel.build3D(calcData, lastResolvedOpenings, lastChassisData, { materials: floorMaterials, osbVisible, isolatieVisible });
? RoofModel.build3D(calcData, lastResolvedOpenings, { materials: floorMaterials, osbVisible, dakleerVisible: osbVisible })
: FloorModel.build3D(calcData, lastResolvedOpenings, lastChassisData, { materials: floorMaterials, osbVisible });
scene.add(floorGroup3D);
applySky(); // schaduw van de nieuwe vloer
}