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

155 lines
8.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*!
* Tectum
* Copyright © 2026 Machiel Heinen.
* Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming.
* Zie LICENTIE.md.
*/
// ============================================================
// elektra/elektra-pdf.js — ElektraPdf: elektra in de woning-PDF.
// drawPlan : meterkast, schakelaars (letter), wcd's (groep), lamp-
// letters en leidingen op de plattegrond (blad Plattegrond)
// drawSheet : eigen blad met groepenverklaring, schakelaars, strekkende
// meters per leidingtype, draad, buis en materiaal.
// Alle gegevens komen uit ElektraModel.compute (parts.elektra).
// Plain script, geladen na pdf-sheet.js en elektra-model.js.
// ============================================================
window.ElektraPdf = (() => {
const fmt = (n, d = 1) => Number(n).toFixed(d).replace('.', ',');
// Kleur per leidingtype (ook de legenda).
const RUN_COLORS = { licht: [215, 160, 0], wcd: [205, 55, 35], buiten: [40, 140, 60], schakel: [150, 95, 20] };
const runColor = (type) => RUN_COLORS[type] || RUN_COLORS.schakel;
// px/py: woning-mm -> blad-mm.
function drawPlan(doc, el, px, py) {
if (!el || !el.enabled) return;
const BLACK = PdfSheet.COLORS.BLACK;
// Leidingen (gestippeld, plattegrond-projectie van het pad).
doc.setLineWidth(0.15);
doc.setLineDashPattern([0.8, 0.6], 0);
el.runs.forEach(r => {
doc.setDrawColor(...runColor(r.type));
r.path.slice(1).forEach((b, i) => {
const a = r.path[i];
if (Math.hypot(px(b.x) - px(a.x), py(b.z) - py(a.z)) > 0.05) doc.line(px(a.x), py(a.z), px(b.x), py(b.z));
});
});
doc.setLineDashPattern([], 0);
// Meterkast
const k = el.meterkast;
if (k) {
doc.setDrawColor(...BLACK);
doc.setFillColor(90, 90, 90);
doc.setLineWidth(0.2);
const c = k.corners;
doc.lines(c.slice(1).map((p, i) => [px(p.x) - px(c[i].x), py(p.z) - py(c[i].z)]), px(c[0].x), py(c[0].z), [1, 1], 'FD', true);
doc.setFontSize(5);
doc.setTextColor(...BLACK);
const m = { x: (c[0].x + c[2].x) / 2 + k.face.n.x * 350, z: (c[0].z + c[2].z) / 2 + k.face.n.z * 350 };
doc.text('MK', px(m.x), py(m.z) + 0.8, { align: 'center' });
}
// Verdeeldozen (plafond): klein vierkantje.
doc.setDrawColor(...runColor('schakel'));
doc.setLineWidth(0.15);
el.boxes.forEach(b => doc.rect(px(b.x) - 0.6, py(b.z) - 0.6, 1.2, 1.2, 'S'));
// Schakelaars: kadertje met de letters, iets de kamer in.
doc.setFontSize(5);
el.combos.forEach(c => {
const q = { x: c.pos.x + c.face.n.x * 260, z: c.pos.z + c.face.n.z * 260 };
const text = c.switches.map(s => s.letter).join(' ');
const w = 1.6 + text.length * 1.0;
doc.setDrawColor(...BLACK);
doc.setFillColor(255, 255, 255);
doc.setLineWidth(0.2);
doc.rect(px(q.x) - w / 2, py(q.z) - 1.2, w, 2.4, 'FD');
doc.setTextColor(...BLACK);
doc.text(text, px(q.x), py(q.z) + 0.7, { align: 'center' });
});
// Wcd: halve cirkel tegen de wand + groepnummer.
el.wcds.forEach(w => {
const q = { x: w.pos.x + w.face.n.x * 120, z: w.pos.z + w.face.n.z * 120 };
doc.setDrawColor(...RUN_COLORS.wcd);
doc.setFillColor(255, 255, 255);
doc.setLineWidth(0.2);
doc.circle(px(q.x), py(q.z), 0.9, 'FD');
doc.setTextColor(...RUN_COLORS.wcd);
doc.setFontSize(4);
doc.text(String(w.group), px(q.x + w.face.n.x * 250), py(q.z + w.face.n.z * 250) + 0.6, { align: 'center' });
});
// Apparaten: kadertje met de code (KK, WM, ...) + groepnummer.
el.apps.forEach(a => {
const it = el.items.find(x => x.key === a.key), q = it.marker;
doc.setDrawColor(...RUN_COLORS.wcd);
doc.setFillColor(255, 255, 255);
doc.setLineWidth(0.2);
doc.rect(px(q.x) - 1.6, py(q.z) - 1.1, 3.2, 2.2, 'FD');
doc.setTextColor(...RUN_COLORS.wcd);
doc.setFontSize(3.6);
doc.text(a.code, px(q.x), py(q.z) + 0.5, { align: 'center' });
doc.text(String(a.group), px(q.x + a.face.n.x * 330), py(q.z + a.face.n.z * 330) + 0.5, { align: 'center' });
});
// Lampletters (zelfde letter als de schakelaar).
doc.setFontSize(3.6);
doc.setTextColor(...BLACK);
el.switches.forEach(s => s.lamps.forEach(l => doc.text(s.letter, px(l.x) + 0.9, py(l.z) - 0.7)));
}
// Twee regels (doc.text met een array).
const PLAN_LEGEND = [
'Elektra: MK = meterkast, kadertje = schakelaar(s) met letter (dezelfde letter staat bij de lampen erop), rood rondje = dubbele wcd (groepnummer), vierkantje = verdeeldoos in plafond;',
'rood kadertje = apparaat (KK kookgroep, WM wasmachine, DR droger, WP warmtepomp, OV oven, VW vaatwasser) met groepnummer; gestippeld = leidingen (geel licht, bruin schakeldraad, rood wcd/apparaat, groen buiten).'
];
function drawSheet(doc, el, sheetOpts) {
const BLACK = PdfSheet.COLORS.BLACK;
doc.setFont('helvetica', 'normal');
doc.setTextColor(...BLACK);
doc.setFontSize(14);
doc.text('Elektra', 15, 18);
const t = (title) => ({ title, fontSize: 7, rowH: 4.4, lastColW: 16 });
let y = 30;
y = PdfSheet.table(doc, 15, y, [0, 12, 62, 74, 88, 104, 140, 156], ['Groep', 'Omschrijving', 'Fase', 'Aardlek', 'Automaat', 'Leiding', 'Punten', 'Lengte'],
el.groups.map(g => [String(g.nr), g.label, g.phase, `AL${g.rcd}`, g.breaker, ElektraModel.cableInfo(g.cable).label.replace(/ in buis.*/, ''),
g.kind === 'app' ? 'apparaat' : `${g.points} ${g.kind === 'wcd' ? 'wcd' : 'lamp'}`, fmt(g.meters) + ' m']),
t('Groepenverklaring (aansluiting 3×25 A)'));
y += 4;
y = PdfSheet.table(doc, 15, y, [0, 14, 60, 76, 92], ['Letter', 'Functie', 'Unit', 'Groep', 'Lampen'],
el.switches.map(s => [s.letter, s.label, s.unitId, String(s.group || '-'),
`${s.lamps.length} (${[['binnenspot', 'binnenspot'], ['boeideelspot', 'boeideel-spot'], ['gevellamp', 'gevellamp']]
.map(([k, l]) => [s.lamps.filter(x => x.kind === k).length, l]).filter(([n]) => n).map(([n, l]) => `${n} ${l}`).join(', ') || '-'})`]),
t('Schakelaars (letter staat ook bij de lampen op de plattegrond)'));
y += 4;
const k = el.meterkast;
const notes = [
k ? `Meterkast ${k.w}×${k.h}×${k.d} mm (b×h×d) in unit ${k.unitId}, wand ${k.element}, onderkant ${k.y} mm boven vloer.` : 'Geen meterkast geplaatst.',
`Schakelaars op ${el.settings.switchHeight} mm, wcd op ${el.settings.wcdHeight} mm (hart, boven bovenkant vloer).`,
`Leidinglengte = route via stijlen/plafond + ${el.settings.reserve} mm reserve (meterkast +${el.settings.meterkastReserve} mm), + ${el.settings.slack}% marge.`,
'Groepen per unit: licht B10 (1,5 mm²), wcd B16 (2,5 mm²); buitenlicht eigen groep B10 (XMvK), zelfde aardlek als het binnenlicht in dezelfde schakeldoos.',
'Per schakeldoos één verdeeldoos per groep; fasen en aardlekschakelaars om en om.',
'Apparaten (kookgroep, wasmachine, ...) automatisch geplaatst zolang er geen keuken/bijkeuken is; verplaatsbaar in de plattegrond.',
'Indicatief eerste ontwerp; uitvoering en controle volgens NEN 1010 door de installateur.'
].concat(el.warnings.map(w => 'Let op: ' + w));
doc.setFontSize(7);
notes.forEach(n => { doc.text(n, 15, y); y += 4.2; });
let y2 = 30;
y2 = PdfSheet.table(doc, 205, y2, [0, 46, 102], ['Leidingtype', 'Gebruik', 'Lengte'],
el.cables.map(c => [c.label, c.use, fmt(c.meters) + ' m']), t('Leidingen (strekkende meter)'));
y2 += 4;
y2 = PdfSheet.table(doc, 205, y2, [0, 102], ['Draad', 'Lengte'], el.wires.map(w => [w.label, fmt(w.meters) + ' m']), t('Draad (per ader)'));
y2 += 4;
PdfSheet.table(doc, 205, y2, [0, 102], ['Buis', 'Lengte'], el.conduits.map(c => [c.label, fmt(c.meters) + ' m']), t('Installatiebuis'));
PdfSheet.table(doc, 330, 30, [0, 66], ['Onderdeel', 'Aantal'], el.materials.map(m => [m.naam, `${m.aantal} ${m.eenheid}`]), Object.assign(t('Materiaal'), { maxY: 240 }));
PdfSheet.titleBlock(doc, sheetOpts.project, sheetOpts);
}
return { RUN_COLORS, drawPlan, drawSheet, PLAN_LEGEND };
})();