v0.4.10: wandhoogtes gekoppeld, middenpunt (nok), binnenspots langs naadwanden
- WallModel: optioneel middenpunt (heightMid, topLine), bovenregels in verstek; stijlen, openingen, beplating, doek, latwerk, BOM, 2D en PDF volgen de knik - wand-editor: schakelaar "Middenpunt (nok)" + nokhoogte, links/rechts = goothoogte - UnitModel.syncWallHeights: tegenoverliggende wand gespiegeld, haakse wanden vlak op de hoekhoogtes; wallPieces met nok per stuk - LightingModel.computeIndoor: raster vanaf binnenzijde eigen naadwanddeel Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
9bd7faeaa6
commit
9126588df7
+154
-84
@@ -14,7 +14,7 @@
|
||||
// voor zowel de losse wand als de unit gelden.
|
||||
//
|
||||
// Werkt met een "wand-state" (zelfde formaat als getStateJson/JSON-export):
|
||||
// { wall: {width, heightAll, heightLeft, heightRight, studWidth, ...},
|
||||
// { wall: {width, heightAll, heightLeft, heightRight, heightMid?, studWidth, ...},
|
||||
// materials: {interior, exterior}, openings: [{id,w,h,y,anchor,offset}] }
|
||||
//
|
||||
// Plain script (geen ES-module), moet na core/geometry.js geladen worden.
|
||||
@@ -102,6 +102,81 @@ window.WallModel = (() => {
|
||||
// deel van de andere unit aan staat (HouseModel, naadwanden).
|
||||
const EXTERIOR_MATERIALS = { vochtdoek: 'Vochtdoek', cementplaat: 'Cement-gebonden platen', spaanplaat: 'Spaanplaat', geen: 'Geen (naadzijde)' };
|
||||
|
||||
// Bovenrand van de wand (daklijn): één recht stuk van links naar rechts,
|
||||
// of met een middenpunt (nok, wallPeak) twee stukken die elkaar daar
|
||||
// raken. De nok ligt nooit lager dan de goten (buildConfig), dus de
|
||||
// bovenrand is overal het MINIMUM van de (doorgetrokken) stukken — dat
|
||||
// houdt at/under/rangeAbove simpel en maakt "laagste punt boven een
|
||||
// opening" gelijk aan het laagste van de twee uiteinden.
|
||||
// x t.o.v. het wandmidden, alles in mm x k (k = 0.001: meters).
|
||||
// at(x) bovenkant (daklijn)
|
||||
// under(x) onderkant bovenregel(s); knee = hun snijpunt (verstek)
|
||||
// rangeAbove(y) [x0, x1] waar de daklijn boven y ligt (of null)
|
||||
// crossings(y) x-posities waar de daklijn y kruist (knikpunten)
|
||||
// underPts(a, b) onderkant bovenregel van a naar b, incl. knie
|
||||
function topLine(CONFIG, k) {
|
||||
k = k || 1;
|
||||
const halfW = CONFIG.wallWidth / 2 * k;
|
||||
const L = { x: -halfW, y: CONFIG.wallHeightLeft * k }, R = { x: halfW, y: CONFIG.wallHeightRight * k };
|
||||
const pk = CONFIG.wallPeak ? { x: (CONFIG.wallPeak.x - CONFIG.wallWidth / 2) * k, y: CONFIG.wallPeak.h * k } : null;
|
||||
const pts = pk ? [L, pk, R] : [L, R];
|
||||
const segs = [];
|
||||
for (let i = 0; i < pts.length - 1; i++) {
|
||||
const a = pts[i], b = pts[i + 1], slope = (b.y - a.y) / (b.x - a.x);
|
||||
segs.push({ a, b, slope, vo: CONFIG.plateThick * k * Math.sqrt(1 + slope * slope) });
|
||||
}
|
||||
const lineY = (sg, x) => sg.a.y + (x - sg.a.x) * sg.slope;
|
||||
const at = (x) => Math.min(...segs.map(sg => lineY(sg, x)));
|
||||
const under = (x) => Math.min(...segs.map(sg => lineY(sg, x) - sg.vo));
|
||||
let knee = null;
|
||||
if (segs.length === 2) {
|
||||
const [s0, s1] = segs, ds = s0.slope - s1.slope;
|
||||
// s0.a.y + (x - s0.a.x) s0.slope - vo0 = s1.a.y + (x - s1.a.x) s1.slope - vo1
|
||||
const x = Math.abs(ds) > 1e-12
|
||||
? ((s1.a.y - s1.a.x * s1.slope - s1.vo) - (s0.a.y - s0.a.x * s0.slope - s0.vo)) / ds
|
||||
: pk.x;
|
||||
knee = { x, y: under(x) };
|
||||
}
|
||||
const rangeAbove = (y) => {
|
||||
let x0 = -halfW, x1 = halfW;
|
||||
segs.forEach(sg => {
|
||||
if (Math.abs(sg.slope) < 1e-12) { if (sg.a.y <= y) x1 = x0 - 1; return; }
|
||||
const xc = sg.a.x + (y - sg.a.y) / sg.slope;
|
||||
if (sg.slope > 0) x0 = Math.max(x0, xc); else x1 = Math.min(x1, xc);
|
||||
});
|
||||
return x1 > x0 ? [x0, x1] : null;
|
||||
};
|
||||
const crossings = (y) => segs.filter(sg => Math.abs(sg.slope) > 1e-12)
|
||||
.map(sg => ({ sg, x: sg.a.x + (y - sg.a.y) / sg.slope }))
|
||||
.filter(c => c.x > c.sg.a.x && c.x < c.sg.b.x).map(c => c.x);
|
||||
const underPts = (xa, xb) => {
|
||||
const out = [{ x: xa, y: under(xa) }];
|
||||
if (knee && knee.x > xa && knee.x < xb) out.push({ x: knee.x, y: knee.y });
|
||||
out.push({ x: xb, y: under(xb) });
|
||||
return out;
|
||||
};
|
||||
return {
|
||||
pts, segs, peak: pk, knee, at, under, rangeAbove, crossings, underPts,
|
||||
maxY: Math.max(...pts.map(p => p.y)), minY: Math.min(L.y, R.y),
|
||||
kinks: pk ? [pk.x] : []
|
||||
};
|
||||
}
|
||||
|
||||
// Omtrek(ken) van de bovenregel(s) (zelfde eenheid als topLine): één
|
||||
// parallellogram, of met nok twee stukken in verstek (nok boven, knie onder).
|
||||
function topPlateOutlines(tl) {
|
||||
const [L, R] = [tl.pts[0], tl.pts[tl.pts.length - 1]];
|
||||
if (!tl.peak) {
|
||||
const vo = tl.segs[0].vo;
|
||||
return [[L, R, { x: R.x, y: R.y - vo }, { x: L.x, y: L.y - vo }]];
|
||||
}
|
||||
const P = tl.peak, K = tl.knee;
|
||||
return [
|
||||
[L, P, K, { x: L.x, y: L.y - tl.segs[0].vo }],
|
||||
[P, R, { x: R.x, y: R.y - tl.segs[1].vo }, K]
|
||||
];
|
||||
}
|
||||
|
||||
// Heuristiek (geen volwaardige constraint-solver): elke opening wordt eerst
|
||||
// individueel gepositioneerd op basis van zijn anker, dan van links naar
|
||||
// rechts gesorteerd en tegen elkaar aan geduwd als er overlap is, en tot
|
||||
@@ -116,7 +191,8 @@ window.WallModel = (() => {
|
||||
// het laagste punt van de hele wand. Bij een hellende wand mag een opening
|
||||
// dicht bij de hoge kant dus hoger/groter zijn dan een opening dicht bij
|
||||
// de lage kant.
|
||||
function resolveOpenings(rawOpenings, wallWidth, studWidth, wallHeightLeft, wallHeightRight, plateThick) {
|
||||
// roofAt(xLocal): daklijn, xLocal vanaf de linker wandrand (0..wallWidth).
|
||||
function resolveOpenings(rawOpenings, wallWidth, studWidth, roofAt, plateThick) {
|
||||
// 2850mm is de vaste referentie-bovengrens (bijv. max. sparingbreedte in
|
||||
// dit constructiesysteem); daarnaast blijft de wand zelf ook een harde
|
||||
// grens (er moet nog stijlruimte overblijven).
|
||||
@@ -179,8 +255,9 @@ window.WallModel = (() => {
|
||||
// die opening — het punt waar een verticale lijn op die plek de
|
||||
// (eventueel hellende) horizontale topbalk zou snijden. Bij een
|
||||
// opening die zelf een breedte heeft, wordt het laagste van de twee
|
||||
// uiteinden aangehouden (de daklijn is lineair, dus dat is voldoende).
|
||||
const localRoofHeightAt = (xLocal) => wallHeightLeft + (xLocal / wallWidth) * (wallHeightRight - wallHeightLeft);
|
||||
// uiteinden aangehouden (de daklijn is lineair of heeft een nok als
|
||||
// hoogste punt, dus dat is voldoende).
|
||||
const localRoofHeightAt = roofAt;
|
||||
|
||||
resolved.forEach(o => {
|
||||
const localCeiling = Math.min(localRoofHeightAt(o.x), localRoofHeightAt(o.x + o.w));
|
||||
@@ -216,11 +293,24 @@ window.WallModel = (() => {
|
||||
const hRight = Math.round(params.heightRight || 2900);
|
||||
const studWidth = Math.round(params.studWidth || STANDARD_BEAM.width);
|
||||
const plateThick = Math.round(params.plateThick || STANDARD_BEAM.width);
|
||||
// Middenpunt (nok): standaard midden op de wand, of op peakX (mm vanaf
|
||||
// links; een wandstuk uit een unit waar de nok niet in het midden
|
||||
// valt). Nooit lager dan de hoogste goot; gelijk = geen nok.
|
||||
let wallPeak = null;
|
||||
if (params.heightMid > 0) {
|
||||
const px = params.peakX !== undefined ? params.peakX : wallWidth / 2;
|
||||
const h = Math.round(Math.max(params.heightMid, hLeft, hRight));
|
||||
if (px > 1 && px < wallWidth - 1 && h > hLeft + (px / wallWidth) * (hRight - hLeft) + 0.5) wallPeak = { x: px, h };
|
||||
}
|
||||
const roofAt = (x) => wallPeak
|
||||
? (x <= wallPeak.x ? hLeft + x / wallPeak.x * (wallPeak.h - hLeft) : wallPeak.h + (x - wallPeak.x) / (wallWidth - wallPeak.x) * (hRight - wallPeak.h))
|
||||
: hLeft + (x / wallWidth) * (hRight - hLeft);
|
||||
|
||||
return {
|
||||
wallWidth,
|
||||
wallHeightLeft: hLeft,
|
||||
wallHeightRight: hRight,
|
||||
wallPeak,
|
||||
studWidth,
|
||||
studDepth: Math.round(params.studDepth || STANDARD_BEAM.depth),
|
||||
plateThick,
|
||||
@@ -228,7 +318,7 @@ window.WallModel = (() => {
|
||||
sheathingThick: Math.round(params.sheathingThick || 10),
|
||||
minSpacing: Math.round(params.minSpacing || 240),
|
||||
maxSpacing: Math.round(params.maxSpacing || 480),
|
||||
openings: resolveOpenings(rawOpenings, wallWidth, studWidth, hLeft, hRight, plateThick)
|
||||
openings: resolveOpenings(rawOpenings, wallWidth, studWidth, roofAt, plateThick)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -247,8 +337,12 @@ window.WallModel = (() => {
|
||||
// Regelwerk: volledige stijlen (incl. koningsstijlen) + kreupele stijlen.
|
||||
function computeFrame(CONFIG) {
|
||||
const startX = -CONFIG.wallWidth / 2;
|
||||
const slope = (CONFIG.wallHeightRight - CONFIG.wallHeightLeft) / CONFIG.wallWidth;
|
||||
const angleDeg = Math.round(Math.atan(slope) * (180 / Math.PI));
|
||||
const top = topLine(CONFIG);
|
||||
const slope = top.segs[0].slope;
|
||||
// Met nok: de steilste helling (beide kanten lopen dan op naar de nok).
|
||||
const angleDeg = top.peak
|
||||
? Math.round(Math.max(...top.segs.map(sg => Math.atan(Math.abs(sg.slope)))) * (180 / Math.PI))
|
||||
: Math.round(Math.atan(slope) * (180 / Math.PI));
|
||||
|
||||
// Dubbele hoekstijlen: de buitenste staat op de wandrand, de tweede er
|
||||
// direct tegenaan. De regelmatige verdeling begint bij de binnenste.
|
||||
@@ -319,8 +413,7 @@ window.WallModel = (() => {
|
||||
// Gebruikt door build3D (fill) én de 2D-tekening (naad-/baanlijnen).
|
||||
function sheathingHoles(CONFIG) {
|
||||
const halfW = CONFIG.wallWidth / 2;
|
||||
const slope = (CONFIG.wallHeightRight - CONFIG.wallHeightLeft) / CONFIG.wallWidth;
|
||||
const topYAt = (x) => CONFIG.wallHeightLeft + (x + halfW) * slope;
|
||||
const topYAt = topLine(CONFIG).at;
|
||||
const roofSafetyMargin = 10;
|
||||
return CONFIG.openings.map(op => {
|
||||
const hx0 = -halfW + op.x - CONFIG.studWidth;
|
||||
@@ -336,16 +429,11 @@ window.WallModel = (() => {
|
||||
// liggen: binnen de wand-omtrek (incl. schuine top) en buiten de
|
||||
// uitsparingen. Geeft [[x0, x1], ...] terug (mm, t.o.v. het wandmidden).
|
||||
function skinSegmentsAtY(CONFIG, y) {
|
||||
const halfW = CONFIG.wallWidth / 2;
|
||||
const hL = CONFIG.wallHeightLeft, hR = CONFIG.wallHeightRight;
|
||||
if (y <= 0 || y >= Math.max(hL, hR)) return [];
|
||||
if (y <= 0) return [];
|
||||
// Deel van de breedte waar de daklijn boven y ligt.
|
||||
let x0 = -halfW, x1 = halfW;
|
||||
if (hL !== hR) {
|
||||
const xAtY = -halfW + (y - hL) / (hR - hL) * CONFIG.wallWidth;
|
||||
if (hR > hL) x0 = Math.max(x0, xAtY); else x1 = Math.min(x1, xAtY);
|
||||
} else if (y >= hL) return [];
|
||||
let segments = [[x0, x1]];
|
||||
const range = topLine(CONFIG).rangeAbove(y);
|
||||
if (!range) return [];
|
||||
let segments = [range];
|
||||
sheathingHoles(CONFIG).filter(h => y > h.hy0 && y < h.hy1).forEach(h => {
|
||||
segments = segments.flatMap(([a, b]) => {
|
||||
if (h.hx1 <= a || h.hx0 >= b) return [[a, b]];
|
||||
@@ -376,15 +464,14 @@ window.WallModel = (() => {
|
||||
const gridX0 = facade ? -CONFIG.wallWidth / 2 - facade.u0 - (facade.extStart ? 2 * BATTEN.thick : 0) : xL;
|
||||
const isEnd = facade ? facade.isEnd : true;
|
||||
const halfW = CONFIG.wallWidth / 2;
|
||||
const hL = CONFIG.wallHeightLeft, hR = CONFIG.wallHeightRight;
|
||||
const topYAt = (x) => hL + (x + halfW) * (hR - hL) / CONFIG.wallWidth;
|
||||
const tl = topLine(CONFIG), topYAt = tl.at;
|
||||
const holes = sheathingHoles(CONFIG);
|
||||
const w = BATTEN.width, sp = BATTEN.spacing;
|
||||
const top = (cladding === 'verticaal') ? 'horizontaal' : 'verticaal';
|
||||
|
||||
// Horizontale latten: vanaf de onderkant elke 400 mm (hartmaat).
|
||||
const horizontaal = [];
|
||||
for (let y = y0 + w / 2; y < Math.max(hL, hR) - w / 2; y += sp) {
|
||||
for (let y = y0 + w / 2; y < tl.maxY - w / 2; y += sp) {
|
||||
skinSegmentsAtY(CONFIG, y).forEach(([x0, x1]) => {
|
||||
if (x0 <= -halfW + 0.5) x0 = xL;
|
||||
if (x1 >= halfW - 0.5) x1 = xR;
|
||||
@@ -551,10 +638,11 @@ window.WallModel = (() => {
|
||||
const hRightM = CONFIG.wallHeightRight * toM;
|
||||
const halfW = wWidthM / 2;
|
||||
|
||||
const slope = (hRightM - hLeftM) / wWidthM;
|
||||
const vertOffset = pThickM * Math.sqrt(1 + slope * slope);
|
||||
const tl = topLine(CONFIG, toM);
|
||||
const startXM = -halfW;
|
||||
const topYAt = (x) => hLeftM + (x + halfW) * slope;
|
||||
const topYAt = tl.at;
|
||||
// Onderkant bovenregel van xLeft naar xRight (incl. verstek bij de nok).
|
||||
const underPts = tl.underPts;
|
||||
|
||||
// Openingen naar meter-coordinaten. LET OP: xMin/xMax hier zijn al naar
|
||||
// wereld-coordinaten verschoven (dezelfde as als de rest van de 3D-opbouw,
|
||||
@@ -603,31 +691,19 @@ window.WallModel = (() => {
|
||||
|
||||
// B. TOPBALK WAND (ongewijzigd door openingen) — dit is zelf ook een
|
||||
// "horizontale balk" (liggend, ook al loopt hij mee met de dakhelling).
|
||||
const topPlateShape = new THREE.Shape();
|
||||
topPlateShape.moveTo(-halfW, hLeftM);
|
||||
topPlateShape.lineTo(halfW, hRightM);
|
||||
topPlateShape.lineTo(halfW, hRightM - vertOffset);
|
||||
topPlateShape.lineTo(-halfW, hLeftM - vertOffset);
|
||||
topPlateShape.closePath();
|
||||
wallGroup3D.add(createExtrudedBeam(topPlateShape, pDepthM, -pDepthM / 2));
|
||||
horizBeamCount++;
|
||||
// Met nok: twee bovenregels die in verstek op de nok samenkomen.
|
||||
topPlateOutlines(tl).forEach(pts => {
|
||||
wallGroup3D.add(createExtrudedBeam(new THREE.Shape(pts.map(p => new THREE.Vector2(p.x, p.y))), pDepthM, -pDepthM / 2));
|
||||
horizBeamCount++;
|
||||
});
|
||||
|
||||
// C. VOLLEDIGE VERTICALE STIJLEN (incl. de "koningsstijlen" naast elke opening)
|
||||
calcData.fullStuds.forEach((xPosMM) => {
|
||||
const xM = xPosMM * toM;
|
||||
const xLeft = xM - (sWidthM / 2);
|
||||
const xRight = xM + (sWidthM / 2);
|
||||
|
||||
const yTopLeft = (hLeftM + (xLeft + halfW) * slope) - vertOffset;
|
||||
const yTopRight = (hLeftM + (xRight + halfW) * slope) - vertOffset;
|
||||
|
||||
const studShape = new THREE.Shape();
|
||||
studShape.moveTo(xLeft, pThickM);
|
||||
studShape.lineTo(xRight, pThickM);
|
||||
studShape.lineTo(xRight, yTopRight);
|
||||
studShape.lineTo(xLeft, yTopLeft);
|
||||
studShape.closePath();
|
||||
wallGroup3D.add(createExtrudedBeam(studShape, sDepthM, -sDepthM / 2));
|
||||
const pts = [{ x: xLeft, y: pThickM }, { x: xRight, y: pThickM }, ...underPts(xLeft, xRight).reverse()];
|
||||
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
|
||||
@@ -638,9 +714,8 @@ window.WallModel = (() => {
|
||||
const xM = j.x * toM;
|
||||
const xLeft = xM - (sWidthM / 2);
|
||||
const xRight = xM + (sWidthM / 2);
|
||||
|
||||
const yTopLeft = (hLeftM + (xLeft + halfW) * slope) - vertOffset;
|
||||
const yTopRight = (hLeftM + (xRight + halfW) * slope) - vertOffset;
|
||||
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).
|
||||
@@ -657,13 +732,8 @@ window.WallModel = (() => {
|
||||
|
||||
const actualOpTop = op.yMax + sWidthM;
|
||||
if (actualOpTop < yTopLeft || actualOpTop < yTopRight) {
|
||||
const studTopShape = new THREE.Shape();
|
||||
studTopShape.moveTo(xLeft, actualOpTop);
|
||||
studTopShape.lineTo(xRight, actualOpTop);
|
||||
studTopShape.lineTo(xRight, yTopRight);
|
||||
studTopShape.lineTo(xLeft, yTopLeft);
|
||||
studTopShape.closePath();
|
||||
wallGroup3D.add(createExtrudedBeam(studTopShape, sDepthM, -sDepthM / 2));
|
||||
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));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -781,7 +851,8 @@ window.WallModel = (() => {
|
||||
// over een plaatnaad wordt een inkeping in de plaatrand, zodat er
|
||||
// geen reepje plaat (met randlijnen) door de opening blijft lopen.
|
||||
const pieces = columnMinusRects(ex0, ex1, yB, skinTopAt,
|
||||
holeExtents.map(h => ({ x0: h.hx0, x1: h.hx1, y0: h.hy0, y1: h.hy1 })), range ? { yCap } : undefined);
|
||||
holeExtents.map(h => ({ x0: h.hx0, x1: h.hx1, y0: h.hy0, y1: h.hy1 })),
|
||||
{ yCap: range ? yCap : undefined, extraXs: range ? tl.kinks.concat(tl.crossings(yCap)) : tl.kinks });
|
||||
let boardShapes;
|
||||
if (pieces) {
|
||||
boardShapes = pieces.map(piece => {
|
||||
@@ -796,6 +867,7 @@ window.WallModel = (() => {
|
||||
boardShape.moveTo(ex0, yB);
|
||||
boardShape.lineTo(ex1, yB);
|
||||
boardShape.lineTo(ex1, skinTopAt(ex1));
|
||||
tl.kinks.filter(x => x > ex0 && x < ex1).forEach(x => boardShape.lineTo(x, skinTopAt(x)));
|
||||
boardShape.lineTo(ex0, skinTopAt(ex0));
|
||||
boardShape.closePath();
|
||||
holeExtents.forEach(h => {
|
||||
@@ -892,11 +964,8 @@ window.WallModel = (() => {
|
||||
// (wat flikkering/z-fighting zou geven nu de platen niet meer
|
||||
// transparant zijn).
|
||||
const doekZStepM = 0.0002; // 0.2 mm per baan, verwaarloosbaar t.o.v. de spouwdiepte
|
||||
const wallOutlinePts = [
|
||||
{ x: -halfW, y: 0 }, { x: halfW, y: 0 },
|
||||
{ x: halfW, y: hRightM }, { x: -halfW, y: hLeftM }
|
||||
];
|
||||
const maxRoofY = Math.max(hLeftM, hRightM);
|
||||
const wallOutlinePts = [{ x: -halfW, y: 0 }, { x: halfW, y: 0 }, ...tl.pts.slice().reverse()];
|
||||
const maxRoofY = tl.maxY;
|
||||
|
||||
let rowY0 = 0;
|
||||
let rowIndex = 0;
|
||||
@@ -906,15 +975,9 @@ window.WallModel = (() => {
|
||||
// Baan = strook [rowY0, min(rowY1, daklijn)] min uitsparingen
|
||||
// (columnMinusRects, zelfde als de platen): een opening over de
|
||||
// baanrand wordt een inkeping, geen reepje doek door de opening.
|
||||
const roofAt = (x) => hLeftM + (x + halfW) * slope;
|
||||
let rx0 = -halfW, rx1 = halfW;
|
||||
const extraXs = [];
|
||||
if (Math.abs(slope) > 1e-12) {
|
||||
const xAtRowBottom = -halfW + (rowY0 - hLeftM) / slope;
|
||||
const xAtRowTop = -halfW + (rowY1 - hLeftM) / slope;
|
||||
if (slope > 0) rx0 = Math.max(rx0, xAtRowBottom); else rx1 = Math.min(rx1, xAtRowBottom);
|
||||
extraXs.push(xAtRowTop);
|
||||
}
|
||||
const roofAt = topYAt;
|
||||
const [rx0, rx1] = tl.rangeAbove(rowY0) || [0, 0];
|
||||
const extraXs = tl.crossings(rowY1).concat(tl.kinks);
|
||||
const pieces = rx1 - rx0 > edgeEps ? columnMinusRects(rx0, rx1, rowY0, (x) => Math.min(rowY1, roofAt(x)),
|
||||
holeExtents.map(h => ({ x0: h.hx0, x1: h.hx1, y0: h.hy0, y1: h.hy1 })), { yCap: rowY1, extraXs }) : [];
|
||||
|
||||
@@ -1042,10 +1105,7 @@ window.WallModel = (() => {
|
||||
// Omhullende van de wand (trapezium, incl. schuine top) als referentie
|
||||
// voor de leegte-berekening — spouwdiepte wordt gelijkgesteld aan de
|
||||
// stijldiepte (vereenvoudiging, zie toelichting in de UI).
|
||||
const envelopePoints = [
|
||||
{ x: -halfW, y: 0 }, { x: halfW, y: 0 },
|
||||
{ x: halfW, y: hRightM }, { x: -halfW, y: hLeftM }
|
||||
];
|
||||
const envelopePoints = [{ x: -halfW, y: 0 }, { x: halfW, y: 0 }, ...topLine(CONFIG, toM).pts.slice().reverse()];
|
||||
const envelopeAreaM2 = polygonArea(envelopePoints);
|
||||
const envelopeVolumeM3 = envelopeAreaM2 * sDepthM;
|
||||
|
||||
@@ -1088,9 +1148,7 @@ window.WallModel = (() => {
|
||||
function computeBOM(CONFIG, calcData) {
|
||||
const wallWidth = CONFIG.wallWidth, hLeft = CONFIG.wallHeightLeft, hRight = CONFIG.wallHeightRight;
|
||||
const halfW = wallWidth / 2;
|
||||
const slope = (hRight - hLeft) / wallWidth;
|
||||
const topYAt = (xWorld) => hLeft + (xWorld + halfW) * slope;
|
||||
const vertOffset = CONFIG.plateThick * Math.sqrt(1 + slope * slope);
|
||||
const tl = topLine(CONFIG);
|
||||
const startX = -halfW;
|
||||
|
||||
const pieces = [];
|
||||
@@ -1109,8 +1167,15 @@ window.WallModel = (() => {
|
||||
pieces.push({ length: halfW - cursor, width: CONFIG.plateThick, depth: CONFIG.plateDepth, kind: 'onderregel', x: (cursor + halfW) / 2, y: CONFIG.plateThick / 2 });
|
||||
}
|
||||
|
||||
const topLength = Math.sqrt(wallWidth * wallWidth + (hRight - hLeft) * (hRight - hLeft));
|
||||
pieces.push({ length: topLength, width: CONFIG.plateThick, depth: CONFIG.plateDepth, kind: 'bovenregel', x: 0, y: (hLeft + hRight) / 2 });
|
||||
// Bovenregel(s): met nok twee stukken (verstek). outline = werkelijke
|
||||
// omtrek (PDF-tekening).
|
||||
topPlateOutlines(tl).forEach((outline, i) => {
|
||||
const sg = tl.segs[i];
|
||||
pieces.push({
|
||||
length: Math.hypot(sg.b.x - sg.a.x, sg.b.y - sg.a.y), width: CONFIG.plateThick, depth: CONFIG.plateDepth, kind: 'bovenregel',
|
||||
x: (sg.a.x + sg.b.x) / 2, y: (sg.a.y + sg.b.y) / 2 - CONFIG.plateThick / 2, outline
|
||||
});
|
||||
});
|
||||
|
||||
// Verticale stukken krijgen, naast de bounding-box velden (length/y —
|
||||
// gebruikt voor de materiaallijst: de lengte van het rechte stuk hout
|
||||
@@ -1120,15 +1185,18 @@ window.WallModel = (() => {
|
||||
// i.p.v. als platte rechthoek tot het hoogste punt — anders komt alleen
|
||||
// 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;
|
||||
const yTopLeft = topYAt(xLeft) - vertOffset, yTopRight = topYAt(xRight) - vertOffset;
|
||||
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(yTopLeft, yTopRight) - yBottom;
|
||||
const length = Math.max(...topPts.map(p => p.y)) - yBottom;
|
||||
pieces.push({
|
||||
length, width: CONFIG.studWidth, depth: CONFIG.studDepth, kind: 'verticale balk', x,
|
||||
y: (yBottom + yBottom + yTopLeft + yTopRight) / 4,
|
||||
yBottomLeft: yBottom, yBottomRight: yBottom, yTopLeft, yTopRight
|
||||
yBottomLeft: yBottom, yBottomRight: yBottom, yTopLeft, yTopRight,
|
||||
outline: [{ x: xLeft, y: yBottom }, { x: xRight, y: yBottom }, ...topPts.slice().reverse()]
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1136,7 +1204,8 @@ window.WallModel = (() => {
|
||||
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 yTopLeft = topYAt(xLeft) - vertOffset, yTopRight = topYAt(xRight) - vertOffset;
|
||||
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;
|
||||
@@ -1150,9 +1219,10 @@ window.WallModel = (() => {
|
||||
const actualOpTop = opYMax + CONFIG.studWidth;
|
||||
if (actualOpTop < yTopLeft || actualOpTop < yTopRight) {
|
||||
pieces.push({
|
||||
length: Math.max(yTopLeft, yTopRight) - actualOpTop, width: CONFIG.studWidth, depth: CONFIG.studDepth, kind: 'kreupele stijl', x: j.x,
|
||||
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
|
||||
yBottomLeft: actualOpTop, yBottomRight: actualOpTop, yTopLeft, yTopRight,
|
||||
outline: [{ x: xLeft, y: actualOpTop }, { x: xRight, y: actualOpTop }, ...topPts.slice().reverse()]
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1248,7 +1318,7 @@ window.WallModel = (() => {
|
||||
|
||||
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,
|
||||
sheathingHoles, skinSegmentsAtY,
|
||||
sheathingHoles, skinSegmentsAtY, topLine, topPlateOutlines,
|
||||
validateState, resolveOpenings, computeBOM, groupBOM, buildConfig, configFromState, thicknessOf, computeFrame,
|
||||
createMaterials, build3D, computeSpecs
|
||||
};
|
||||
|
||||
+17
-22
@@ -67,7 +67,7 @@ window.WallPdf = (() => {
|
||||
|
||||
// --- Materiaal binnen/buiten + isolatie ---
|
||||
let my = 240;
|
||||
const midH = Math.round((CONFIG.wallHeightLeft + CONFIG.wallHeightRight) / 2);
|
||||
const midH = Math.round(WallModel.topLine(CONFIG).maxY);
|
||||
doc.setFontSize(8.5);
|
||||
doc.text('Materiaal binnen', tblX[0], my); my += 4.5;
|
||||
doc.setFontSize(6.5);
|
||||
@@ -103,7 +103,8 @@ window.WallPdf = (() => {
|
||||
const availW = (drawX1 - drawX0) - marginSides * 2;
|
||||
const availH = (drawY1 - drawY0) - marginTop - marginBottom;
|
||||
const wallWidth = CONFIG.wallWidth, hLeft = CONFIG.wallHeightLeft, hRight = CONFIG.wallHeightRight;
|
||||
const maxH = Math.max(hLeft, hRight);
|
||||
const tl = WallModel.topLine(CONFIG);
|
||||
const maxH = tl.maxY;
|
||||
const scale = Math.min(availW / wallWidth, availH / maxH);
|
||||
|
||||
const originX = drawX0 + marginSides;
|
||||
@@ -115,15 +116,18 @@ window.WallPdf = (() => {
|
||||
doc.setLineWidth(0.25);
|
||||
doc.line(px(-wallWidth / 2), py(0), px(wallWidth / 2), py(0));
|
||||
doc.line(px(wallWidth / 2), py(0), px(wallWidth / 2), py(hRight));
|
||||
doc.line(px(wallWidth / 2), py(hRight), px(-wallWidth / 2), py(hLeft));
|
||||
for (let i = 0; i < tl.pts.length - 1; i++) doc.line(px(tl.pts[i].x), py(tl.pts[i].y), px(tl.pts[i + 1].x), py(tl.pts[i + 1].y));
|
||||
doc.line(px(-wallWidth / 2), py(hLeft), px(-wallWidth / 2), py(0));
|
||||
|
||||
doc.setDrawColor(...RED);
|
||||
doc.setLineWidth(0.3);
|
||||
pieces.forEach(p => {
|
||||
if (p.kind === 'bovenregel') return; // apart getekend hieronder, volgt de helling
|
||||
const isVertical = p.kind === 'verticale balk' || p.kind === 'kreupele stijl';
|
||||
if (isVertical) {
|
||||
if (p.outline) {
|
||||
// Werkelijke omtrek: schuine kop (volgt de daklijn, evt. met knik
|
||||
// onder de nok) resp. bovenregel in verstek.
|
||||
const c = p.outline.map(q => [px(q.x), py(q.y)]);
|
||||
c.forEach((q, i) => { const n = c[(i + 1) % c.length]; doc.line(q[0], q[1], n[0], n[1]); });
|
||||
} else if (p.kind === 'verticale balk' || p.kind === 'kreupele stijl') {
|
||||
// Viervlak i.p.v. rechthoek: de bovenkant loopt schuin mee met de
|
||||
// daklijn (yTopLeft/yTopRight kunnen verschillen), zodat zowel de
|
||||
// hoogste als de laagste kant van de balk op de juiste plek komt.
|
||||
@@ -143,22 +147,6 @@ window.WallPdf = (() => {
|
||||
doc.text(String(p.posnum), px(p.x), py(p.y), { align: 'center' });
|
||||
});
|
||||
|
||||
// Bovenregel: volgt de dakhelling, dus als schuin parallellogram
|
||||
// getekend (niet als vlakke rechthoek zoals de rest hierboven).
|
||||
const topPlatePiece = pieces.find(p => p.kind === 'bovenregel');
|
||||
if (topPlatePiece) {
|
||||
const slope = (hRight - hLeft) / wallWidth;
|
||||
const vOff = CONFIG.plateThick * Math.sqrt(1 + slope * slope);
|
||||
const tp = [
|
||||
[px(-wallWidth / 2), py(hLeft)], [px(wallWidth / 2), py(hRight)],
|
||||
[px(wallWidth / 2), py(hRight - vOff)], [px(-wallWidth / 2), py(hLeft - vOff)]
|
||||
];
|
||||
for (let i = 0; i < 4; i++) doc.line(tp[i][0], tp[i][1], tp[(i + 1) % 4][0], tp[(i + 1) % 4][1]);
|
||||
doc.setTextColor(...BLACK);
|
||||
doc.setFontSize(5);
|
||||
doc.text(String(topPlatePiece.posnum), px(topPlatePiece.x), py(topPlatePiece.y), { align: 'center' });
|
||||
}
|
||||
|
||||
doc.setDrawColor(...BLACK);
|
||||
doc.setLineWidth(0.2);
|
||||
CONFIG.openings.forEach(op => {
|
||||
@@ -188,6 +176,13 @@ window.WallPdf = (() => {
|
||||
doc.text(String(Math.round(hLeft)), px(-wallWidth / 2) - 9, (py(0) + py(hLeft)) / 2, { align: 'center', angle: 90 });
|
||||
drawDimLine(px(wallWidth / 2) + 7, py(0), px(wallWidth / 2) + 7, py(hRight));
|
||||
doc.text(String(Math.round(hRight)), px(wallWidth / 2) + 11, (py(0) + py(hRight)) / 2, { align: 'center', angle: 90 });
|
||||
if (tl.peak) {
|
||||
// Nokhoogte: stippellijn van vloer tot nok.
|
||||
doc.setLineDashPattern([1, 1], 0);
|
||||
doc.line(px(tl.peak.x), py(0), px(tl.peak.x), py(tl.peak.y));
|
||||
doc.setLineDashPattern([], 0);
|
||||
doc.text('nok ' + Math.round(tl.peak.y), px(tl.peak.x) + 1.5, py(tl.peak.y) + 4);
|
||||
}
|
||||
|
||||
const studEdges = new Set([0, wallWidth]);
|
||||
calcData.fullStuds.forEach(x => studEdges.add(Math.round(x - CONFIG.studWidth / 2 + wallWidth / 2)));
|
||||
|
||||
+46
-21
@@ -65,13 +65,21 @@ const WALL_TEMPLATE = `
|
||||
<div><input type="number" id="inp-wall-h-all" value="2400" step="50" min="500" max="5000"><span class="unit">mm</span></div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label for="inp-wall-h-left">Hoogte links</label>
|
||||
<label for="inp-wall-h-left" id="lbl-wall-h-left">Hoogte links</label>
|
||||
<div><input type="number" id="inp-wall-h-left" value="2400" step="50" min="500" max="5000"><span class="unit">mm</span></div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label for="inp-wall-h-right">Hoogte rechts</label>
|
||||
<label for="inp-wall-h-right" id="lbl-wall-h-right">Hoogte rechts</label>
|
||||
<div><input type="number" id="inp-wall-h-right" value="2400" step="50" min="500" max="5000"><span class="unit">mm</span></div>
|
||||
</div>
|
||||
<div class="control-group" title="Middenpunt van de bovenrand verhogen (nok): links en rechts worden dan goothoogtes.">
|
||||
<label for="inp-wall-peak">Middenpunt (nok)</label>
|
||||
<div><label class="ui-switch"><input type="checkbox" id="inp-wall-peak"><span></span></label></div>
|
||||
</div>
|
||||
<div class="control-group" id="grp-wall-h-mid" style="display:none">
|
||||
<label for="inp-wall-h-mid">Nokhoogte</label>
|
||||
<div><input type="number" id="inp-wall-h-mid" value="3400" step="50" min="500" max="8000"><span class="unit">mm</span></div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
<h1><i class="bi bi-columns-gap"></i> Stijlen</h1>
|
||||
@@ -543,6 +551,8 @@ function mountWallEditor(container, callbacks) {
|
||||
heightAll: parseFloat(document.getElementById('inp-wall-h-all').value),
|
||||
heightLeft: parseFloat(document.getElementById('inp-wall-h-left').value),
|
||||
heightRight: parseFloat(document.getElementById('inp-wall-h-right').value),
|
||||
// Middenpunt (nok); null = rechte bovenrand.
|
||||
heightMid: document.getElementById('inp-wall-peak').checked ? parseFloat(document.getElementById('inp-wall-h-mid').value) : null,
|
||||
studWidth: parseFloat(document.getElementById('inp-stud-w').value),
|
||||
studDepth: parseFloat(document.getElementById('inp-stud-d').value),
|
||||
plateThick: parseFloat(document.getElementById('inp-plate-w').value),
|
||||
@@ -566,6 +576,9 @@ function mountWallEditor(container, callbacks) {
|
||||
document.getElementById('inp-wall-h-all').value = state.wall.heightAll;
|
||||
document.getElementById('inp-wall-h-left').value = state.wall.heightLeft;
|
||||
document.getElementById('inp-wall-h-right').value = state.wall.heightRight;
|
||||
document.getElementById('inp-wall-peak').checked = state.wall.heightMid > 0;
|
||||
document.getElementById('inp-wall-h-mid').value = state.wall.heightMid > 0 ? state.wall.heightMid
|
||||
: Math.max(state.wall.heightLeft, state.wall.heightRight) + 800;
|
||||
document.getElementById('inp-stud-w').value = state.wall.studWidth;
|
||||
document.getElementById('inp-stud-d').value = state.wall.studDepth;
|
||||
document.getElementById('inp-plate-w').value = state.wall.plateThick;
|
||||
@@ -702,24 +715,35 @@ function mountWallEditor(container, callbacks) {
|
||||
const hLeftInput = document.getElementById('inp-wall-h-left');
|
||||
const hRightInput = document.getElementById('inp-wall-h-right');
|
||||
|
||||
const hMidInput = document.getElementById('inp-wall-h-mid');
|
||||
const peakOn = document.getElementById('inp-wall-peak').checked;
|
||||
|
||||
let hAll = Math.round(parseFloat(hAllInput.value) || 2400);
|
||||
let hLeft = Math.round(parseFloat(hLeftInput.value) || 2400);
|
||||
let hRight = Math.round(parseFloat(hRightInput.value) || 2900);
|
||||
let hMid = Math.round(parseFloat(hMidInput.value) || 0);
|
||||
|
||||
if (hAll !== lastBaseHeight) {
|
||||
const delta = hAll - lastBaseHeight;
|
||||
hLeft = Math.max(500, hLeft + delta);
|
||||
hRight = Math.max(500, hRight + delta);
|
||||
hMid = Math.max(500, hMid + delta);
|
||||
hLeftInput.value = hLeft;
|
||||
hRightInput.value = hRight;
|
||||
hMidInput.value = hMid;
|
||||
lastBaseHeight = hAll;
|
||||
}
|
||||
// Met nok: links/rechts zijn goothoogtes.
|
||||
document.getElementById('grp-wall-h-mid').style.display = peakOn ? '' : 'none';
|
||||
document.getElementById('lbl-wall-h-left').textContent = peakOn ? 'Goothoogte links' : 'Hoogte links';
|
||||
document.getElementById('lbl-wall-h-right').textContent = peakOn ? 'Goothoogte rechts' : 'Hoogte rechts';
|
||||
|
||||
// Afronding/fallbacks + ankerresolutie van de openingen: WallModel.
|
||||
CONFIG = WallModel.buildConfig({
|
||||
width: wallWidth,
|
||||
heightLeft: hLeft,
|
||||
heightRight: hRight,
|
||||
heightMid: peakOn ? hMid : null,
|
||||
studWidth: parseFloat(document.getElementById('inp-stud-w').value),
|
||||
studDepth: parseFloat(document.getElementById('inp-stud-d').value),
|
||||
plateThick: parseFloat(document.getElementById('inp-plate-w').value),
|
||||
@@ -827,10 +851,8 @@ function mountWallEditor(container, callbacks) {
|
||||
const hLeftMM = CONFIG.wallHeightLeft;
|
||||
const hRightMM = CONFIG.wallHeightRight;
|
||||
const halfWMM = wallWidthMM / 2;
|
||||
const slopeMM = (hRightMM - hLeftMM) / wallWidthMM;
|
||||
const topYAtMM = (x) => hLeftMM + (x + halfWMM) * slopeMM;
|
||||
const vertOffsetMM = CONFIG.plateThick * Math.sqrt(1 + slopeMM * slopeMM);
|
||||
const maxRoofMM = Math.max(hLeftMM, hRightMM);
|
||||
const tl = WallModel.topLine(CONFIG);
|
||||
const maxRoofMM = tl.maxY;
|
||||
|
||||
const scale = Math.min(w / (wallWidthMM * 1.3), h / (maxRoofMM * 1.3));
|
||||
const centerX = w / 2;
|
||||
@@ -882,8 +904,7 @@ function mountWallEditor(container, callbacks) {
|
||||
ctxFront.beginPath();
|
||||
ctxFront.moveTo(toCanvasX(-halfWMM), toCanvasY(0));
|
||||
ctxFront.lineTo(toCanvasX(halfWMM), toCanvasY(0));
|
||||
ctxFront.lineTo(toCanvasX(halfWMM), toCanvasY(hRightMM));
|
||||
ctxFront.lineTo(toCanvasX(-halfWMM), toCanvasY(hLeftMM));
|
||||
tl.pts.slice().reverse().forEach(p => ctxFront.lineTo(toCanvasX(p.x), toCanvasY(p.y)));
|
||||
ctxFront.closePath();
|
||||
ctxFront.stroke();
|
||||
|
||||
@@ -898,14 +919,13 @@ function mountWallEditor(container, callbacks) {
|
||||
ctxFront.fill();
|
||||
ctxFront.stroke();
|
||||
|
||||
ctxFront.beginPath();
|
||||
ctxFront.moveTo(toCanvasX(-halfWMM), toCanvasY(hLeftMM));
|
||||
ctxFront.lineTo(toCanvasX(halfWMM), toCanvasY(hRightMM));
|
||||
ctxFront.lineTo(toCanvasX(halfWMM), toCanvasY(hRightMM - vertOffsetMM));
|
||||
ctxFront.lineTo(toCanvasX(-halfWMM), toCanvasY(hLeftMM - vertOffsetMM));
|
||||
ctxFront.closePath();
|
||||
ctxFront.fill();
|
||||
ctxFront.stroke();
|
||||
WallModel.topPlateOutlines(tl).forEach(pts => {
|
||||
ctxFront.beginPath();
|
||||
pts.forEach((p, i) => (i ? ctxFront.lineTo : ctxFront.moveTo).call(ctxFront, toCanvasX(p.x), toCanvasY(p.y)));
|
||||
ctxFront.closePath();
|
||||
ctxFront.fill();
|
||||
ctxFront.stroke();
|
||||
});
|
||||
|
||||
// X/Y-assen (lijn + label), altijd binnen het canvas gehouden zodat er
|
||||
// niks buiten het zichtbare venster valt. Stijl: core/canvas-labels.js.
|
||||
@@ -920,20 +940,25 @@ function mountWallEditor(container, callbacks) {
|
||||
const yAxisRightX = Math.min(w - 10, toCanvasX(halfWMM) + 14);
|
||||
drawCanvasDim(ctxFront, yAxisRightX, toCanvasY(0), yAxisRightX, toCanvasY(hRightMM),
|
||||
'Y: ' + Math.round(hRightMM) + 'mm', Math.min(w - 4, yAxisRightX + 12), (toCanvasY(0) + toCanvasY(hRightMM)) / 2);
|
||||
if (tl.peak) {
|
||||
const pxC = toCanvasX(tl.peak.x);
|
||||
ctxFront.fillStyle = '#9ab';
|
||||
ctxFront.font = '11px sans-serif';
|
||||
ctxFront.textAlign = 'center';
|
||||
ctxFront.fillText('nok ' + Math.round(tl.peak.y) + 'mm', pxC, Math.max(12, toCanvasY(tl.peak.y) - 6));
|
||||
ctxFront.textAlign = 'start';
|
||||
}
|
||||
|
||||
const drawStudFront = (xMM, isJack) => {
|
||||
const xLeft = xMM - CONFIG.studWidth / 2;
|
||||
const xRight = xMM + CONFIG.studWidth / 2;
|
||||
const yTopLeft = topYAtMM(xLeft) - vertOffsetMM;
|
||||
const yTopRight = topYAtMM(xRight) - vertOffsetMM;
|
||||
const yBot = CONFIG.plateThick;
|
||||
ctxFront.fillStyle = isJack ? '#a37c45' : '#c29b62';
|
||||
ctxFront.strokeStyle = '#8a6836';
|
||||
ctxFront.beginPath();
|
||||
ctxFront.moveTo(toCanvasX(xLeft), toCanvasY(yBot));
|
||||
ctxFront.lineTo(toCanvasX(xRight), toCanvasY(yBot));
|
||||
ctxFront.lineTo(toCanvasX(xRight), toCanvasY(yTopRight));
|
||||
ctxFront.lineTo(toCanvasX(xLeft), toCanvasY(yTopLeft));
|
||||
tl.underPts(xLeft, xRight).reverse().forEach(p => ctxFront.lineTo(toCanvasX(p.x), toCanvasY(p.y)));
|
||||
ctxFront.closePath();
|
||||
ctxFront.fill();
|
||||
ctxFront.stroke();
|
||||
@@ -1128,7 +1153,7 @@ function mountWallEditor(container, callbacks) {
|
||||
const buitenThick = CONFIG.sheathingThick;
|
||||
const spaanThick = CONFIG.sheathingThick;
|
||||
const totalDepth = buitenThick + CONFIG.studDepth + spaanThick;
|
||||
const maxH = Math.max(CONFIG.wallHeightLeft, CONFIG.wallHeightRight);
|
||||
const maxH = WallModel.topLine(CONFIG).maxY;
|
||||
|
||||
const scale = Math.min((w * 0.45) / totalDepth, (h * 0.8) / maxH);
|
||||
const originX = w / 2 - (totalDepth * scale) / 2;
|
||||
|
||||
Reference in New Issue
Block a user