v0.4.12: dak volgt wandvorm (plat/lessenaar/zadel), dakrand per hoogte, naadwand midden of geheel in unit A/B
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
001206a108
commit
373c2c2fb7
+156
-138
@@ -95,6 +95,9 @@ window.WallModel = (() => {
|
||||
deur: { label: 'Deur', w: 900, h: 2150, y: 0 },
|
||||
voordeur: { label: 'Voordeur', w: 1150, h: 2150, y: 0 }
|
||||
};
|
||||
// Deuren krijgen aan weerszijden een tweede stijl direct tegen de
|
||||
// koningsstijl (stevigheid, computeFrame).
|
||||
const isDoor = (op) => op.type === 'deur' || op.type === 'voordeur';
|
||||
|
||||
// Buitenzijde-materialen (sleutel -> weergavenaam). 'spaanplaat' is voor
|
||||
// binnenwanden (aan beide zijden spaanplaat).
|
||||
@@ -220,34 +223,44 @@ window.WallModel = (() => {
|
||||
o.x = Math.max(edgeMargin, Math.min(o.x, wallWidth - o.w - edgeMargin));
|
||||
});
|
||||
|
||||
// Links-naar-rechts: duw overlappende openingen uit elkaar. 2x stijldikte
|
||||
// i.p.v. 1x: de OSB-uitsparing rond elke opening steekt aan weerszijden
|
||||
// al 1x stijldikte uit (om de koningsstijl zichtbaar te laten), dus bij
|
||||
// minder afstand tussen twee openingen zouden hun uitsparingen elkaar
|
||||
// kunnen overlappen (gecontroleerd, zie eerdere test).
|
||||
const minGap = studWidth * 2;
|
||||
// Openingen die elkaar in de hoogte raken (incl. boven-/onderdorpel en
|
||||
// 2x stijldikte ertussen) moeten naast elkaar; staan ze boven elkaar
|
||||
// (bijv. een raam boven een deur), dan mogen ze in x overlappen.
|
||||
// Hoogte hier op basis van de invoer; na fase 2 wordt het nog gecontroleerd.
|
||||
const vBand = (o, h, y) => [y > plateThick ? y - studWidth : 0, y + h + studWidth];
|
||||
const stacked = (a, b, useFinal) => {
|
||||
const [a0, a1] = useFinal ? vBand(a, a.h, a.y) : vBand(a, Math.max(400, a.rawH), a.rawY);
|
||||
const [b0, b1] = useFinal ? vBand(b, b.h, b.y) : vBand(b, Math.max(400, b.rawH), b.rawY);
|
||||
return a1 <= b0 + 0.5 || b1 <= a0 + 0.5;
|
||||
};
|
||||
// Tussenruimte naast elkaar: 2x stijldikte i.p.v. 1x: de OSB-uitsparing
|
||||
// rond elke opening steekt aan weerszijden al 1x stijldikte uit (om de
|
||||
// koningsstijl zichtbaar te laten), dus bij minder afstand zouden hun
|
||||
// uitsparingen elkaar kunnen overlappen. Naast een deur 1x extra voor
|
||||
// de dubbele stijl.
|
||||
const gapOf = (a, b) => studWidth * (2 + (isDoor(a) ? 1 : 0) + (isDoor(b) ? 1 : 0));
|
||||
|
||||
// Links-naar-rechts: duw botsende openingen uit elkaar.
|
||||
for (let i = 1; i < resolved.length; i++) {
|
||||
const prev = resolved[i - 1];
|
||||
const cur = resolved[i];
|
||||
const minX = prev.x + prev.w + minGap;
|
||||
if (cur.x < minX) cur.x = minX;
|
||||
for (let j = 0; j < i; j++) {
|
||||
const prev = resolved[j];
|
||||
if (stacked(prev, cur)) continue;
|
||||
cur.x = Math.max(cur.x, prev.x + prev.w + gapOf(prev, cur));
|
||||
}
|
||||
}
|
||||
|
||||
// Rechts-naar-links correctie als de laatste opening nu over de rand steekt
|
||||
// Rechts-naar-links: wat over de rechterrand steekt terugtrekken, en de
|
||||
// botsende openingen links ervan mee.
|
||||
const rightLimit = wallWidth - edgeMargin;
|
||||
let overflow = 0;
|
||||
if (resolved.length) {
|
||||
const last = resolved[resolved.length - 1];
|
||||
overflow = Math.max(0, (last.x + last.w) - rightLimit);
|
||||
}
|
||||
if (overflow > 0) {
|
||||
for (let i = resolved.length - 1; i >= 0; i--) {
|
||||
resolved[i].x -= overflow;
|
||||
if (i > 0) {
|
||||
const maxX = resolved[i].x - minGap - resolved[i - 1].w;
|
||||
if (resolved[i - 1].x > maxX) resolved[i - 1].x = maxX;
|
||||
}
|
||||
for (let i = resolved.length - 1; i >= 0; i--) {
|
||||
const cur = resolved[i];
|
||||
let maxX = rightLimit - cur.w;
|
||||
for (let j = i + 1; j < resolved.length; j++) {
|
||||
const next = resolved[j];
|
||||
if (!stacked(cur, next)) maxX = Math.min(maxX, next.x - gapOf(cur, next) - cur.w);
|
||||
}
|
||||
if (cur.x > maxX) cur.x = maxX;
|
||||
}
|
||||
|
||||
// Fase 2: hoogte (h) en verticale positie (y), per opening geklemd tegen
|
||||
@@ -270,14 +283,16 @@ window.WallModel = (() => {
|
||||
});
|
||||
|
||||
// Detecteer of het na dit alles nog steeds niet past (te veel/te brede
|
||||
// openingen voor deze wandbreedte) zodat de UI dit kan tonen.
|
||||
// openingen voor deze wandbreedte, of boven elkaar zonder ruimte
|
||||
// ertussen) zodat de UI dit kan tonen.
|
||||
resolved.forEach(o => {
|
||||
o.overflowing = (o.x < edgeMargin - 0.5) || (o.x + o.w > wallWidth - edgeMargin + 0.5);
|
||||
});
|
||||
for (let i = 1; i < resolved.length; i++) {
|
||||
if (resolved[i].x < resolved[i - 1].x + resolved[i - 1].w + minGap - 0.5) {
|
||||
resolved[i].overflowing = true;
|
||||
resolved[i - 1].overflowing = true;
|
||||
for (let i = 0; i < resolved.length; i++) {
|
||||
for (let j = i + 1; j < resolved.length; j++) {
|
||||
const a = resolved[i], b = resolved[j];
|
||||
const [l, r] = a.x <= b.x ? [a, b] : [b, a];
|
||||
if (!stacked(a, b, true) && r.x < l.x + l.w + gapOf(l, r) - 0.5) { a.overflowing = true; b.overflowing = true; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,52 +371,88 @@ window.WallModel = (() => {
|
||||
cornerStuds.push(outerLeftStudX + i * CONFIG.studWidth, outerRightStudX - i * CONFIG.studWidth);
|
||||
}
|
||||
|
||||
let fullStuds = [];
|
||||
let jackStuds = [];
|
||||
// Stijlen als stukken { x, y0, y1, kind } (mm; y1 null = tot de
|
||||
// daklijn). Eerst kandidaat-posities, daarna wordt elke kandidaat
|
||||
// opgeknipt door de openingen (incl. boven-/onderdorpel) die hij kruist.
|
||||
// Zo kunnen openingen ook boven elkaar staan (raam boven een deur): de
|
||||
// stijlen ertussen lopen van dorpel tot dorpel.
|
||||
// hoek dubbele hoekstijl
|
||||
// koning koningsstijl direct naast een opening
|
||||
// dubbel tweede stijl direct tegen de koningsstijl van een deur
|
||||
// regel regelmatige verdeling in de vrije zones
|
||||
// kreupel boven/onder een opening, binnen de openingbreedte
|
||||
const sw = CONFIG.studWidth;
|
||||
const ops = CONFIG.openings; // al gesorteerd op x door resolveOpenings
|
||||
const bands = ops.map(op => ({
|
||||
op, x0: startX + op.x, x1: startX + op.x + op.w,
|
||||
y0: op.y > CONFIG.plateThick ? op.y - sw : 0, y1: op.y + op.h + sw
|
||||
}));
|
||||
const cands = [];
|
||||
cornerStuds.forEach(x => cands.push({ x, kind: 'hoek' }));
|
||||
ops.forEach(op => {
|
||||
const x0 = startX + op.x, x1 = x0 + op.w;
|
||||
cands.push({ x: x0 - sw / 2, kind: 'koning' }, { x: x1 + sw / 2, kind: 'koning' });
|
||||
if (isDoor(op)) cands.push({ x: x0 - 1.5 * sw, kind: 'dubbel' }, { x: x1 + 1.5 * sw, kind: 'dubbel' });
|
||||
});
|
||||
|
||||
if (ops.length === 0) {
|
||||
fullStuds.push(...cornerStuds);
|
||||
const availWidth = wallRightStudX - wallLeftStudX;
|
||||
calculateSegmentPositions(availWidth, CONFIG.minSpacing, CONFIG.maxSpacing)
|
||||
.forEach(p => fullStuds.push(wallLeftStudX + p));
|
||||
} else {
|
||||
// Hoekstijlen van de wand horen er, met of zonder opening, altijd bij.
|
||||
fullStuds.push(...cornerStuds);
|
||||
|
||||
// Randen van de "vrije zones" tussen wand en openingen, per paar verwerkt
|
||||
const edges = [wallLeftStudX];
|
||||
|
||||
ops.forEach(op => {
|
||||
const opStartX = startX + op.x;
|
||||
const kingLeftX = opStartX - CONFIG.studWidth / 2;
|
||||
const kingRightX = opStartX + op.w + CONFIG.studWidth / 2;
|
||||
|
||||
fullStuds.push(kingLeftX, kingRightX);
|
||||
edges.push(kingLeftX, kingRightX);
|
||||
|
||||
// Kreupele stijlen binnen de opening-breedte zelf
|
||||
calculateSegmentPositions(op.w, CONFIG.minSpacing, CONFIG.maxSpacing)
|
||||
.forEach(p => jackStuds.push({ x: opStartX + p, opening: op }));
|
||||
});
|
||||
|
||||
edges.push(wallRightStudX);
|
||||
|
||||
for (let i = 0; i < edges.length; i += 2) {
|
||||
const a = edges[i];
|
||||
const b = edges[i + 1];
|
||||
const span = (b - CONFIG.studWidth / 2) - (a + CONFIG.studWidth / 2);
|
||||
if (span > 0) {
|
||||
calculateSegmentPositions(span + CONFIG.studWidth, CONFIG.minSpacing, CONFIG.maxSpacing)
|
||||
.forEach(p => fullStuds.push(a + p));
|
||||
}
|
||||
}
|
||||
// Kolommen: openingen (incl. hun stijlen) die elkaar in x overlappen.
|
||||
// Tussen de kolommen en de hoekstijlen: regelmatige verdeling.
|
||||
const cols = [];
|
||||
ops.map(op => {
|
||||
const extra = (isDoor(op) ? 2 : 1) * sw;
|
||||
return [startX + op.x - extra + sw / 2, startX + op.x + op.w + extra - sw / 2]; // hartlijnen buitenste stijlen
|
||||
}).sort((p, q) => p[0] - q[0]).forEach(r => {
|
||||
const last = cols[cols.length - 1];
|
||||
if (last && r[0] < last[1] + sw) last[1] = Math.max(last[1], r[1]);
|
||||
else cols.push(r.slice());
|
||||
});
|
||||
const edges = [wallLeftStudX];
|
||||
cols.forEach(c => edges.push(c[0], c[1]));
|
||||
edges.push(wallRightStudX);
|
||||
for (let i = 0; i < edges.length; i += 2) {
|
||||
const a = edges[i], b = edges[i + 1];
|
||||
if (b - a > sw) calculateSegmentPositions(b - a, CONFIG.minSpacing, CONFIG.maxSpacing).forEach(p => cands.push({ x: a + p, kind: 'regel' }));
|
||||
}
|
||||
ops.forEach(op => calculateSegmentPositions(op.w, CONFIG.minSpacing, CONFIG.maxSpacing)
|
||||
.forEach(p => cands.push({ x: startX + op.x + p, kind: 'kreupel', owner: op })));
|
||||
|
||||
fullStuds = [...new Set(fullStuds.map(x => Math.round(x)))].sort((a, b) => a - b);
|
||||
jackStuds.sort((a, b) => a.x - b.x);
|
||||
// Knippen op de openingen.
|
||||
const PRIO = { hoek: 0, koning: 1, dubbel: 2, regel: 3, kreupel: 4 };
|
||||
const roofMin = (x) => Math.min(...top.underPts(x - sw / 2, x + sw / 2).map(p => p.y));
|
||||
const segs = [];
|
||||
cands.forEach(c => {
|
||||
const x = Math.round(c.x);
|
||||
const hit = bands.filter(bd => Math.min(bd.x1, x + sw / 2) - Math.max(bd.x0, x - sw / 2) > 0.5).sort((p, q) => p.y0 - q.y0);
|
||||
let cur = CONFIG.plateThick, below = null;
|
||||
const push = (y0, y1, above) => {
|
||||
if (c.kind === 'kreupel') {
|
||||
// Alleen direct boven of onder de eigen opening; onder de opening
|
||||
// niet als hij op een andere opening uitkomt (dat stuk vult de
|
||||
// kreupele stijl van de onderste opening al).
|
||||
const onTop = below && below.op === c.owner, underneath = above && above.op === c.owner;
|
||||
if (!onTop && !(underneath && !below)) return;
|
||||
}
|
||||
segs.push({ x, y0, y1, kind: c.kind });
|
||||
};
|
||||
hit.forEach(bd => {
|
||||
if (bd.y0 > cur + 1) push(cur, bd.y0, bd);
|
||||
if (bd.y1 > cur) { cur = bd.y1; below = bd; }
|
||||
});
|
||||
if (roofMin(x) > cur + 1) push(cur, null, null);
|
||||
});
|
||||
// Dubbelingen (bijv. koningsstijl op de hoekstijl) weg: hoogste prioriteit wint.
|
||||
const studs = [];
|
||||
segs.sort((p, q) => PRIO[p.kind] - PRIO[q.kind]).forEach(sg => {
|
||||
const top1 = sg.y1 === null ? Infinity : sg.y1;
|
||||
const clash = studs.some(o => Math.abs(o.x - sg.x) < sw - 0.5 &&
|
||||
Math.min(top1, o.y1 === null ? Infinity : o.y1) - Math.max(sg.y0, o.y0) > 1);
|
||||
if (!clash) studs.push(sg);
|
||||
});
|
||||
studs.sort((p, q) => p.x - q.x || p.y0 - q.y0);
|
||||
// Volle stijlen (onderregel tot daklijn), o.a. voor de maatvoering.
|
||||
const fullStuds = [...new Set(studs.filter(sg => sg.y1 === null && sg.y0 <= CONFIG.plateThick).map(sg => sg.x))].sort((a, b) => a - b);
|
||||
|
||||
return { fullStuds, jackStuds, slope, angleDeg };
|
||||
return { studs, fullStuds, slope, angleDeg };
|
||||
}
|
||||
|
||||
// Uitsparing in de beplating/het vochtdoek per opening (mm; x t.o.v. het
|
||||
@@ -457,8 +508,10 @@ window.WallModel = (() => {
|
||||
// linkerrand van dit stuk. Het verticale latten-raster volgt dan de
|
||||
// hele gevel i.p.v. per stuk opnieuw te beginnen; een eindlat komt
|
||||
// alleen aan het einde van de gevel.
|
||||
function computeBattens(CONFIG, cladding, trasraam, skinExt, facade) {
|
||||
const y0 = skinStartY(trasraam); // boven het trasraam
|
||||
// skinFrom (optioneel, mm): buitenafwerking pas vanaf deze hoogte (het
|
||||
// blootliggende deel van een naadwand boven het dak van de andere unit).
|
||||
function computeBattens(CONFIG, cladding, trasraam, skinExt, facade, skinFrom) {
|
||||
const y0 = Math.max(skinStartY(trasraam), skinFrom || 0); // boven het trasraam
|
||||
const ext = cornerExt(skinExt, 2 * BATTEN.thick); // over de hoekstijlen van de doorlopende wand
|
||||
const xL = -CONFIG.wallWidth / 2 - ext.left, xR = CONFIG.wallWidth / 2 + ext.right;
|
||||
const gridX0 = facade ? -CONFIG.wallWidth / 2 - facade.u0 - (facade.extStart ? 2 * BATTEN.thick : 0) : xL;
|
||||
@@ -586,6 +639,10 @@ window.WallModel = (() => {
|
||||
const trasraam = buitenMateriaal === 'vochtdoek' ? (opts.trasraam || null) : null;
|
||||
const skinExt = opts.skinExt || null;
|
||||
const facade = opts.facade || null;
|
||||
const skinFrom = opts.skinFrom > 0 ? opts.skinFrom : 0; // buitenafwerking vanaf (mm), zie computeBattens
|
||||
// spaanplaatBelow: onder skinFrom aan de buitenzijde spaanplaat (naadwand
|
||||
// geheel in één unit: onder het dak van de andere unit is het een binnenwand).
|
||||
const spaanplaatBelow = !!opts.spaanplaatBelow && skinFrom > 0 && buitenMateriaal === 'vochtdoek';
|
||||
// Watermerk-UV's (doek) in gevel-coördinaten, zodat het patroon over
|
||||
// naden/wandstukken doorloopt (UV = vormcoördinaat in m + verschuiving).
|
||||
const uvShiftM = facade ? (CONFIG.wallWidth / 2 + facade.u0) * 0.001 : 0;
|
||||
@@ -697,46 +754,21 @@ window.WallModel = (() => {
|
||||
horizBeamCount++;
|
||||
});
|
||||
|
||||
// C. VOLLEDIGE VERTICALE STIJLEN (incl. de "koningsstijlen" naast elke opening)
|
||||
calcData.fullStuds.forEach((xPosMM) => {
|
||||
const xM = xPosMM * toM;
|
||||
// C. VERTICALE STIJLEN (vol, koning, dubbel bij deuren, kreupel): elk
|
||||
// stuk van y0 tot y1, of tot onder de daklijn (y1 null), zie computeFrame.
|
||||
calcData.studs.forEach(st => {
|
||||
const xM = st.x * toM;
|
||||
const xLeft = xM - (sWidthM / 2);
|
||||
const xRight = xM + (sWidthM / 2);
|
||||
const pts = [{ x: xLeft, y: pThickM }, { x: xRight, y: pThickM }, ...underPts(xLeft, xRight).reverse()];
|
||||
const y0 = st.y0 * toM;
|
||||
const pts = st.y1 === null
|
||||
? [{ x: xLeft, y: y0 }, { x: xRight, y: y0 }, ...underPts(xLeft, xRight).reverse()]
|
||||
: [{ x: xLeft, y: y0 }, { x: xRight, y: y0 }, { x: xRight, y: st.y1 * toM }, { x: xLeft, y: st.y1 * toM }];
|
||||
wallGroup3D.add(createExtrudedBeam(new THREE.Shape(pts.map(p => new THREE.Vector2(p.x, p.y))), sDepthM, -sDepthM / 2));
|
||||
});
|
||||
|
||||
// D. KREUPELE STIJLEN & DORPELS, per opening
|
||||
// D. DORPELS, per opening
|
||||
opsM.forEach(op => {
|
||||
calcData.jackStuds
|
||||
.filter(j => j.opening.id === op.id)
|
||||
.forEach(j => {
|
||||
const xM = j.x * toM;
|
||||
const xLeft = xM - (sWidthM / 2);
|
||||
const xRight = xM + (sWidthM / 2);
|
||||
const topPts = underPts(xLeft, xRight);
|
||||
const yTopLeft = topPts[0].y, yTopRight = topPts[topPts.length - 1].y;
|
||||
|
||||
// Onderste kreupele stijl mag niet door de onderdorpel heen lopen
|
||||
// (die neemt zelf de band net onder op.yMin in beslag).
|
||||
const sillBottomM = op.yMin - sWidthM;
|
||||
if (sillBottomM > pThickM) {
|
||||
const studBotShape = new THREE.Shape();
|
||||
studBotShape.moveTo(xLeft, pThickM);
|
||||
studBotShape.lineTo(xRight, pThickM);
|
||||
studBotShape.lineTo(xRight, sillBottomM);
|
||||
studBotShape.lineTo(xLeft, sillBottomM);
|
||||
studBotShape.closePath();
|
||||
wallGroup3D.add(createExtrudedBeam(studBotShape, sDepthM, -sDepthM / 2));
|
||||
}
|
||||
|
||||
const actualOpTop = op.yMax + sWidthM;
|
||||
if (actualOpTop < yTopLeft || actualOpTop < yTopRight) {
|
||||
const pts = [{ x: xLeft, y: actualOpTop }, { x: xRight, y: actualOpTop }, ...topPts.slice().reverse()];
|
||||
wallGroup3D.add(createExtrudedBeam(new THREE.Shape(pts.map(p => new THREE.Vector2(p.x, p.y))), sDepthM, -sDepthM / 2));
|
||||
}
|
||||
});
|
||||
|
||||
// Bovendorpel / linteel (horizontale balk)
|
||||
const opTopShape = new THREE.Shape();
|
||||
opTopShape.moveTo(op.xMin, op.yMax);
|
||||
@@ -937,6 +969,11 @@ window.WallModel = (() => {
|
||||
|
||||
// Buitenzijde (zuid): cement-gebonden platen (zelfde plaatlogica als
|
||||
// hierboven) OF vochtdoek (horizontale banen, zie onder) — nooit beide.
|
||||
if (spaanplaatBelow) {
|
||||
const spaanOnder = buildVerticalBoardSkin(spaanplaatMaterial, 'front', buitenVisible, { y0: 0, y1: skinFrom * 0.001 });
|
||||
spaanplaatBuitenAreaM2 = spaanOnder.areaM2;
|
||||
spaanplaatBuitenVolumeM3 = spaanOnder.volumeM3;
|
||||
}
|
||||
if (buitenMateriaal === 'spaanplaat') {
|
||||
// Binnenwand: aan beide zijden spaanplaat.
|
||||
const spaanBuiten = buildVerticalBoardSkin(spaanplaatMaterial, 'front', buitenVisible);
|
||||
@@ -967,7 +1004,7 @@ window.WallModel = (() => {
|
||||
const wallOutlinePts = [{ x: -halfW, y: 0 }, { x: halfW, y: 0 }, ...tl.pts.slice().reverse()];
|
||||
const maxRoofY = tl.maxY;
|
||||
|
||||
let rowY0 = 0;
|
||||
let rowY0 = skinFrom * toM;
|
||||
let rowIndex = 0;
|
||||
while (rowY0 < maxRoofY - 1e-9) {
|
||||
const rowY1 = rowY0 + doekHeightM;
|
||||
@@ -1063,7 +1100,7 @@ window.WallModel = (() => {
|
||||
|
||||
// G. KRUISLAT op het vochtdoek (computeBattens): onderste laag direct op
|
||||
// het doek, bovenste laag (haaks op de gevelbekleding) daaroverheen.
|
||||
const bat = computeBattens(CONFIG, cladding, trasraam, skinExt, facade);
|
||||
const bat = computeBattens(CONFIG, cladding, trasraam, skinExt, facade, skinFrom);
|
||||
const bT = BATTEN.thick * toM, bW = BATTEN.width * toM;
|
||||
const zDoek = (maxDepth / 2) + skinGapM + sThickM + rowIndex * doekZStepM + skinGapM;
|
||||
const addLayer = (orientation, z) => {
|
||||
@@ -1186,47 +1223,28 @@ window.WallModel = (() => {
|
||||
// de hoogste kant van de balk goed op de daklijn te staan, en blijft de
|
||||
// laagste kant er (visueel) los van.
|
||||
// (outline: ook de knik onder de nok, als de stijl daar staat.)
|
||||
calcData.fullStuds.forEach(x => {
|
||||
const xLeft = x - CONFIG.studWidth / 2, xRight = x + CONFIG.studWidth / 2;
|
||||
calcData.studs.forEach(st => {
|
||||
const x = st.x, xLeft = x - CONFIG.studWidth / 2, xRight = x + CONFIG.studWidth / 2;
|
||||
const yBottom = st.y0;
|
||||
const kind = st.kind === 'kreupel' ? 'kreupele stijl' : 'verticale balk';
|
||||
if (st.y1 !== null) {
|
||||
pieces.push({
|
||||
length: st.y1 - yBottom, width: CONFIG.studWidth, depth: CONFIG.studDepth, kind, x,
|
||||
y: (yBottom + st.y1) / 2,
|
||||
yBottomLeft: yBottom, yBottomRight: yBottom, yTopLeft: st.y1, yTopRight: st.y1
|
||||
});
|
||||
return;
|
||||
}
|
||||
const topPts = tl.underPts(xLeft, xRight);
|
||||
const yTopLeft = topPts[0].y, yTopRight = topPts[topPts.length - 1].y;
|
||||
const yBottom = CONFIG.plateThick;
|
||||
const length = Math.max(...topPts.map(p => p.y)) - yBottom;
|
||||
pieces.push({
|
||||
length, width: CONFIG.studWidth, depth: CONFIG.studDepth, kind: 'verticale balk', x,
|
||||
length: Math.max(...topPts.map(p => p.y)) - yBottom, width: CONFIG.studWidth, depth: CONFIG.studDepth, kind, x,
|
||||
y: (yBottom + yBottom + yTopLeft + yTopRight) / 4,
|
||||
yBottomLeft: yBottom, yBottomRight: yBottom, yTopLeft, yTopRight,
|
||||
outline: [{ x: xLeft, y: yBottom }, { x: xRight, y: yBottom }, ...topPts.slice().reverse()]
|
||||
});
|
||||
});
|
||||
|
||||
calcData.jackStuds.forEach(j => {
|
||||
const op = j.opening;
|
||||
const opYMin = op.y, opYMax = op.y + op.h;
|
||||
const xLeft = j.x - CONFIG.studWidth / 2, xRight = j.x + CONFIG.studWidth / 2;
|
||||
const topPts = tl.underPts(xLeft, xRight);
|
||||
const yTopLeft = topPts[0].y, yTopRight = topPts[topPts.length - 1].y;
|
||||
// De kreupele stijl mag niet door de onderdorpel heen lopen — die
|
||||
// neemt zelf al de onderste stijldikte in vanaf opYMin in beslag.
|
||||
const sillBottom = opYMin - CONFIG.studWidth;
|
||||
if (sillBottom > CONFIG.plateThick) {
|
||||
pieces.push({
|
||||
length: sillBottom - CONFIG.plateThick, width: CONFIG.studWidth, depth: CONFIG.studDepth, kind: 'kreupele stijl', x: j.x,
|
||||
y: (CONFIG.plateThick + sillBottom) / 2,
|
||||
yBottomLeft: CONFIG.plateThick, yBottomRight: CONFIG.plateThick, yTopLeft: sillBottom, yTopRight: sillBottom
|
||||
});
|
||||
}
|
||||
const actualOpTop = opYMax + CONFIG.studWidth;
|
||||
if (actualOpTop < yTopLeft || actualOpTop < yTopRight) {
|
||||
pieces.push({
|
||||
length: Math.max(...topPts.map(p => p.y)) - actualOpTop, width: CONFIG.studWidth, depth: CONFIG.studDepth, kind: 'kreupele stijl', x: j.x,
|
||||
y: (actualOpTop + actualOpTop + yTopLeft + yTopRight) / 4,
|
||||
yBottomLeft: actualOpTop, yBottomRight: actualOpTop, yTopLeft, yTopRight,
|
||||
outline: [{ x: xLeft, y: actualOpTop }, { x: xRight, y: actualOpTop }, ...topPts.slice().reverse()]
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
CONFIG.openings.forEach(op => {
|
||||
const opXWorld = startX + op.x;
|
||||
pieces.push({ length: op.w, width: CONFIG.studWidth, depth: CONFIG.studDepth, kind: 'bovendorpel', x: opXWorld + op.w / 2, y: op.y + op.h + CONFIG.studWidth / 2 });
|
||||
|
||||
+2
-2
@@ -80,7 +80,7 @@ window.WallPdf = (() => {
|
||||
doc.text('Isolatie: glaswol (spouwvullend)', tblX[0], my);
|
||||
if (buitenMateriaal === 'vochtdoek') {
|
||||
// Kruislat op het vochtdoek (zelfde berekening als de 3D: WallModel.computeBattens).
|
||||
const bat = WallModel.computeBattens(CONFIG, opts.cladding || 'horizontaal', opts.trasraam, opts.skinExt, opts.facade);
|
||||
const bat = WallModel.computeBattens(CONFIG, opts.cladding || 'horizontaal', opts.trasraam, opts.skinExt, opts.facade, opts.skinFrom);
|
||||
const B = WallModel.BATTEN;
|
||||
my += 4.5;
|
||||
doc.text(`Latwerk ${B.thick}x${B.width}: ${bat.stock.count} st. \u00e0 ${B.length} mm, kruislat ${B.spacing} h.o.h.`, tblX[0], my);
|
||||
@@ -251,7 +251,7 @@ window.WallPdf = (() => {
|
||||
// Stijlen (vol + kreupel) als doorsneden: rechthoek met kruis.
|
||||
doc.setDrawColor(...RED);
|
||||
doc.setLineWidth(0.2);
|
||||
const studXs = calcData.fullStuds.concat(calcData.jackStuds.map(j => j.x));
|
||||
const studXs = [...new Set(calcData.studs.map(st => st.x))];
|
||||
studXs.forEach(x => {
|
||||
const x0 = px(x - CONFIG.studWidth / 2), x1 = px(x + CONFIG.studWidth / 2);
|
||||
doc.rect(x0, tvFrame0, x1 - x0, tvFrame1 - tvFrame0, 'S');
|
||||
|
||||
+16
-14
@@ -274,7 +274,7 @@ function mountWallEditor(container, callbacks) {
|
||||
// Dichtheden/gewichten: zie WallModel (wall/wall-model.js).
|
||||
|
||||
let CONFIG = {};
|
||||
let calcData = { fullStuds: [], jackStuds: [] };
|
||||
let calcData = { fullStuds: [], studs: [] };
|
||||
let wallGroup3D = null;
|
||||
let lastBaseHeight = 2400;
|
||||
|
||||
@@ -759,7 +759,7 @@ function mountWallEditor(container, callbacks) {
|
||||
readInputs();
|
||||
calcData = WallModel.computeFrame(CONFIG);
|
||||
|
||||
setSpecAll('vert-beams', (calcData.fullStuds.length + calcData.jackStuds.length) + ' stuks');
|
||||
setSpecAll('vert-beams', calcData.studs.length + ' stuks');
|
||||
setSpecAll('roof-angle', calcData.angleDeg + '°');
|
||||
}
|
||||
|
||||
@@ -811,8 +811,10 @@ function mountWallEditor(container, callbacks) {
|
||||
ctx.strokeRect(screenX, screenY, studWidthPx, studDepthPx);
|
||||
};
|
||||
|
||||
calcData.fullStuds.forEach(x => drawStud2D(x, false));
|
||||
calcData.jackStuds.forEach(j => drawStud2D(j.x, true));
|
||||
// Bovenaanzicht: één doorsnede per stijlpositie (kreupel alleen als er
|
||||
// op die plek geen volle stijl staat).
|
||||
const fullXs = new Set(calcData.studs.filter(st => st.kind !== 'kreupel').map(st => st.x));
|
||||
[...new Set(calcData.studs.map(st => st.x))].forEach(x => drawStud2D(x, !fullXs.has(x)));
|
||||
|
||||
const startX = -CONFIG.wallWidth / 2;
|
||||
CONFIG.openings.forEach(op => {
|
||||
@@ -949,22 +951,22 @@ function mountWallEditor(container, callbacks) {
|
||||
ctxFront.textAlign = 'start';
|
||||
}
|
||||
|
||||
const drawStudFront = (xMM, isJack) => {
|
||||
const xLeft = xMM - CONFIG.studWidth / 2;
|
||||
const xRight = xMM + CONFIG.studWidth / 2;
|
||||
const yBot = CONFIG.plateThick;
|
||||
ctxFront.fillStyle = isJack ? '#a37c45' : '#c29b62';
|
||||
// Stijlstuk van y0 tot y1 (of tot de daklijn), zie WallModel.computeFrame.
|
||||
const drawStudFront = (st) => {
|
||||
const xLeft = st.x - CONFIG.studWidth / 2;
|
||||
const xRight = st.x + CONFIG.studWidth / 2;
|
||||
ctxFront.fillStyle = st.kind === 'kreupel' ? '#a37c45' : '#c29b62';
|
||||
ctxFront.strokeStyle = '#8a6836';
|
||||
ctxFront.beginPath();
|
||||
ctxFront.moveTo(toCanvasX(xLeft), toCanvasY(yBot));
|
||||
ctxFront.lineTo(toCanvasX(xRight), toCanvasY(yBot));
|
||||
tl.underPts(xLeft, xRight).reverse().forEach(p => ctxFront.lineTo(toCanvasX(p.x), toCanvasY(p.y)));
|
||||
ctxFront.moveTo(toCanvasX(xLeft), toCanvasY(st.y0));
|
||||
ctxFront.lineTo(toCanvasX(xRight), toCanvasY(st.y0));
|
||||
if (st.y1 === null) tl.underPts(xLeft, xRight).reverse().forEach(p => ctxFront.lineTo(toCanvasX(p.x), toCanvasY(p.y)));
|
||||
else { ctxFront.lineTo(toCanvasX(xRight), toCanvasY(st.y1)); ctxFront.lineTo(toCanvasX(xLeft), toCanvasY(st.y1)); }
|
||||
ctxFront.closePath();
|
||||
ctxFront.fill();
|
||||
ctxFront.stroke();
|
||||
};
|
||||
calcData.fullStuds.forEach(x => drawStudFront(x, false));
|
||||
calcData.jackStuds.forEach(j => drawStudFront(j.x, true));
|
||||
calcData.studs.forEach(drawStudFront);
|
||||
|
||||
const startXMM = -halfWMM;
|
||||
frontOpeningRects = [];
|
||||
|
||||
Reference in New Issue
Block a user