/*! * 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 }; })();