v0.7.4: light-versie voor de modelpagina's (light.html, js/light/, css/light.css; later in de HUGO-site in een Bootstrap col-12): één model in 3D, doorsnede (snijhoogte tot de goot) en 2D-plattegrond, met beperkte keuzes: per unit breedte en diepte (binnen UnitModel.LIMITS, per model/pagina te beperken), dakvorm met richting, goot- en nokhoogte (helling erbij) en wolfseind, dakbedekking, gevelbekleding met kleur en richting, accent van het model, trasraam (geen/steen, hoogte), binnenspots en buitenverlichting; zelfde berekening als de configurator maar alleen prijs (indicatief, incl. btw), maten, m², inhoud en gewicht zichtbaar; LightModel past keuzes toe op een schone kopie van het model (binnenwanden en inrichting schuiven mee, binnenwanden volgen de goot) en weigert keuzes die de indeling breken (ramen/deuren, binnenwanden, inrichting, ruimtes, overlap) met de dichtstbijzijnde maat die wel past; accordeon-paneel, responsief op de breedte van de tool (container queries), scrollwiel scrolt de pagina tenzij je in de 3D klikt of Ctrl houdt; opslaan = PDF (LightPdf): voorblad "Uw woningontwerp" met render, kerncijfers, keuzes, prijs, contact en QR-code naar de modelpagina (qrcode-generator, MIT), aanzichten en plattegrond (HousePdf klantvariant zonder productie-informatie) en het stappenplan met maatwerk en uitnodiging; doorsnede-afdekking naar HouseModel.sectionCaps (ook in de configurator): 1 mm boven de snede en over de wandtop van wanden die lager blijven, geen flikkerend regelwerk meer; OrbitControls met touch (één vinger draaien, twee vingers knijpen/schuiven); model Vecht (U-vorm 13 × 9,5 m, drie units rond een holte)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Machiel
2026-10-14 11:48:51 +02:00
co-authored by Claude Opus 5.5
parent 51921650e3
commit decbbb4b72
12 changed files with 4104 additions and 78 deletions
+26 -16
View File
@@ -42,11 +42,13 @@ window.HousePdf = (() => {
return list;
}
// customer (light-versie): zonder onderdelenlijst, elementcodes en elektra; de tekening schuift dan naar links.
function drawPlanSheet(doc, parts, sheets, sheetOpts) {
const { RED, BLUE, BLACK, GREY } = PdfSheet.COLORS;
const customer = !!sheetOpts.customer;
doc.setFont('helvetica', 'normal');
PdfSheet.table(doc, 8, 14, [0, 16, 60], ['Element', 'Onderdeel', 'Blad'],
if (!customer) PdfSheet.table(doc, 8, 14, [0, 16, 60], ['Element', 'Onderdeel', 'Blad'],
sheets.map((s, i) => [s.element, s.label, String(i + 1)]).filter(r => r[0] !== PLAN_ELEMENT),
{ title: 'Onderdelen', lastColW: 8, maxY: 285, rowH: 3.9, fontSize: 6 });
PdfSheet.titleBlock(doc, sheetOpts.project, sheetOpts);
@@ -54,7 +56,7 @@ window.HousePdf = (() => {
const rects = parts.rects;
const bx0 = Math.min(...rects.map(r => r.x0)), bx1 = Math.max(...rects.map(r => r.x1));
const bz0 = Math.min(...rects.map(r => r.z0)), bz1 = Math.max(...rects.map(r => r.z1));
const drawX0 = 105, drawX1 = 405, drawY0 = 20, drawY1 = 228;
const drawX0 = customer ? 15 : 105, drawX1 = 405, drawY0 = 20, drawY1 = 228;
const margin = 32;
const scale = Math.min((drawX1 - drawX0 - margin * 2) / (bx1 - bx0), (drawY1 - drawY0 - margin * 2) / (bz1 - bz0));
const ox = (drawX0 + drawX1) / 2 - ((bx0 + bx1) / 2) * scale;
@@ -155,7 +157,7 @@ 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');
});
ElektraPdf.drawPlan(doc, parts.elektra, px, py);
if (!customer) ElektraPdf.drawPlan(doc, parts.elektra, px, py);
doc.setDrawColor(230, 130, 30);
doc.setLineWidth(0.4);
@@ -173,13 +175,13 @@ window.HousePdf = (() => {
const b = UnitModel.localToWorld(p.place, p.place.width / 2, p.place.thickness / 2);
const x0 = px(a.x) + n.x * off, y0 = py(a.z) + n.y * off, x1 = px(b.x) + n.x * off, y1 = py(b.z) + n.y * off;
PdfSheet.dimLine(doc, x0, y0, x1, y1);
const text = `${p.element}: ${p.place.width}`;
const text = customer ? String(p.place.width) : `${p.element}: ${p.place.width}`;
const tx = (x0 + x1) / 2 + n.x * 2.5, ty = (y0 + y1) / 2 + n.y * 2.5 + (n.y > 0 ? 1.5 : 0);
if (n.y === 0) doc.text(text, tx, ty, { align: 'center', angle: 90 });
else doc.text(text, tx, ty, { align: 'center' });
})));
doc.setTextColor(...BLACK);
parts.interior.forEach(p => {
if (!customer) parts.interior.forEach(p => {
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 });
});
@@ -226,7 +228,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, kleurvlak = ruimte (netto m²)', drawX0, drawY1 + 4);
if (parts.elektra && parts.elektra.enabled) doc.text(ElektraPdf.PLAN_LEGEND, drawX0, drawY1 + 7.5);
if (!customer && parts.elektra && parts.elektra.enabled) doc.text(ElektraPdf.PLAN_LEGEND, drawX0, drawY1 + 7.5);
}
const hexRgb = (h) => { const n = parseInt(String(h).replace('#', ''), 16); return [(n >> 16) & 255, (n >> 8) & 255, n & 255]; };
@@ -764,29 +766,37 @@ window.HousePdf = (() => {
PdfSheet.titleBlock(doc, sheetOpts.project, sheetOpts);
}
function generate(state) {
// opts.customer (light-versie): alleen aanzichten en plattegrond, zonder productie-informatie en kosten.
// opts.doc: verder in een bestaand document (nieuwe pagina's erachter); opts.pageOffset/pageTotal: bladnummering
// als er pagina's omheen komen (voorblad e.d.).
function generate(state, opts) {
const o = opts || {};
const customer = !!o.customer;
const parts = HouseModel.computeParts(state, {
wallMaterials: WallModel.createMaterials(), floorMaterials: FloorModel.createMaterials(), roofMaterials: RoofModel.createMaterials(),
elektraRough: false
});
const totals = HouseModel.computeTotals(parts);
const sheets = sheetList(parts);
const cost = CostModel.compute(parts, totals);
const sheets = sheetList(parts).filter(s => !customer || s.kind === 'views' || s.kind === 'plan');
const totals = customer ? null : HouseModel.computeTotals(parts);
const cost = customer ? null : 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 || [],
overlay: ElektraPdf.wallOverlay(parts.elektra, sheet.piece) }, 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 }));
if (!customer) {
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();
const doc = o.doc || PdfSheet.createDoc();
const offset = o.pageOffset || 0, total = o.pageTotal || sheets.length;
sheets.forEach((sheet, i) => {
if (i > 0) doc.addPage('a3', 'landscape');
const opts = { project, titel: sheet.label, element: sheet.element, blad: `${i + 1} / ${sheets.length}` };
if (i > 0 || o.doc) doc.addPage('a3', 'landscape');
const opts = { project, titel: sheet.label, element: sheet.element, blad: `${offset + i + 1} / ${total}`, customer };
if (sheet.kind === 'views') drawViewsSheet(doc, parts, state, opts);
else if (sheet.kind === 'plan') drawPlanSheet(doc, parts, sheets, opts);
else if (sheet.kind === 'floor') {