v0.6.0: ruimtes (verfemmer: functie, netto m², vloerafwerking, FIBO-wandpanelen, eigen verfkleur, vals plafond, ruimtestaat op de PDF), inrichting (sanitair, keuken, kookeiland, kasten, bed, zithoek, boiler; slepen met snappen op het wandvlak, draaien, kosten), kozijnen met deurrichting, glasdeur en schuifdeuren (inbouw/opbouw, deuren open in de loopmodus) plus dagkanten, gevel stucwerk en wandstukken splitsen met eigen gevel, dak over de hele woning met overkapping, geschilderd plafond, model Amstel als nieuw ontwerp, kosten en prijzen uit kosten/intern.csv en kosten/leveranciers.csv (CostCsv); easter egg verwijderd; overbodige comments opgeschoond (alleen copyright, uitzonderingen en valkuilen blijven)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b5510d56c7
commit
ceda0b554d
+78
-80
@@ -4,17 +4,6 @@
|
||||
* Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming.
|
||||
* Zie LICENTIE.md.
|
||||
*/
|
||||
// ============================================================
|
||||
// house/house-pdf.js — HousePdf: de samenhangende woning-PDF.
|
||||
// Blad 1 aanzichten noord/oost/zuid/west (vlakke "render", vectorvormen)
|
||||
// Blad 2 plattegrond woning (units, wandstukken, naden, binnenwanden)
|
||||
// daarna per unit: vloer (FloorPdf) + elk wandstuk (WallPdf)
|
||||
// daarna elke binnenwand (WallPdf)
|
||||
// daarna totalen (balken, inkoop, glaswol, plaatwerk, gewicht, dakrand)
|
||||
// laatste zaaglijst vuren + zaagmachine-overzicht (tijdelijk)
|
||||
// De losse bladen komen uit de gedeelde WallPdf/FloorPdf.
|
||||
// Plain script, geladen na house-model.js.
|
||||
// ============================================================
|
||||
|
||||
window.HousePdf = (() => {
|
||||
const KIND_LABELS = {
|
||||
@@ -29,14 +18,11 @@ window.HousePdf = (() => {
|
||||
return n.toLocaleString('nl-NL', { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
|
||||
}
|
||||
|
||||
// Rechthoek van een wandstuk (bovenaanzicht) als 4 hoekpunten.
|
||||
function pieceCorners(place, lx0, lx1) {
|
||||
const t = place.thickness / 2;
|
||||
return [[lx0, -t], [lx1, -t], [lx1, t], [lx0, t]].map(([lx, lz]) => UnitModel.localToWorld(place, lx, lz));
|
||||
}
|
||||
|
||||
// Bladvolgorde + elementnummers; gedeeld door generate() en de
|
||||
// onderdelenlijst op blad 1.
|
||||
function sheetList(parts) {
|
||||
const list = [
|
||||
{ kind: 'views', element: VIEWS_ELEMENT, label: 'Aanzichten woning' },
|
||||
@@ -46,7 +32,6 @@ 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 }));
|
||||
@@ -78,7 +63,6 @@ window.HousePdf = (() => {
|
||||
doc.lines(pts.slice(1).map((p, i) => [px(p.x) - px(pts[i].x), py(p.z) - py(pts[i].z)]), px(pts[0].x), py(pts[0].z), [1, 1], style, true);
|
||||
};
|
||||
|
||||
// Vloeren + unit-letter
|
||||
rects.forEach(r => {
|
||||
doc.setDrawColor(...GREY);
|
||||
doc.setLineWidth(0.2);
|
||||
@@ -88,7 +72,13 @@ window.HousePdf = (() => {
|
||||
doc.text(r.id, px(r.cx), py(r.cz) + 2, { align: 'center' });
|
||||
});
|
||||
|
||||
// Wandstukken (buiten + binnen) met openingen
|
||||
const R = parts.rooms;
|
||||
const light = (hex, k) => [1, 3, 5].map(i => Math.round(255 - (255 - parseInt(hex.slice(i, i + 2), 16)) * k));
|
||||
R.rooms.forEach(r => {
|
||||
doc.setFillColor(...light(r.color, 0.35));
|
||||
r.rects.forEach(c => doc.rect(px(c.x0), py(c.z0), (c.x1 - c.x0) * scale, (c.z1 - c.z0) * scale, 'F'));
|
||||
});
|
||||
|
||||
const drawPiece = (p) => {
|
||||
doc.setDrawColor(...RED);
|
||||
doc.setLineWidth(0.3);
|
||||
@@ -101,11 +91,33 @@ window.HousePdf = (() => {
|
||||
poly(c, 'FD');
|
||||
doc.line(px(c[0].x), py(c[0].z), px(c[2].x), py(c[2].z));
|
||||
doc.line(px(c[1].x), py(c[1].z), px(c[3].x), py(c[3].z));
|
||||
if (op.fill) {
|
||||
const W = p.place.width, T = p.place.thickness;
|
||||
KozijnModel.planSymbol(op, op.fill, { zIn: -T / 2, zOut: T / 2 }).forEach(sh => {
|
||||
doc.setLineWidth(sh.kind === 'leaf' ? 0.25 : 0.12);
|
||||
doc.setLineDashPattern(sh.kind === 'arc' || sh.kind === 'rail' ? [0.8, 0.8] : [], 0);
|
||||
pline(sh.pts.map(pt => UnitModel.localToWorld(p.place, -W / 2 + op.x + pt.x, pt.z)), !!sh.closed);
|
||||
});
|
||||
doc.setLineDashPattern([], 0);
|
||||
doc.setLineWidth(0.2);
|
||||
}
|
||||
});
|
||||
};
|
||||
const pline = (pts, closed) => {
|
||||
if (pts.length < 2) return;
|
||||
doc.lines(pts.slice(1).map((q, i) => [px(q.x) - px(pts[i].x), py(q.z) - py(pts[i].z)]), px(pts[0].x), py(pts[0].z), [1, 1], 'S', closed);
|
||||
};
|
||||
doc.setDrawColor(110, 110, 110);
|
||||
(parts.fixtures || []).forEach(it => FixtureModel.planShapes(it).forEach(sh => {
|
||||
doc.setLineWidth(sh.kind === 'thin' ? 0.1 : 0.18);
|
||||
doc.setLineDashPattern(sh.kind === 'dash' ? [0.8, 0.6] : [], 0);
|
||||
if (sh.circle) doc.circle(px(sh.circle.x), py(sh.circle.z), Math.max(0.2, sh.circle.r * scale), 'S');
|
||||
else if (sh.text) { doc.setFontSize(4); doc.setTextColor(110, 110, 110); doc.text(sh.text, px(sh.at.x), py(sh.at.z) + 0.6, { align: 'center' }); }
|
||||
else pline(sh.pts, !!sh.closed);
|
||||
}));
|
||||
doc.setLineDashPattern([], 0);
|
||||
HouseModel.allWallPieces(parts).forEach(drawPiece);
|
||||
|
||||
// Dak: dakranden (grijs gestippeld), boeideel/daktrim-buitenrand, spots
|
||||
doc.setLineDashPattern([1, 1.2], 0);
|
||||
doc.setDrawColor(...GREY);
|
||||
doc.setLineWidth(0.15);
|
||||
@@ -123,7 +135,6 @@ window.HousePdf = (() => {
|
||||
}
|
||||
doc.setLineDashPattern([], 0);
|
||||
|
||||
// Gevellampen
|
||||
doc.setDrawColor(...BLACK);
|
||||
doc.setFillColor(255, 211, 107);
|
||||
doc.setLineWidth(0.15);
|
||||
@@ -131,17 +142,14 @@ 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);
|
||||
doc.setLineDashPattern([1.5, 1], 0);
|
||||
parts.seams.forEach(s => doc.line(px(s.x0), py(s.z0), px(s.x1), py(s.z1)));
|
||||
doc.setLineDashPattern([], 0);
|
||||
|
||||
// Maatlijnen buiten elk buiten-wandstuk + labels binnenwanden
|
||||
doc.setDrawColor(...BLUE);
|
||||
doc.setTextColor(...BLUE);
|
||||
doc.setLineWidth(0.15);
|
||||
@@ -162,7 +170,30 @@ window.HousePdf = (() => {
|
||||
doc.text(`${p.element}: ${p.place.width}`, px(p.place.cx), py(p.place.cz) - 1.5, { align: 'center', angle: Math.abs(Math.sin(p.place.rotY)) < 0.5 ? 0 : 90 });
|
||||
});
|
||||
|
||||
// Buitenmaten woning
|
||||
R.rooms.concat(R.free).forEach(r => {
|
||||
const x = px(r.label.x), y = py(r.label.z), named = !!r.name;
|
||||
const lines = [[named ? r.name : 'Ruimte (geen functie)', named ? 'bold' : 'italic', 7], [`${fmt(r.areaM2, 2)} m²`, 'normal', 6.5]];
|
||||
if (r.ceilingActive) lines.push([`plafond ${r.ceiling}`, 'normal', 5.5]);
|
||||
doc.setTextColor(...(named ? BLACK : GREY));
|
||||
lines.forEach(([t, style, size], i) => {
|
||||
doc.setFont('helvetica', style);
|
||||
doc.setFontSize(size);
|
||||
doc.text(t, x, y + (i - (lines.length - 1) / 2) * 3 + 1, { align: 'center' });
|
||||
});
|
||||
});
|
||||
doc.setFont('helvetica', 'normal');
|
||||
|
||||
if (R.rooms.length || R.free.length) {
|
||||
doc.setFontSize(6);
|
||||
const fit = (t, mm) => { let v = String(t); while (v.length > 3 && doc.getTextWidth(v) > mm) v = v.slice(0, -2); return v === String(t) ? v : v + '.'; };
|
||||
const rows = R.rooms.map(r => [fit(r.name, 32), fit(r.typeLabel, 26), fmt(r.areaM2, 2), fit(RoomModel.ceilingLabel(r), 30), fit(RoomModel.floorLabel(r), 30), fit(RoomModel.wallLabel(r), 38)])
|
||||
.concat(R.free.map(r => ['Ruimte', '(geen functie)', fmt(r.areaM2, 2), '', '', '']));
|
||||
rows.push(['Totaal netto', '', fmt(R.rooms.concat(R.free).reduce((a, r) => a + r.areaM2, 0), 2), '', '', '']);
|
||||
const many = rows.length > 8;
|
||||
PdfSheet.table(doc, 105, 242, [0, 34, 62, 78, 110, 142], ['Ruimte', 'Functie', 'Netto m²', 'Plafond', 'Vloer', 'Wanden'], rows,
|
||||
{ title: 'Ruimtestaat', titleSize: 8, lastColW: 40, maxY: 287, rowH: many ? 3.1 : 3.8, fontSize: many ? 5.5 : 6 });
|
||||
}
|
||||
|
||||
doc.setDrawColor(...BLUE);
|
||||
doc.setTextColor(...BLUE);
|
||||
doc.setFontSize(7);
|
||||
@@ -172,7 +203,6 @@ window.HousePdf = (() => {
|
||||
PdfSheet.dimLine(doc, leftX, py(bz0), leftX, py(bz1));
|
||||
doc.text('Y: ' + Math.round(bz1 - bz0), leftX - 2, (py(bz0) + py(bz1)) / 2, { align: 'center', angle: 90 });
|
||||
|
||||
// Noordpijl (boven = noord)
|
||||
doc.setFillColor(...BLUE);
|
||||
doc.triangle(drawX1 - 8, drawY0, drawX1 - 11, drawY0 + 9, drawX1 - 5, drawY0 + 9, 'F');
|
||||
doc.setTextColor(...BLUE);
|
||||
@@ -182,16 +212,10 @@ window.HousePdf = (() => {
|
||||
doc.setTextColor(...GREY);
|
||||
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);
|
||||
'boeideel (o = spot), grijs = daktrim, geel blokje = gevellamp, kleurvlak = ruimte (netto m²)', drawX0, drawY1 + 4);
|
||||
if (parts.elektra && parts.elektra.enabled) doc.text(ElektraPdf.PLAN_LEGEND, drawX0, drawY1 + 7.5);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Aanzichten: per windrichting een vlakke "render" van de gevels. Alle
|
||||
// zichtbare vlakken (gevels incl. trasraam en openingen, boeideel/daktrim,
|
||||
// hellende dakvlakken) als polygonen in het aanzicht, van achter naar voren
|
||||
// getekend (schildersalgoritme), in egale kleuren uit het model.
|
||||
// ------------------------------------------------------------
|
||||
const hexRgb = (h) => { const n = parseInt(String(h).replace('#', ''), 16); return [(n >> 16) & 255, (n >> 8) & 255, n & 255]; };
|
||||
const numRgb = (n) => [(n >> 16) & 255, (n >> 8) & 255, n & 255];
|
||||
const VIEW_COLORS = {
|
||||
@@ -205,12 +229,11 @@ window.HousePdf = (() => {
|
||||
{ key: 'west', title: 'Westgevel', d: { x: -1, z: 0 }, u: (p) => p.z }
|
||||
];
|
||||
|
||||
// Alle vlakken in wereldcoördinaten: { pts: [{x, y, z}], n: {x, z} | null (dakvlak: nH), color, bias }.
|
||||
function viewFaces(parts, state) {
|
||||
const faces = [];
|
||||
const wallFace = (p, y0, color, depthBias) => {
|
||||
const W = p.place.width, T = p.place.thickness;
|
||||
const c = WallModel.claddingOffset(p.buitenMateriaal, p.stone), e = p.skinExt || {};
|
||||
const c = WallModel.claddingOffset(p.buitenMateriaal, p.stone, p.stuc), e = p.skinExt || {};
|
||||
const xl = -W / 2 - (e.left > 0 ? e.left + c : 0), xr = W / 2 + (e.right > 0 ? e.right + c : 0);
|
||||
const tl = WallModel.topLine(p.config), up = p.skinAbove || 0;
|
||||
const top = (x) => tl.at(Math.max(-W / 2, Math.min(W / 2, x))) + up;
|
||||
@@ -222,10 +245,9 @@ window.HousePdf = (() => {
|
||||
const toWorld = (p, lx, y, lz) => { const w = UnitModel.localToWorld(p.place, lx, lz); return { x: w.x, y: p.place.floorTop + y, z: w.z }; };
|
||||
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') : WallModel.rabatHex(state.settings.facade.rabatColor);
|
||||
const zOut = p.place.thickness / 2 + WallModel.claddingOffset(p.buitenMateriaal, p.stone, p.stuc);
|
||||
const color = p.stone ? (WallModel.STONE_COLORS[p.stone] || '#a0724f') : p.stuc ? WallModel.stucHex(p.stuc) : 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;
|
||||
if (tr && tr.top > y0) {
|
||||
const W = p.place.width;
|
||||
@@ -234,10 +256,8 @@ window.HousePdf = (() => {
|
||||
const zT = p.place.thickness / 2 + (WallModel.isStoneFinish(tr.finish) ? WallModel.STONE.THICK_MM : 12);
|
||||
faces.push({ pts: band.map(q => toWorld(p, q.x, q.y, zT)), n, color: trColor, bias: 1 });
|
||||
}
|
||||
// Openingen (glas / deur), vóór de gevel
|
||||
(p.config.openings || []).forEach(op => {
|
||||
if (WallModel.openingTopMax(op) <= y0) return;
|
||||
// Schuine bovenzijde: bovenkant links/rechts.
|
||||
const x0 = -p.place.width / 2 + op.x, x1 = x0 + op.w, ya = Math.max(y0, op.y);
|
||||
const ybL = Math.max(ya, op.y + op.h), ybR = Math.max(ya, op.y + op.hR);
|
||||
const color = op.type === 'raam' ? VIEW_COLORS.glas : VIEW_COLORS.deur;
|
||||
@@ -247,14 +267,12 @@ window.HousePdf = (() => {
|
||||
parts.units.forEach(u => u.walls.forEach(w => w.pieces.forEach(p => addWall(p, 0))));
|
||||
parts.interior.filter(p => p.skinFrom !== undefined && p.place.normal).forEach(p => addWall(p, p.skinFrom));
|
||||
|
||||
// Hellende dakvlakken (bovenkant, egaal in de kleur van de bedekking).
|
||||
parts.units.forEach(u => {
|
||||
const r = u.roof, sh = r.shape;
|
||||
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;
|
||||
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;
|
||||
let nx = uy * vz - uz * vy, ny = uz * vx - ux * vz, nz = ux * vy - uy * vx;
|
||||
@@ -262,7 +280,6 @@ window.HousePdf = (() => {
|
||||
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);
|
||||
@@ -280,11 +297,6 @@ window.HousePdf = (() => {
|
||||
});
|
||||
});
|
||||
|
||||
// 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;
|
||||
@@ -305,22 +317,25 @@ window.HousePdf = (() => {
|
||||
return faces;
|
||||
}
|
||||
|
||||
// Legenda "Afwerking": gevel(s) (incl. overrides), trasraam, dak per unit,
|
||||
// vloer, boeideel/daktrim en binnenwanden. Per item een voorvertoning
|
||||
// (thumb, via PdfSheet.preloadImage vooraf geladen) of een kleurvlak.
|
||||
function materialLegend(state, parts) {
|
||||
const items = [];
|
||||
const st = state.settings;
|
||||
const seen = new Set();
|
||||
const facades = [];
|
||||
state.units.forEach(u => Object.keys(u.unit.walls).forEach(side => {
|
||||
const fc = HouseModel.facadeFor(state, u, side);
|
||||
const key = fc.type === 'steen' ? 'steen:' + fc.stone : 'rabat';
|
||||
facades.push(HouseModel.facadeFor(state, u, side));
|
||||
UnitModel.normalizeSplits(u.unit.walls[side].splits).filter(sp => sp.facade).forEach(sp => facades.push(HouseModel.facadeFor(state, u, side, sp.facade)));
|
||||
}));
|
||||
facades.forEach(fc => {
|
||||
const key = fc.type === 'steen' ? 'steen:' + fc.stone : fc.type === 'stuc' ? 'stuc:' + fc.stuc : 'rabat';
|
||||
if (seen.has(key)) return;
|
||||
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.RABAT_COLORS[st.facade.rabatColor] || {}).label || '').toLowerCase()} (${(WallModel.CLADDING_DIRECTIONS[st.cladding] || st.cladding).toLowerCase()})`, color: WallModel.rabatHex(st.facade.rabatColor) });
|
||||
}));
|
||||
: fc.type === 'stuc'
|
||||
? { label: 'Gevel', value: `Stucwerk ${((WallModel.STUC_COLORS[fc.stuc] || {}).label || '').toLowerCase()}`, color: WallModel.stucHex(fc.stuc) }
|
||||
: { 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;
|
||||
items.push(WallModel.isStoneFinish(tf)
|
||||
@@ -345,7 +360,6 @@ window.HousePdf = (() => {
|
||||
: { label: 'Binnenwanden', value: 'Spaanplaat', color: '#1e4620' });
|
||||
return items;
|
||||
}
|
||||
// Voorvertoningen die de legenda gebruikt (om vooraf te laden).
|
||||
function materialThumbUrls(state) {
|
||||
return materialLegend(state, null).map(i => i.thumb).filter(Boolean);
|
||||
}
|
||||
@@ -379,7 +393,6 @@ window.HousePdf = (() => {
|
||||
});
|
||||
}
|
||||
|
||||
// Afmetingen (zelfde rijen als het infopaneel in de app), in twee kolommen.
|
||||
function drawDimensions(doc, rows, x0, y0, x1) {
|
||||
const { BLACK, GREY } = PdfSheet.COLORS;
|
||||
doc.setFont('helvetica', 'bold');
|
||||
@@ -403,7 +416,6 @@ window.HousePdf = (() => {
|
||||
const { BLACK } = PdfSheet.COLORS;
|
||||
const faces = viewFaces(parts, state);
|
||||
const groundY = Math.max(...parts.units.map(u => u.layout.floorTop)) - (state.settings.groundLevel || 0);
|
||||
// Per aanzicht de zichtbare vlakken in (u, v) mm, met diepte.
|
||||
const views = VIEWS.map(v => {
|
||||
const dot = (p) => p.x * v.d.x + p.z * v.d.z;
|
||||
const list = faces.filter(f => f.roof ? (f.n.x * v.d.x + f.n.z * v.d.z) > 0.01 : (f.n.x * v.d.x + f.n.z * v.d.z) > 0.5)
|
||||
@@ -413,7 +425,6 @@ window.HousePdf = (() => {
|
||||
return { v, list, u0: Math.min(...us), u1: Math.max(...us), v0: Math.min(...vs), v1: Math.max(...vs) };
|
||||
}).filter(x => x.list.length);
|
||||
|
||||
// Raster 2 x 2 boven het titelblok; één schaal voor alle aanzichten.
|
||||
const X0 = 12, X1 = 408, Y0 = 14, Y1 = 248, gap = 10, titleH = 7;
|
||||
const cellW = (X1 - X0 - gap) / 2, cellH = (Y1 - Y0 - gap) / 2;
|
||||
const scale = Math.min(...views.map(w => Math.min((cellW - 16) / (w.u1 - w.u0), (cellH - titleH - 10) / (w.v1 - w.v0))));
|
||||
@@ -421,7 +432,7 @@ window.HousePdf = (() => {
|
||||
views.forEach((w, i) => {
|
||||
const cx = X0 + (i % 2) * (cellW + gap), cy = Y0 + Math.floor(i / 2) * (cellH + gap);
|
||||
const ox = cx + cellW / 2 - (w.u0 + w.u1) / 2 * scale;
|
||||
const base = cy + cellH - 6; // grondlijn
|
||||
const base = cy + cellH - 6;
|
||||
const P = (p) => [ox + p.u * scale, base - (p.v - groundY) * scale];
|
||||
doc.setTextColor(...BLACK);
|
||||
doc.setFontSize(11);
|
||||
@@ -434,7 +445,6 @@ window.HousePdf = (() => {
|
||||
doc.setFillColor(...col);
|
||||
doc.lines(pts.slice(1).map((q, k) => [q[0] - pts[k][0], q[1] - pts[k][1]]), pts[0][0], pts[0][1], [1, 1], 'FD', true);
|
||||
});
|
||||
// Grondlijn
|
||||
doc.setDrawColor(...VIEW_COLORS.grond);
|
||||
doc.setLineWidth(0.4);
|
||||
doc.line(ox + w.u0 * scale - 6, base, ox + w.u1 * scale + 6, base);
|
||||
@@ -447,14 +457,10 @@ 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);
|
||||
@@ -469,15 +475,12 @@ window.HousePdf = (() => {
|
||||
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);
|
||||
@@ -489,7 +492,6 @@ window.HousePdf = (() => {
|
||||
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}`;
|
||||
@@ -506,7 +508,7 @@ window.HousePdf = (() => {
|
||||
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 dakvlakken', `${deg(c.slope)}°`],
|
||||
['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²`],
|
||||
@@ -558,6 +560,13 @@ window.HousePdf = (() => {
|
||||
const wRows = totals.weights.map(r => [r.label, fmt(r.kg) + ' kg']);
|
||||
wRows.push(['Totaal woning', fmt(totals.totalKg) + ' kg']);
|
||||
y = PdfSheet.table(doc, 120, y, [0, 50], ['Onderdeel', 'Gewicht'], wRows, tableOpts('Gewicht'));
|
||||
if (totals.nokbalken.length) {
|
||||
y += 4;
|
||||
y = PdfSheet.table(doc, 120, y, [0, 22, 60], ['Unit', 'Profiel', 'Lengte, gewicht, vulhout'], totals.nokbalken.map(n => [
|
||||
n.unit, `${n.name} S235${n.ok ? '' : ' (te licht!)'}`,
|
||||
`${fmt(n.lengthM, 2)} m, ${fmt(n.kg)} kg, ${fmt(n.fillM3, 3)} m3${n.below ? `, ${n.below} mm onder plafond` : ''}`]),
|
||||
tableOpts('Stalen nokbalk (IPE, vulhout in de holling)'));
|
||||
}
|
||||
if (totals.roofEdge) {
|
||||
y += 4;
|
||||
const e = totals.roofEdge, L = totals.lamps;
|
||||
@@ -576,7 +585,6 @@ window.HousePdf = (() => {
|
||||
PdfSheet.titleBlock(doc, sheetOpts.project, sheetOpts);
|
||||
}
|
||||
|
||||
// Eigen blad na de totalen: zaaglijst vuren (in kolommen) + zaagmachine.
|
||||
function drawSawSheet(doc, totals, cost, sheetOpts) {
|
||||
const { BLACK } = PdfSheet.COLORS;
|
||||
doc.setFont('helvetica', 'normal');
|
||||
@@ -608,19 +616,13 @@ 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) {
|
||||
@@ -632,14 +634,12 @@ window.HousePdf = (() => {
|
||||
}
|
||||
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: '' });
|
||||
rows.push({ head: true, label: 'Kostprijs', mat: CostModel.eur(el.kostprijs), time: '' });
|
||||
const fit = Math.floor((yMax - y - 5) / COST_ROW_H);
|
||||
let shown = rows;
|
||||
if (rows.length > fit) {
|
||||
@@ -647,7 +647,6 @@ window.HousePdf = (() => {
|
||||
}
|
||||
drawCostTable(doc, x, y, shown);
|
||||
}
|
||||
// Verdeelt de regels over bladen (kolommen vullen van boven naar beneden).
|
||||
function paginateCostRows(rows) {
|
||||
const pages = [];
|
||||
let i = 0;
|
||||
@@ -751,7 +750,6 @@ window.HousePdf = (() => {
|
||||
} else if (sheet.kind === 'roof') {
|
||||
const pu = parts.units.find(u => u.roof.planes.includes(sheet.plane));
|
||||
FloorPdf.drawSheet(doc, sheet.plane.data.calcData, sheet.plane.data.resolvedOpenings, Object.assign({ roof: true, overlay: pu && ElektraPdf.roofOverlay(parts.elektra, pu, sheet.plane) }, 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);
|
||||
|
||||
Reference in New Issue
Block a user