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
@@ -181,6 +181,17 @@ assets/ logo (preloader) + bestemmingsmap voor toekomstige
|
|||||||
'geen'); de openingen gelden voor beide delen en worden gespiegeld. Vloer en
|
'geen'); de openingen gelden voor beide delen en worden gespiegeld. Vloer en
|
||||||
dak krijgen op een naad langs voor-/achterrand een kopbalk
|
dak krijgen op een naad langs voor-/achterrand een kopbalk
|
||||||
(FloorModel.computeFrame rims), langs de zijranden ligt al een balk.
|
(FloorModel.computeFrame rims), langs de zijranden ligt al een balk.
|
||||||
|
- **Wandhoogtes gekoppeld + nok (v0.4.10).** Een wand kan een middenpunt
|
||||||
|
(nok, wall.heightMid) krijgen: de bovenrand wordt dan twee stukken, links/
|
||||||
|
rechts zijn goothoogtes (WallModel.topLine; bovenregels in verstek). In een
|
||||||
|
unit bepaalt de laatst in hoogte gewijzigde wand de dakvorm
|
||||||
|
(UnitModel.syncWallHeights): de tegenoverliggende wand krijgt dezelfde
|
||||||
|
hoogtes gespiegeld (incl. nok), de haakse wanden worden vlak op de
|
||||||
|
hoekhoogtes. Binnen- en naadwanden doen (nog) niet mee; de daklogica volgt
|
||||||
|
apart.
|
||||||
|
- **Binnenspots langs naadwanden (v0.4.10).** Het raster begint vanaf de
|
||||||
|
binnenzijde van het eigen naadwanddeel (niet de unitrand), zodat de rij
|
||||||
|
langs de naad niet meer wegvalt.
|
||||||
- **Isolatie niet meer getekend (v0.4.9).** Filter "Isolatie" en de 3D-lagen
|
- **Isolatie niet meer getekend (v0.4.9).** Filter "Isolatie" en de 3D-lagen
|
||||||
zijn weg; de spouw-/leegte-inhoud blijft in specificaties en totalen.
|
zijn weg; de spouw-/leegte-inhoud blijft in specificaties en totalen.
|
||||||
- **Startpunt gevellampen (v0.4.8).** Een gevel telt vanaf de zichtbare,
|
- **Startpunt gevellampen (v0.4.8).** Een gevel telt vanaf de zichtbare,
|
||||||
|
|||||||
@@ -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.9';
|
const APP_VERSION = '0.4.10';
|
||||||
// 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 = [];
|
||||||
|
|||||||
@@ -376,7 +376,7 @@ window.HouseModel = (() => {
|
|||||||
// wandbovenkant van deze unit.
|
// wandbovenkant van deze unit.
|
||||||
const roofData = FloorModel.computeFromState(u.roof.deck, rims);
|
const roofData = FloorModel.computeFromState(u.roof.deck, rims);
|
||||||
const roofSpecs = RoofModel.computeSpecs(roofData.calcData, roofData.resolvedOpenings);
|
const roofSpecs = RoofModel.computeSpecs(roofData.calcData, roofData.resolvedOpenings);
|
||||||
const wallTop = Math.max(...Object.values(u.unit.walls).map(ws => Math.max(ws.wall.heightLeft, ws.wall.heightRight)));
|
const wallTop = Math.max(...Object.values(u.unit.walls).map(ws => UnitModel.wallTopOf(ws)));
|
||||||
const roofW = roofData.calcData.width, roofD = roofData.calcData.depth;
|
const roofW = roofData.calcData.width, roofD = roofData.calcData.depth;
|
||||||
const roofCx = rect.cx + (u.roof.offsetX || 0), roofCz = rect.cz + (u.roof.offsetZ || 0);
|
const roofCx = rect.cx + (u.roof.offsetX || 0), roofCz = rect.cz + (u.roof.offsetZ || 0);
|
||||||
const roof = {
|
const roof = {
|
||||||
|
|||||||
+9
-1
@@ -133,7 +133,15 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
if (!activeEditor || typeof activeEditor.getStateJson !== 'function' || !activePart) return;
|
if (!activeEditor || typeof activeEditor.getStateJson !== 'function' || !activePart) return;
|
||||||
const [kind, a, b] = activePart.split(':');
|
const [kind, a, b] = activePart.split(':');
|
||||||
if (kind === 'floor' && unitById(a)) unitById(a).unit.floor = activeEditor.getStateJson();
|
if (kind === 'floor' && unitById(a)) unitById(a).unit.floor = activeEditor.getStateJson();
|
||||||
else if (kind === 'wall' && unitById(a)) unitById(a).unit.walls[b] = activeEditor.getStateJson();
|
else if (kind === 'wall' && unitById(a)) {
|
||||||
|
// Hoogtes gewijzigd: tegenoverliggende wand spiegelen, haakse wanden
|
||||||
|
// vlak op de hoekhoogtes (UnitModel.syncWallHeights).
|
||||||
|
const unit = unitById(a).unit, before = unit.walls[b].wall;
|
||||||
|
const next = activeEditor.getStateJson();
|
||||||
|
unit.walls[b] = next;
|
||||||
|
const h = (w) => [w.heightLeft, w.heightRight, w.heightMid > 0 ? w.heightMid : null].join('|');
|
||||||
|
if (h(before) !== h(next.wall)) UnitModel.syncWallHeights(unit, b);
|
||||||
|
}
|
||||||
else if (kind === 'roof' && unitById(a)) unitById(a).roof.deck = activeEditor.getStateJson();
|
else if (kind === 'roof' && unitById(a)) unitById(a).roof.deck = activeEditor.getStateJson();
|
||||||
else if (kind === 'iw' && iwById(Number(a))) {
|
else if (kind === 'iw' && iwById(Number(a))) {
|
||||||
const iw = iwById(Number(a));
|
const iw = iwById(Number(a));
|
||||||
|
|||||||
@@ -256,13 +256,23 @@ window.LightingModel = (() => {
|
|||||||
const T = {};
|
const T = {};
|
||||||
u.walls.forEach(w => { T[w.side] = w.removed ? 0 : w.thickness; });
|
u.walls.forEach(w => { T[w.side] = w.removed ? 0 : w.thickness; });
|
||||||
const r = u.rect, off = s.indoorOffset;
|
const r = u.rect, off = s.indoorOffset;
|
||||||
|
// Naadwanddelen van deze unit staan binnen de rect tegen de naad: het
|
||||||
|
// raster begint dan vanaf hun binnenzijde (net als bij een buitenwand).
|
||||||
|
const own = interior.filter(iw => !iw.seamUnit || iw.seamUnit === u.id);
|
||||||
|
own.filter(iw => iw.seamUnit).forEach(iw => {
|
||||||
|
const p = iw.place, horiz = Math.abs(Math.sin(p.rotY)) < 0.5;
|
||||||
|
const [side, d] = horiz
|
||||||
|
? (Math.abs(p.cz - r.z0) < Math.abs(p.cz - r.z1) ? ['voor', p.cz - r.z0] : ['achter', r.z1 - p.cz])
|
||||||
|
: (Math.abs(p.cx - r.x0) < Math.abs(p.cx - r.x1) ? ['links', p.cx - r.x0] : ['rechts', r.x1 - p.cx]);
|
||||||
|
T[side] = Math.max(T[side] || 0, d + p.thickness / 2);
|
||||||
|
});
|
||||||
const xs = gridPositions(r.x0 + (T.links || 0) + off, r.x1 - (T.rechts || 0) - off, s.indoorMin, s.indoorMax);
|
const xs = gridPositions(r.x0 + (T.links || 0) + off, r.x1 - (T.rechts || 0) - off, s.indoorMin, s.indoorMax);
|
||||||
const zs = gridPositions(r.z0 + (T.voor || 0) + off, r.z1 - (T.achter || 0) - off, s.indoorMin, s.indoorMax);
|
const zs = gridPositions(r.z0 + (T.voor || 0) + off, r.z1 - (T.achter || 0) - off, s.indoorMin, s.indoorMax);
|
||||||
xs.forEach(x => zs.forEach(z => {
|
xs.forEach(x => zs.forEach(z => {
|
||||||
const tooClose = interior.some(iw => {
|
const tooClose = own.some(iw => {
|
||||||
const half = iw.place.width / 2, c = Math.cos(iw.place.rotY), sn = Math.sin(iw.place.rotY);
|
const half = iw.place.width / 2, c = Math.cos(iw.place.rotY), sn = Math.sin(iw.place.rotY);
|
||||||
const a = { x: iw.place.cx - half * c, z: iw.place.cz + half * sn }, b = { x: iw.place.cx + half * c, z: iw.place.cz - half * sn };
|
const a = { x: iw.place.cx - half * c, z: iw.place.cz + half * sn }, b = { x: iw.place.cx + half * c, z: iw.place.cz - half * sn };
|
||||||
return distToSegment({ x, z }, a, b) < iw.place.thickness / 2 + off;
|
return distToSegment({ x, z }, a, b) < iw.place.thickness / 2 + off - 1;
|
||||||
});
|
});
|
||||||
if (!tooClose) out.push({ unitId: u.id, x, z, y: u.roof.base, floorY: u.layout.floorTop });
|
if (!tooClose) out.push({ unitId: u.id, x, z, y: u.roof.base, floorY: u.layout.floorTop });
|
||||||
}));
|
}));
|
||||||
|
|||||||
+44
-3
@@ -174,7 +174,12 @@ window.UnitModel = (() => {
|
|||||||
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;
|
||||||
const heightAt = (u) => hL + (u / W) * (hR - hL);
|
// Middenpunt (nok) midden op de wand; een stuk krijgt de nok alleen als
|
||||||
|
// die erin valt, anders de daklijn ter plaatse als gewone helling.
|
||||||
|
const hM = wallState.wall.heightMid > 0 ? Math.max(wallState.wall.heightMid, hL, hR) : null;
|
||||||
|
const pU = W / 2;
|
||||||
|
const heightAt = (u) => hM === null ? hL + (u / W) * (hR - hL)
|
||||||
|
: u <= pU ? hL + (u / pU) * (hM - hL) : hM + ((u - pU) / (W - pU)) * (hR - hM);
|
||||||
const multi = wallLayout.pieces.length > 1;
|
const multi = wallLayout.pieces.length > 1;
|
||||||
const used = new Set();
|
const used = new Set();
|
||||||
|
|
||||||
@@ -182,8 +187,10 @@ window.UnitModel = (() => {
|
|||||||
const openings = fullConfig.openings
|
const openings = fullConfig.openings
|
||||||
.filter(op => op.x >= a - EPS && op.x + op.w <= b + EPS)
|
.filter(op => op.x >= a - EPS && op.x + op.w <= b + EPS)
|
||||||
.map(op => { used.add(op.id); return { id: op.id, type: op.type, w: op.w, h: op.h, y: op.y, anchor: 'left', offset: op.x - a }; });
|
.map(op => { used.add(op.id); return { id: op.id, type: op.type, w: op.w, h: op.h, y: op.y, anchor: 'left', offset: op.x - a }; });
|
||||||
|
const hasPeak = hM !== null && a < pU - 1 && b > pU + 1;
|
||||||
const params = Object.assign({}, wallState.wall, {
|
const params = Object.assign({}, wallState.wall, {
|
||||||
width: b - a, heightLeft: Math.round(heightAt(a)), heightRight: Math.round(heightAt(b))
|
width: b - a, heightLeft: Math.round(heightAt(a)), heightRight: Math.round(heightAt(b)),
|
||||||
|
heightMid: hasPeak ? hM : null, peakX: hasPeak ? pU - a : undefined
|
||||||
});
|
});
|
||||||
const config = WallModel.buildConfig(params, openings);
|
const config = WallModel.buildConfig(params, openings);
|
||||||
return {
|
return {
|
||||||
@@ -197,6 +204,40 @@ window.UnitModel = (() => {
|
|||||||
return { pieces, dropped, fullConfig };
|
return { pieces, dropped, fullConfig };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wandhoogtes koppelen, zodat er altijd een logisch dak op past. De
|
||||||
|
// bewerkte wand (side) bepaalt de dakvorm:
|
||||||
|
// - de tegenoverliggende wand krijgt dezelfde hoogtes gespiegeld (van
|
||||||
|
// buitenaf gezien wisselen links en rechts), incl. middenpunt (nok);
|
||||||
|
// - de twee haakse wanden worden vlak op de hoogte van de hoek waar ze
|
||||||
|
// op aansluiten (goothoogte bij een lessenaar- of zadeldak).
|
||||||
|
// Voorbeeld: west zuid hoger dan noord -> oost ook, noordwand vlak op de
|
||||||
|
// lage, zuidwand vlak op de hoge hoogte. Muteert state.
|
||||||
|
// Welke haakse wand zit aan de linker/rechterkant (van buitenaf gezien).
|
||||||
|
const WALL_ENDS = {
|
||||||
|
voor: { left: 'rechts', right: 'links' },
|
||||||
|
achter: { left: 'links', right: 'rechts' },
|
||||||
|
links: { left: 'voor', right: 'achter' },
|
||||||
|
rechts: { left: 'achter', right: 'voor' }
|
||||||
|
};
|
||||||
|
const OPPOSITE = { voor: 'achter', achter: 'voor', links: 'rechts', rechts: 'links' };
|
||||||
|
function syncWallHeights(state, side) {
|
||||||
|
const w = state.walls[side].wall;
|
||||||
|
const mid = w.heightMid > 0 ? w.heightMid : null;
|
||||||
|
const set = (key, hL, hR, hM) => {
|
||||||
|
const t = state.walls[key].wall;
|
||||||
|
t.heightLeft = hL; t.heightRight = hR; t.heightMid = hM;
|
||||||
|
t.heightAll = hM === null && hL === hR ? hL : w.heightAll;
|
||||||
|
};
|
||||||
|
set(OPPOSITE[side], w.heightRight, w.heightLeft, mid);
|
||||||
|
set(WALL_ENDS[side].left, w.heightLeft, w.heightLeft, null);
|
||||||
|
set(WALL_ENDS[side].right, w.heightRight, w.heightRight, null);
|
||||||
|
}
|
||||||
|
// Hoogste punt van een wand (incl. nok).
|
||||||
|
function wallTopOf(wallState) {
|
||||||
|
const w = wallState.wall;
|
||||||
|
return Math.max(w.heightLeft, w.heightRight, w.heightMid > 0 ? w.heightMid : 0);
|
||||||
|
}
|
||||||
|
|
||||||
function validateState(design) {
|
function validateState(design) {
|
||||||
if (!design.floor) throw new Error('veld "floor" ontbreekt');
|
if (!design.floor) throw new Error('veld "floor" ontbreekt');
|
||||||
try { FloorModel.validateState(design.floor); } catch (err) { throw new Error('vloer: ' + err.message); }
|
try { FloorModel.validateState(design.floor); } catch (err) { throw new Error('vloer: ' + err.message); }
|
||||||
@@ -210,5 +251,5 @@ window.UnitModel = (() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { WALL_SIDES, CORNER_MODES, clone, createDefaultState, localToWorld, computeLayout, wallPieces, validateState };
|
return { WALL_SIDES, CORNER_MODES, clone, createDefaultState, localToWorld, computeLayout, wallPieces, syncWallHeights, wallTopOf, validateState };
|
||||||
})();
|
})();
|
||||||
|
|||||||
+153
-83
@@ -14,7 +14,7 @@
|
|||||||
// voor zowel de losse wand als de unit gelden.
|
// voor zowel de losse wand als de unit gelden.
|
||||||
//
|
//
|
||||||
// Werkt met een "wand-state" (zelfde formaat als getStateJson/JSON-export):
|
// 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}] }
|
// materials: {interior, exterior}, openings: [{id,w,h,y,anchor,offset}] }
|
||||||
//
|
//
|
||||||
// Plain script (geen ES-module), moet na core/geometry.js geladen worden.
|
// 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).
|
// deel van de andere unit aan staat (HouseModel, naadwanden).
|
||||||
const EXTERIOR_MATERIALS = { vochtdoek: 'Vochtdoek', cementplaat: 'Cement-gebonden platen', spaanplaat: 'Spaanplaat', geen: 'Geen (naadzijde)' };
|
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
|
// Heuristiek (geen volwaardige constraint-solver): elke opening wordt eerst
|
||||||
// individueel gepositioneerd op basis van zijn anker, dan van links naar
|
// individueel gepositioneerd op basis van zijn anker, dan van links naar
|
||||||
// rechts gesorteerd en tegen elkaar aan geduwd als er overlap is, en tot
|
// 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
|
// 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
|
// dicht bij de hoge kant dus hoger/groter zijn dan een opening dicht bij
|
||||||
// de lage kant.
|
// 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
|
// 2850mm is de vaste referentie-bovengrens (bijv. max. sparingbreedte in
|
||||||
// dit constructiesysteem); daarnaast blijft de wand zelf ook een harde
|
// dit constructiesysteem); daarnaast blijft de wand zelf ook een harde
|
||||||
// grens (er moet nog stijlruimte overblijven).
|
// grens (er moet nog stijlruimte overblijven).
|
||||||
@@ -179,8 +255,9 @@ window.WallModel = (() => {
|
|||||||
// die opening — het punt waar een verticale lijn op die plek de
|
// die opening — het punt waar een verticale lijn op die plek de
|
||||||
// (eventueel hellende) horizontale topbalk zou snijden. Bij een
|
// (eventueel hellende) horizontale topbalk zou snijden. Bij een
|
||||||
// opening die zelf een breedte heeft, wordt het laagste van de twee
|
// opening die zelf een breedte heeft, wordt het laagste van de twee
|
||||||
// uiteinden aangehouden (de daklijn is lineair, dus dat is voldoende).
|
// uiteinden aangehouden (de daklijn is lineair of heeft een nok als
|
||||||
const localRoofHeightAt = (xLocal) => wallHeightLeft + (xLocal / wallWidth) * (wallHeightRight - wallHeightLeft);
|
// hoogste punt, dus dat is voldoende).
|
||||||
|
const localRoofHeightAt = roofAt;
|
||||||
|
|
||||||
resolved.forEach(o => {
|
resolved.forEach(o => {
|
||||||
const localCeiling = Math.min(localRoofHeightAt(o.x), localRoofHeightAt(o.x + o.w));
|
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 hRight = Math.round(params.heightRight || 2900);
|
||||||
const studWidth = Math.round(params.studWidth || STANDARD_BEAM.width);
|
const studWidth = Math.round(params.studWidth || STANDARD_BEAM.width);
|
||||||
const plateThick = Math.round(params.plateThick || 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 {
|
return {
|
||||||
wallWidth,
|
wallWidth,
|
||||||
wallHeightLeft: hLeft,
|
wallHeightLeft: hLeft,
|
||||||
wallHeightRight: hRight,
|
wallHeightRight: hRight,
|
||||||
|
wallPeak,
|
||||||
studWidth,
|
studWidth,
|
||||||
studDepth: Math.round(params.studDepth || STANDARD_BEAM.depth),
|
studDepth: Math.round(params.studDepth || STANDARD_BEAM.depth),
|
||||||
plateThick,
|
plateThick,
|
||||||
@@ -228,7 +318,7 @@ window.WallModel = (() => {
|
|||||||
sheathingThick: Math.round(params.sheathingThick || 10),
|
sheathingThick: Math.round(params.sheathingThick || 10),
|
||||||
minSpacing: Math.round(params.minSpacing || 240),
|
minSpacing: Math.round(params.minSpacing || 240),
|
||||||
maxSpacing: Math.round(params.maxSpacing || 480),
|
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.
|
// Regelwerk: volledige stijlen (incl. koningsstijlen) + kreupele stijlen.
|
||||||
function computeFrame(CONFIG) {
|
function computeFrame(CONFIG) {
|
||||||
const startX = -CONFIG.wallWidth / 2;
|
const startX = -CONFIG.wallWidth / 2;
|
||||||
const slope = (CONFIG.wallHeightRight - CONFIG.wallHeightLeft) / CONFIG.wallWidth;
|
const top = topLine(CONFIG);
|
||||||
const angleDeg = Math.round(Math.atan(slope) * (180 / Math.PI));
|
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
|
// Dubbele hoekstijlen: de buitenste staat op de wandrand, de tweede er
|
||||||
// direct tegenaan. De regelmatige verdeling begint bij de binnenste.
|
// 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).
|
// Gebruikt door build3D (fill) én de 2D-tekening (naad-/baanlijnen).
|
||||||
function sheathingHoles(CONFIG) {
|
function sheathingHoles(CONFIG) {
|
||||||
const halfW = CONFIG.wallWidth / 2;
|
const halfW = CONFIG.wallWidth / 2;
|
||||||
const slope = (CONFIG.wallHeightRight - CONFIG.wallHeightLeft) / CONFIG.wallWidth;
|
const topYAt = topLine(CONFIG).at;
|
||||||
const topYAt = (x) => CONFIG.wallHeightLeft + (x + halfW) * slope;
|
|
||||||
const roofSafetyMargin = 10;
|
const roofSafetyMargin = 10;
|
||||||
return CONFIG.openings.map(op => {
|
return CONFIG.openings.map(op => {
|
||||||
const hx0 = -halfW + op.x - CONFIG.studWidth;
|
const hx0 = -halfW + op.x - CONFIG.studWidth;
|
||||||
@@ -336,16 +429,11 @@ window.WallModel = (() => {
|
|||||||
// liggen: binnen de wand-omtrek (incl. schuine top) en buiten de
|
// liggen: binnen de wand-omtrek (incl. schuine top) en buiten de
|
||||||
// uitsparingen. Geeft [[x0, x1], ...] terug (mm, t.o.v. het wandmidden).
|
// uitsparingen. Geeft [[x0, x1], ...] terug (mm, t.o.v. het wandmidden).
|
||||||
function skinSegmentsAtY(CONFIG, y) {
|
function skinSegmentsAtY(CONFIG, y) {
|
||||||
const halfW = CONFIG.wallWidth / 2;
|
if (y <= 0) return [];
|
||||||
const hL = CONFIG.wallHeightLeft, hR = CONFIG.wallHeightRight;
|
|
||||||
if (y <= 0 || y >= Math.max(hL, hR)) return [];
|
|
||||||
// Deel van de breedte waar de daklijn boven y ligt.
|
// Deel van de breedte waar de daklijn boven y ligt.
|
||||||
let x0 = -halfW, x1 = halfW;
|
const range = topLine(CONFIG).rangeAbove(y);
|
||||||
if (hL !== hR) {
|
if (!range) return [];
|
||||||
const xAtY = -halfW + (y - hL) / (hR - hL) * CONFIG.wallWidth;
|
let segments = [range];
|
||||||
if (hR > hL) x0 = Math.max(x0, xAtY); else x1 = Math.min(x1, xAtY);
|
|
||||||
} else if (y >= hL) return [];
|
|
||||||
let segments = [[x0, x1]];
|
|
||||||
sheathingHoles(CONFIG).filter(h => y > h.hy0 && y < h.hy1).forEach(h => {
|
sheathingHoles(CONFIG).filter(h => y > h.hy0 && y < h.hy1).forEach(h => {
|
||||||
segments = segments.flatMap(([a, b]) => {
|
segments = segments.flatMap(([a, b]) => {
|
||||||
if (h.hx1 <= a || h.hx0 >= b) return [[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 gridX0 = facade ? -CONFIG.wallWidth / 2 - facade.u0 - (facade.extStart ? 2 * BATTEN.thick : 0) : xL;
|
||||||
const isEnd = facade ? facade.isEnd : true;
|
const isEnd = facade ? facade.isEnd : true;
|
||||||
const halfW = CONFIG.wallWidth / 2;
|
const halfW = CONFIG.wallWidth / 2;
|
||||||
const hL = CONFIG.wallHeightLeft, hR = CONFIG.wallHeightRight;
|
const tl = topLine(CONFIG), topYAt = tl.at;
|
||||||
const topYAt = (x) => hL + (x + halfW) * (hR - hL) / CONFIG.wallWidth;
|
|
||||||
const holes = sheathingHoles(CONFIG);
|
const holes = sheathingHoles(CONFIG);
|
||||||
const w = BATTEN.width, sp = BATTEN.spacing;
|
const w = BATTEN.width, sp = BATTEN.spacing;
|
||||||
const top = (cladding === 'verticaal') ? 'horizontaal' : 'verticaal';
|
const top = (cladding === 'verticaal') ? 'horizontaal' : 'verticaal';
|
||||||
|
|
||||||
// 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 < 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]) => {
|
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;
|
||||||
@@ -551,10 +638,11 @@ window.WallModel = (() => {
|
|||||||
const hRightM = CONFIG.wallHeightRight * toM;
|
const hRightM = CONFIG.wallHeightRight * toM;
|
||||||
const halfW = wWidthM / 2;
|
const halfW = wWidthM / 2;
|
||||||
|
|
||||||
const slope = (hRightM - hLeftM) / wWidthM;
|
const tl = topLine(CONFIG, toM);
|
||||||
const vertOffset = pThickM * Math.sqrt(1 + slope * slope);
|
|
||||||
const startXM = -halfW;
|
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
|
// Openingen naar meter-coordinaten. LET OP: xMin/xMax hier zijn al naar
|
||||||
// wereld-coordinaten verschoven (dezelfde as als de rest van de 3D-opbouw,
|
// 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
|
// B. TOPBALK WAND (ongewijzigd door openingen) — dit is zelf ook een
|
||||||
// "horizontale balk" (liggend, ook al loopt hij mee met de dakhelling).
|
// "horizontale balk" (liggend, ook al loopt hij mee met de dakhelling).
|
||||||
const topPlateShape = new THREE.Shape();
|
// Met nok: twee bovenregels die in verstek op de nok samenkomen.
|
||||||
topPlateShape.moveTo(-halfW, hLeftM);
|
topPlateOutlines(tl).forEach(pts => {
|
||||||
topPlateShape.lineTo(halfW, hRightM);
|
wallGroup3D.add(createExtrudedBeam(new THREE.Shape(pts.map(p => new THREE.Vector2(p.x, p.y))), pDepthM, -pDepthM / 2));
|
||||||
topPlateShape.lineTo(halfW, hRightM - vertOffset);
|
|
||||||
topPlateShape.lineTo(-halfW, hLeftM - vertOffset);
|
|
||||||
topPlateShape.closePath();
|
|
||||||
wallGroup3D.add(createExtrudedBeam(topPlateShape, pDepthM, -pDepthM / 2));
|
|
||||||
horizBeamCount++;
|
horizBeamCount++;
|
||||||
|
});
|
||||||
|
|
||||||
// C. VOLLEDIGE VERTICALE STIJLEN (incl. de "koningsstijlen" naast elke opening)
|
// C. VOLLEDIGE VERTICALE STIJLEN (incl. de "koningsstijlen" naast elke opening)
|
||||||
calcData.fullStuds.forEach((xPosMM) => {
|
calcData.fullStuds.forEach((xPosMM) => {
|
||||||
const xM = xPosMM * toM;
|
const xM = xPosMM * toM;
|
||||||
const xLeft = xM - (sWidthM / 2);
|
const xLeft = xM - (sWidthM / 2);
|
||||||
const xRight = xM + (sWidthM / 2);
|
const xRight = xM + (sWidthM / 2);
|
||||||
|
const pts = [{ x: xLeft, y: pThickM }, { x: xRight, y: pThickM }, ...underPts(xLeft, xRight).reverse()];
|
||||||
const yTopLeft = (hLeftM + (xLeft + halfW) * slope) - vertOffset;
|
wallGroup3D.add(createExtrudedBeam(new THREE.Shape(pts.map(p => new THREE.Vector2(p.x, p.y))), sDepthM, -sDepthM / 2));
|
||||||
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));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// D. KREUPELE STIJLEN & DORPELS, per opening
|
// D. KREUPELE STIJLEN & DORPELS, per opening
|
||||||
@@ -638,9 +714,8 @@ window.WallModel = (() => {
|
|||||||
const xM = j.x * toM;
|
const xM = j.x * toM;
|
||||||
const xLeft = xM - (sWidthM / 2);
|
const xLeft = xM - (sWidthM / 2);
|
||||||
const xRight = xM + (sWidthM / 2);
|
const xRight = xM + (sWidthM / 2);
|
||||||
|
const topPts = underPts(xLeft, xRight);
|
||||||
const yTopLeft = (hLeftM + (xLeft + halfW) * slope) - vertOffset;
|
const yTopLeft = topPts[0].y, yTopRight = topPts[topPts.length - 1].y;
|
||||||
const yTopRight = (hLeftM + (xRight + halfW) * slope) - vertOffset;
|
|
||||||
|
|
||||||
// Onderste kreupele stijl mag niet door de onderdorpel heen lopen
|
// Onderste kreupele stijl mag niet door de onderdorpel heen lopen
|
||||||
// (die neemt zelf de band net onder op.yMin in beslag).
|
// (die neemt zelf de band net onder op.yMin in beslag).
|
||||||
@@ -657,13 +732,8 @@ window.WallModel = (() => {
|
|||||||
|
|
||||||
const actualOpTop = op.yMax + sWidthM;
|
const actualOpTop = op.yMax + sWidthM;
|
||||||
if (actualOpTop < yTopLeft || actualOpTop < yTopRight) {
|
if (actualOpTop < yTopLeft || actualOpTop < yTopRight) {
|
||||||
const studTopShape = new THREE.Shape();
|
const pts = [{ x: xLeft, y: actualOpTop }, { x: xRight, y: actualOpTop }, ...topPts.slice().reverse()];
|
||||||
studTopShape.moveTo(xLeft, actualOpTop);
|
wallGroup3D.add(createExtrudedBeam(new THREE.Shape(pts.map(p => new THREE.Vector2(p.x, p.y))), sDepthM, -sDepthM / 2));
|
||||||
studTopShape.lineTo(xRight, actualOpTop);
|
|
||||||
studTopShape.lineTo(xRight, yTopRight);
|
|
||||||
studTopShape.lineTo(xLeft, yTopLeft);
|
|
||||||
studTopShape.closePath();
|
|
||||||
wallGroup3D.add(createExtrudedBeam(studTopShape, sDepthM, -sDepthM / 2));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -781,7 +851,8 @@ window.WallModel = (() => {
|
|||||||
// over een plaatnaad wordt een inkeping in de plaatrand, zodat er
|
// over een plaatnaad wordt een inkeping in de plaatrand, zodat er
|
||||||
// geen reepje plaat (met randlijnen) door de opening blijft lopen.
|
// geen reepje plaat (met randlijnen) door de opening blijft lopen.
|
||||||
const pieces = columnMinusRects(ex0, ex1, yB, skinTopAt,
|
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;
|
let boardShapes;
|
||||||
if (pieces) {
|
if (pieces) {
|
||||||
boardShapes = pieces.map(piece => {
|
boardShapes = pieces.map(piece => {
|
||||||
@@ -796,6 +867,7 @@ window.WallModel = (() => {
|
|||||||
boardShape.moveTo(ex0, yB);
|
boardShape.moveTo(ex0, yB);
|
||||||
boardShape.lineTo(ex1, yB);
|
boardShape.lineTo(ex1, yB);
|
||||||
boardShape.lineTo(ex1, skinTopAt(ex1));
|
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.lineTo(ex0, skinTopAt(ex0));
|
||||||
boardShape.closePath();
|
boardShape.closePath();
|
||||||
holeExtents.forEach(h => {
|
holeExtents.forEach(h => {
|
||||||
@@ -892,11 +964,8 @@ window.WallModel = (() => {
|
|||||||
// (wat flikkering/z-fighting zou geven nu de platen niet meer
|
// (wat flikkering/z-fighting zou geven nu de platen niet meer
|
||||||
// transparant zijn).
|
// transparant zijn).
|
||||||
const doekZStepM = 0.0002; // 0.2 mm per baan, verwaarloosbaar t.o.v. de spouwdiepte
|
const doekZStepM = 0.0002; // 0.2 mm per baan, verwaarloosbaar t.o.v. de spouwdiepte
|
||||||
const wallOutlinePts = [
|
const wallOutlinePts = [{ x: -halfW, y: 0 }, { x: halfW, y: 0 }, ...tl.pts.slice().reverse()];
|
||||||
{ x: -halfW, y: 0 }, { x: halfW, y: 0 },
|
const maxRoofY = tl.maxY;
|
||||||
{ x: halfW, y: hRightM }, { x: -halfW, y: hLeftM }
|
|
||||||
];
|
|
||||||
const maxRoofY = Math.max(hLeftM, hRightM);
|
|
||||||
|
|
||||||
let rowY0 = 0;
|
let rowY0 = 0;
|
||||||
let rowIndex = 0;
|
let rowIndex = 0;
|
||||||
@@ -906,15 +975,9 @@ window.WallModel = (() => {
|
|||||||
// Baan = strook [rowY0, min(rowY1, daklijn)] min uitsparingen
|
// Baan = strook [rowY0, min(rowY1, daklijn)] min uitsparingen
|
||||||
// (columnMinusRects, zelfde als de platen): een opening over de
|
// (columnMinusRects, zelfde als de platen): een opening over de
|
||||||
// baanrand wordt een inkeping, geen reepje doek door de opening.
|
// baanrand wordt een inkeping, geen reepje doek door de opening.
|
||||||
const roofAt = (x) => hLeftM + (x + halfW) * slope;
|
const roofAt = topYAt;
|
||||||
let rx0 = -halfW, rx1 = halfW;
|
const [rx0, rx1] = tl.rangeAbove(rowY0) || [0, 0];
|
||||||
const extraXs = [];
|
const extraXs = tl.crossings(rowY1).concat(tl.kinks);
|
||||||
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 pieces = rx1 - rx0 > edgeEps ? columnMinusRects(rx0, rx1, rowY0, (x) => Math.min(rowY1, roofAt(x)),
|
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 }) : [];
|
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
|
// Omhullende van de wand (trapezium, incl. schuine top) als referentie
|
||||||
// voor de leegte-berekening — spouwdiepte wordt gelijkgesteld aan de
|
// voor de leegte-berekening — spouwdiepte wordt gelijkgesteld aan de
|
||||||
// stijldiepte (vereenvoudiging, zie toelichting in de UI).
|
// stijldiepte (vereenvoudiging, zie toelichting in de UI).
|
||||||
const envelopePoints = [
|
const envelopePoints = [{ x: -halfW, y: 0 }, { x: halfW, y: 0 }, ...topLine(CONFIG, toM).pts.slice().reverse()];
|
||||||
{ x: -halfW, y: 0 }, { x: halfW, y: 0 },
|
|
||||||
{ x: halfW, y: hRightM }, { x: -halfW, y: hLeftM }
|
|
||||||
];
|
|
||||||
const envelopeAreaM2 = polygonArea(envelopePoints);
|
const envelopeAreaM2 = polygonArea(envelopePoints);
|
||||||
const envelopeVolumeM3 = envelopeAreaM2 * sDepthM;
|
const envelopeVolumeM3 = envelopeAreaM2 * sDepthM;
|
||||||
|
|
||||||
@@ -1088,9 +1148,7 @@ window.WallModel = (() => {
|
|||||||
function computeBOM(CONFIG, calcData) {
|
function computeBOM(CONFIG, calcData) {
|
||||||
const wallWidth = CONFIG.wallWidth, hLeft = CONFIG.wallHeightLeft, hRight = CONFIG.wallHeightRight;
|
const wallWidth = CONFIG.wallWidth, hLeft = CONFIG.wallHeightLeft, hRight = CONFIG.wallHeightRight;
|
||||||
const halfW = wallWidth / 2;
|
const halfW = wallWidth / 2;
|
||||||
const slope = (hRight - hLeft) / wallWidth;
|
const tl = topLine(CONFIG);
|
||||||
const topYAt = (xWorld) => hLeft + (xWorld + halfW) * slope;
|
|
||||||
const vertOffset = CONFIG.plateThick * Math.sqrt(1 + slope * slope);
|
|
||||||
const startX = -halfW;
|
const startX = -halfW;
|
||||||
|
|
||||||
const pieces = [];
|
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 });
|
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));
|
// Bovenregel(s): met nok twee stukken (verstek). outline = werkelijke
|
||||||
pieces.push({ length: topLength, width: CONFIG.plateThick, depth: CONFIG.plateDepth, kind: 'bovenregel', x: 0, y: (hLeft + hRight) / 2 });
|
// 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 —
|
// Verticale stukken krijgen, naast de bounding-box velden (length/y —
|
||||||
// gebruikt voor de materiaallijst: de lengte van het rechte stuk hout
|
// 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
|
// 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
|
// de hoogste kant van de balk goed op de daklijn te staan, en blijft de
|
||||||
// laagste kant er (visueel) los van.
|
// laagste kant er (visueel) los van.
|
||||||
|
// (outline: ook de knik onder de nok, als de stijl daar staat.)
|
||||||
calcData.fullStuds.forEach(x => {
|
calcData.fullStuds.forEach(x => {
|
||||||
const xLeft = x - CONFIG.studWidth / 2, xRight = x + CONFIG.studWidth / 2;
|
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 yBottom = CONFIG.plateThick;
|
||||||
const length = Math.max(yTopLeft, yTopRight) - yBottom;
|
const length = Math.max(...topPts.map(p => p.y)) - yBottom;
|
||||||
pieces.push({
|
pieces.push({
|
||||||
length, width: CONFIG.studWidth, depth: CONFIG.studDepth, kind: 'verticale balk', x,
|
length, width: CONFIG.studWidth, depth: CONFIG.studDepth, kind: 'verticale balk', x,
|
||||||
y: (yBottom + yBottom + yTopLeft + yTopRight) / 4,
|
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 op = j.opening;
|
||||||
const opYMin = op.y, opYMax = op.y + op.h;
|
const opYMin = op.y, opYMax = op.y + op.h;
|
||||||
const xLeft = j.x - CONFIG.studWidth / 2, xRight = j.x + CONFIG.studWidth / 2;
|
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
|
// De kreupele stijl mag niet door de onderdorpel heen lopen — die
|
||||||
// neemt zelf al de onderste stijldikte in vanaf opYMin in beslag.
|
// neemt zelf al de onderste stijldikte in vanaf opYMin in beslag.
|
||||||
const sillBottom = opYMin - CONFIG.studWidth;
|
const sillBottom = opYMin - CONFIG.studWidth;
|
||||||
@@ -1150,9 +1219,10 @@ window.WallModel = (() => {
|
|||||||
const actualOpTop = opYMax + CONFIG.studWidth;
|
const actualOpTop = opYMax + CONFIG.studWidth;
|
||||||
if (actualOpTop < yTopLeft || actualOpTop < yTopRight) {
|
if (actualOpTop < yTopLeft || actualOpTop < yTopRight) {
|
||||||
pieces.push({
|
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,
|
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 {
|
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, computeBattens, planPurchase, purchaseText,
|
||||||
sheathingHoles, skinSegmentsAtY,
|
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
|
||||||
};
|
};
|
||||||
|
|||||||
+17
-22
@@ -67,7 +67,7 @@ window.WallPdf = (() => {
|
|||||||
|
|
||||||
// --- Materiaal binnen/buiten + isolatie ---
|
// --- Materiaal binnen/buiten + isolatie ---
|
||||||
let my = 240;
|
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.setFontSize(8.5);
|
||||||
doc.text('Materiaal binnen', tblX[0], my); my += 4.5;
|
doc.text('Materiaal binnen', tblX[0], my); my += 4.5;
|
||||||
doc.setFontSize(6.5);
|
doc.setFontSize(6.5);
|
||||||
@@ -103,7 +103,8 @@ window.WallPdf = (() => {
|
|||||||
const availW = (drawX1 - drawX0) - marginSides * 2;
|
const availW = (drawX1 - drawX0) - marginSides * 2;
|
||||||
const availH = (drawY1 - drawY0) - marginTop - marginBottom;
|
const availH = (drawY1 - drawY0) - marginTop - marginBottom;
|
||||||
const wallWidth = CONFIG.wallWidth, hLeft = CONFIG.wallHeightLeft, hRight = CONFIG.wallHeightRight;
|
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 scale = Math.min(availW / wallWidth, availH / maxH);
|
||||||
|
|
||||||
const originX = drawX0 + marginSides;
|
const originX = drawX0 + marginSides;
|
||||||
@@ -115,15 +116,18 @@ window.WallPdf = (() => {
|
|||||||
doc.setLineWidth(0.25);
|
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(0));
|
||||||
doc.line(px(wallWidth / 2), py(0), px(wallWidth / 2), py(hRight));
|
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.line(px(-wallWidth / 2), py(hLeft), px(-wallWidth / 2), py(0));
|
||||||
|
|
||||||
doc.setDrawColor(...RED);
|
doc.setDrawColor(...RED);
|
||||||
doc.setLineWidth(0.3);
|
doc.setLineWidth(0.3);
|
||||||
pieces.forEach(p => {
|
pieces.forEach(p => {
|
||||||
if (p.kind === 'bovenregel') return; // apart getekend hieronder, volgt de helling
|
if (p.outline) {
|
||||||
const isVertical = p.kind === 'verticale balk' || p.kind === 'kreupele stijl';
|
// Werkelijke omtrek: schuine kop (volgt de daklijn, evt. met knik
|
||||||
if (isVertical) {
|
// 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
|
// Viervlak i.p.v. rechthoek: de bovenkant loopt schuin mee met de
|
||||||
// daklijn (yTopLeft/yTopRight kunnen verschillen), zodat zowel de
|
// daklijn (yTopLeft/yTopRight kunnen verschillen), zodat zowel de
|
||||||
// hoogste als de laagste kant van de balk op de juiste plek komt.
|
// 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' });
|
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.setDrawColor(...BLACK);
|
||||||
doc.setLineWidth(0.2);
|
doc.setLineWidth(0.2);
|
||||||
CONFIG.openings.forEach(op => {
|
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 });
|
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));
|
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 });
|
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]);
|
const studEdges = new Set([0, wallWidth]);
|
||||||
calcData.fullStuds.forEach(x => studEdges.add(Math.round(x - CONFIG.studWidth / 2 + wallWidth / 2)));
|
calcData.fullStuds.forEach(x => studEdges.add(Math.round(x - CONFIG.studWidth / 2 + wallWidth / 2)));
|
||||||
|
|||||||
+42
-17
@@ -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><input type="number" id="inp-wall-h-all" value="2400" step="50" min="500" max="5000"><span class="unit">mm</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<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><input type="number" id="inp-wall-h-left" value="2400" step="50" min="500" max="5000"><span class="unit">mm</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<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><input type="number" id="inp-wall-h-right" value="2400" step="50" min="500" max="5000"><span class="unit">mm</span></div>
|
||||||
</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>
|
<div class="divider"></div>
|
||||||
<h1><i class="bi bi-columns-gap"></i> Stijlen</h1>
|
<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),
|
heightAll: parseFloat(document.getElementById('inp-wall-h-all').value),
|
||||||
heightLeft: parseFloat(document.getElementById('inp-wall-h-left').value),
|
heightLeft: parseFloat(document.getElementById('inp-wall-h-left').value),
|
||||||
heightRight: parseFloat(document.getElementById('inp-wall-h-right').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),
|
studWidth: parseFloat(document.getElementById('inp-stud-w').value),
|
||||||
studDepth: parseFloat(document.getElementById('inp-stud-d').value),
|
studDepth: parseFloat(document.getElementById('inp-stud-d').value),
|
||||||
plateThick: parseFloat(document.getElementById('inp-plate-w').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-all').value = state.wall.heightAll;
|
||||||
document.getElementById('inp-wall-h-left').value = state.wall.heightLeft;
|
document.getElementById('inp-wall-h-left').value = state.wall.heightLeft;
|
||||||
document.getElementById('inp-wall-h-right').value = state.wall.heightRight;
|
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-w').value = state.wall.studWidth;
|
||||||
document.getElementById('inp-stud-d').value = state.wall.studDepth;
|
document.getElementById('inp-stud-d').value = state.wall.studDepth;
|
||||||
document.getElementById('inp-plate-w').value = state.wall.plateThick;
|
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 hLeftInput = document.getElementById('inp-wall-h-left');
|
||||||
const hRightInput = document.getElementById('inp-wall-h-right');
|
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 hAll = Math.round(parseFloat(hAllInput.value) || 2400);
|
||||||
let hLeft = Math.round(parseFloat(hLeftInput.value) || 2400);
|
let hLeft = Math.round(parseFloat(hLeftInput.value) || 2400);
|
||||||
let hRight = Math.round(parseFloat(hRightInput.value) || 2900);
|
let hRight = Math.round(parseFloat(hRightInput.value) || 2900);
|
||||||
|
let hMid = Math.round(parseFloat(hMidInput.value) || 0);
|
||||||
|
|
||||||
if (hAll !== lastBaseHeight) {
|
if (hAll !== lastBaseHeight) {
|
||||||
const delta = hAll - lastBaseHeight;
|
const delta = hAll - lastBaseHeight;
|
||||||
hLeft = Math.max(500, hLeft + delta);
|
hLeft = Math.max(500, hLeft + delta);
|
||||||
hRight = Math.max(500, hRight + delta);
|
hRight = Math.max(500, hRight + delta);
|
||||||
|
hMid = Math.max(500, hMid + delta);
|
||||||
hLeftInput.value = hLeft;
|
hLeftInput.value = hLeft;
|
||||||
hRightInput.value = hRight;
|
hRightInput.value = hRight;
|
||||||
|
hMidInput.value = hMid;
|
||||||
lastBaseHeight = hAll;
|
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.
|
// Afronding/fallbacks + ankerresolutie van de openingen: WallModel.
|
||||||
CONFIG = WallModel.buildConfig({
|
CONFIG = WallModel.buildConfig({
|
||||||
width: wallWidth,
|
width: wallWidth,
|
||||||
heightLeft: hLeft,
|
heightLeft: hLeft,
|
||||||
heightRight: hRight,
|
heightRight: hRight,
|
||||||
|
heightMid: peakOn ? hMid : null,
|
||||||
studWidth: parseFloat(document.getElementById('inp-stud-w').value),
|
studWidth: parseFloat(document.getElementById('inp-stud-w').value),
|
||||||
studDepth: parseFloat(document.getElementById('inp-stud-d').value),
|
studDepth: parseFloat(document.getElementById('inp-stud-d').value),
|
||||||
plateThick: parseFloat(document.getElementById('inp-plate-w').value),
|
plateThick: parseFloat(document.getElementById('inp-plate-w').value),
|
||||||
@@ -827,10 +851,8 @@ function mountWallEditor(container, callbacks) {
|
|||||||
const hLeftMM = CONFIG.wallHeightLeft;
|
const hLeftMM = CONFIG.wallHeightLeft;
|
||||||
const hRightMM = CONFIG.wallHeightRight;
|
const hRightMM = CONFIG.wallHeightRight;
|
||||||
const halfWMM = wallWidthMM / 2;
|
const halfWMM = wallWidthMM / 2;
|
||||||
const slopeMM = (hRightMM - hLeftMM) / wallWidthMM;
|
const tl = WallModel.topLine(CONFIG);
|
||||||
const topYAtMM = (x) => hLeftMM + (x + halfWMM) * slopeMM;
|
const maxRoofMM = tl.maxY;
|
||||||
const vertOffsetMM = CONFIG.plateThick * Math.sqrt(1 + slopeMM * slopeMM);
|
|
||||||
const maxRoofMM = Math.max(hLeftMM, hRightMM);
|
|
||||||
|
|
||||||
const scale = Math.min(w / (wallWidthMM * 1.3), h / (maxRoofMM * 1.3));
|
const scale = Math.min(w / (wallWidthMM * 1.3), h / (maxRoofMM * 1.3));
|
||||||
const centerX = w / 2;
|
const centerX = w / 2;
|
||||||
@@ -882,8 +904,7 @@ function mountWallEditor(container, callbacks) {
|
|||||||
ctxFront.beginPath();
|
ctxFront.beginPath();
|
||||||
ctxFront.moveTo(toCanvasX(-halfWMM), toCanvasY(0));
|
ctxFront.moveTo(toCanvasX(-halfWMM), toCanvasY(0));
|
||||||
ctxFront.lineTo(toCanvasX(halfWMM), toCanvasY(0));
|
ctxFront.lineTo(toCanvasX(halfWMM), toCanvasY(0));
|
||||||
ctxFront.lineTo(toCanvasX(halfWMM), toCanvasY(hRightMM));
|
tl.pts.slice().reverse().forEach(p => ctxFront.lineTo(toCanvasX(p.x), toCanvasY(p.y)));
|
||||||
ctxFront.lineTo(toCanvasX(-halfWMM), toCanvasY(hLeftMM));
|
|
||||||
ctxFront.closePath();
|
ctxFront.closePath();
|
||||||
ctxFront.stroke();
|
ctxFront.stroke();
|
||||||
|
|
||||||
@@ -898,14 +919,13 @@ function mountWallEditor(container, callbacks) {
|
|||||||
ctxFront.fill();
|
ctxFront.fill();
|
||||||
ctxFront.stroke();
|
ctxFront.stroke();
|
||||||
|
|
||||||
|
WallModel.topPlateOutlines(tl).forEach(pts => {
|
||||||
ctxFront.beginPath();
|
ctxFront.beginPath();
|
||||||
ctxFront.moveTo(toCanvasX(-halfWMM), toCanvasY(hLeftMM));
|
pts.forEach((p, i) => (i ? ctxFront.lineTo : ctxFront.moveTo).call(ctxFront, toCanvasX(p.x), toCanvasY(p.y)));
|
||||||
ctxFront.lineTo(toCanvasX(halfWMM), toCanvasY(hRightMM));
|
|
||||||
ctxFront.lineTo(toCanvasX(halfWMM), toCanvasY(hRightMM - vertOffsetMM));
|
|
||||||
ctxFront.lineTo(toCanvasX(-halfWMM), toCanvasY(hLeftMM - vertOffsetMM));
|
|
||||||
ctxFront.closePath();
|
ctxFront.closePath();
|
||||||
ctxFront.fill();
|
ctxFront.fill();
|
||||||
ctxFront.stroke();
|
ctxFront.stroke();
|
||||||
|
});
|
||||||
|
|
||||||
// X/Y-assen (lijn + label), altijd binnen het canvas gehouden zodat er
|
// X/Y-assen (lijn + label), altijd binnen het canvas gehouden zodat er
|
||||||
// niks buiten het zichtbare venster valt. Stijl: core/canvas-labels.js.
|
// 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);
|
const yAxisRightX = Math.min(w - 10, toCanvasX(halfWMM) + 14);
|
||||||
drawCanvasDim(ctxFront, yAxisRightX, toCanvasY(0), yAxisRightX, toCanvasY(hRightMM),
|
drawCanvasDim(ctxFront, yAxisRightX, toCanvasY(0), yAxisRightX, toCanvasY(hRightMM),
|
||||||
'Y: ' + Math.round(hRightMM) + 'mm', Math.min(w - 4, yAxisRightX + 12), (toCanvasY(0) + toCanvasY(hRightMM)) / 2);
|
'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 drawStudFront = (xMM, isJack) => {
|
||||||
const xLeft = xMM - CONFIG.studWidth / 2;
|
const xLeft = xMM - CONFIG.studWidth / 2;
|
||||||
const xRight = xMM + CONFIG.studWidth / 2;
|
const xRight = xMM + CONFIG.studWidth / 2;
|
||||||
const yTopLeft = topYAtMM(xLeft) - vertOffsetMM;
|
|
||||||
const yTopRight = topYAtMM(xRight) - vertOffsetMM;
|
|
||||||
const yBot = CONFIG.plateThick;
|
const yBot = CONFIG.plateThick;
|
||||||
ctxFront.fillStyle = isJack ? '#a37c45' : '#c29b62';
|
ctxFront.fillStyle = isJack ? '#a37c45' : '#c29b62';
|
||||||
ctxFront.strokeStyle = '#8a6836';
|
ctxFront.strokeStyle = '#8a6836';
|
||||||
ctxFront.beginPath();
|
ctxFront.beginPath();
|
||||||
ctxFront.moveTo(toCanvasX(xLeft), toCanvasY(yBot));
|
ctxFront.moveTo(toCanvasX(xLeft), toCanvasY(yBot));
|
||||||
ctxFront.lineTo(toCanvasX(xRight), toCanvasY(yBot));
|
ctxFront.lineTo(toCanvasX(xRight), toCanvasY(yBot));
|
||||||
ctxFront.lineTo(toCanvasX(xRight), toCanvasY(yTopRight));
|
tl.underPts(xLeft, xRight).reverse().forEach(p => ctxFront.lineTo(toCanvasX(p.x), toCanvasY(p.y)));
|
||||||
ctxFront.lineTo(toCanvasX(xLeft), toCanvasY(yTopLeft));
|
|
||||||
ctxFront.closePath();
|
ctxFront.closePath();
|
||||||
ctxFront.fill();
|
ctxFront.fill();
|
||||||
ctxFront.stroke();
|
ctxFront.stroke();
|
||||||
@@ -1128,7 +1153,7 @@ function mountWallEditor(container, callbacks) {
|
|||||||
const buitenThick = CONFIG.sheathingThick;
|
const buitenThick = CONFIG.sheathingThick;
|
||||||
const spaanThick = CONFIG.sheathingThick;
|
const spaanThick = CONFIG.sheathingThick;
|
||||||
const totalDepth = buitenThick + CONFIG.studDepth + spaanThick;
|
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 scale = Math.min((w * 0.45) / totalDepth, (h * 0.8) / maxH);
|
||||||
const originX = w / 2 - (totalDepth * scale) / 2;
|
const originX = w / 2 - (totalDepth * scale) / 2;
|
||||||
|
|||||||
Reference in New Issue
Block a user