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
@@ -59,9 +59,12 @@ assets/ logo, texturen, skybox
|
|||||||
- **Woning:** units koppelen aan een zijde (uitlijning + verschuiving); op
|
- **Woning:** units koppelen aan een zijde (uitlijning + verschuiving); op
|
||||||
naden vervallen de buitenwanden en komt een naadwand met deur: midden op
|
naden vervallen de buitenwanden en komt een naadwand met deur: midden op
|
||||||
de naad in twee delen (één per unit), of als één binnenwand geheel in unit
|
de naad in twee delen (één per unit), of als één binnenwand geheel in unit
|
||||||
A of B. De hoogte volgt per deel de daklijn van zijn unit; steekt een deel
|
A of B. De hoogte volgt per deel de daklijn van zijn unit. Staat een unit
|
||||||
boven het dak van de andere unit uit, dan krijgt dat stuk buitenafwerking
|
met een hogere daklijn tegen een lager dak, dan krijgt zijn buitenwand aan
|
||||||
(vochtdoek + latwerk) die met de gevel meeloopt.
|
de naadzijde een opbouw vanaf de bovenkant van het lage dak (ramen daar
|
||||||
|
teken je op die buitenwand) en stopt de naadwand op die hoogte. Loopt het
|
||||||
|
lage dak langs de naad schuin, dan loopt de naadwand door en krijgt het
|
||||||
|
uitstekende stuk buitenafwerking (vochtdoek + latwerk).
|
||||||
Binnenwanden teken je in de plattegrond.
|
Binnenwanden teken je in de plattegrond.
|
||||||
- **Dak:** per unit opgebouwd als gespiegelde vloer + dakleer, in de vorm
|
- **Dak:** per unit opgebouwd als gespiegelde vloer + dakleer, in de vorm
|
||||||
die de wanden aangeven: plat, lessenaar (schuine kopgevels) of zadel
|
die de wanden aangeven: plat, lessenaar (schuine kopgevels) of zadel
|
||||||
@@ -70,7 +73,9 @@ assets/ logo, texturen, skybox
|
|||||||
(boeidelen/daktrim, spots) loopt als één rand door en volgt de helling.
|
(boeidelen/daktrim, spots) loopt als één rand door en volgt de helling.
|
||||||
Is een dak langs de naad minstens een boeideelhoogte hoger dan het
|
Is een dak langs de naad minstens een boeideelhoogte hoger dan het
|
||||||
aangrenzende, dan krijgt het een eigen rand rondom (boven het lage dak
|
aangrenzende, dan krijgt het een eigen rand rondom (boven het lage dak
|
||||||
zonder spots) en loopt de rand van het lage dak door tot de gevel.
|
zonder spots). De rand van het lage dak loopt dan door tot de gevel van
|
||||||
|
het hoge, of langs een zijgevel tot de volle overstek (onder de hoge
|
||||||
|
rand). Boeideel en daktrim steken 80 mm boven het dakleer uit.
|
||||||
- **Gevel:** latwerk en vochtdoek lopen per gevel door over unit-naden;
|
- **Gevel:** latwerk en vochtdoek lopen per gevel door over unit-naden;
|
||||||
sandwichpanelen rondom, 600 mm de grond in.
|
sandwichpanelen rondom, 600 mm de grond in.
|
||||||
- **Verlichting:** boeideel-spots of gevellampen (per gevel verdeeld,
|
- **Verlichting:** boeideel-spots of gevellampen (per gevel verdeeld,
|
||||||
|
|||||||
@@ -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.12';
|
const APP_VERSION = '0.4.13';
|
||||||
// 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 = [];
|
||||||
|
|||||||
+102
-22
@@ -318,8 +318,18 @@ window.HouseModel = (() => {
|
|||||||
const sLeft = dir > 0 ? run.s0 : run.s1, sRight = dir > 0 ? run.s1 : run.s0;
|
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 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;
|
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 => {
|
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) };
|
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 units = state.units.map(u => {
|
||||||
const rect = rectById[u.id];
|
const rect = rectById[u.id];
|
||||||
const layout = UnitModel.computeLayout(u.unit, cuts[u.id]);
|
const layout = UnitModel.computeLayout(u.unit, cuts[u.id]);
|
||||||
@@ -355,23 +397,7 @@ window.HouseModel = (() => {
|
|||||||
materials: o.floorMaterials, osbVisible: o.beplatingVisible !== false
|
materials: o.floorMaterials, osbVisible: o.beplatingVisible !== false
|
||||||
}) : null;
|
}) : null;
|
||||||
|
|
||||||
const walls = layout.walls.map(wl => {
|
const walls = layout.walls.map(wl => wallOf(u, rect, layout, 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 });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Dak: dek = vloer-state (180° gespiegeld, RoofModel), in de vorm die
|
// Dak: dek = vloer-state (180° gespiegeld, RoofModel), in de vorm die
|
||||||
// de wanden aangeven (plat, lessenaar of zadel; RoofModel.computeShape).
|
// 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.
|
// Binnenwanden staan op de vloer van de unit waarin hun midden valt.
|
||||||
// Naadwanden (iw.seam) leveren twee delen op, één per unit (seamLeaves).
|
// Naadwanden (iw.seam) leveren twee delen op, één per unit (seamLeaves).
|
||||||
const interior = [];
|
const interior = [];
|
||||||
@@ -455,8 +530,9 @@ window.HouseModel = (() => {
|
|||||||
// de naad aan, met de hoogte van de daklijn van die unit.
|
// 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 inUnit = iw.seam.in === 'a' || iw.seam.in === 'b' ? iw.seam[iw.seam.in] : null;
|
||||||
const T2 = WallModel.thicknessOf(baseConfig, 'spaanplaat');
|
const T2 = WallModel.thicknessOf(baseConfig, 'spaanplaat');
|
||||||
|
// Opbouwstukken (baseY) staan boven de naadwand, niet ernaast.
|
||||||
const extPieces = [];
|
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 along = (q) => horiz ? q.x : q.z, perpOf = (q) => horiz ? q.z : q.x;
|
||||||
const seamPerp = horiz ? sm.z0 : sm.x0;
|
const seamPerp = horiz ? sm.z0 : sm.x0;
|
||||||
const s0 = horiz ? sm.x0 : sm.z0, s1 = horiz ? sm.x1 : sm.z1;
|
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 W = lf.place.width;
|
||||||
const pL = axisOf(UnitModel.localToWorld(lf.place, -W / 2, 0)), pR = axisOf(UnitModel.localToWorld(lf.place, W / 2, 0));
|
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 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 {
|
return {
|
||||||
heightLeft: Math.round(RoofModel.profileHeight(prof, pL)), heightRight: Math.round(RoofModel.profileHeight(prof, pR)),
|
heightLeft: Math.round(hL), heightRight: Math.round(hR),
|
||||||
heightMid: hasPeak ? Math.round(peak.h) : null, peakX: hasPeak ? Math.abs(peak.p - pL) : undefined
|
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));
|
const paramsOf = (lf) => Object.assign({}, iw.wall.wall, { width: lf.place.width }, heightsOf(lf));
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ window.LightingModel = (() => {
|
|||||||
const segs = [];
|
const segs = [];
|
||||||
units.forEach(u => u.walls.forEach(w => w.pieces.forEach(p => {
|
units.forEach(u => u.walls.forEach(w => w.pieces.forEach(p => {
|
||||||
p.lamps = [];
|
p.lamps = [];
|
||||||
|
if (p.baseY) return; // opbouw boven een lager dak: geen gevellampen
|
||||||
const W = p.place.width;
|
const W = p.place.width;
|
||||||
// Lampen hangen op de afgewerkte gevel: wandvlak + kruislat + bekleding.
|
// Lampen hangen op de afgewerkte gevel: wandvlak + kruislat + bekleding.
|
||||||
const T = p.place.thickness + 2 * WallModel.claddingOffset(p.buitenMateriaal);
|
const T = p.place.thickness + 2 * WallModel.claddingOffset(p.buitenMateriaal);
|
||||||
|
|||||||
+32
-11
@@ -28,7 +28,9 @@
|
|||||||
// dakrand = standaard de buitenkant gevel), standaard 300 mm hoog.
|
// dakrand = standaard de buitenkant gevel), standaard 300 mm hoog.
|
||||||
// Spots in de onderplaat staan op 40% van de overstek (spotLine); welke
|
// Spots in de onderplaat staan op 40% van de overstek (spotLine); welke
|
||||||
// spots er komen bepaalt LightingModel.
|
// spots er komen bepaalt LightingModel.
|
||||||
// - zonder boeidelen: een daktrim van 80 mm hoog.
|
// - zonder boeidelen: een daktrim van 80 mm hoog (onder het dakleer).
|
||||||
|
// De bovenkant van boeideel en daktrim ligt EDGE_ABOVE_MM boven het dakleer
|
||||||
|
// (opstand rondom), dus het dakleer ligt altijd lager dan de rand.
|
||||||
//
|
//
|
||||||
// Plain script, geladen na floor-model.js en core/geometry.js.
|
// Plain script, geladen na floor-model.js en core/geometry.js.
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -37,7 +39,7 @@ window.RoofModel = (() => {
|
|||||||
const DAKLEER_MM = 2; // visuele dikte van het dakleer
|
const DAKLEER_MM = 2; // visuele dikte van het dakleer
|
||||||
const DENSITY_DAKLEER_KGM2 = 1.4; // EPDM ~1,4 kg/m2
|
const DENSITY_DAKLEER_KGM2 = 1.4; // EPDM ~1,4 kg/m2
|
||||||
const TRIM_THICK_MM = 25; // daktrim-profiel, uitstekend buiten de dakrand
|
const TRIM_THICK_MM = 25; // daktrim-profiel, uitstekend buiten de dakrand
|
||||||
const TRIM_ABOVE_MM = 30; // daktrim steekt zoveel boven het dakleer uit
|
const EDGE_ABOVE_MM = 80; // bovenkant boeideel/daktrim ligt zoveel boven het dakleer
|
||||||
const SPOT_RATIO = 0.4; // spots op 40% van de overstek, gemeten vanaf de gevel
|
const SPOT_RATIO = 0.4; // spots op 40% van de overstek, gemeten vanaf de gevel
|
||||||
const OVERHANG_MIN = 200, OVERHANG_MAX = 400;
|
const OVERHANG_MIN = 200, OVERHANG_MAX = 400;
|
||||||
|
|
||||||
@@ -357,17 +359,35 @@ window.RoofModel = (() => {
|
|||||||
return {
|
return {
|
||||||
a, b, owner: owner.unitId, fascia, normal: rectilinearNormal(a, b),
|
a, b, owner: owner.unitId, fascia, normal: rectilinearNormal(a, b),
|
||||||
outerA: off[i], outerB: off[j], spotA: offSpot[i], spotB: offSpot[j],
|
outerA: off[i], outerB: off[j], spotA: offSpot[i], spotB: offSpot[j],
|
||||||
topAt: (p) => owner.topAt(p.x, p.z), ridges: owner.ridges || [],
|
// Bovenkant van de rand (dakleer + opstand).
|
||||||
|
topAt: (p) => owner.topAt(p.x, p.z) + EDGE_ABOVE_MM, ridges: owner.ridges || [],
|
||||||
overRoof: !!nb && !dropped, dropped
|
overRoof: !!nb && !dropped, dropped
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
// Naast een vervallen ribbe loopt de rand recht door tot de gevel.
|
// Naast een vervallen ribbe (tegen een hoger dak):
|
||||||
|
// - ligt het verlengde van de rand in het hoge deel, dan loopt hij
|
||||||
|
// recht door tot die gevel (spot op spotafstand van de gevel);
|
||||||
|
// - ligt het verlengde ernaast (de rand loopt langs de zijgevel van
|
||||||
|
// het hoge deel), dan loopt hij recht door tot de overstek, onder
|
||||||
|
// de rand van het hoge dak (van bovenaf overlappen ze).
|
||||||
|
const insideOther = (p) => others.some(r => p.x > r.rect.x0 + 0.5 && p.x < r.rect.x1 - 0.5 && p.z > r.rect.z0 + 0.5 && p.z < r.rect.z1 - 0.5);
|
||||||
segs.forEach((sg, i) => {
|
segs.forEach((sg, i) => {
|
||||||
|
if (sg.dropped) return;
|
||||||
const prev = segs[(i - 1 + n) % n], next = segs[(i + 1) % n];
|
const prev = segs[(i - 1 + n) % n], next = segs[(i + 1) % n];
|
||||||
const alongZ = Math.abs(sg.a.x - sg.b.x) < 0.5;
|
const alongZ = Math.abs(sg.a.x - sg.b.x) < 0.5;
|
||||||
const square = (key, v) => { sg[key] = alongZ ? { x: sg[key].x, z: v.z } : { x: v.x, z: sg[key].z }; };
|
const at = (q, v) => alongZ ? { x: q.x, z: v.z } : { x: v.x, z: q.z };
|
||||||
if (!sg.dropped && prev.dropped) square('outerA', sg.a);
|
[['a', 'outerA', 'spotA', 'b', prev], ['b', 'outerB', 'spotB', 'a', next]].forEach(([k, outerK, spotK, otherK, nb]) => {
|
||||||
if (!sg.dropped && next.dropped) square('outerB', sg.b);
|
if (!nb.dropped) return;
|
||||||
|
const v = sg[k], corner = sg[outerK];
|
||||||
|
if (insideOther({ x: (v.x + corner.x) / 2, z: (v.z + corner.z) / 2 })) {
|
||||||
|
sg[outerK] = at(corner, v);
|
||||||
|
const dir = Math.sign(alongZ ? sg[otherK].z - v.z : sg[otherK].x - v.x);
|
||||||
|
const inset = dir * s.overhang * SPOT_RATIO;
|
||||||
|
sg[spotK] = alongZ ? { x: sg[spotK].x, z: v.z + inset } : { x: v.x + inset, z: sg[spotK].z };
|
||||||
|
} else {
|
||||||
|
sg[k] = at(v, corner);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
segs.filter(sg => !sg.dropped).forEach(sg => segments.push(sg));
|
segs.filter(sg => !sg.dropped).forEach(sg => segments.push(sg));
|
||||||
});
|
});
|
||||||
@@ -377,7 +397,7 @@ window.RoofModel = (() => {
|
|||||||
return {
|
return {
|
||||||
segments, overhang: s.overhang, spotRatio: SPOT_RATIO,
|
segments, overhang: s.overhang, spotRatio: SPOT_RATIO,
|
||||||
fasciaHeight: s.fasciaHeight, trimHeight: s.trimHeight,
|
fasciaHeight: s.fasciaHeight, trimHeight: s.trimHeight,
|
||||||
top, trimAbove: TRIM_ABOVE_MM,
|
top, edgeAbove: EDGE_ABOVE_MM,
|
||||||
fasciaLengthMM: segments.filter(sg => sg.fascia).reduce((t, sg) => t + segLen(sg), 0),
|
fasciaLengthMM: segments.filter(sg => sg.fascia).reduce((t, sg) => t + segLen(sg), 0),
|
||||||
trimLengthMM: segments.filter(sg => !sg.fascia).reduce((t, sg) => t + segLen(sg), 0),
|
trimLengthMM: segments.filter(sg => !sg.fascia).reduce((t, sg) => t + segLen(sg), 0),
|
||||||
soffitM2: segments.filter(sg => sg.fascia).reduce((t, sg) => t + quadArea([sg.a, sg.b, sg.outerB, sg.outerA]), 0) / 1e6,
|
soffitM2: segments.filter(sg => sg.fascia).reduce((t, sg) => t + quadArea([sg.a, sg.b, sg.outerB, sg.outerA]), 0) / 1e6,
|
||||||
@@ -431,10 +451,11 @@ window.RoofModel = (() => {
|
|||||||
const f = (p) => r.axis === 'x' ? p.x - r.value : p.z - r.value;
|
const f = (p) => r.axis === 'x' ? p.x - r.value : p.z - r.value;
|
||||||
polys = polys.flatMap(pl => [clipPoly(pl, f), clipPoly(pl, p => -f(p))]).filter(pl => pl.length >= 3);
|
polys = polys.flatMap(pl => [clipPoly(pl, f), clipPoly(pl, p => -f(p))]).filter(pl => pl.length >= 3);
|
||||||
});
|
});
|
||||||
const h = sg.fascia ? edge.fasciaHeight : edge.trimHeight;
|
// Boeideel: totale hoogte fasciaHeight vanaf de bovenkant van de rand;
|
||||||
const topFn = sg.fascia ? sg.topAt : (p) => sg.topAt(p) + edge.trimAbove;
|
// daktrim: trimHeight onder het dakleer + de opstand erboven.
|
||||||
|
const h = sg.fascia ? edge.fasciaHeight : edge.trimHeight + edge.edgeAbove;
|
||||||
polys.forEach(pl => {
|
polys.forEach(pl => {
|
||||||
const mesh = new THREE.Mesh(prismGeometry(pl, topFn, h), sg.fascia ? materials.fasciaMaterial : materials.trimMaterial);
|
const mesh = new THREE.Mesh(prismGeometry(pl, sg.topAt, h), sg.fascia ? materials.fasciaMaterial : materials.trimMaterial);
|
||||||
mesh.visible = o.edgeVisible !== false;
|
mesh.visible = o.edgeVisible !== false;
|
||||||
group.add(mesh);
|
group.add(mesh);
|
||||||
});
|
});
|
||||||
|
|||||||
+44
-2
@@ -157,9 +157,18 @@ window.UnitModel = (() => {
|
|||||||
const full = s.axis === 'x' ? W / 2 : D / 2;
|
const full = s.axis === 'x' ? W / 2 : D / 2;
|
||||||
const ext0 = Math.max(0, Math.round(s0 + full)), ext1 = Math.max(0, Math.round(full - s1));
|
const ext0 = Math.max(0, Math.round(s0 + full)), ext1 = Math.max(0, Math.round(full - s1));
|
||||||
const skinExt = s.dir > 0 ? { left: ext0, right: ext1 } : { left: ext1, right: ext0 };
|
const skinExt = s.dir > 0 ? { left: ext0, right: ext1 } : { left: ext1, right: ext0 };
|
||||||
|
// Doorlopende wand die op een hoek vervalt: de haakse wand loopt daar
|
||||||
|
// tot de vloerrand door en staat dus op deze wandlijn (endInset, u-mm
|
||||||
|
// vanaf links/rechts). Een opbouw in die naad-zone begint erna.
|
||||||
|
const through = state.layout.corners === 'zijkanten' ? s.axis === 'z' : s.axis === 'x';
|
||||||
|
const pL = s.dir > 0 ? s0 : s1, pR = s.dir > 0 ? s1 : s0;
|
||||||
|
const endInset = {
|
||||||
|
left: through && cutAt(s.key, pL) ? T[WALL_ENDS[s.key].left] : 0,
|
||||||
|
right: through && cutAt(s.key, pR) ? T[WALL_ENDS[s.key].right] : 0
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
side: s.key, label: s.label, nr: s.nr, rotY: s.rotY, normal: s.normal, axis: s.axis,
|
side: s.key, label: s.label, nr: s.nr, rotY: s.rotY, normal: s.normal, axis: s.axis,
|
||||||
thickness: T[s.key], width, cx, cz, cutsU, pieces, skinExt
|
thickness: T[s.key], width, cx, cz, cutsU, pieces, skinExt, endInset
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -170,7 +179,13 @@ window.UnitModel = (() => {
|
|||||||
// op de volle zijde opgelost (ankers gelden voor de hele zijde) en daarna
|
// op de volle zijde opgelost (ankers gelden voor de hele zijde) en daarna
|
||||||
// aan het stuk toegewezen waar ze volledig in vallen; openingen die in een
|
// aan het stuk toegewezen waar ze volledig in vallen; openingen die in een
|
||||||
// naad vallen vervallen (dropped).
|
// naad vallen vervallen (dropped).
|
||||||
function wallPieces(wallLayout, wallState) {
|
// raised (optioneel): [{ u0, u1, base }] = opbouw in een naad-zone: daar
|
||||||
|
// staat de wand pas vanaf hoogte `base` (mm vanaf het loopvlak, bijv. boven
|
||||||
|
// het lagere dak van de buur-unit) tot de daklijn. Zo'n stuk heeft
|
||||||
|
// baseY = base; openingen die er volledig boven base in vallen horen erbij
|
||||||
|
// (y t.o.v. de onderkant van het stuk). Te lage opbouw vervalt.
|
||||||
|
const MIN_RAISE_MM = 150;
|
||||||
|
function wallPieces(wallLayout, wallState, raised) {
|
||||||
const fullConfig = WallModel.configFromState(wallState);
|
const fullConfig = WallModel.configFromState(wallState);
|
||||||
const W = wallLayout.width;
|
const W = wallLayout.width;
|
||||||
const hL = wallState.wall.heightLeft, hR = wallState.wall.heightRight;
|
const hL = wallState.wall.heightLeft, hR = wallState.wall.heightRight;
|
||||||
@@ -200,6 +215,33 @@ window.UnitModel = (() => {
|
|||||||
config, frame: WallModel.computeFrame(config)
|
config, frame: WallModel.computeFrame(config)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
const raisedList = (raised || []).filter(r => r.u1 - r.u0 >= MIN_PIECE_MM);
|
||||||
|
const ins = wallLayout.endInset || { left: 0, right: 0 };
|
||||||
|
raisedList.forEach((r, i) => {
|
||||||
|
const { u0: a, u1: b, base } = r;
|
||||||
|
const hasPeak = hM !== null && a < pU - 1 && b > pU + 1;
|
||||||
|
const hA = heightAt(a) - base, hB = heightAt(b) - base;
|
||||||
|
if (Math.min(hA, hB) < MIN_RAISE_MM) return;
|
||||||
|
const openings = fullConfig.openings
|
||||||
|
.filter(op => op.x >= a - EPS && op.x + op.w <= b + EPS && op.y >= base - EPS)
|
||||||
|
.map(op => { used.add(op.id); return { id: op.id, type: op.type, w: op.w, h: op.h, y: op.y - base, anchor: 'left', offset: op.x - a }; });
|
||||||
|
const params = Object.assign({}, wallState.wall, {
|
||||||
|
width: b - a, heightLeft: Math.round(hA), heightRight: Math.round(hB),
|
||||||
|
heightMid: hasPeak ? hM - base : null, peakX: hasPeak ? pU - a : undefined
|
||||||
|
});
|
||||||
|
const config = WallModel.buildConfig(params, openings);
|
||||||
|
pieces.push({
|
||||||
|
suffix: 'o' + (raisedList.length > 1 ? i + 1 : ''), // opbouw
|
||||||
|
u0: a, u1: b, lxCenter: -W / 2 + (a + b) / 2, baseY: Math.round(base),
|
||||||
|
// Naast de haakse wand in de hoek (endInset) bedekken de buitenlagen
|
||||||
|
// ook diens kopse kant, die boven het lage dak bloot ligt.
|
||||||
|
skinExt: {
|
||||||
|
left: ins.left > 0 && a <= ins.left + EPS ? ins.left : 0,
|
||||||
|
right: ins.right > 0 && b >= W - ins.right - EPS ? ins.right : 0
|
||||||
|
},
|
||||||
|
config, frame: WallModel.computeFrame(config)
|
||||||
|
});
|
||||||
|
});
|
||||||
const dropped = fullConfig.openings.filter(op => !used.has(op.id));
|
const dropped = fullConfig.openings.filter(op => !used.has(op.id));
|
||||||
return { pieces, dropped, fullConfig };
|
return { pieces, dropped, fullConfig };
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-4
@@ -508,6 +508,9 @@ window.WallModel = (() => {
|
|||||||
// linkerrand van dit stuk. Het verticale latten-raster volgt dan de
|
// linkerrand van dit stuk. Het verticale latten-raster volgt dan de
|
||||||
// hele gevel i.p.v. per stuk opnieuw te beginnen; een eindlat komt
|
// hele gevel i.p.v. per stuk opnieuw te beginnen; een eindlat komt
|
||||||
// alleen aan het einde van de gevel.
|
// alleen aan het einde van de gevel.
|
||||||
|
// facade.gridY0 (optioneel, mm, wand-coördinaten): hoogte waarop het
|
||||||
|
// horizontale latten-raster van de hele gevel begint. Een wandstuk dat
|
||||||
|
// hoger begint (opbouw, blootliggende naadwand) volgt zo hetzelfde raster.
|
||||||
// skinFrom (optioneel, mm): buitenafwerking pas vanaf deze hoogte (het
|
// skinFrom (optioneel, mm): buitenafwerking pas vanaf deze hoogte (het
|
||||||
// blootliggende deel van een naadwand boven het dak van de andere unit).
|
// blootliggende deel van een naadwand boven het dak van de andere unit).
|
||||||
function computeBattens(CONFIG, cladding, trasraam, skinExt, facade, skinFrom) {
|
function computeBattens(CONFIG, cladding, trasraam, skinExt, facade, skinFrom) {
|
||||||
@@ -524,7 +527,12 @@ window.WallModel = (() => {
|
|||||||
|
|
||||||
// Horizontale latten: vanaf de onderkant elke 400 mm (hartmaat).
|
// Horizontale latten: vanaf de onderkant elke 400 mm (hartmaat).
|
||||||
const horizontaal = [];
|
const horizontaal = [];
|
||||||
for (let y = y0 + w / 2; y < tl.maxY - w / 2; y += sp) {
|
let yStart = y0 + w / 2;
|
||||||
|
if (facade && facade.gridY0 !== undefined) {
|
||||||
|
const g = facade.gridY0 + w / 2;
|
||||||
|
yStart = g + Math.max(0, Math.ceil((yStart - g) / sp - 1e-9)) * sp;
|
||||||
|
}
|
||||||
|
for (let y = yStart; y < tl.maxY - w / 2; y += sp) {
|
||||||
skinSegmentsAtY(CONFIG, y).forEach(([x0, x1]) => {
|
skinSegmentsAtY(CONFIG, y).forEach(([x0, x1]) => {
|
||||||
if (x0 <= -halfW + 0.5) x0 = xL;
|
if (x0 <= -halfW + 0.5) x0 = xL;
|
||||||
if (x1 >= halfW - 0.5) x1 = xR;
|
if (x1 >= halfW - 0.5) x1 = xR;
|
||||||
@@ -646,10 +654,13 @@ window.WallModel = (() => {
|
|||||||
// Watermerk-UV's (doek) in gevel-coördinaten, zodat het patroon over
|
// Watermerk-UV's (doek) in gevel-coördinaten, zodat het patroon over
|
||||||
// naden/wandstukken doorloopt (UV = vormcoördinaat in m + verschuiving).
|
// naden/wandstukken doorloopt (UV = vormcoördinaat in m + verschuiving).
|
||||||
const uvShiftM = facade ? (CONFIG.wallWidth / 2 + facade.u0) * 0.001 : 0;
|
const uvShiftM = facade ? (CONFIG.wallWidth / 2 + facade.u0) * 0.001 : 0;
|
||||||
|
// Verticaal: een hoger beginnend stuk (facade.yOff, mm boven het
|
||||||
|
// loopvlak van de gevel) schuift het watermerk mee omhoog.
|
||||||
|
const uvShiftYM = facade && facade.yOff ? facade.yOff * 0.001 : 0;
|
||||||
const shiftUV = (geo) => {
|
const shiftUV = (geo) => {
|
||||||
if (!uvShiftM) return geo;
|
if (!uvShiftM && !uvShiftYM) return geo;
|
||||||
const uv = geo.attributes.uv;
|
const uv = geo.attributes.uv;
|
||||||
for (let k = 0; k < uv.count; k++) uv.setX(k, uv.getX(k) + uvShiftM);
|
for (let k = 0; k < uv.count; k++) { uv.setX(k, uv.getX(k) + uvShiftM); uv.setY(k, uv.getY(k) + uvShiftYM); }
|
||||||
return geo;
|
return geo;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1335,7 +1346,7 @@ window.WallModel = (() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
STANDARD_BEAM, STOCK_LENGTHS_MM, CORNER_STUDS, EXTERIOR_MATERIALS, OPENING_TYPES, BATTEN, CLADDING_DIRECTIONS, CLADDING_THICK_MM, claddingOffset, cornerExt, TRASRAAM_DEFAULT_MM, trasraamFor, computeBattens, planPurchase, purchaseText,
|
STANDARD_BEAM, STOCK_LENGTHS_MM, CORNER_STUDS, EXTERIOR_MATERIALS, OPENING_TYPES, BATTEN, CLADDING_DIRECTIONS, CLADDING_THICK_MM, claddingOffset, cornerExt, TRASRAAM_DEFAULT_MM, trasraamFor, skinStartY, computeBattens, planPurchase, purchaseText,
|
||||||
sheathingHoles, skinSegmentsAtY, topLine, topPlateOutlines,
|
sheathingHoles, skinSegmentsAtY, topLine, topPlateOutlines,
|
||||||
validateState, resolveOpenings, computeBOM, groupBOM, buildConfig, configFromState, thicknessOf, computeFrame,
|
validateState, resolveOpenings, computeBOM, groupBOM, buildConfig, configFromState, thicknessOf, computeFrame,
|
||||||
createMaterials, build3D, computeSpecs
|
createMaterials, build3D, computeSpecs
|
||||||
|
|||||||
+3
-2
@@ -1028,10 +1028,11 @@ function mountWallEditor(container, callbacks) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Naad-zones (wand vervalt daar, zie woning) als gearceerde overlay.
|
// Naad-zones (wand vervalt daar, zie woning) als gearceerde overlay.
|
||||||
|
// Met upTo (mm): opbouw boven het lagere dak, alleen gearceerd tot daar.
|
||||||
function drawSeamOverlay(toCanvasX, toCanvasY, halfWMM, maxRoofMM) {
|
function drawSeamOverlay(toCanvasX, toCanvasY, halfWMM, maxRoofMM) {
|
||||||
seamCuts.forEach(([u0, u1]) => {
|
seamCuts.forEach(([u0, u1, upTo]) => {
|
||||||
const x0 = toCanvasX(-halfWMM + u0), x1 = toCanvasX(-halfWMM + u1);
|
const x0 = toCanvasX(-halfWMM + u0), x1 = toCanvasX(-halfWMM + u1);
|
||||||
const yTop = toCanvasY(maxRoofMM), yBot = toCanvasY(0);
|
const yTop = toCanvasY(upTo > 0 ? Math.min(upTo, maxRoofMM) : maxRoofMM), yBot = toCanvasY(0);
|
||||||
ctxFront.save();
|
ctxFront.save();
|
||||||
ctxFront.fillStyle = 'rgba(17, 17, 17, 0.8)';
|
ctxFront.fillStyle = 'rgba(17, 17, 17, 0.8)';
|
||||||
ctxFront.fillRect(x0, yTop, x1 - x0, yBot - yTop);
|
ctxFront.fillRect(x0, yTop, x1 - x0, yBot - yTop);
|
||||||
|
|||||||
Reference in New Issue
Block a user