Files
Tectum/js/floor/floor-pdf.js
T

193 lines
8.4 KiB
JavaScript

/*!
* Tectum
* Copyright © 2026 Machiel Heinen.
* Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming.
* Zie LICENTIE.md.
*/
// ============================================================
// floor/floor-pdf.js — ENIGE bron van het vloer-tekenblad (A3 liggend):
// frame- en plaatmateriaal, bovenaanzicht met balken/doorvoeren en
// maatketens, doorsnede en titelblok. Gebruikt door de losse vloer-editor en
// door de unit-PDF.
// Plain script, geladen na floor-model.js en core/pdf-sheet.js.
// ============================================================
window.FloorPdf = (() => {
// ------------------------------------------------------------
// Zelfde opzet als WallPdf (frame-materiaallijst, plaatmateriaallijst,
// titelblok, hoofdaanzicht met maatketens). Layout/kolommen zijn
// afgestemd op de bestaande "Vloer^V-x"-tekeningen (zie examples/) —
// die voorbeelden splitsen een grote vloer in meerdere modules; hier
// wordt bewust altijd maar één, doorlopende module getekend.
// ------------------------------------------------------------
// Tekent één volledig vloerblad op de HUIDIGE pagina van `doc`.
// opts: { project, titel, element, blad, roof }
function drawSheet(doc, calcData, resolvedOpenings, opts) {
const { width, depth, joists, joistW, joistH, underlayThick } = calcData;
const PAGE_H = doc.internal.pageSize.getHeight();
const { RED, BLUE, BLACK, GREY } = PdfSheet.COLORS;
doc.setFont('helvetica', 'normal');
// --- Frame materiaal-tabel (linksboven) — één groep: alle vloerbalken
// hebben in dit model dezelfde doorsnede/lengte. ---
const tblX = [8, 26, 42, 58, 76, 92];
let ty = 14;
doc.setTextColor(...BLACK);
doc.setFontSize(9);
doc.text('Frame materiaal', tblX[0], ty);
ty += 6;
doc.setFontSize(6.5);
['Posnum', 'Dikte', 'Hoogte', 'Lengte', 'Aantal'].forEach((h, i) => doc.text(h, tblX[i], ty));
ty += 1.5;
doc.setDrawColor(...GREY);
doc.setLineWidth(0.15);
doc.line(tblX[0], ty, tblX[4] + 12, ty);
ty += 4;
const rims = calcData.rims || {}, joistLen = calcData.joistLen || depth;
const rimCount = (rims.front ? 1 : 0) + (rims.back ? 1 : 0);
[String(1), String(joistW), String(joistH), String(Math.round(joistLen)), String(joists.length)]
.forEach((v, i) => doc.text(v, tblX[i], ty));
if (rimCount) {
ty += 4.3; // kopbalk(en) op een naad (FloorModel.computeFrame)
[String(2), String(joistW), String(joistH), String(Math.round(width)), String(rimCount)]
.forEach((v, i) => doc.text(v, tblX[i], ty));
}
// --- Plaatmateriaal + isolatie ---
let my = 240;
doc.setFontSize(8.5);
doc.text('Plaat materiaal', tblX[0], my); my += 4.5;
doc.setFontSize(6.5);
['Dikte', 'Hoogte', 'Lengte', 'Materiaal', 'Aantal'].forEach((h, i) => doc.text(h, tblX[i], my));
my += 4;
[String(underlayThick), String(Math.round(depth)), String(Math.round(width)), opts.roof ? 'OSB (plafond)' : 'OSB', '1']
.forEach((v, i) => doc.text(v, tblX[i], my));
if (opts.roof) {
my += 4;
[String(RoofModel.DAKLEER_MM), String(Math.round(depth)), String(Math.round(width)), 'Dakleer', '1']
.forEach((v, i) => doc.text(v, tblX[i], my));
}
my += 6;
doc.text('Isolatie: leegte tussen de balken (spouwvullend)', tblX[0], my);
// --- Titelblok (rechtsonder) ---
PdfSheet.titleBlock(doc, opts.project, { titel: opts.titel, element: opts.element, blad: opts.blad });
// --- Hoofdaanzicht (bovenaanzicht) ---
const drawX0 = 95, drawX1 = 410, drawY0 = 15, drawY1 = 178;
const marginSides = 25, marginTop = 15, marginBottom = 30;
const availW = (drawX1 - drawX0) - marginSides * 2;
const availH = (drawY1 - drawY0) - marginTop - marginBottom;
const scale = Math.min(availW / width, availH / depth);
const originX = drawX0 + marginSides;
const topY = drawY0 + marginTop;
const px = (xmm) => originX + xmm * scale;
const py = (ymm) => topY + ymm * scale; // y=0 (voor) boven, groeit naar beneden
doc.setDrawColor(...GREY);
doc.setLineWidth(0.25);
doc.rect(px(0), py(0), width * scale, depth * scale, 'S');
doc.setDrawColor(...RED);
doc.setLineWidth(0.3);
doc.setTextColor(...BLACK);
doc.setFontSize(5);
const jz0 = rims.front ? joistW : 0;
joists.forEach(x => {
const xLeft = x + width / 2 - joistW / 2;
doc.rect(px(xLeft), py(jz0), joistW * scale, joistLen * scale, 'S');
doc.text('1', px(xLeft + joistW / 2), py(depth / 2), { align: 'center' });
});
[['front', 0], ['back', depth - joistW]].filter(([k]) => rims[k]).forEach(([, z]) => {
doc.rect(px(0), py(z), width * scale, joistW * scale, 'S');
doc.text('2', px(width / 2), py(z + joistW / 2) + 1.2, { align: 'center' });
});
doc.setDrawColor(...RED);
doc.setLineWidth(0.2);
resolvedOpenings.filter(o => !o.removed).forEach(o => {
const cx = px(o.x + width / 2), cy = py(o.y);
doc.circle(cx, cy, o.r * scale, 'S');
doc.setTextColor(...BLACK);
doc.setFontSize(5);
doc.text('#' + o.id, cx, cy - o.r * scale - 2, { align: 'center' });
});
// Extra laag van de aanroeper (bijv. elektra, ElektraPdf): overlay(doc, px, py),
// px/py = mm vanaf linksvoor van het dek -> blad-mm.
if (opts.overlay) opts.overlay(doc, px, py);
const drawDimLine = (x1, y1, x2, y2) => PdfSheet.dimLine(doc, x1, y1, x2, y2);
doc.setDrawColor(...BLUE);
doc.setTextColor(...BLUE);
doc.setLineWidth(0.15);
doc.setFontSize(7);
const dimTopY = py(0) - 7;
drawDimLine(px(0), dimTopY, px(width), dimTopY);
doc.text(String(Math.round(width)), (px(0) + px(width)) / 2, dimTopY - 1.5, { align: 'center' });
const dimLeftX = px(0) - 7;
drawDimLine(dimLeftX, py(0), dimLeftX, py(depth));
doc.text(String(Math.round(depth)), dimLeftX - 2, (py(0) + py(depth)) / 2, { align: 'center', angle: 90 });
// H.o.h.-segmenten + cumulatieve maten onder de tekening — zelfde
// opbouw als de stijl-maatketens in wall.js, nu op de balkposities.
const joistEdges = new Set([0, width]);
joists.forEach(x => joistEdges.add(Math.round(x + width / 2 - joistW / 2)));
const cumPoints = [...joistEdges].filter(v => v >= 0 && v <= width).sort((a, b) => a - b);
doc.setDrawColor(...BLACK);
doc.setTextColor(...BLACK);
doc.setLineWidth(0.1);
doc.setFontSize(5.5);
const segY = py(depth) + 9, cumY = py(depth) + 17;
for (let i = 0; i < cumPoints.length - 1; i++) {
const mid = (cumPoints[i] + cumPoints[i + 1]) / 2;
doc.text(String(cumPoints[i + 1] - cumPoints[i]), px(mid), segY, { align: 'center' });
}
cumPoints.forEach(v => {
doc.text(String(v), px(v), cumY, { align: 'center' });
doc.line(px(v), cumY - 2.5, px(v), py(depth) + 2);
});
// --- Doorsnede (balkprofiel) — kleine profielweergave onderaan,
// analoog aan de bestaande 2D-doorsnede-canvas. ---
const secY0 = Math.min(PAGE_H - 20, py(depth) + 30);
const secScale = Math.min((drawX1 - drawX0 - 40) / width, 18 / (joistH + underlayThick));
const secOriginX = drawX0 + 20;
const secFloorY = secY0 + (joistH + underlayThick) * secScale;
const spx = (xmm) => secOriginX + xmm * secScale;
const spy = (ymm) => secFloorY - ymm * secScale;
// Dak (opts.roof): 180° gespiegeld — OSB onder (plafond), balken
// erboven, dakleer bovenop (RoofModel).
const joistY0 = opts.roof ? underlayThick : 0;
const osbY0 = opts.roof ? 0 : joistH;
doc.setDrawColor(...RED);
doc.setLineWidth(0.2);
joists.forEach(x => {
doc.rect(spx(x + width / 2 - joistW / 2), spy(joistY0 + joistH), joistW * secScale, joistH * secScale, 'S');
});
doc.setDrawColor(...GREY);
doc.rect(spx(0), spy(osbY0 + underlayThick), width * secScale, underlayThick * secScale, 'S');
doc.setTextColor(...BLACK);
doc.setFontSize(6);
if (opts.roof) {
doc.setLineWidth(0.5);
doc.line(spx(0), spy(joistH + underlayThick), spx(width), spy(joistH + underlayThick));
doc.text('Dakleer', spx(width / 2), spy(joistH + underlayThick) - 3, { align: 'center' });
doc.text(`${underlayThick}mm OSB (plafond) — isolatie tussen de balken`, spx(width / 2), spy(0) + 6, { align: 'center' });
} else {
doc.text(`${underlayThick}mm OSB`, spx(width / 2), spy(joistH + underlayThick) - 3, { align: 'center' });
doc.text('Isolatie: leegte tussen de balken', spx(width / 2), spy(0) + 6, { align: 'center' });
}
}
return { drawSheet };
})();