v0.4.13: opbouw buitenwand boven lager dak, latwerk-raster per gevel, dakrand 80 mm boven dakleer, lage rand loopt door
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
373c2c2fb7
commit
94a5215f82
+102
-22
@@ -318,8 +318,18 @@ window.HouseModel = (() => {
|
||||
const sLeft = dir > 0 ? run.s0 : run.s1, sRight = dir > 0 ? run.s1 : run.s0;
|
||||
const first = run.segs.reduce((best, sg) => Math.abs(sg.sAt0 - sLeft) < Math.abs(best.sAt0 - sLeft) ? sg : best);
|
||||
const extLeft = (first.piece.skinExt && first.piece.skinExt.left) || 0;
|
||||
// Horizontaal latten-raster: vanaf het trasraam van een gewoon stuk
|
||||
// (absolute hoogte), zodat opbouw/naadwand-stukken erop aansluiten.
|
||||
const ref = run.segs.find(sg => !sg.piece.place.baseY && sg.piece.skinFrom === undefined);
|
||||
const gridAbs = ref ? ref.piece.place.floorTop + WallModel.skinStartY(ref.piece.trasraam) : undefined;
|
||||
const floorAbs = ref ? ref.piece.place.floorTop : undefined;
|
||||
run.segs.forEach(sg => {
|
||||
sg.piece.facade = { u0: Math.abs(sg.sAt0 - sLeft) + extLeft, extStart: extLeft > 0, isEnd: Math.abs(sg.sAtW - sRight) < 5 };
|
||||
const p = sg.piece;
|
||||
sg.piece.facade = {
|
||||
u0: Math.abs(sg.sAt0 - sLeft) + extLeft, extStart: extLeft > 0, isEnd: Math.abs(sg.sAtW - sRight) < 5,
|
||||
gridY0: gridAbs === undefined ? undefined : gridAbs - p.place.floorTop,
|
||||
yOff: floorAbs === undefined ? 0 : p.place.floorTop - floorAbs
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -343,6 +353,38 @@ window.HouseModel = (() => {
|
||||
return { group: built.group, stats: built.stats, specs: WallModel.computeSpecs(config, built.stats, buitenMateriaal) };
|
||||
}
|
||||
|
||||
// Wandstukken van één zijde (UnitModel.wallPieces), met plaats in de
|
||||
// woning. raised: opbouw-zones (zie raiseSeamWalls). Een opbouwstuk
|
||||
// (baseY) staat op die hoogte boven het loopvlak.
|
||||
function wallOf(u, rect, layout, wl, raised) {
|
||||
const ws = u.unit.walls[wl.side];
|
||||
const buitenMateriaal = (ws.materials || {}).exterior || 'vochtdoek';
|
||||
const cladding = (ws.materials || {}).cladding || 'horizontaal';
|
||||
const trasraam = buitenMateriaal === 'vochtdoek' ? ((ws.materials || {}).trasraam || null) : null;
|
||||
const wp = UnitModel.wallPieces(wl, ws, raised);
|
||||
const pieces = wp.pieces.map(p => {
|
||||
const c = UnitModel.localToWorld(wl, p.lxCenter, 0);
|
||||
const base = p.baseY || 0;
|
||||
return Object.assign(p, {
|
||||
element: `${u.id}-W${wl.nr}${p.suffix}`,
|
||||
label: `Unit ${u.id} ${wl.label.toLowerCase()}` + (base ? ` opbouw (vanaf ${base} mm)` : p.suffix ? ` (${p.suffix})` : ''),
|
||||
buitenMateriaal, cladding, trasraam: base ? null : trasraam,
|
||||
place: { cx: rect.cx + c.x, cz: rect.cz + c.z, rotY: wl.rotY, width: p.config.wallWidth, thickness: wl.thickness, normal: wl.normal, floorTop: layout.floorTop + base, baseY: base }
|
||||
}); // 3D volgt na assignFacades (latwerk/doek per gevel)
|
||||
});
|
||||
// Naad-zones met opbouw: in de wand-editor alleen gearceerd tot base.
|
||||
// Naast de opbouw (haakse wand in de hoek) blijft het over de volle hoogte.
|
||||
const cutsU = [];
|
||||
wl.cutsU.forEach(([a, b]) => {
|
||||
const r = pieces.find(p => p.baseY && p.u0 < b - EPS && p.u1 > a + EPS);
|
||||
if (!r) { cutsU.push([a, b]); return; }
|
||||
if (r.u0 > a + EPS) cutsU.push([a, r.u0]);
|
||||
cutsU.push([Math.max(a, r.u0), Math.min(b, r.u1), r.baseY]);
|
||||
if (r.u1 < b - EPS) cutsU.push([r.u1, b]);
|
||||
});
|
||||
return Object.assign({}, wl, { cutsU, pieces, dropped: wp.dropped, removed: pieces.length === 0 });
|
||||
}
|
||||
|
||||
const units = state.units.map(u => {
|
||||
const rect = rectById[u.id];
|
||||
const layout = UnitModel.computeLayout(u.unit, cuts[u.id]);
|
||||
@@ -355,23 +397,7 @@ window.HouseModel = (() => {
|
||||
materials: o.floorMaterials, osbVisible: o.beplatingVisible !== false
|
||||
}) : null;
|
||||
|
||||
const walls = layout.walls.map(wl => {
|
||||
const ws = u.unit.walls[wl.side];
|
||||
const buitenMateriaal = (ws.materials || {}).exterior || 'vochtdoek';
|
||||
const cladding = (ws.materials || {}).cladding || 'horizontaal';
|
||||
const trasraam = buitenMateriaal === 'vochtdoek' ? ((ws.materials || {}).trasraam || null) : null;
|
||||
const wp = UnitModel.wallPieces(wl, ws);
|
||||
const pieces = wp.pieces.map(p => {
|
||||
const c = UnitModel.localToWorld(wl, p.lxCenter, 0);
|
||||
return Object.assign(p, {
|
||||
element: `${u.id}-W${wl.nr}${p.suffix}`,
|
||||
label: `Unit ${u.id} ${wl.label.toLowerCase()}${p.suffix ? ' (' + p.suffix + ')' : ''}`,
|
||||
buitenMateriaal, cladding, trasraam,
|
||||
place: { cx: rect.cx + c.x, cz: rect.cz + c.z, rotY: wl.rotY, width: p.config.wallWidth, thickness: wl.thickness, normal: wl.normal, floorTop: layout.floorTop }
|
||||
}); // 3D volgt na assignFacades (latwerk/doek per gevel)
|
||||
});
|
||||
return Object.assign({}, wl, { pieces, dropped: wp.dropped, removed: pieces.length === 0 });
|
||||
});
|
||||
const walls = layout.walls.map(wl => wallOf(u, rect, layout, wl));
|
||||
|
||||
// Dak: dek = vloer-state (180° gespiegeld, RoofModel), in de vorm die
|
||||
// de wanden aangeven (plat, lessenaar of zadel; RoofModel.computeShape).
|
||||
@@ -412,6 +438,55 @@ window.HouseModel = (() => {
|
||||
};
|
||||
});
|
||||
|
||||
// Opbouw op een naad: staat een unit met een hogere daklijn tegen een
|
||||
// unit met een lager dak, dan loopt zijn buitenwand aan de naadzijde door
|
||||
// vanaf de bovenkant van het lage dak tot zijn eigen daklijn (zoals een
|
||||
// buitenwand die bij een schuin dak omhoog groeit). Ramen daar teken je
|
||||
// op die buitenwand. De naadwand eronder stopt op die hoogte (raisedCap).
|
||||
// Alleen bij een rechte voet: het lage dak ligt langs de hele naad even
|
||||
// hoog; anders blijft de naadwand doorlopen met buitenafwerking.
|
||||
const raisedCap = {}; // `${uid}|${andere uid}` -> base (mm vanaf loopvlak uid)
|
||||
(function raiseSeamWalls() {
|
||||
const zones = {}; // uid -> side -> [{ u0, u1, base, other }]
|
||||
seams.forEach(sm => {
|
||||
const horiz = Math.abs(sm.z0 - sm.z1) < EPS;
|
||||
const seamPerp = horiz ? sm.z0 : sm.x0;
|
||||
const s0 = horiz ? sm.x0 : sm.z0, s1 = horiz ? sm.x1 : sm.z1;
|
||||
[[sm.a, sm.b, horiz ? 'achter' : 'rechts'], [sm.b, sm.a, horiz ? 'voor' : 'links']].forEach(([uid, oid, side]) => {
|
||||
const u = units.find(x => x.id === uid), other = units.find(x => x.id === oid);
|
||||
const ts = [s0, s1].concat(other.roof.ridges.filter(r => r.axis === (horiz ? 'x' : 'z') && r.value > s0 && r.value < s1).map(r => r.value));
|
||||
const tops = ts.map(t => horiz ? other.roof.topAt(t, seamPerp) : other.roof.topAt(seamPerp, t));
|
||||
if (Math.max(...tops) - Math.min(...tops) > 2) return; // schuine voet: terugval
|
||||
const base = Math.round(tops[0] - u.layout.floorTop);
|
||||
const wl = u.walls.find(w => w.side === side);
|
||||
const sd = UnitModel.WALL_SIDES.find(x => x.key === side);
|
||||
const c0 = sd.axis === 'x' ? u.rect.cx : u.rect.cz;
|
||||
const start = (sd.axis === 'x' ? wl.cx : wl.cz) - wl.width / 2;
|
||||
const toU = (t) => sd.dir > 0 ? t - c0 - start : start + wl.width - (t - c0);
|
||||
const z0 = Math.min(toU(s0), toU(s1)), z1 = Math.max(toU(s0), toU(s1));
|
||||
wl.cutsU.forEach(([a, b]) => {
|
||||
let lo = Math.max(a, z0), hi = Math.min(b, z1);
|
||||
if (lo < EPS) lo = wl.endInset.left;
|
||||
if (hi > wl.width - EPS) hi = wl.width - wl.endInset.right;
|
||||
if (hi - lo > EPS) ((zones[uid] = zones[uid] || {})[side] = zones[uid][side] || []).push({ u0: lo, u1: hi, base, other: oid });
|
||||
});
|
||||
});
|
||||
});
|
||||
Object.keys(zones).forEach(uid => {
|
||||
const u = units.find(x => x.id === uid);
|
||||
const su = state.units.find(x => x.id === uid);
|
||||
Object.keys(zones[uid]).forEach(side => {
|
||||
const i = u.walls.findIndex(w => w.side === side);
|
||||
const wl = u.layout.walls.find(w => w.side === side);
|
||||
const next = wallOf(su, u.rect, u.layout, wl, zones[uid][side]);
|
||||
u.walls[i] = next;
|
||||
zones[uid][side].forEach(z => {
|
||||
if (next.pieces.some(p => p.baseY && Math.abs(p.u0 - z.u0) < EPS)) raisedCap[`${uid}|${z.other}`] = z.base;
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
// Binnenwanden staan op de vloer van de unit waarin hun midden valt.
|
||||
// Naadwanden (iw.seam) leveren twee delen op, één per unit (seamLeaves).
|
||||
const interior = [];
|
||||
@@ -455,8 +530,9 @@ window.HouseModel = (() => {
|
||||
// de naad aan, met de hoogte van de daklijn van die unit.
|
||||
const inUnit = iw.seam.in === 'a' || iw.seam.in === 'b' ? iw.seam[iw.seam.in] : null;
|
||||
const T2 = WallModel.thicknessOf(baseConfig, 'spaanplaat');
|
||||
// Opbouwstukken (baseY) staan boven de naadwand, niet ernaast.
|
||||
const extPieces = [];
|
||||
units.forEach(u => u.walls.forEach(w => w.pieces.forEach(p => extPieces.push(p))));
|
||||
units.forEach(u => u.walls.forEach(w => w.pieces.forEach(p => { if (!p.baseY) extPieces.push(p); })));
|
||||
const along = (q) => horiz ? q.x : q.z, perpOf = (q) => horiz ? q.z : q.x;
|
||||
const seamPerp = horiz ? sm.z0 : sm.x0;
|
||||
const s0 = horiz ? sm.x0 : sm.z0, s1 = horiz ? sm.x1 : sm.z1;
|
||||
@@ -500,10 +576,14 @@ window.HouseModel = (() => {
|
||||
const W = lf.place.width;
|
||||
const pL = axisOf(UnitModel.localToWorld(lf.place, -W / 2, 0)), pR = axisOf(UnitModel.localToWorld(lf.place, W / 2, 0));
|
||||
const peak = prof.length > 2 ? prof[1] : null;
|
||||
const hasPeak = !!peak && peak.p > Math.min(pL, pR) + 1 && peak.p < Math.max(pL, pR) - 1;
|
||||
// Opbouw erboven (raiseSeamWalls): het deel stopt op de voet daarvan.
|
||||
const cap = raisedCap[`${lf.uid}|${lf.uid === sm.a ? sm.b : sm.a}`];
|
||||
const lim = (h) => cap !== undefined ? Math.min(h, cap) : h;
|
||||
const hL = lim(RoofModel.profileHeight(prof, pL)), hR = lim(RoofModel.profileHeight(prof, pR));
|
||||
const hasPeak = !!peak && peak.p > Math.min(pL, pR) + 1 && peak.p < Math.max(pL, pR) - 1 && lim(peak.h) > Math.max(hL, hR) + 0.5;
|
||||
return {
|
||||
heightLeft: Math.round(RoofModel.profileHeight(prof, pL)), heightRight: Math.round(RoofModel.profileHeight(prof, pR)),
|
||||
heightMid: hasPeak ? Math.round(peak.h) : null, peakX: hasPeak ? Math.abs(peak.p - pL) : undefined
|
||||
heightLeft: Math.round(hL), heightRight: Math.round(hR),
|
||||
heightMid: hasPeak ? Math.round(lim(peak.h)) : null, peakX: hasPeak ? Math.abs(peak.p - pL) : undefined
|
||||
};
|
||||
};
|
||||
const paramsOf = (lf) => Object.assign({}, iw.wall.wall, { width: lf.place.width }, heightsOf(lf));
|
||||
|
||||
Reference in New Issue
Block a user