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:
co-authored by
Claude Opus 5.5
parent
51921650e3
commit
decbbb4b72
+175
-2
@@ -760,6 +760,122 @@ window.HouseModel = (() => {
|
||||
return st;
|
||||
}
|
||||
|
||||
// Model Vecht: U-vorm 13 × 9,5 m (91,5 m²), de holte (4 m breed) open naar het noorden, de entree aan de
|
||||
// zuidkant (de onderkant van de U). Drie units: slaapvleugel west (4,6 × 9,5 m), tussenlid met entree en
|
||||
// toilet (4 × 3,6 m) en leefvleugel oost (4,4 × 7,6 m); de vleugels verschillen 1,9 m in lengte. De vleugels
|
||||
// hebben een lessenaar die naar de holte afloopt (laag 2800, buiten 3600), het tussenlid een plat dak op
|
||||
// dezelfde 2800: rond de holte één goothoogte. Het dak over de holte is de overkapping van het tussenlid
|
||||
// (Unit B › Overkapping › Noord): 0 = open holte, 2400 (standaard) = over de verandadeuren, 4000 = tot het eind
|
||||
// van de leefvleugel. De rand rust aan beide kanten op een vleugel; h.o.h. 4500, dus zonder palen. Buiten
|
||||
// zwarte verticale rabat, de gevels aan de holte wit stucwerk. Alles buitenwerks vanaf de noordwesthoek.
|
||||
function createVecht() {
|
||||
const st = newModelState('Vecht');
|
||||
const S = st.settings, LO = 2800, HI = 3600, FELS = 'fels_antraciet';
|
||||
Object.assign(S.facade, { type: 'rabat', rabatColor: 'zwart', stucColor: 'wit' });
|
||||
S.cladding = 'verticaal';
|
||||
S.trasraam.enabled = false;
|
||||
Object.assign(S.roof, { fasciaColor: 'zwart', overhang: 300, fasciaHeight: 300 });
|
||||
S.roof.posts = PostModel.normalizeSettings({ enabled: true, size: 150, wood: 'eiken', maxSpan: 4500 });
|
||||
S.lighting.mode = 'spots';
|
||||
S.lighting.direction = 'both';
|
||||
if (S.elektra && S.elektra.apparaten) Object.assign(S.elektra.apparaten, { koken: true, oven: true, vaatwasser: true });
|
||||
if (S.elektra) S.elektra.plateColor = 'antraciet';
|
||||
|
||||
const A = createUnitEntry('A', null, { width: 4600, depth: 9500, height: LO, windowOn: [] });
|
||||
const B = createUnitEntry('B', { to: 'A', side: 'rechts', align: 'end', offset: 0 }, { width: 4000, depth: 3600, height: LO, windowOn: [] });
|
||||
const C = createUnitEntry('C', { to: 'B', side: 'rechts', align: 'end', offset: 0 }, { width: 4400, depth: 7600, height: LO, windowOn: [] });
|
||||
st.units.push(A, B, C);
|
||||
UnitModel.applyRoofForm(A.unit, { type: 'lessenaar', dir: 'links', low: LO, high: HI });
|
||||
UnitModel.applyRoofForm(C.unit, { type: 'lessenaar', dir: 'rechts', low: LO, high: HI });
|
||||
A.roof.covering = C.roof.covering = FELS;
|
||||
B.roof.extend = { f: 2400, r: 0, b: 0, l: 0 };
|
||||
const hof = { type: 'stuc', stuc: 'wit' };
|
||||
A.facade.walls.rechts = Object.assign({}, hof);
|
||||
B.facade.walls.voor = Object.assign({}, hof);
|
||||
C.facade.walls.links = Object.assign({}, hof);
|
||||
|
||||
const { K, binnen, ops, iw, room, fx } = modelKit(st, LO, 90);
|
||||
// Noord 'right' en zuid 'left' = vanaf de westhoek van de unit; west 'left' en oost 'right' = vanaf de
|
||||
// binnenkant van de noordgevel (z 256 in de unit). Wanden 256 mm, binnenwanden 91 mm.
|
||||
ops(A.unit.walls.voor, [['raam', 1600, 1500, 600, 'right', 2600, K('raam')]]);
|
||||
ops(A.unit.walls.links, [
|
||||
['raam', 1600, 500, 1700, 'left', 600, K('raam')], // boven het bed
|
||||
['raam', 800, 600, 1500, 'left', 4644, K('raam')], // badkamer
|
||||
['raam', 800, 1400, 700, 'left', 7344, K('raam')]]); // slaapkamer 2
|
||||
ops(A.unit.walls.rechts, [
|
||||
['raam', 1400, 2100, 300, 'right', 944, K('raam')], // slaapkamer → holte
|
||||
['raam', 1500, 2400, 0, 'right', 3994, K('raam')]]); // gang
|
||||
ops(A.unit.walls.achter, [['raam', 1200, 1400, 700, 'left', 900, K('raam')]]);
|
||||
ops(B.unit.walls.voor, [['raam', 2000, 2400, 0, 'right', 400, K('raam')]]); // zicht vanaf de voordeur op de holte
|
||||
ops(B.unit.walls.achter, [
|
||||
['voordeur', 1100, 2100, 0, 'left', 800, K('deur', { hinge: 'links', swing: 'buiten', glas: true })],
|
||||
['raam', 400, 2100, 0, 'left', 2000, K('raam')],
|
||||
['raam', 500, 700, 1400, 'left', 3100, K('raam')]]);
|
||||
ops(C.unit.walls.voor, [['raam', 2400, 2100, 300, 'right', 1000, K('raam')]]);
|
||||
// Verandadeuren naar de holte, onder de overkapping.
|
||||
ops(C.unit.walls.links, [
|
||||
['deur', 900, 2100, 0, 'left', 1744, K('deur', { leaf: 'rechts', hinge: 'links', swing: 'buiten', volglas: true })],
|
||||
['deur', 900, 2100, 0, 'left', 2644, K('deur', { leaf: 'links', hinge: 'rechts', swing: 'buiten', volglas: true })]]);
|
||||
ops(C.unit.walls.rechts, [
|
||||
['raam', 2000, 900, 1900, 'right', 500, K('raam')], // boven de bank
|
||||
['raam', 1000, 2100, 300, 'right', 2844, K('raam')],
|
||||
['raam', 1200, 1100, 1000, 'right', 4744, K('raam')]]);
|
||||
ops(C.unit.walls.achter, [['raam', 1400, 1000, 1100, 'left', 1400, K('raam')]]); // boven de spoelbak
|
||||
|
||||
const DH = 2115;
|
||||
// Noord–zuid: 'buiten' = west, 'left' = vanaf de noordkant; west–oost: 'buiten' = zuid, 'left' = vanaf de westkant.
|
||||
iw(256, 3902, 4344, 3902, [ // slaapkamer | badkamer, gang
|
||||
['deur', 930, DH, 'left', 1944, binnen({ hinge: 'rechts', swing: 'buiten' })],
|
||||
['deur', 930, DH, 'left', 3124, binnen({ hinge: 'rechts', swing: 'binnen' })]]);
|
||||
iw(3300, 3948, 3300, 9244, [['deur', 930, DH, 'left', 2452, binnen({ hinge: 'links', swing: 'buiten' })]]); // badkamer, slaapkamer 2 | gang, techniek
|
||||
iw(256, 6293, 3254, 6293, [['deur', 930, DH, 'left', 1944, binnen({ hinge: 'rechts', swing: 'binnen' })]]); // badkamer | slaapkamer 2
|
||||
iw(3346, 7405, 4500, 7405, [['deur', 800, DH, 'middle', 0, binnen({ hinge: 'links', swing: 'buiten' })]]); // gang | techniek
|
||||
iw(7260, 7555, 8500, 7555); // hal | toilet
|
||||
iw(7305, 7601, 7305, 9244, [['deur', 850, DH, 'left', 100, binnen({ hinge: 'links', swing: 'buiten' })]]); // toilet → hal
|
||||
|
||||
ensureSeamWalls(st);
|
||||
const seamOf = (a, b) => st.interiorWalls.find(w => w.seam && sameSeam(w.seam, a, b));
|
||||
// Noord–zuid-naadwand: 'left' = vanaf de binnenkant van de zuidgevel (z 9244). Openingen bij de noordkant van de hal.
|
||||
const sAB = seamOf('A', 'B'), sBC = seamOf('B', 'C');
|
||||
if (sAB) sAB.wall.openings = [{ id: 1, type: 'deur', w: 1000, h: 2300, y: 0, anchor: 'left', offset: 1994, // open doorgang hal → gang (z 6250–7250)
|
||||
fill: Object.assign(KozijnModel.defaultFill('deur'), { type: 'geen' }) }];
|
||||
if (sBC) sBC.wall.openings = [{ id: 1, type: 'deur', w: 930, h: DH, y: 0, anchor: 'left', offset: 2014, // hal → keuken (z 6300–7230)
|
||||
fill: binnen({ hinge: 'rechts', swing: 'binnen', glas: true }) }];
|
||||
|
||||
const plafond = { ceiling: LO };
|
||||
room('A', 2000, 1500, 'slaapkamer', 'Slaapkamer', plafond);
|
||||
room('A', 2300, 5100, 'badkamer');
|
||||
room('A', 1500, 8800, 'slaapkamer', 'Slaapkamer 2', plafond);
|
||||
room('A', 3900, 5000, 'hal', 'Gang', plafond);
|
||||
room('A', 3900, 8000, 'wasruimte', 'Techniek/was', plafond);
|
||||
room('B', 1200, 1500, 'hal', 'Entree');
|
||||
room('B', 3325, 2400, 'toilet');
|
||||
room('C', 2000, 3500, 'woonkeuken', 'Woonkamer/keuken');
|
||||
|
||||
const xB = (x) => x - 4600, zB = (z) => z - 5900, xC = (x) => x - 8600, zC = (z) => z - 1900;
|
||||
fx('bed', 'A', 1306, 1656, 270, { w: 1600, d: 2100 }, { nachtkastjes: false });
|
||||
fx('kledingkast', 'A', 1206, 3556, 180, { w: 1800 });
|
||||
fx('douche', 'A', 856, 4398, 0, { w: 1200, d: 900 }, { glas: 'rechts', glasLengte: 900 });
|
||||
fx('toilet', 'A', 601, 5250, 270);
|
||||
fx('wastafel', 'A', 900, 6017, 180, { w: 1000 });
|
||||
fx('bed', 'A', 1300, 7339, 0, { w: 1400, d: 2000 }, { nachtkastjes: false });
|
||||
fx('kledingkast', 'A', 2954, 8300, 90, { w: 1800 });
|
||||
fx('wasmachine', 'A', 3700, 8944, 180);
|
||||
fx('boiler', 'A', 4200, 9019, 180);
|
||||
fx('toilet', 'B', xB(7925), zB(8899), 180);
|
||||
fx('fontein', 'B', xB(7925), zB(7726), 0);
|
||||
fx('keuken', 'C', xC(10944), zC(8944), 180, { w: 3600 }, { front: 'antraciet', hogeKast: 'links', spoelbak: 0.57, kookplaat: false });
|
||||
fx('kookeiland', 'C', xC(10800), zC(7094), 0, { w: 2400, d: 900 }, { front: 'antraciet', kookplaat: true });
|
||||
fx('eettafel', 'C', xC(10500), zC(5300), 0, { w: 1800, d: 1000 }, { vorm: 'ovaal', stoelen: 6 });
|
||||
fx('bank', 'C', xC(12294), zC(3656), 90, { w: 2400, d: 900 }, { chaise: 'geen' });
|
||||
fx('salontafel', 'C', xC(11200), zC(3500), 0, { w: 800, d: 800 }, { vorm: 'rond' });
|
||||
fx('fauteuil', 'C', xC(10300), zC(2850), 45);
|
||||
fx('tvmeubel', 'C', xC(9056), zC(3100), 270, { w: 1400 });
|
||||
|
||||
computeParts(st, { skip3D: true });
|
||||
return st;
|
||||
}
|
||||
|
||||
// Standaardmodellen: Bestand › Snel nieuw en het venster Nieuw. De sleutel is de template.
|
||||
const MODELS = {
|
||||
amstel: { label: 'Woning Amstel', icon: 'house-heart', create: createAmstel },
|
||||
@@ -768,7 +884,8 @@ window.HouseModel = (() => {
|
||||
waal: { label: 'Woning Waal', icon: 'house-add', create: createWaal },
|
||||
maas: { label: 'Woning Maas', icon: 'house-check', create: createMaas },
|
||||
rijn: { label: 'Woning Rijn', icon: 'house-gear', create: createRijn },
|
||||
reest: { label: 'Woning Reest', icon: 'house-up', create: createReest }
|
||||
reest: { label: 'Woning Reest', icon: 'house-up', create: createReest },
|
||||
vecht: { label: 'Woning Vecht', icon: 'house-down', create: createVecht }
|
||||
};
|
||||
|
||||
function createDefaultState(template) {
|
||||
@@ -1858,6 +1975,62 @@ window.HouseModel = (() => {
|
||||
if (parts.roomsGroup && parts.roomsGroup.userData.ceilings) parts.roomsGroup.userData.ceilings.visible = true;
|
||||
}
|
||||
|
||||
// Doorsnede: witte afdekking van de wanden. Hij volgt de bovenkant van elke wand, afgetopt op de snijhoogte cutAbs
|
||||
// (mm, absoluut): op de snede vlak en onderbroken door de openingen, daaronder over de wandtop (ook schuin), zodat
|
||||
// een wand die niet tot de snede reikt ook wit afgedekt is. Met gevelbekleding vanaf de buitenkant daarvan (latwerk
|
||||
// is open); zonder beplating tussen de platen. De afdekking ligt CAP_LIFT_MM boven wand of snede: alles wat daar
|
||||
// eindigt (regelwerk, platen) valt eronder en flikkert niet mee (dieptebuffer op afstand).
|
||||
const CAP_LIFT_MM = 1;
|
||||
function sectionCaps(parts, cutAbs, material, layers) {
|
||||
const toM = 0.001, g = new THREE.Group();
|
||||
const S = WallModel.STONE;
|
||||
const gevel = ViewLayers.shows(layers, 'gevel'), beplating = ViewLayers.shows(layers, 'beplating');
|
||||
allWallPieces(parts).forEach(p => {
|
||||
const y = cutAbs - p.place.floorTop;
|
||||
if (y <= 0) return;
|
||||
const W = p.place.width, T = p.place.thickness, tl = WallModel.topLine(p.config);
|
||||
const topAt = (x) => tl.at(Math.max(-W / 2, Math.min(W / 2, x)));
|
||||
const hAt = (x) => Math.min(y, topAt(x));
|
||||
const outAt = (h) => {
|
||||
if (!gevel || !p.place.normal || p.buitenMateriaal === 'geen') return 0;
|
||||
const tr = p.trasraam;
|
||||
return tr && h < tr.top ? (WallModel.isStoneFinish(tr.finish) ? S.THICK_MM : 0) : WallModel.claddingOffset(p.buitenMateriaal, p.stone, p.stuc);
|
||||
};
|
||||
const sh = beplating ? 0 : p.config.sheathingThick;
|
||||
const e = p.skinExt || {}, out0 = outAt(hAt(0));
|
||||
const xL = -W / 2 - (e.left > 0 ? e.left + out0 : 0), xR = W / 2 + (e.right > 0 ? e.right + out0 : 0);
|
||||
// Knikpunten: de wandtop en waar die door de snijhoogte gaat; op de snede de openingen eruit.
|
||||
const xs = [...new Set([xL, xR, ...tl.pts.map(q => q.x), ...tl.crossings(y)].filter(x => x >= xL && x <= xR))].sort((a, b) => a - b);
|
||||
const gaps = p.config.openings.map(op => WallModel.openingSpanAtY(op, y)).filter(Boolean)
|
||||
.map(([a, b]) => [-W / 2 + a, -W / 2 + b]).sort((a, b) => a[0] - b[0]);
|
||||
const spans = [];
|
||||
for (let i = 0; i < xs.length - 1; i++) {
|
||||
const a = xs[i], b = xs[i + 1];
|
||||
if (b - a <= 1) continue;
|
||||
const onCut = topAt((a + b) / 2) >= y - 0.5;
|
||||
if (!onCut) { spans.push([a, b]); continue; }
|
||||
let cur = a;
|
||||
gaps.forEach(([g0, g1]) => { if (g1 <= cur || g0 >= b) return; if (g0 > cur) spans.push([cur, g0]); cur = Math.max(cur, g1); });
|
||||
if (b > cur) spans.push([cur, b]);
|
||||
}
|
||||
const pos = [];
|
||||
spans.filter(([a, b]) => b - a > 1).forEach(([a, b]) => {
|
||||
const ha = hAt(a) + CAP_LIFT_MM, hb = hAt(b) + CAP_LIFT_MM, out = outAt((ha + hb) / 2);
|
||||
const z0 = -T / 2 + sh, z1 = T / 2 + out - (p.buitenMateriaal !== 'geen' ? sh : 0);
|
||||
// Twee driehoeken, normaal omhoog.
|
||||
pos.push(a, ha, z0, a, ha, z1, b, hb, z1, a, ha, z0, b, hb, z1, b, hb, z0);
|
||||
});
|
||||
if (!pos.length) return;
|
||||
const geo = new THREE.BufferGeometry();
|
||||
geo.setAttribute('position', new THREE.Float32BufferAttribute(pos.map(v => v * toM), 3));
|
||||
const m = new THREE.Mesh(geo, material);
|
||||
m.rotation.y = p.place.rotY;
|
||||
m.position.set(p.place.cx * toM, p.place.floorTop * toM, p.place.cz * toM);
|
||||
g.add(m);
|
||||
});
|
||||
return g;
|
||||
}
|
||||
|
||||
// 3D-groepen op hun plek in de woning (m); elektra, palen en dakrand staan al in woningcoördinaten.
|
||||
function placeGroups(parts) {
|
||||
const toM = 0.001;
|
||||
@@ -2206,6 +2379,6 @@ window.HouseModel = (() => {
|
||||
clone, createEmptyProject, createDefaultSettings, createUnitEntry, applyGlobalSettings, nextUnitId, MODELS, createDefaultState, unitRects, computeSeams, overlappingPairs,
|
||||
interiorWallDefaults, setThickness, thicknessOf, interiorLayout, ensureSeamWalls, syncSeamWalls, missingSeamWalls,
|
||||
houseRoofShape, houseRoofFits, setHouseRoof, coverExtensions, EXTEND_MAX,
|
||||
computeParts, placeGroups, allWallPieces, computeTotals, computeDimensions, dimensionRows, TRANSPORT, TRANSPORT_CLASSES, computeTransport, validateState, fromUnitDesign, normalize
|
||||
computeParts, placeGroups, sectionCaps, allWallPieces, computeTotals, computeDimensions, dimensionRows, TRANSPORT, TRANSPORT_CLASSES, computeTransport, validateState, fromUnitDesign, normalize
|
||||
};
|
||||
})();
|
||||
|
||||
+26
-16
@@ -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') {
|
||||
|
||||
+2
-37
@@ -373,43 +373,8 @@ function mountHouseEditor(container, callbacks) {
|
||||
const cutPlane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0);
|
||||
renderer.localClippingEnabled = true;
|
||||
const capMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
|
||||
function buildPlanCaps(cutAbs) {
|
||||
const toM = 0.001, g = new THREE.Group();
|
||||
const S = WallModel.STONE;
|
||||
// Latwerk is open: pas vanaf de gevel afdekken.
|
||||
const gevel = view3D.layers.gevel, beplating = view3D.layers.beplating;
|
||||
HouseModel.allWallPieces(parts).forEach(p => {
|
||||
const y = cutAbs - p.place.floorTop;
|
||||
if (y <= 0 || y >= WallModel.topLine(p.config).maxY) return;
|
||||
const W = p.place.width, T = p.place.thickness;
|
||||
let out = 0;
|
||||
if (gevel && p.place.normal && p.buitenMateriaal !== 'geen') {
|
||||
const tr = p.trasraam;
|
||||
out = tr && y < tr.top ? (WallModel.isStoneFinish(tr.finish) ? S.THICK_MM : 0) : WallModel.claddingOffset(p.buitenMateriaal, p.stone, p.stuc);
|
||||
}
|
||||
const e = p.skinExt || {};
|
||||
const xL = -W / 2 - (e.left > 0 ? e.left + out : 0), xR = W / 2 + (e.right > 0 ? e.right + out : 0);
|
||||
const gaps = p.config.openings.map(op => WallModel.openingSpanAtY(op, y)).filter(Boolean)
|
||||
.map(([a, b]) => [-W / 2 + a, -W / 2 + b]).sort((a, b) => a[0] - b[0]);
|
||||
const spans = [];
|
||||
let cur = xL;
|
||||
gaps.forEach(([a, b]) => { if (a > cur) spans.push([cur, Math.min(a, xR)]); cur = Math.max(cur, b); });
|
||||
if (xR > cur) spans.push([cur, xR]);
|
||||
const sh = beplating ? 0 : p.config.sheathingThick;
|
||||
const z0 = -T / 2 + sh, z1 = T / 2 + out - (p.buitenMateriaal !== 'geen' ? sh : 0);
|
||||
spans.filter(([a, b]) => b - a > 1).forEach(([a, b]) => {
|
||||
const m = new THREE.Mesh(new THREE.PlaneGeometry((b - a) * toM, (z1 - z0) * toM), capMaterial);
|
||||
m.rotation.x = -Math.PI / 2;
|
||||
m.position.set((a + b) / 2 * toM, (y - 0.5) * toM, (z0 + z1) / 2 * toM);
|
||||
const holder = new THREE.Group();
|
||||
holder.rotation.y = p.place.rotY;
|
||||
holder.position.set(p.place.cx * toM, p.place.floorTop * toM, p.place.cz * toM);
|
||||
holder.add(m);
|
||||
g.add(holder);
|
||||
});
|
||||
});
|
||||
return g;
|
||||
}
|
||||
// Snede wit afgedekt; latwerk is open, pas vanaf de gevel afdekken (HouseModel.sectionCaps).
|
||||
const buildPlanCaps = (cutAbs) => HouseModel.sectionCaps(parts, cutAbs, capMaterial, view3D.layers);
|
||||
let fullRender = false;
|
||||
let skyMode = Skybox.getMode();
|
||||
const skyEnv = Skybox.createEnvironment(scene);
|
||||
|
||||
Reference in New Issue
Block a user