v0.5.2: elektra (meterkast 3×25 A, schakelaars met letters op hun lampen, wcd, apparaatgroepen incl. perilex, groepenverklaring met fasen/aardlek, leidingen naar de meterkast met strekkende meters per type, PDF-blad + plattegrond, verplaatsbaar door slepen, bouwfase Elektra met installatiebuizen in 3D); ribbon, bouwfasen, kozijnen, kostenmodel en eerdere ongecommitte wijzigingen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8cdfa21f46
commit
e1a688728c
+205
-67
@@ -64,7 +64,7 @@ window.HouseModel = (() => {
|
||||
// rabat = vochtdoek + kruislat + rabat (richting: cladding);
|
||||
// steen = cement-gebonden plaat + steenblok (stone = steencode, WallModel.STONE).
|
||||
// Het trasraam (eigen afwerking/steen) komt onder beide.
|
||||
facade: { type: 'rabat', stone: 'K490' },
|
||||
facade: { type: 'rabat', stone: 'K490', rabatColor: 'naturel' }, // rabatColor: WallModel.RABAT_COLORS
|
||||
cladding: 'horizontaal', // richting rabat, voorlopig voor alle buitenwanden
|
||||
// Trasraam: cement-gebonden plaat onderaan alle vochtdoek-gevels, hoogte
|
||||
// vanaf het beloopbare vloervlak (WallModel.trasraamFor).
|
||||
@@ -78,7 +78,8 @@ window.HouseModel = (() => {
|
||||
groundLevel: 0, // maaiveld: zoveel mm onder het loopvlak (bovenkant vloer); voor de 3D-omgeving
|
||||
floorFinish: 'visgraat_001', // vloerafwerking (FloorModel.FINISHES), voorlopig globaal
|
||||
roof: Object.assign({}, RoofModel.DEFAULT_SETTINGS),
|
||||
lighting: Object.assign({}, LightingModel.DEFAULT_SETTINGS)
|
||||
lighting: Object.assign({}, LightingModel.DEFAULT_SETTINGS),
|
||||
elektra: ElektraModel.normalizeSettings() // meterkast, schakelaars, wcd, leidingen (ElektraModel)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -98,21 +99,52 @@ window.HouseModel = (() => {
|
||||
return { type, stone, override: !!o };
|
||||
}
|
||||
|
||||
// TIJDELIJK (testen): een nieuwe woning is standaard het testontwerp:
|
||||
// L-vorm, unit A lessenaar (noord 2600, zuid 3200) met riet. Op false
|
||||
// zetten om terug te gaan naar de gewone standaard (één unit).
|
||||
// TIJDELIJK (testen): een nieuwe woning is standaard het voorbeeldontwerp
|
||||
// (ook als json/premade/vlinderwoning.json): 92 m², A (straat) en B (tuin)
|
||||
// met een vlinderdak (lessenaars laag op de naad), C een lage tuinkamer
|
||||
// met plat dak, veel glas. Op false zetten voor de gewone standaard (één unit).
|
||||
const TEST_DEFAULT = true;
|
||||
|
||||
function createVlinderwoning() {
|
||||
const st = { project: createEmptyProject(), settings: createDefaultSettings(), units: [], interiorWalls: [] };
|
||||
st.project.naam = 'Vlinderwoning';
|
||||
st.settings.trasraam.finish = 'K490';
|
||||
const A = createUnitEntry('A', null, { width: 10000, depth: 4000, windowOn: [] });
|
||||
const B = createUnitEntry('B', { to: 'A', side: 'achter', align: 'start', offset: 0 }, { width: 10000, depth: 3600, windowOn: [] });
|
||||
const C = createUnitEntry('C', { to: 'A', side: 'rechts', align: 'start', offset: 0 }, { width: 4000, depth: 4000, windowOn: [] });
|
||||
st.units.push(A, B, C);
|
||||
const h = (u, k, l, r) => Object.assign(u.unit.walls[k].wall, { heightAll: Math.min(l, r), heightLeft: l, heightRight: r, heightMid: null });
|
||||
const HI = 3400, LO = 2700;
|
||||
h(A, 'voor', HI, HI); h(A, 'achter', LO, LO); h(A, 'links', HI, LO); h(A, 'rechts', LO, HI);
|
||||
h(B, 'voor', LO, LO); h(B, 'achter', HI, HI); h(B, 'links', LO, HI); h(B, 'rechts', HI, LO);
|
||||
['voor', 'achter', 'links', 'rechts'].forEach(k => h(C, k, LO, LO));
|
||||
A.roof.covering = B.roof.covering = 'riet_002';
|
||||
C.roof.covering = 'dakleer';
|
||||
// Vulling (KozijnModel): deuren met glasstrook, ramen met een
|
||||
// borstwering (vanaf 900 mm) met ventilatierooster.
|
||||
const ops = (u, side, list) => {
|
||||
u.unit.walls[side].openings = list.map(([type, w, hh, y, anchor, offset], i) => ({
|
||||
id: i + 1, type, w, h: hh, y, anchor, offset,
|
||||
fill: Object.assign(KozijnModel.defaultFill(type), type === 'raam' ? { rooster: y >= 900 } : { glas: true })
|
||||
}));
|
||||
};
|
||||
ops(A, 'voor', [['raam', 1200, 1100, 1000, 'left', 700], ['voordeur', 1150, 2300, 0, 'left', 2700], ['raam', 500, 2300, 0, 'left', 3950],
|
||||
['raam', 2200, 600, 2000, 'left', 5200], ['raam', 900, 2100, 300, 'right', 1800], ['raam', 900, 2100, 300, 'right', 600]]);
|
||||
ops(A, 'links', [['raam', 1400, 1200, 900, 'middle', 0]]);
|
||||
ops(B, 'achter', [['raam', 600, 2600, 250, 'left', 800], ['raam', 600, 2600, 250, 'left', 1700], ['deur', 900, 2300, 0, 'middle', -1600],
|
||||
['raam', 2000, 2300, 0, 'middle', 0], ['deur', 900, 2300, 0, 'middle', 1600], ['raam', 1200, 1200, 1000, 'right', 700]]);
|
||||
ops(B, 'links', [['raam', 1600, 500, 1700, 'middle', 0]]);
|
||||
ops(B, 'rechts', [['raam', 1000, 1300, 900, 'left', 500], ['raam', 1000, 1300, 900, 'right', 500]]);
|
||||
ops(C, 'voor', [['raam', 2600, 1500, 700, 'middle', 0]]);
|
||||
ops(C, 'rechts', [['raam', 1100, 2100, 250, 'left', 500], ['raam', 1100, 2100, 250, 'right', 500]]);
|
||||
ops(C, 'achter', [['deur', 900, 2200, 0, 'left', 500], ['raam', 2000, 2200, 0, 'right', 400]]);
|
||||
ensureSeamWalls(st);
|
||||
computeParts(st, { skip3D: true });
|
||||
return st;
|
||||
}
|
||||
|
||||
function createDefaultState(template) {
|
||||
if (TEST_DEFAULT && (!template || template === 'single')) {
|
||||
const st = createDefaultState('L');
|
||||
const A = st.units[0], W = A.unit.walls;
|
||||
const h = (k, l, r) => Object.assign(W[k].wall, { heightAll: Math.min(l, r), heightLeft: l, heightRight: r, heightMid: null });
|
||||
h('achter', 3200, 3200); h('links', 2600, 3200); h('rechts', 3200, 2600);
|
||||
A.roof.covering = 'riet_001';
|
||||
computeParts(st, { skip3D: true });
|
||||
return st;
|
||||
}
|
||||
if (TEST_DEFAULT && (!template || template === 'single')) return createVlinderwoning();
|
||||
const state = { project: createEmptyProject(), settings: createDefaultSettings(), units: [], interiorWalls: [] };
|
||||
state.units.push(createUnitEntry('A', null, { width: 6000, depth: 3000 }));
|
||||
if (template === 'L') {
|
||||
@@ -120,12 +152,12 @@ window.HouseModel = (() => {
|
||||
// Standaard L-woning: voordeur midden in de noordgevel van A, drie
|
||||
// ramen in de zuidgevel van B (bovenkanten gelijk met de voordeur).
|
||||
state.units[0].unit.walls.voor.openings = [
|
||||
{ id: 1, type: 'voordeur', w: 1150, h: 2150, y: 0, anchor: 'middle', offset: 0 }
|
||||
{ id: 1, type: 'voordeur', w: 1150, h: 2150, y: 0, anchor: 'middle', offset: 0, fill: KozijnModel.defaultFill('voordeur') }
|
||||
];
|
||||
state.units[1].unit.walls.achter.openings = [
|
||||
{ id: 1, type: 'raam', w: 500, h: 1900, y: 250, anchor: 'left', offset: 450 },
|
||||
{ id: 2, type: 'raam', w: 500, h: 1900, y: 250, anchor: 'middle', offset: 0 },
|
||||
{ id: 3, type: 'raam', w: 500, h: 1900, y: 250, anchor: 'right', offset: 450 }
|
||||
{ id: 1, type: 'raam', w: 500, h: 1900, y: 250, anchor: 'left', offset: 450, fill: KozijnModel.defaultFill('raam') },
|
||||
{ id: 2, type: 'raam', w: 500, h: 1900, y: 250, anchor: 'middle', offset: 0, fill: KozijnModel.defaultFill('raam') },
|
||||
{ id: 3, type: 'raam', w: 500, h: 1900, y: 250, anchor: 'right', offset: 450, fill: KozijnModel.defaultFill('raam') }
|
||||
];
|
||||
}
|
||||
ensureSeamWalls(state);
|
||||
@@ -276,7 +308,8 @@ window.HouseModel = (() => {
|
||||
wall.wall.heightAll = wall.wall.heightLeft = wall.wall.heightRight = height || wall.wall.heightAll;
|
||||
wall.materials = { interior: 'spaanplaat', exterior: 'geen' };
|
||||
const d = WallModel.OPENING_TYPES.deur;
|
||||
wall.openings = [{ id: 1, type: 'deur', w: d.w, h: d.h, y: d.y, anchor: 'middle', offset: 0 }];
|
||||
// Binnendeur: wit, zonder glas.
|
||||
wall.openings = [{ id: 1, type: 'deur', w: d.w, h: d.h, y: d.y, anchor: 'middle', offset: 0, fill: Object.assign(KozijnModel.defaultFill('deur'), { out: 'wit', in: 'wit' }) }];
|
||||
setThickness(wall, DEFAULT_SEAM_LEAF_MM);
|
||||
return wall;
|
||||
}
|
||||
@@ -325,13 +358,27 @@ window.HouseModel = (() => {
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Alle onderdelen doorgerekend (+ 3D, tenzij opts.skip3D).
|
||||
// opts: { wallMaterials, floorMaterials, beplatingVisible, skip3D }
|
||||
// opts: { wallMaterials, floorMaterials, ..., stage, lampenVisible, skip3D }
|
||||
// stage = bouwfase (BuildStages, standaard afgewerkt): bepaalt welke lagen
|
||||
// gebouwd worden; de berekening (stats, totalen) is altijd volledig.
|
||||
// ------------------------------------------------------------
|
||||
// Past de woning-brede instellingen toe op de onderdeel-states (muteert
|
||||
// state): h.o.h. van alle wanden, en de dakmaat als die de vloer volgt.
|
||||
// Grenzen (UnitModel.LIMITS) afdwingen: vloermaat, goot/plat dak en nok.
|
||||
// Muteert state.
|
||||
function enforceLimits(state) {
|
||||
const L = UnitModel.LIMITS;
|
||||
state.units.forEach(u => {
|
||||
UnitModel.clampSize(u.unit.floor.floor);
|
||||
const f = UnitModel.roofForm(u.unit);
|
||||
if (f.low > L.eave + 0.5 || f.high > L.ridge + 0.5) UnitModel.applyRoofForm(u.unit, f);
|
||||
});
|
||||
}
|
||||
|
||||
function applyGlobalSettings(state) {
|
||||
const sp = state.settings.spacing;
|
||||
const tr = state.settings.trasraam;
|
||||
enforceLimits(state);
|
||||
state.units.forEach(u => {
|
||||
const trasraam = tr.enabled ? WallModel.trasraamFor(tr.height, tr.finish) : null;
|
||||
Object.entries(u.unit.walls).forEach(([side, ws]) => {
|
||||
@@ -339,8 +386,8 @@ window.HouseModel = (() => {
|
||||
ws.wall.maxSpacing = sp.exterior.max;
|
||||
const fc = facadeFor(state, u, side);
|
||||
ws.materials = Object.assign({}, ws.materials, fc.type === 'steen'
|
||||
? { exterior: 'cementplaat', stone: fc.stone, cladding: state.settings.cladding, trasraam }
|
||||
: { exterior: 'vochtdoek', stone: null, cladding: state.settings.cladding, trasraam });
|
||||
? { exterior: 'cementplaat', stone: fc.stone, cladding: state.settings.cladding, trasraam, rabatColor: null }
|
||||
: { exterior: 'vochtdoek', stone: null, cladding: state.settings.cladding, trasraam, rabatColor: state.settings.facade.rabatColor });
|
||||
});
|
||||
if (u.roof.followFloor) {
|
||||
u.roof.deck.floor.width = u.unit.floor.floor.width;
|
||||
@@ -399,6 +446,8 @@ window.HouseModel = (() => {
|
||||
function computeParts(state, opts) {
|
||||
const o = opts || {};
|
||||
const build = !o.skip3D;
|
||||
const stage = BuildStages.clamp(o.stage);
|
||||
const shows = (key) => BuildStages.shows(stage, key);
|
||||
applyGlobalSettings(state);
|
||||
const rects = unitRects(state);
|
||||
const rectById = {};
|
||||
@@ -406,12 +455,11 @@ window.HouseModel = (() => {
|
||||
const wallThickness = (id, side) => WallModel.thicknessOf(WallModel.configFromState(state.units.find(u => u.id === id).unit.walls[side]));
|
||||
const { cuts, seams } = computeSeams(rects, wallThickness);
|
||||
|
||||
function buildWall(config, frame, buitenMateriaal, cladding, trasraam, skinExt, facade, skinFrom, spaanplaatBelow, stone, skinAbove, trimTop) {
|
||||
function buildWall(config, frame, buitenMateriaal, cladding, trasraam, skinExt, facade, skinFrom, spaanplaatBelow, stone, skinAbove, trimTop, battenAbove) {
|
||||
if (!build) return { group: null, stats: null, specs: null };
|
||||
const built = WallModel.build3D(config, frame, {
|
||||
materials: o.wallMaterials, buitenMateriaal, cladding, trasraam, skinExt, facade, skinFrom, spaanplaatBelow, stone, skinAbove, trimTop,
|
||||
painted: state.settings.interiorPaint,
|
||||
spaanplaatVisible: o.beplatingVisible !== false, buitenVisible: o.beplatingVisible !== false
|
||||
materials: o.wallMaterials, buitenMateriaal, cladding, trasraam, skinExt, facade, skinFrom, spaanplaatBelow, stone, skinAbove, trimTop, battenAbove,
|
||||
painted: state.settings.interiorPaint && shows('afwerking'), stage, rabatColor: state.settings.facade.rabatColor
|
||||
});
|
||||
return { group: built.group, stats: built.stats, specs: WallModel.computeSpecs(config, built.stats, buitenMateriaal) };
|
||||
}
|
||||
@@ -458,21 +506,22 @@ window.HouseModel = (() => {
|
||||
const floorData = FloorModel.computeFromState(u.unit.floor, rims);
|
||||
const floorSpecs = FloorModel.computeSpecs(floorData.calcData, floorData.resolvedOpenings);
|
||||
const floorGroup = build ? FloorModel.build3D(floorData.calcData, floorData.resolvedOpenings, floorData.chassisData, {
|
||||
materials: o.floorMaterials, osbVisible: o.beplatingVisible !== false,
|
||||
finish: { key: state.settings.floorFinish, originX: rect.cx * 0.001, originZ: rect.cz * 0.001 }
|
||||
materials: o.floorMaterials, osbVisible: shows('beplating'),
|
||||
finish: shows('afwerking') ? { key: state.settings.floorFinish, originX: rect.cx * 0.001, originZ: rect.cz * 0.001 } : null
|
||||
}) : null;
|
||||
|
||||
const walls = layout.walls.map(wl => wallOf(u, rect, layout, wl));
|
||||
|
||||
// Dak: dek = vloer-state (180° gespiegeld, RoofModel), in de vorm die
|
||||
// de wanden aangeven (plat, lessenaar of zadel; RoofModel.computeShape).
|
||||
// de wanden aangeven (plat, lessenaar, zadel, mansarde of wolfsdak;
|
||||
// RoofModel.computeShape).
|
||||
const shape = RoofModel.computeShape(u.unit, layout);
|
||||
const deckFrame = FloorModel.computeFrame(u.roof.deck.floor);
|
||||
const roofW = deckFrame.width, roofD = deckFrame.depth;
|
||||
const ox = u.roof.offsetX || 0, oz = u.roof.offsetZ || 0;
|
||||
const ur = RoofModel.computeUnitRoof(u.roof, shape, { x0: ox - roofW / 2, x1: ox + roofW / 2, z0: oz - roofD / 2, z1: oz + roofD / 2 }, rims);
|
||||
const roofCx = rect.cx + ox, roofCz = rect.cz + oz;
|
||||
const shapeLabel = { plat: 'plat', lessenaar: 'lessenaar', zadel: 'zadel' }[shape.type];
|
||||
const shapeLabel = { plat: 'plat', lessenaar: 'lessenaar', zadel: 'zadel', mansarde: 'mansarde', wolf: 'wolfsdak' }[shape.type];
|
||||
const multi = ur.planes.length > 1;
|
||||
ur.planes.forEach((pl, i) => {
|
||||
pl.element = `${u.id}-D${i + 1}`;
|
||||
@@ -481,6 +530,7 @@ window.HouseModel = (() => {
|
||||
});
|
||||
const roof = {
|
||||
element: `${u.id}-D1`, shape, covering: ur.covering, planes: ur.planes, specs: ur.specs,
|
||||
unitRoof: ur, // o.a. het bovenste deel van een wolfsdak (cap, RoofModel.capRegions)
|
||||
rect: { x0: roofCx - roofW / 2, x1: roofCx + roofW / 2, z0: roofCz - roofD / 2, z1: roofCz + roofD / 2, cx: roofCx, cz: roofCz, W: roofW, D: roofD },
|
||||
base: layout.floorTop, // 3D: group-Y = 0 op het loopvlak
|
||||
// Onderkant plafond resp. bovenkant dakleer op (x, z) (woning-mm, absoluut).
|
||||
@@ -488,12 +538,11 @@ window.HouseModel = (() => {
|
||||
topAt: (x, z) => layout.floorTop + ur.topAt(x - rect.cx, z - rect.cz),
|
||||
ridges: ur.ridges.map(r => ({ axis: r.axis, value: r.value + (r.axis === 'x' ? rect.cx : rect.cz) })),
|
||||
top: layout.floorTop + ur.maxTop,
|
||||
// Zichtbaarheid zoals wanden/vloeren: plafondplaat + dakleer (EPDM)
|
||||
// horen bij "Beplating".
|
||||
// Bouwfase: plafondplaat bij "beplating", bedekking bij "gevel".
|
||||
group: build ? RoofModel.buildUnit3D(ur, {
|
||||
materials: o.roofMaterials,
|
||||
osbVisible: o.beplatingVisible !== false,
|
||||
dakleerVisible: o.beplatingVisible !== false
|
||||
osbVisible: shows('beplating'),
|
||||
dakleerVisible: shows('gevel')
|
||||
}) : null
|
||||
};
|
||||
|
||||
@@ -640,15 +689,17 @@ window.HouseModel = (() => {
|
||||
const axisOf = (q) => horiz ? q.x - u.rect.cx : q.z - u.rect.cz;
|
||||
const W = lf.place.width;
|
||||
const pL = axisOf(UnitModel.localToWorld(lf.place, -W / 2, 0)), pR = axisOf(UnitModel.localToWorld(lf.place, W / 2, 0));
|
||||
const peak = prof.length > 2 ? prof[1] : null;
|
||||
// Opbouw erboven (raiseSeamWalls): het deel stopt op de voet daarvan.
|
||||
const cap = raisedCap[`${lf.uid}|${lf.uid === sm.a ? sm.b : sm.a}`];
|
||||
const lim = (h) => cap !== undefined ? Math.min(h, cap) : h;
|
||||
const hL = lim(RoofModel.profileHeight(prof, pL)), hR = lim(RoofModel.profileHeight(prof, pR));
|
||||
const hasPeak = !!peak && peak.p > Math.min(pL, pR) + 1 && peak.p < Math.max(pL, pR) - 1 && lim(peak.h) > Math.max(hL, hR) + 0.5;
|
||||
// Tussenpunten (nok, knikken) die in het deel vallen, als fractie vanaf links.
|
||||
const lo = Math.min(pL, pR), hi = Math.max(pL, pR);
|
||||
const inner = prof.slice(1, -1).filter(q => q.p > lo + 1 && q.p < hi - 1)
|
||||
.map(q => ({ f: Math.abs(q.p - pL) / (hi - lo), h: Math.round(lim(q.h)) })).sort((a, b) => a.f - b.f);
|
||||
return {
|
||||
heightLeft: Math.round(hL), heightRight: Math.round(hR),
|
||||
heightMid: hasPeak ? Math.round(lim(peak.h)) : null, peakX: hasPeak ? Math.abs(peak.p - pL) : undefined
|
||||
heightMid: null, peakX: undefined, topPts: inner.length ? inner : undefined
|
||||
};
|
||||
};
|
||||
const paramsOf = (lf) => Object.assign({}, iw.wall.wall, { width: lf.place.width }, heightsOf(lf));
|
||||
@@ -669,7 +720,7 @@ window.HouseModel = (() => {
|
||||
const first = leaves[0];
|
||||
const firstParams = paramsOf(first);
|
||||
// Voor de wand-editor: breedte en daklijn van het eerste deel.
|
||||
Object.assign(iw.wall.wall, { width: first.place.width, heightLeft: firstParams.heightLeft, heightRight: firstParams.heightRight, heightMid: firstParams.heightMid });
|
||||
Object.assign(iw.wall.wall, { width: first.place.width, heightLeft: firstParams.heightLeft, heightRight: firstParams.heightRight, heightMid: null, topPts: firstParams.topPts });
|
||||
const firstConfig = WallModel.buildConfig(firstParams, iw.wall.openings || []);
|
||||
const toLocalU = (pl, q) => {
|
||||
const c = Math.cos(pl.rotY), sn = Math.sin(pl.rotY);
|
||||
@@ -683,6 +734,7 @@ window.HouseModel = (() => {
|
||||
const wb = UnitModel.localToWorld(first.place, -first.place.width / 2 + op.x + op.w, 0);
|
||||
const ua = toLocalU(lf.place, wa), ub = toLocalU(lf.place, wb);
|
||||
// Gespiegeld (andere kijkrichting): schuine bovenzijde klapt om.
|
||||
// Het kozijn (fill) staat alleen in het eerste deel.
|
||||
const flip = ua > ub;
|
||||
return { id: op.id, type: op.type, w: op.w, h: flip ? op.hR : op.h, hR: flip ? op.h : op.hR, y: op.y, anchor: 'left', offset: Math.round(Math.min(ua, ub)) };
|
||||
});
|
||||
@@ -706,6 +758,23 @@ window.HouseModel = (() => {
|
||||
});
|
||||
}
|
||||
|
||||
// Schuine regel op de bovenregel (WallModel, CONFIG.topWedge): helt het
|
||||
// dak dwars over de wand (goot van een zadel-/lessenaardak, onder een
|
||||
// wolfseind), dan zit er tussen de vlakke bovenregel en de onderkant van
|
||||
// het dak een wig. Per positie langs de wand de hoogte van die wig aan
|
||||
// de buiten- (o) en binnenkant (i) van het regelwerk (mm), of null.
|
||||
function topWedge(p, roof) {
|
||||
const cfg = p.config, half = cfg.wallWidth / 2, zF = cfg.studDepth / 2, tl = WallModel.topLine(cfg);
|
||||
const xs = new Set([-half, half].concat(tl.kinks));
|
||||
for (let x = -half + 250; x < half; x += 250) xs.add(x);
|
||||
const pts = [...xs].sort((a, b) => a - b).map(x => {
|
||||
const top = p.place.floorTop + tl.at(x);
|
||||
const at = (z) => { const w = UnitModel.localToWorld(p.place, x, z); return Math.max(0, Math.min(800, roof.ceilingAt(w.x, w.z) - top)); };
|
||||
return { x, o: Math.round(at(zF)), i: Math.round(at(-zF)) };
|
||||
});
|
||||
return pts.some(q => Math.max(q.o, q.i) >= 5) ? { pts } : null;
|
||||
}
|
||||
|
||||
// Doek en latwerk volgen de hele gevel (niet per unit/wandstuk), ook
|
||||
// over het blootliggende deel van een naadwand. Daarna pas bouwen.
|
||||
const extPieces = [];
|
||||
@@ -718,7 +787,10 @@ window.HouseModel = (() => {
|
||||
units.forEach(u => {
|
||||
u.walls.forEach(w => w.pieces.forEach(p => {
|
||||
const m = UnitModel.localToWorld(p.place, 0, p.place.thickness / 2);
|
||||
p.skinAbove = Math.max(0, Math.round(u.roof.topAt(m.x, m.z) - u.roof.ceilingAt(m.x, m.z)));
|
||||
// (vanaf de bovenkant van de wand: daar kan nog een schuine regel op)
|
||||
const wallTop = p.place.floorTop + WallModel.topLine(p.config).at(0);
|
||||
p.skinAbove = Math.max(0, Math.round(u.roof.topAt(m.x, m.z) - Math.min(wallTop, u.roof.ceilingAt(m.x, m.z))));
|
||||
p.config.topWedge = topWedge(p, u.roof);
|
||||
// Binnenhoek-doorloop (negatieve skinExt): tot de bovenkant van het
|
||||
// dak van de buur-unit die daar staat geen buitenlagen (trimTop).
|
||||
const e = p.skinExt || {}, W = p.place.width;
|
||||
@@ -731,23 +803,11 @@ window.HouseModel = (() => {
|
||||
});
|
||||
}));
|
||||
});
|
||||
assignFacades(extPieces.concat(interior.filter(p => p.skinFrom !== undefined)));
|
||||
extPieces.concat(interior).forEach(p => {
|
||||
const built = buildWall(p.config, p.frame, p.buitenMateriaal, p.cladding, p.trasraam, p.skinExt, p.facade, p.skinFrom, p.spaanplaatBelow, p.stone, p.skinAbove, p.trimTop);
|
||||
if (built.group && p.shiftZ) {
|
||||
const outer = new THREE.Group();
|
||||
built.group.position.z = p.shiftZ * 0.001;
|
||||
outer.add(built.group);
|
||||
built.group = outer;
|
||||
}
|
||||
Object.assign(p, built);
|
||||
});
|
||||
|
||||
// Dakrand (boeidelen/daktrim) als één doorlopende rand om alle daken;
|
||||
// boeidelen aan/uit per unit. De rand volgt de dakvorm van de unit waar
|
||||
// de ribbe bij hoort; platte daken delen één randhoogte (de hoogste).
|
||||
const flatTops = units.filter(u => u.roof.shape.type === 'plat').map(u => u.roof.top);
|
||||
const flatTop = flatTops.length ? Math.max(...flatTops) : 0;
|
||||
// boeidelen aan/uit per unit. De rand volgt de dakvorm (en hoogte) van
|
||||
// de unit waar de ribbe bij hoort, ook bij platte daken: een hoogte-
|
||||
// verschil verspringt, en vanaf de boeideelhoogte krijgt het hoge dak een
|
||||
// eigen rand en loopt die van het lage door (RoofModel.computeEdge).
|
||||
// Dikte van de gevelafwerking langs een dakrand-ribbe (boeideel komt
|
||||
// erbovenop): gevelstuk op die lijn met dezelfde buitenrichting (+), of
|
||||
// een gevel van een ander deel die naar dit dak toe kijkt (-).
|
||||
@@ -771,27 +831,66 @@ window.HouseModel = (() => {
|
||||
const roofEdge = units.length ? RoofModel.computeEdge(state.settings.roof,
|
||||
units.map(u => ({
|
||||
unitId: u.id, rect: u.roof.rect, fascia: state.units.find(su => su.id === u.id).roof.fascia !== false,
|
||||
topAt: u.roof.shape.type === 'plat' ? () => flatTop : u.roof.topAt, ridges: u.roof.ridges
|
||||
topAt: u.roof.topAt, ridges: u.roof.ridges
|
||||
})), facadeDepth) : null;
|
||||
// Latwerk stopt onder het boeideel/de daktrim die boven het wandstuk
|
||||
// zit (battenAbove, mm t.o.v. de wandbovenkant, kan negatief zijn): rand
|
||||
// bovenkant = dak + EDGE_ABOVE, hoogte fasciaHeight resp. trimHeight + EDGE_ABOVE.
|
||||
const pointInPoly = (q, poly) => {
|
||||
let inside = false;
|
||||
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
||||
const a = poly[i], b = poly[j];
|
||||
if ((a.z > q.z) !== (b.z > q.z) && q.x < (b.x - a.x) * (q.z - a.z) / (b.z - a.z) + a.x) inside = !inside;
|
||||
}
|
||||
return inside;
|
||||
};
|
||||
if (roofEdge) extPieces.forEach(p => {
|
||||
const nrm = { x: p.place.normal.x, z: p.place.normal.y };
|
||||
const m = UnitModel.localToWorld(p.place, 0, p.place.thickness / 2);
|
||||
// Een paar afstanden vóór de gevel: de rand begint op de dakrand, die
|
||||
// op of iets vóór het wandvlak kan liggen.
|
||||
const sg = [10, 60, 150].map(d => ({ x: m.x + nrm.x * d, z: m.z + nrm.z * d }))
|
||||
.map(q => roofEdge.segments.find(e => pointInPoly(q, [e.a, e.b, e.outerB, e.outerA]))).find(Boolean);
|
||||
if (!sg) return;
|
||||
const h = sg.fascia ? roofEdge.fasciaHeight : roofEdge.trimHeight + roofEdge.edgeAbove;
|
||||
p.battenAbove = (p.skinAbove || 0) + roofEdge.edgeAbove - h;
|
||||
});
|
||||
assignFacades(extPieces.concat(interior.filter(p => p.skinFrom !== undefined)));
|
||||
extPieces.concat(interior).forEach(p => {
|
||||
const built = buildWall(p.config, p.frame, p.buitenMateriaal, p.cladding, p.trasraam, p.skinExt, p.facade, p.skinFrom, p.spaanplaatBelow, p.stone, p.skinAbove, p.trimTop, p.battenAbove);
|
||||
if (built.group && p.shiftZ) {
|
||||
const outer = new THREE.Group();
|
||||
built.group.position.z = p.shiftZ * 0.001;
|
||||
outer.add(built.group);
|
||||
built.group = outer;
|
||||
}
|
||||
Object.assign(p, built);
|
||||
});
|
||||
|
||||
// Buitenverlichting: gevellampen per wandstuk + boeideel-spots (vult roofEdge.spots).
|
||||
const lighting = LightingModel.compute(state.settings.lighting, state.settings.roof, units, state.units, roofEdge);
|
||||
// Zichtbaarheid: boeideel/daktrim volgt "Beplating", spots en lampen volgen "Lampen".
|
||||
// Zichtbaarheid: dakrand en lampen in de laatste bouwfase; lampen ook via "Lampen".
|
||||
const roofEdgeGroup = build && roofEdge ? RoofModel.buildEdge3D(roofEdge, o.roofMaterials, {
|
||||
edgeVisible: o.beplatingVisible !== false, spotsVisible: o.lampenVisible !== false
|
||||
edgeVisible: shows('afwerking'), spotsVisible: o.lampenVisible !== false && shows('afwerking')
|
||||
}) : null;
|
||||
// Binnenspots in het plafond, per unit (LightingModel.computeIndoor).
|
||||
lighting.indoor = LightingModel.computeIndoor(state.settings.lighting, units, interior);
|
||||
const lightingGroup = build ? LightingModel.build3D(lighting.lamps, o.lightingMaterials || LightingModel.createMaterials(), lighting.indoor) : null;
|
||||
if (lightingGroup) lightingGroup.visible = o.lampenVisible !== false;
|
||||
if (lightingGroup) lightingGroup.visible = o.lampenVisible !== false && shows('afwerking');
|
||||
// Elektra: meterkast, schakelaars (letter = ook op hun lampen), wcd's,
|
||||
// groepen en leidingen naar de meterkast (ElektraModel).
|
||||
const elektra = ElektraModel.compute(state.settings.elektra, units, interior, lighting, roofEdge);
|
||||
const elektraGroup = build ? ElektraModel.build3D(elektra, o.elektraMaterials, stage) : null;
|
||||
if (elektraGroup) elektraGroup.visible = o.elektraVisible !== false && shows('elektra');
|
||||
|
||||
// Sandwichpanelen rondom de vloeren, vanaf het loopvlak naar beneden.
|
||||
const sandwich = state.settings.sandwich && units.length
|
||||
? SandwichModel.compute(rects, Math.max(...units.map(u => u.layout.floorTop))) : null;
|
||||
const sandwichGroup = build && sandwich ? SandwichModel.build3D(sandwich, o.sandwichMaterials || SandwichModel.createMaterials(), {
|
||||
visible: o.beplatingVisible !== false
|
||||
visible: shows('gevel')
|
||||
}) : null;
|
||||
|
||||
return { rects, seams, overlaps: overlappingPairs(rects), units, interior, roofEdge, roofEdgeGroup, lighting, lightingGroup, sandwich, sandwichGroup };
|
||||
return { rects, seams, overlaps: overlappingPairs(rects), units, interior, roofEdge, roofEdgeGroup, lighting, lightingGroup, elektra, elektraGroup, sandwich, sandwichGroup };
|
||||
}
|
||||
|
||||
// Alle wandstukken (buiten + binnen) plat op een rij.
|
||||
@@ -872,8 +971,38 @@ window.HouseModel = (() => {
|
||||
];
|
||||
}
|
||||
|
||||
// TIJDELIJK (verwijderen voor productie): capaciteit zaagmachine.
|
||||
const SAW_M_PER_H = 2400;
|
||||
// ------------------------------------------------------------
|
||||
// Transport: elke unit gaat als losse module op de wagen (vloer, wanden
|
||||
// incl. zijn naadwanddelen, dak; zonder dakrand). Buitenmaat incl. de
|
||||
// gevelafwerking (latwerk + rabat, steen). Breedteklassen indicatief
|
||||
// (wegvervoer NL): tot 2550 mm regulier, tot 3500 mm met ontheffing,
|
||||
// daarboven met begeleiding. Controleer altijd bij de vervoerder.
|
||||
// ------------------------------------------------------------
|
||||
const TRANSPORT = { widthRegular: 2550, widthPermit: 3500 };
|
||||
const TRANSPORT_CLASSES = { regulier: 'Regulier', ontheffing: 'Ontheffing', begeleiding: 'Ontheffing + begeleiding' };
|
||||
function computeTransport(parts) {
|
||||
const kgOf = (p) => (p.specs && p.specs.totalKg) || 0;
|
||||
return parts.units.map(u => {
|
||||
const leaves = parts.interior.filter(p => p.seamUnit === u.id);
|
||||
const kg = {
|
||||
vloer: u.floorSpecs.totalKg,
|
||||
wanden: u.walls.reduce((a, w) => a + w.pieces.reduce((b, p) => b + kgOf(p), 0), 0) + leaves.reduce((a, p) => a + kgOf(p), 0),
|
||||
dak: u.roof.specs.totalKg
|
||||
};
|
||||
// Afwerking buiten het wandvlak per zijde (0 als de wand vervalt).
|
||||
const out = (side) => Math.max(0, ...((u.walls.find(w => w.side === side) || { pieces: [] }).pieces
|
||||
.filter(p => !p.baseY)
|
||||
.map(p => Math.max(WallModel.claddingOffset(p.buitenMateriaal, p.stone),
|
||||
p.trasraam && WallModel.isStoneFinish(p.trasraam.finish) ? WallModel.STONE.THICK_MM : 0))));
|
||||
const ew = u.rect.W + out('links') + out('rechts'), ed = u.rect.D + out('voor') + out('achter');
|
||||
const width = Math.min(ew, ed);
|
||||
return {
|
||||
id: u.id, kg, totalKg: kg.vloer + kg.wanden + kg.dak,
|
||||
length: Math.max(ew, ed), width, height: u.roof.top,
|
||||
cls: width <= TRANSPORT.widthRegular ? 'regulier' : width <= TRANSPORT.widthPermit ? 'ontheffing' : 'begeleiding'
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function computeTotals(parts) {
|
||||
const pieces = allWallPieces(parts);
|
||||
@@ -906,6 +1035,9 @@ window.HouseModel = (() => {
|
||||
rc.joists.forEach(() => addPiece('dakbalk', rc.joistW, rc.joistH, rc.joistLen, [ang, ang]));
|
||||
['front', 'back'].filter(k => rc.rims[k]).forEach(() => addPiece('dakbalk', rc.joistW, rc.joistH, rc.width));
|
||||
});
|
||||
// Wolfsdak: bovenste deel (sporen, hoekkepers, nokbalk, kopbalken).
|
||||
const cap = u.roof.unitRoof && u.roof.unitRoof.cap;
|
||||
if (cap) cap.beams.forEach(bm => addPiece('dakbalk', cap.joistW, cap.joistH, bm.length, [bm.angle, bm.angle]));
|
||||
});
|
||||
const wallBom = [];
|
||||
pieces.forEach(p => WallModel.computeBOM(p.config, p.frame).forEach(b => { wallBom.push(b); addPiece(b.kind, b.width, b.depth, b.length, b.cutAngles); }));
|
||||
@@ -934,9 +1066,9 @@ window.HouseModel = (() => {
|
||||
return {
|
||||
kinds: kindList,
|
||||
sawList: Array.from(sawList.values()).sort((a, b) => b.length - a.length || a.width - b.width),
|
||||
// TIJDELIJK (verwijderen voor productie): zaagmachine, SAW_M_PER_H meter per uur.
|
||||
// Zaagmachine: totale zaaglengte, sneden en hoeken (tijd: CostModel).
|
||||
saw: {
|
||||
meters: saw.lengthMM / 1000, hours: saw.lengthMM / 1000 / SAW_M_PER_H, cuts: saw.cuts,
|
||||
meters: saw.lengthMM / 1000, cuts: saw.cuts,
|
||||
angles: Array.from(saw.angles.entries()).map(([deg, count]) => ({ deg, count })).sort((a, b) => a.deg - b.deg)
|
||||
},
|
||||
beamCount: kindList.reduce((a, k) => a + k.count, 0),
|
||||
@@ -986,6 +1118,10 @@ window.HouseModel = (() => {
|
||||
{
|
||||
naam: 'Vochtdoek (buitenzijde)', m2: byMat('vochtdoek').reduce((a, p) => a + p.stats.doekAreaM2, 0),
|
||||
m3: null, kg: byMat('vochtdoek').reduce((a, p) => a + p.specs.buitenKg, 0)
|
||||
},
|
||||
{
|
||||
naam: `Rabat ${WallModel.CLADDING_THICK_MM} mm (gevelbekleding)`, m2: byMat('vochtdoek').reduce((a, p) => a + (p.stats.rabatAreaM2 || 0), 0),
|
||||
m3: null, kg: 0 // gewicht volgt
|
||||
}
|
||||
],
|
||||
weights,
|
||||
@@ -1053,6 +1189,7 @@ window.HouseModel = (() => {
|
||||
state.settings.facade = Object.assign(def.facade, state.settings.facade || {});
|
||||
if (!FACADE_TYPES[state.settings.facade.type]) state.settings.facade.type = 'rabat';
|
||||
if (!WallModel.isStoneFinish(state.settings.facade.stone)) state.settings.facade.stone = def.facade.stone;
|
||||
if (!WallModel.RABAT_COLORS[state.settings.facade.rabatColor]) state.settings.facade.rabatColor = def.facade.rabatColor;
|
||||
state.settings.trasraam = Object.assign(def.trasraam, state.settings.trasraam || {});
|
||||
state.settings.trasraam.enabled = state.settings.trasraam.enabled !== false;
|
||||
if (!(state.settings.trasraam.height > 0)) state.settings.trasraam.height = WallModel.TRASRAAM_DEFAULT_MM;
|
||||
@@ -1071,6 +1208,7 @@ window.HouseModel = (() => {
|
||||
if (!lightIn.mode) lightIn.mode = legacyUnitLight ? legacyUnitLight.mode : (oldRoof.spots === false ? 'geen' : 'spots');
|
||||
if (!lightIn.direction && legacyUnitLight && legacyUnitLight.direction) lightIn.direction = legacyUnitLight.direction;
|
||||
state.settings.lighting = LightingModel.normalizeSettings(lightIn);
|
||||
state.settings.elektra = ElektraModel.normalizeSettings(state.settings.elektra);
|
||||
state.units.forEach(u => {
|
||||
if (!u.unit.layout) u.unit.layout = { corners: 'voor-achter' };
|
||||
if (!u.roof) { u.roof = RoofModel.createDefaultRoof(u.unit.floor); delete u.roof.fascia; }
|
||||
@@ -1086,9 +1224,9 @@ window.HouseModel = (() => {
|
||||
}
|
||||
|
||||
return {
|
||||
ALIGN_LABELS, SIDE_LABELS, DEFAULT_INTERIOR_THICKNESS, FACADE_TYPES, facadeFor, SAW_M_PER_H, // SAW_M_PER_H: tijdelijk
|
||||
ALIGN_LABELS, SIDE_LABELS, DEFAULT_INTERIOR_THICKNESS, FACADE_TYPES, facadeFor,
|
||||
clone, createEmptyProject, createDefaultSettings, createUnitEntry, applyGlobalSettings, nextUnitId, createDefaultState, unitRects, computeSeams, overlappingPairs,
|
||||
interiorWallDefaults, setThickness, thicknessOf, interiorLayout, ensureSeamWalls, syncSeamWalls, missingSeamWalls,
|
||||
computeParts, allWallPieces, computeTotals, computeDimensions, dimensionRows, validateState, fromUnitDesign, normalize
|
||||
computeParts, allWallPieces, computeTotals, computeDimensions, dimensionRows, TRANSPORT, TRANSPORT_CLASSES, computeTransport, validateState, fromUnitDesign, normalize
|
||||
};
|
||||
})();
|
||||
|
||||
+256
-23
@@ -20,7 +20,7 @@ window.HousePdf = (() => {
|
||||
const KIND_LABELS = {
|
||||
'vloerbalk': 'Vloerbalk', 'dakbalk': 'Dakbalk', 'onderregel': 'Onderregel', 'bovenregel': 'Bovenregel',
|
||||
'verticale balk': 'Verticale balk', 'kreupele stijl': 'Kreupele stijl',
|
||||
'bovendorpel': 'Bovendorpel', 'onderdorpel': 'Onderdorpel'
|
||||
'bovendorpel': 'Bovendorpel', 'onderdorpel': 'Onderdorpel', 'schuine regel': 'Schuine regel'
|
||||
};
|
||||
const PLAN_ELEMENT = 'PL-1';
|
||||
const VIEWS_ELEMENT = 'AZ-1';
|
||||
@@ -46,10 +46,13 @@ window.HousePdf = (() => {
|
||||
list.push({ kind: 'floor', element: u.floorElement, label: `Unit ${u.id} vloer`, unit: u });
|
||||
u.walls.forEach(w => w.pieces.forEach(p => list.push({ kind: 'wall', element: p.element, label: p.label, piece: p })));
|
||||
u.roof.planes.forEach(pl => list.push({ kind: 'roof', element: pl.element, label: pl.label, plane: pl }));
|
||||
// Wolfsdak: het bovenste deel (wolfseinden, nok) op een eigen blad.
|
||||
if (u.roof.unitRoof && u.roof.unitRoof.cap) list.push({ kind: 'cap', element: `${u.id}-D${u.roof.planes.length + 1}`, label: `Unit ${u.id} dak bovenste deel (wolfsdak)`, unit: u });
|
||||
});
|
||||
parts.interior.forEach(p => list.push({ kind: 'wall', element: p.element, label: p.label, piece: p }));
|
||||
list.push({ kind: 'totals', element: 'TOT', label: 'Totalen' });
|
||||
list.push({ kind: 'saw', element: 'ZAAG', label: 'Zaaglijst & zaagmachine' });
|
||||
if (parts.elektra && parts.elektra.enabled) list.push({ kind: 'elektra', element: 'EL', label: 'Elektra' });
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -128,6 +131,9 @@ window.HousePdf = (() => {
|
||||
doc.rect(px(l.x + l.normal.x * 80) - 0.9, py(l.z + l.normal.z * 80) - 0.9, 1.8, 1.8, 'FD');
|
||||
});
|
||||
|
||||
// Elektra: meterkast, schakelaars (letters), wcd, leidingen (ElektraPdf).
|
||||
ElektraPdf.drawPlan(doc, parts.elektra, px, py);
|
||||
|
||||
// Naden: gestippeld oranje
|
||||
doc.setDrawColor(230, 130, 30);
|
||||
doc.setLineWidth(0.4);
|
||||
@@ -177,6 +183,7 @@ window.HousePdf = (() => {
|
||||
doc.setFontSize(6);
|
||||
doc.text('Plattegrond woning — oranje gestippeld = naad tussen units (wand vervalt), openingen gekruist, grijs gestippeld = dakrand, zwart gestippeld = ' +
|
||||
'boeideel (o = spot), grijs = daktrim, geel blokje = gevellamp', drawX0, drawY1 + 4);
|
||||
if (parts.elektra && parts.elektra.enabled) doc.text(ElektraPdf.PLAN_LEGEND, drawX0, drawY1 + 7.5);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
@@ -216,7 +223,7 @@ window.HousePdf = (() => {
|
||||
const addWall = (p, y0) => {
|
||||
const n = { x: p.place.normal.x, z: p.place.normal.y };
|
||||
const zOut = p.place.thickness / 2 + WallModel.claddingOffset(p.buitenMateriaal, p.stone);
|
||||
const color = p.stone ? (WallModel.STONE_COLORS[p.stone] || '#a0724f') : VIEW_COLORS.rabat;
|
||||
const color = p.stone ? (WallModel.STONE_COLORS[p.stone] || '#a0724f') : WallModel.rabatHex(state.settings.facade.rabatColor);
|
||||
faces.push({ pts: wallFace(p, y0).map(q => toWorld(p, q.x, q.y, zOut)), n, color, bias: 0 });
|
||||
// Trasraam (strook onderaan, iets naar voren)
|
||||
const tr = p.trasraam;
|
||||
@@ -246,14 +253,7 @@ window.HousePdf = (() => {
|
||||
if (sh.type === 'plat') return;
|
||||
const cov = RoofModel.COVERINGS[r.covering] || RoofModel.COVERINGS.dakleer;
|
||||
const ctr = sh.axis === 'x' ? u.rect.cx : u.rect.cz;
|
||||
r.planes.forEach(pl => {
|
||||
const a0 = ctr + pl.p0, a1 = ctr + pl.p1, mid = (a0 + a1) / 2;
|
||||
// Hoogte net binnen het vlak (op de nok geeft topAt het hoogste vlak).
|
||||
const inward = (t) => t + Math.sign(mid - t) * 0.5;
|
||||
const corners = sh.axis === 'x'
|
||||
? [[a0, r.rect.z0], [a1, r.rect.z0], [a1, r.rect.z1], [a0, r.rect.z1]]
|
||||
: [[r.rect.x0, a0], [r.rect.x1, a0], [r.rect.x1, a1], [r.rect.x0, a1]];
|
||||
const pts = corners.map(([x, z]) => ({ x, y: sh.axis === 'x' ? r.topAt(inward(x), z) : r.topAt(x, inward(z)), z }));
|
||||
const pushRoof = (pts) => {
|
||||
// Normaal (omhoog) uit het vlak.
|
||||
const [A, B, C] = pts;
|
||||
const ux = B.x - A.x, uy = B.y - A.y, uz = B.z - A.z, vx = C.x - A.x, vy = C.y - A.y, vz = C.z - A.z;
|
||||
@@ -261,18 +261,45 @@ window.HousePdf = (() => {
|
||||
if (ny < 0) { nx = -nx; ny = -ny; nz = -nz; }
|
||||
const len = Math.hypot(nx, ny, nz) || 1;
|
||||
faces.push({ pts, n: { x: nx / len, z: nz / len }, roof: true, color: cov.color || '#2a2a2a', bias: 0 });
|
||||
};
|
||||
// Wolfsdak: bovenste deel per vlak (RoofModel.capRegions, unit-lokaal (p, q)).
|
||||
RoofModel.capRegions(r.unitRoof).forEach(rg => {
|
||||
pushRoof(rg.poly.map(pt => {
|
||||
const x = u.rect.cx + (sh.axis === 'x' ? pt.x : pt.z), z = u.rect.cz + (sh.axis === 'x' ? pt.z : pt.x);
|
||||
return { x, y: u.layout.floorTop + rg.under(pt) + rg.vThick, z };
|
||||
}));
|
||||
});
|
||||
r.planes.forEach(pl => {
|
||||
const a0 = ctr + pl.p0, a1 = ctr + pl.p1, mid = (a0 + a1) / 2;
|
||||
// Hoogte net binnen het vlak (op de nok geeft topAt het hoogste vlak).
|
||||
const inward = (t) => t + Math.sign(mid - t) * 0.5;
|
||||
const corners = sh.axis === 'x'
|
||||
? [[a0, r.rect.z0], [a1, r.rect.z0], [a1, r.rect.z1], [a0, r.rect.z1]]
|
||||
: [[r.rect.x0, a0], [r.rect.x1, a0], [r.rect.x1, a1], [r.rect.x0, a1]];
|
||||
pushRoof(corners.map(([x, z]) => ({ x, y: sh.axis === 'x' ? r.topAt(inward(x), z) : r.topAt(x, inward(z)), z })));
|
||||
});
|
||||
});
|
||||
|
||||
// Dakrand: buitenvlak van elk boeideel / elke daktrim.
|
||||
// Dakrand: elk boeideel / elke daktrim zoals je hem van buiten ziet: de
|
||||
// bovenkant volgt het dak (sg.topAt, langs de ribbe bemonsterd, zodat
|
||||
// knikken van een kopgevel - zadel, mansarde, wolfsdak - meekomen), en
|
||||
// telt zowel de dakkant als de buitenkant mee (bij een hellend dak ligt
|
||||
// de buitenkant lager, de bovenkant van het boeideel dekt de gevel af).
|
||||
const edge = parts.roofEdge;
|
||||
if (edge) {
|
||||
const fc = (RoofModel.FASCIA_COLORS[edge.fasciaColor] || RoofModel.FASCIA_COLORS.antraciet).hex;
|
||||
const lerp = (a, b, t) => ({ x: a.x + (b.x - a.x) * t, z: a.z + (b.z - a.z) * t });
|
||||
edge.segments.forEach(sg => {
|
||||
const h = sg.fascia ? edge.fasciaHeight : edge.trimHeight + edge.edgeAbove;
|
||||
const A = sg.outerA, B = sg.outerB, tA = sg.topAt(A), tB = sg.topAt(B);
|
||||
const pts = [{ x: A.x, y: tA, z: A.z }, { x: B.x, y: tB, z: B.z }, { x: B.x, y: tB - h, z: B.z }, { x: A.x, y: tA - h, z: A.z }];
|
||||
faces.push({ pts, n: { x: sg.normal.x, z: sg.normal.z }, color: sg.fascia ? fc : 0xb8bcc0, bias: 3 });
|
||||
const n = Math.max(1, Math.ceil(Math.hypot(sg.outerB.x - sg.outerA.x, sg.outerB.z - sg.outerA.z) / 100));
|
||||
const top = [], bottom = [];
|
||||
for (let k = 0; k <= n; k++) {
|
||||
const t = k / n, I = lerp(sg.a, sg.b, t), O = lerp(sg.outerA, sg.outerB, t);
|
||||
const tI = sg.topAt(I), tO = sg.topAt(O);
|
||||
top.push({ x: O.x, y: Math.max(tI, tO), z: O.z });
|
||||
bottom.push({ x: O.x, y: Math.min(tI, tO) - h, z: O.z });
|
||||
}
|
||||
faces.push({ pts: top.concat(bottom.reverse()), n: { x: sg.normal.x, z: sg.normal.z }, color: sg.fascia ? fc : 0xb8bcc0, bias: 3 });
|
||||
});
|
||||
}
|
||||
return faces;
|
||||
@@ -292,7 +319,7 @@ window.HousePdf = (() => {
|
||||
seen.add(key);
|
||||
items.push(fc.type === 'steen'
|
||||
? { label: 'Gevel', value: `Steen ${fc.stone}`, thumb: WallModel.stoneThumbUrl(fc.stone, 64), color: WallModel.STONE_COLORS[fc.stone] }
|
||||
: { label: 'Gevel', value: `Rabat (${(WallModel.CLADDING_DIRECTIONS[st.cladding] || st.cladding).toLowerCase()})`, color: VIEW_COLORS.rabat });
|
||||
: { label: 'Gevel', value: `Rabat ${((WallModel.RABAT_COLORS[st.facade.rabatColor] || {}).label || '').toLowerCase()} (${(WallModel.CLADDING_DIRECTIONS[st.cladding] || st.cladding).toLowerCase()})`, color: WallModel.rabatHex(st.facade.rabatColor) });
|
||||
}));
|
||||
if (st.trasraam.enabled) {
|
||||
const tf = st.trasraam.finish;
|
||||
@@ -420,6 +447,80 @@ window.HousePdf = (() => {
|
||||
PdfSheet.titleBlock(doc, sheetOpts.project, sheetOpts);
|
||||
}
|
||||
|
||||
// Blad voor het bovenste deel van een wolfsdak (RoofModel.computeCap):
|
||||
// bovenaanzicht van het houtwerk (sporen, hoekkepers, nokbalk, kopbalken)
|
||||
// op de dakvlakken, en de stuklijst.
|
||||
const CAP_KINDS = { spoor: 'Spoor', hoekkeper: 'Hoekkeper', nokbalk: 'Nokbalk', kopbalk: 'Kopbalk' };
|
||||
function drawCapSheet(doc, u, sheetOpts) {
|
||||
const { RED, BLUE, BLACK, GREY } = PdfSheet.COLORS;
|
||||
const ur = u.roof.unitRoof, c = ur.cap;
|
||||
// (p, q) -> plattegrond (x, z), unit-lokaal mm.
|
||||
const xz = (pt) => ur.alongX ? { x: pt.x, z: pt.z } : { x: pt.z, z: pt.x };
|
||||
const band = [{ x: c.pA, z: c.q0 }, { x: c.pB, z: c.q0 }, { x: c.pB, z: c.q1 }, { x: c.pA, z: c.q1 }].map(xz);
|
||||
const xs = band.map(p => p.x), zs = band.map(p => p.z);
|
||||
const X0 = 20, X1 = 275, Y0 = 32, Y1 = 230;
|
||||
const sc = Math.min((X1 - X0) / (Math.max(...xs) - Math.min(...xs)), (Y1 - Y0) / (Math.max(...zs) - Math.min(...zs)));
|
||||
const ox = (X0 + X1) / 2 - (Math.min(...xs) + Math.max(...xs)) / 2 * sc, oz = (Y0 + Y1) / 2 - (Math.min(...zs) + Math.max(...zs)) / 2 * sc;
|
||||
const P = (p) => [ox + p.x * sc, oz + p.z * sc];
|
||||
const poly = (pts, style) => {
|
||||
const q = pts.map(P);
|
||||
doc.lines(q.slice(1).map((b, k) => [b[0] - q[k][0], b[1] - q[k][1]]), q[0][0], q[0][1], [1, 1], style, true);
|
||||
};
|
||||
doc.setTextColor(...BLACK);
|
||||
doc.setFontSize(11);
|
||||
doc.text('Bovenaanzicht bovenste deel (wolfseinden en nok)', X0, 20);
|
||||
// Dakvlakken (grijs) en de band.
|
||||
doc.setDrawColor(...GREY);
|
||||
doc.setLineWidth(0.15);
|
||||
c.regions.forEach(rg => poly(rg.poly.map(xz), 'S'));
|
||||
// Balken (rood, plattegrond: rechthoek van balkbreedte rond de hartlijn).
|
||||
doc.setDrawColor(...RED);
|
||||
doc.setLineWidth(0.2);
|
||||
c.beams.forEach(bm => poly(bm.poly.map(xz), 'S'));
|
||||
// Maten: lengte en diepte van de band, diepte van een wolfseind.
|
||||
doc.setDrawColor(...BLUE);
|
||||
doc.setTextColor(...BLUE);
|
||||
doc.setFontSize(7);
|
||||
const bx0 = Math.min(...xs), bx1 = Math.max(...xs), bz0 = Math.min(...zs), bz1 = Math.max(...zs);
|
||||
const [ax, ay] = P({ x: bx0, z: bz1 }), [bx, by] = P({ x: bx1, z: bz1 });
|
||||
PdfSheet.dimLine(doc, ax, ay + 8, bx, by + 8);
|
||||
doc.text(`${Math.round(bx1 - bx0)}`, (ax + bx) / 2, ay + 7, { align: 'center' });
|
||||
const [cx0, cy0] = P({ x: bx1, z: bz0 }), [cx1, cy1] = P({ x: bx1, z: bz1 });
|
||||
PdfSheet.dimLine(doc, cx0 + 8, cy0, cx1 + 8, cy1);
|
||||
doc.text(`${Math.round(bz1 - bz0)}`, cx0 + 9.5, (cy0 + cy1) / 2, { angle: 90 });
|
||||
|
||||
// Stuklijst en kerngegevens.
|
||||
const groups = new Map();
|
||||
c.beams.forEach(bm => {
|
||||
const key = `${bm.kind}|${Math.round(bm.length / 10) * 10}|${bm.angle}`;
|
||||
const g = groups.get(key) || { kind: bm.kind, length: Math.round(bm.length / 10) * 10, angle: bm.angle, n: 0 };
|
||||
g.n++;
|
||||
groups.set(key, g);
|
||||
});
|
||||
const order = Object.keys(CAP_KINDS);
|
||||
const rows = [...groups.values()].sort((a, b) => order.indexOf(a.kind) - order.indexOf(b.kind) || b.length - a.length)
|
||||
.map(g => [CAP_KINDS[g.kind] || g.kind, String(g.length), String(g.angle).replace('.', ','), String(g.n)]);
|
||||
const H = (p) => RoofModel.profileHeight(ur.shape.profile, p);
|
||||
const deg = (t) => String(Math.round(Math.atan(t) * 1800 / Math.PI) / 10).replace('.', ',');
|
||||
const hip = c.regions.find(rg => rg.key === 'h1');
|
||||
const facts = [
|
||||
['Wolfseind (onderkant dak)', `${Math.round(c.hipH)} mm`],
|
||||
['Nok (onderkant dak)', `${Math.round(H(c.pR))} mm`],
|
||||
['Helling dakvlakken', `${deg((H(c.pR) - H(c.pA)) / (c.pR - c.pA))}°`],
|
||||
['Helling wolfseinden', hip ? `${deg(Math.tan(Math.acos(hip.cos)))}°` : '-'],
|
||||
['Balken', `${c.joistW} x ${c.joistH} mm`],
|
||||
['Oppervlak (schuin)', `${fmt(c.specs.osbAreaM2, 2)} m²`],
|
||||
['Hout', `${fmt(c.specs.joistWoodM3, 3)} m³`]
|
||||
];
|
||||
doc.setTextColor(...BLACK);
|
||||
let y = PdfSheet.table(doc, 290, 20, [0, 52], ['Gegeven', 'Waarde'], facts, { title: 'Wolfsdak, bovenste deel', fontSize: 7, rowH: 4.6, lastColW: 40 });
|
||||
PdfSheet.table(doc, 290, y + 6, [0, 32, 56, 78], ['Soort', 'Lengte (mm)', 'Hoek (°)', 'Aantal'], rows, { title: 'Stuklijst balken', fontSize: 7, rowH: 4.6, lastColW: 14, maxY: 240 });
|
||||
doc.setFontSize(7);
|
||||
doc.setTextColor(...GREY);
|
||||
doc.text('Hoogtes vanaf bovenkant vloer. Lengte = langs de balk; hoek = helling van de balk (zaagsnede aan beide koppen). Grijs = dakvlakken, rood = houtwerk.', X0, 240);
|
||||
PdfSheet.titleBlock(doc, sheetOpts.project, sheetOpts);
|
||||
}
|
||||
|
||||
function drawTotalsSheet(doc, totals, sheetOpts) {
|
||||
const { BLACK } = PdfSheet.COLORS;
|
||||
doc.setFont('helvetica', 'normal');
|
||||
@@ -476,7 +577,7 @@ window.HousePdf = (() => {
|
||||
}
|
||||
|
||||
// Eigen blad na de totalen: zaaglijst vuren (in kolommen) + zaagmachine.
|
||||
function drawSawSheet(doc, totals, sheetOpts) {
|
||||
function drawSawSheet(doc, totals, cost, sheetOpts) {
|
||||
const { BLACK } = PdfSheet.COLORS;
|
||||
doc.setFont('helvetica', 'normal');
|
||||
doc.setTextColor(...BLACK);
|
||||
@@ -496,10 +597,10 @@ window.HousePdf = (() => {
|
||||
|
||||
// TIJDELIJK (verwijderen voor productie): zaagmachine-overzicht.
|
||||
const sw = totals.saw;
|
||||
const hm = (h) => `${Math.floor(h)}:${String(Math.round((h % 1) * 60)).padStart(2, '0')}`;
|
||||
PdfSheet.table(doc, 290, 30, [0, 55], ['Onderdeel', 'Waarde'], [
|
||||
['Zaagmeters', fmt(sw.meters, 1) + ' m'],
|
||||
[`Zaagtijd (${HouseModel.SAW_M_PER_H} m/u)`, hm(sw.hours) + ' u:mm'],
|
||||
['Zaagtijd robot', CostModel.uur(cost.saw.robotSec / 3600) + ' uur'],
|
||||
[`Zaagtijd handmatig (\u00d7${cost.settings.zagen.handFactor})`, CostModel.uur(cost.saw.handSec / 3600) + ' uur'],
|
||||
['Zaagsneden', sw.cuts + ' st.'],
|
||||
['Verschillende hoeken', String(sw.angles.length)]
|
||||
].concat(sw.angles.map(a => [a.deg === 0 ? 'Haaks (0\u00b0)' : `Schuin ${fmt(a.deg, 1)}\u00b0`, a.count + ' sneden'])), tableOpts('Zaagmachine (tijdelijk)'));
|
||||
@@ -507,12 +608,134 @@ window.HousePdf = (() => {
|
||||
PdfSheet.titleBlock(doc, sheetOpts.project, sheetOpts);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Kostenraming (CostModel): samenvatting + regels per element.
|
||||
// ------------------------------------------------------------
|
||||
const COST_ROW_H = 3.4;
|
||||
// Kolommen van een detailblad: x, onderkant (rechts het titelblok).
|
||||
const COST_COLS = [{ x: 15, yMax: 278 }, { x: 150, yMax: 278 }, { x: 285, yMax: 246 }];
|
||||
const COST_Y0 = 30;
|
||||
const clip = (t, n) => (t.length > n ? t.slice(0, n - 1) + '\u2026' : t);
|
||||
// Platte regels voor de detailbladen: kop per element, dan de regels.
|
||||
function costDetailRows(cost) {
|
||||
return cost.elements.flatMap(el => elementCostRows(el));
|
||||
}
|
||||
// Kop + regels van één element.
|
||||
function elementCostRows(el) {
|
||||
const rows = [];
|
||||
if (el.lines.length) {
|
||||
rows.push({ head: true, label: `${el.element} ${el.label}`, mat: CostModel.eur(el.mat), time: CostModel.uur(el.sec / 3600) });
|
||||
el.lines.forEach(l => rows.push({
|
||||
label: l.label, qty: `${fmt(l.qty, ['st.', 'sneden', 'sectie'].includes(l.unit) ? 0 : 2)} ${l.unit}`,
|
||||
mat: l.mat ? CostModel.eur(l.mat, 2) : '', time: l.sec ? CostModel.uur(l.sec / 3600) : ''
|
||||
}));
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
// Kostenblok op het blad van het onderdeel zelf (onder de hoofdtekening,
|
||||
// links van het titelblok). Te veel regels: rest op het detailblad.
|
||||
function drawElementCost(doc, el, extraLabel) {
|
||||
if (!el || !el.lines.length) return;
|
||||
const x = 132, y = 236, yMax = 287;
|
||||
const rows = elementCostRows(el);
|
||||
rows[0] = Object.assign({}, rows[0], { label: `Kosten ${el.element}${extraLabel ? ' (' + extraLabel + ')' : ''}` });
|
||||
rows.push({ head: true, label: 'Kostprijs (incl. verlies, bezetting, indirect, QA, afschrijving)', mat: CostModel.eur(el.kostprijs), time: '' });
|
||||
const fit = Math.floor((yMax - y - 5) / COST_ROW_H);
|
||||
let shown = rows;
|
||||
if (rows.length > fit) {
|
||||
shown = rows.slice(0, fit - 2).concat([{ label: `+ ${rows.length - fit + 1} regels: zie kostenblad detail`, mat: '', time: '' }, rows[rows.length - 1]]);
|
||||
}
|
||||
drawCostTable(doc, x, y, shown);
|
||||
}
|
||||
// Verdeelt de regels over bladen (kolommen vullen van boven naar beneden).
|
||||
function paginateCostRows(rows) {
|
||||
const pages = [];
|
||||
let i = 0;
|
||||
while (i < rows.length) {
|
||||
const cols = COST_COLS.map(c => {
|
||||
const n = Math.floor((c.yMax - COST_Y0 - 6) / COST_ROW_H);
|
||||
const chunk = rows.slice(i, i + n);
|
||||
i += chunk.length;
|
||||
return chunk;
|
||||
});
|
||||
pages.push(cols);
|
||||
}
|
||||
return pages;
|
||||
}
|
||||
function drawCostTable(doc, x, y, rows) {
|
||||
const { BLACK, GREY } = PdfSheet.COLORS;
|
||||
doc.setFontSize(6.5);
|
||||
doc.setTextColor(...GREY);
|
||||
doc.text('Omschrijving', x, y); doc.text('Hoeveelheid', x + 84, y, { align: 'right' });
|
||||
doc.text('Materiaal', x + 104, y, { align: 'right' }); doc.text('Manuur', x + 124, y, { align: 'right' });
|
||||
doc.setDrawColor(...GREY); doc.setLineWidth(0.15);
|
||||
doc.line(x, y + 1.5, x + 126, y + 1.5);
|
||||
y += 5;
|
||||
rows.forEach(r => {
|
||||
doc.setTextColor(...BLACK);
|
||||
doc.setFont('helvetica', r.head ? 'bold' : 'normal');
|
||||
doc.text(clip(r.label, r.head ? 60 : 52), r.head ? x : x + 2, y);
|
||||
if (r.qty) doc.text(r.qty, x + 84, y, { align: 'right' });
|
||||
doc.text(r.mat, x + 104, y, { align: 'right' });
|
||||
doc.text(r.time, x + 124, y, { align: 'right' });
|
||||
y += COST_ROW_H;
|
||||
});
|
||||
doc.setFont('helvetica', 'normal');
|
||||
}
|
||||
|
||||
function drawCostSheet(doc, cost, sheetOpts) {
|
||||
const { BLACK } = PdfSheet.COLORS;
|
||||
const s = cost.summary, S = cost.settings, eur = (n) => CostModel.eur(n);
|
||||
doc.setFont('helvetica', 'normal');
|
||||
doc.setTextColor(...BLACK);
|
||||
doc.setFontSize(14);
|
||||
doc.text('Kostenraming (indicatief)', 15, 18);
|
||||
const tableOpts = (title) => ({ title, fontSize: 7, rowH: 4.6, lastColW: 24 });
|
||||
let y = PdfSheet.table(doc, 15, 30, [0, 82], ['Post', 'Bedrag'],
|
||||
s.rows.map(r => [r.label, eur(r.amount)]).concat([['Totaal incl. btw', eur(s.inclBtw)]]), tableOpts('Samenvatting'));
|
||||
y = PdfSheet.table(doc, 15, y + 6, [0, 82], ['Groep', 'Materiaal'],
|
||||
cost.categories.filter(r => r.mat > 0).map(r => [r.name, eur(r.mat)]), tableOpts('Materiaal per groep'));
|
||||
PdfSheet.table(doc, 15, y + 6, [0, 82], ['Werkzaamheid', 'Manuur'],
|
||||
cost.activities.filter(r => r.sec > 0).map(r => [r.name, CostModel.uur(r.sec / 3600)])
|
||||
.concat([[`Toeslag indirect ${S.arbeid.toeslagUren}%`, CostModel.uur(s.hoursIndirect)], [`QA ${S.arbeid.qa}%`, CostModel.uur(s.hoursQa)], ['Totaal', CostModel.uur(s.hours)]]),
|
||||
tableOpts('Arbeid per werkzaamheid'));
|
||||
|
||||
PdfSheet.table(doc, 130, 30, [0, 72, 97, 122], ['Element / onderdeel', 'Materiaal', 'Manuur', 'Kostprijs*'],
|
||||
cost.elements.filter(el => el.lines.length).map(el => [clip(`${el.element} ${el.label}`, 48), eur(el.mat), CostModel.uur(el.sec / 3600), eur(el.kostprijs)]),
|
||||
Object.assign(tableOpts('Per element'), { maxY: 278 }));
|
||||
PdfSheet.table(doc, 290, 30, [0, 40, 65, 88], ['Unit', 'Materiaal', 'Manuur', 'Kostprijs*'],
|
||||
cost.units.map(u => [u.id ? `Unit ${u.id}` : 'Woning-breed', eur(u.mat), CostModel.uur(u.sec / 3600), eur(u.kostprijs)]), tableOpts('Per unit'));
|
||||
doc.setFontSize(6.5);
|
||||
doc.text('* Kostprijs: materiaal incl. verlies en klein materiaal + arbeid (manuren incl. bezetting, indirect en QA) + afschrijving/huisvesting', 290, 236);
|
||||
doc.text(' (+ transport per unit, kraan bij woning-breed); zonder algemene kosten, marge en btw.', 290, 240);
|
||||
doc.text('Schattingen op basis van Instellingen > Kosten & tijden; bedragen excl. btw tenzij vermeld.', 290, 244);
|
||||
PdfSheet.titleBlock(doc, sheetOpts.project, sheetOpts);
|
||||
}
|
||||
|
||||
function drawCostDetailSheet(doc, cols, sheetOpts) {
|
||||
doc.setFont('helvetica', 'normal');
|
||||
doc.setTextColor(...PdfSheet.COLORS.BLACK);
|
||||
doc.setFontSize(14);
|
||||
doc.text('Kostenraming per element (detail)', 15, 18);
|
||||
cols.forEach((rows, i) => { if (rows.length) drawCostTable(doc, COST_COLS[i].x, COST_Y0, rows); });
|
||||
PdfSheet.titleBlock(doc, sheetOpts.project, sheetOpts);
|
||||
}
|
||||
|
||||
function generate(state) {
|
||||
const parts = HouseModel.computeParts(state, {
|
||||
wallMaterials: WallModel.createMaterials(), floorMaterials: FloorModel.createMaterials(), roofMaterials: RoofModel.createMaterials()
|
||||
});
|
||||
const totals = HouseModel.computeTotals(parts);
|
||||
const sheets = sheetList(parts);
|
||||
const cost = CostModel.compute(parts, totals);
|
||||
const costOf = (element) => cost.elements.find(el => el.element === element);
|
||||
const drawWallWithCost = (sheet, opts) => {
|
||||
WallPdf.drawSheet(doc, sheet.piece.config, sheet.piece.frame, Object.assign({ buitenMateriaal: sheet.piece.buitenMateriaal, cladding: sheet.piece.cladding, trasraam: sheet.piece.trasraam, skinExt: sheet.piece.skinExt, facade: sheet.piece.facade, skinFrom: sheet.piece.skinFrom, lamps: sheet.piece.lamps || [] }, opts));
|
||||
drawElementCost(doc, costOf(sheet.element));
|
||||
};
|
||||
const detailPages = paginateCostRows(costDetailRows(cost));
|
||||
sheets.push({ kind: 'cost', element: 'KOST', label: 'Kostenraming' });
|
||||
detailPages.forEach((cols, i) => sheets.push({ kind: 'costdetail', element: `KOST-${i + 1}`, label: 'Kostenraming detail' + (detailPages.length > 1 ? ` (${i + 1})` : ''), cols }));
|
||||
const project = state.project;
|
||||
const doc = PdfSheet.createDoc();
|
||||
|
||||
@@ -521,14 +744,24 @@ window.HousePdf = (() => {
|
||||
const opts = { project, titel: sheet.label, element: sheet.element, blad: `${i + 1} / ${sheets.length}` };
|
||||
if (sheet.kind === 'views') drawViewsSheet(doc, parts, state, opts);
|
||||
else if (sheet.kind === 'plan') drawPlanSheet(doc, parts, sheets, opts);
|
||||
else if (sheet.kind === 'floor') FloorPdf.drawSheet(doc, sheet.unit.floorData.calcData, sheet.unit.floorData.resolvedOpenings, opts);
|
||||
else if (sheet.kind === 'roof') FloorPdf.drawSheet(doc, sheet.plane.data.calcData, sheet.plane.data.resolvedOpenings, Object.assign({ roof: true }, opts));
|
||||
else if (sheet.kind === 'wall') WallPdf.drawSheet(doc, sheet.piece.config, sheet.piece.frame, Object.assign({ buitenMateriaal: sheet.piece.buitenMateriaal, cladding: sheet.piece.cladding, trasraam: sheet.piece.trasraam, skinExt: sheet.piece.skinExt, facade: sheet.piece.facade, skinFrom: sheet.piece.skinFrom, lamps: sheet.piece.lamps || [] }, opts));
|
||||
else if (sheet.kind === 'saw') drawSawSheet(doc, totals, opts);
|
||||
else if (sheet.kind === 'floor') {
|
||||
FloorPdf.drawSheet(doc, sheet.unit.floorData.calcData, sheet.unit.floorData.resolvedOpenings, opts);
|
||||
drawElementCost(doc, costOf(sheet.element));
|
||||
} else if (sheet.kind === 'roof') {
|
||||
FloorPdf.drawSheet(doc, sheet.plane.data.calcData, sheet.plane.data.resolvedOpenings, Object.assign({ roof: true }, opts));
|
||||
// Dakkosten per unit (alle dakvlakken samen): op het eerste dakvlak.
|
||||
if (/-D1$/.test(sheet.element)) drawElementCost(doc, costOf(sheet.element.replace(/\d+$/, '')), 'alle dakvlakken');
|
||||
}
|
||||
else if (sheet.kind === 'cap') drawCapSheet(doc, sheet.unit, opts);
|
||||
else if (sheet.kind === 'wall') drawWallWithCost(sheet, opts);
|
||||
else if (sheet.kind === 'saw') drawSawSheet(doc, totals, cost, opts);
|
||||
else if (sheet.kind === 'elektra') ElektraPdf.drawSheet(doc, parts.elektra, opts);
|
||||
else if (sheet.kind === 'cost') drawCostSheet(doc, cost, opts);
|
||||
else if (sheet.kind === 'costdetail') drawCostDetailSheet(doc, sheet.cols, opts);
|
||||
else drawTotalsSheet(doc, totals, opts);
|
||||
});
|
||||
return doc;
|
||||
}
|
||||
|
||||
return { generate, sheetList, materialThumbUrls };
|
||||
return { generate, sheetList, materialThumbUrls, viewFaces, VIEWS };
|
||||
})();
|
||||
|
||||
+1062
-368
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user