diff --git a/README.md b/README.md index 7acd10d..bfdc987 100644 --- a/README.md +++ b/README.md @@ -271,8 +271,15 @@ hout.balk.38x235;Constructiehout vuren C24 38×235;€/m;4,91;Houthandel Y;;2026 stucwerk wit met verticaal hout aan de slaapkamerkant, binnenwanden, dubbele schuifdeur tussen woonkamer en hal, en complete inrichting. Langer maken: beide units breder (alles is vanaf de westgevel gemaat). - Ook via Bestand › Woning Amstel. De L-woning (8 × 3,6 m + 4,5 × 3,6 m) - blijft beschikbaar als "Woning L-vorm". + Model **Wetering** (`HouseModel.createWetering`, naar + `examples/ontwerp_wetering.pdf`): 10,5 × 4,8 m, twee units van 2,6 en + 2,2 m zonder naadwand (open woonkamer over de volle diepte), verticaal + rabat grijs, plat dak met zwart boeideel, hoekkeuken met kookplaat, + badkamer, slaapkamer met kledingkast. De standaardmodellen staan in + `HouseModel.MODELS`; Bestand › Snel nieuw en het venster Nieuw worden + daaruit opgebouwd (nieuw model = één regel plus een `create`-functie die + `modelKit` gebruikt). De L-woning (8 × 3,6 m + 4,5 × 3,6 m) blijft + beschikbaar als "Woning L-vorm". - Netto m² per ruimte telt de hele vloer, ook onder een laag schuin dak (geen NEN 2580-correctie voor hoogte < 1,5 m). - Units kunnen niet gedraaid worden. diff --git a/css/app.css b/css/app.css index 2b5b86c..a4d437f 100644 --- a/css/app.css +++ b/css/app.css @@ -428,6 +428,7 @@ body { display: flex; flex-direction: column; } color: var(--nav-link-color); cursor: pointer; width: 110px; } .new-option svg { width: 40px; height: 40px; } +.new-option .bi { font-size: 36px; line-height: 40px; } .new-option span { font-size: 13px; } .new-option:hover:not(.disabled) { border-color: var(--primary); color: var(--primary); background: var(--primary-soft); } .new-option.disabled { opacity: .4; cursor: not-allowed; } diff --git a/index.html b/index.html index 93e0a0c..78a7c7c 100644 --- a/index.html +++ b/index.html @@ -51,10 +51,6 @@

Nieuw

- `).join('')); overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.classList.remove('open'); }); document.querySelectorAll('.new-option:not(.disabled)').forEach(opt => { opt.addEventListener('click', () => { diff --git a/js/cost/cost-model.js b/js/cost/cost-model.js index f46de7b..021c82a 100644 --- a/js/cost/cost-model.js +++ b/js/cost/cost-model.js @@ -324,11 +324,10 @@ window.CostModel = (() => { const m = f.w / 1000, crew = IN.personeel; const perM = (what, price, sec) => add(el, 'Inrichting', 'Inrichting', `${what} ${f.w} mm`, m, 'm', m * (price || 0), m * (sec || 0), crew); if (f.cost === 'keuken') perM('Keukenblok', IN.keukenM, IN.keukenMontage); - else if (f.cost === 'kookeiland') { - perM('Kookeiland', IN.kookeilandM, IN.kookeilandMontage); - if (f.opts && f.opts.kookplaat) add(el, 'Inrichting', 'Inrichting', 'Inductiekookplaat', 1, 'st.', IN.kookplaat || 0, 0); - } else if (f.cost === 'kast') perM(f.label, IN.kastM, IN.kastMontage); + else if (f.cost === 'kookeiland') perM('Kookeiland', IN.kookeilandM, IN.kookeilandMontage); + else if (f.cost === 'kast') perM(f.label, IN.kastM, IN.kastMontage); else add(el, 'Inrichting', 'Inrichting', f.label, 1, 'st.', IN[f.cost] || 0, IN[f.cost + 'Montage'] || 0, crew); + if ((f.cost === 'keuken' || f.cost === 'kookeiland') && f.opts && f.opts.kookplaat) add(el, 'Inrichting', 'Inrichting', 'Inductiekookplaat', 1, 'st.', IN.kookplaat || 0, 0); }); const sumLines = (lines) => lines.reduce((a, l) => ({ mat: a.mat + l.mat, sec: a.sec + l.sec, clock: a.clock + l.clock }), { mat: 0, sec: 0, clock: 0 }); diff --git a/js/house/house-model.js b/js/house/house-model.js index 47a9d2b..9171fd5 100644 --- a/js/house/house-model.js +++ b/js/house/house-model.js @@ -73,12 +73,17 @@ window.HouseModel = (() => { return { exterior: 'vochtdoek', stone: null, stuc: null, cladding, trasraam, rabatColor: state.settings.facade.rabatColor }; } + function newModelState(naam) { + const st = { project: createEmptyProject(), settings: createDefaultSettings(), units: [], interiorWalls: [], rooms: [], fixtures: [] }; + st.project.naam = naam || ''; + return st; + } + // TIJDELIJK (testen): nieuwe woning = voorbeeldontwerp (json/premade/vlinderwoning.json); false = één unit. const TEST_DEFAULT = true; function createVlinderwoning() { - const st = { project: createEmptyProject(), settings: createDefaultSettings(), units: [], interiorWalls: [], rooms: [], fixtures: [] }; - st.project.naam = 'Vlinderwoning'; + const st = newModelState('Vlinderwoning'); st.settings.trasraam.finish = 'K490'; const A = createUnitEntry('A', null, { width: 10000, depth: 4000, windowOn: [] }); const B = createUnitEntry('B', { to: 'A', side: 'achter', align: 'start', offset: 0 }, { width: 10000, depth: 3600, windowOn: [] }); @@ -112,10 +117,35 @@ window.HouseModel = (() => { return st; } + // Bouwstenen voor de standaardmodellen. Gevelopeningen: [type, w, h, y, anchor, offset, fill]; + // binnenwanden: [type, w, h, anchor, offset, fill] vanaf de vloer. + function modelKit(st, H, T) { + const K = (type, extra) => Object.assign(KozijnModel.defaultFill(type === 'raam' ? 'raam' : 'deur'), { out: 'zwart', in: 'zwart' }, extra || {}); + const binnen = (extra) => Object.assign(KozijnModel.defaultFill('deur'), { out: 'wit', in: 'wit', glas: false }, extra || {}); + const ops = (ws, list) => { ws.openings = list.map(([type, w, h, y, anchor, offset, fill], i) => ({ id: i + 1, type, w, h, y, anchor, offset, fill })); }; + const iw = (x1, z1, x2, z2, openings) => { + const wall = interiorWallDefaults(T, H); + wall.openings = (openings || []).map(([type, w, h, anchor, offset, fill], i) => ({ id: i + 1, type, w, h, y: 0, anchor, offset, fill })); + st.interiorWalls.push({ id: st.interiorWalls.length + 1, x1, z1, x2, z2, wall }); + }; + // zaadpunt t.o.v. de noordwesthoek van de unit + const room = (unitId, x, z, type, name) => + st.rooms.push(Object.assign(RoomModel.applyType({ id: st.rooms.length + 1, unitId, x, z, name: '' }, type), { name: name || '' })); + // Midden voetafdruk t.o.v. de noordwesthoek van de unit; rot = richting van de rug (0 = noord, 90 = oost). + // anchor 'oost': x opgeven vanaf de westgevel, het meubel schuift mee met de oostgevel. + const fx = (type, unitId, x, z, rot, size, opts) => { + const f = FixtureModel.create(st, type, unitId, x, z, rot); + if (size && size.anchor === 'oost') f.x = Math.round(st.units.find(u => u.id === unitId).unit.floor.floor.width - x); + Object.assign(f, size || {}); + Object.assign(f.opts, opts || {}); + st.fixtures.push(f); + }; + return { K, binnen, ops, iw, room, fx }; + } + // Model Amstel (examples/ontwerp_amstel.pdf). Alles vanaf de westgevel gemaat, zodat de woning in de lengte kan groeien. function createAmstel() { - const st = { project: createEmptyProject(), settings: createDefaultSettings(), units: [], interiorWalls: [], rooms: [], fixtures: [] }; - st.project.naam = 'Amstel'; + const st = newModelState('Amstel'); const S = st.settings, H = 2800; Object.assign(S.facade, { type: 'stuc', stucColor: 'wit', rabatColor: 'naturel' }); S.cladding = 'verticaal'; @@ -131,9 +161,7 @@ window.HouseModel = (() => { setHouseRoof(st, true); Object.assign(S.roof.house, { form: { type: 'plat', low: H, high: H }, cover: true }); - const K = (type, extra) => Object.assign(KozijnModel.defaultFill(type === 'raam' ? 'raam' : 'deur'), { out: 'zwart', in: 'zwart' }, extra || {}); - const binnen = (extra) => Object.assign(KozijnModel.defaultFill('deur'), { out: 'wit', in: 'wit', glas: false }, extra || {}); - const ops = (ws, list) => { ws.openings = list.map(([type, w, h, y, anchor, offset, fill], i) => ({ id: i + 1, type, w, h, y, anchor, offset, fill })); }; + const { K, binnen, ops, iw, room, fx } = modelKit(st, H, 90); // Noordgevel: 'right' = vanaf de westhoek. ops(A.unit.walls.voor, [ ['deur', 1474, 2650, 0, 'right', 150, K('deur', { leaf: 'links', hinge: 'rechts', swing: 'buiten', volglas: true })], @@ -152,13 +180,6 @@ window.HouseModel = (() => { B.facade.walls.rechts = { type: 'rabat' }; B.lighting.walls.achter = 'openingen'; - const T = 90; - let iwId = 0; - const iw = (x1, z1, x2, z2, openings) => { - const wall = interiorWallDefaults(T, H); - wall.openings = (openings || []).map(([type, w, h, anchor, offset, fill], i) => ({ id: i + 1, type, w, h, y: 0, anchor, offset, fill })); - st.interiorWalls.push({ id: ++iwId, x1, z1, x2, z2, wall }); - }; const D = 2115; iw(2555, 3700, 2555, 4800, [['deur', 850, D, 'left', 80, binnen({ hinge: 'links', swing: 'buiten' })]]); // hal | logeerkamer iw(1980, 4845, 3070, 4845); // boven boilerkast en garderobe @@ -180,25 +201,14 @@ window.HouseModel = (() => { fill: Object.assign(KozijnModel.defaultFill('deur'), { type: 'schuifdeur', leaves: 2, mount: 'opbouw', side: 'binnen', glas: true, out: 'zwart', in: 'zwart' }) }]; } - // zaadpunt t.o.v. de noordwesthoek van de unit - const room = (id, unitId, x, z, type, name) => st.rooms.push(Object.assign(RoomModel.applyType({ id, unitId, x, z, name: '' }, type), { name })); - room(1, 'A', 5600, 1200, 'woonkeuken', 'Woonkamer/keuken'); - room(2, 'B', 1000, 2000, 'slaapkamer', 'Logeerkamer'); - room(3, 'B', 3600, 2600, 'hal', 'Entree'); - room(4, 'B', 2480, 2800, 'toilet', ''); - room(5, 'B', 5300, 2700, 'badkamer', ''); - room(6, 'B', 8300, 1800, 'slaapkamer', 'Slaapkamer'); - room(7, 'B', 2245, 1650, 'techniek', 'Boilerkast'); + room('A', 5600, 1200, 'woonkeuken', 'Woonkamer/keuken'); + room('B', 1000, 2000, 'slaapkamer', 'Logeerkamer'); + room('B', 3600, 2600, 'hal', 'Entree'); + room('B', 2480, 2800, 'toilet'); + room('B', 5300, 2700, 'badkamer'); + room('B', 8300, 1800, 'slaapkamer', 'Slaapkamer'); + room('B', 2245, 1650, 'techniek', 'Boilerkast'); - // Midden voetafdruk t.o.v. de noordwesthoek van de unit; rot = richting van de rug (0 = noord, 90 = oost). - const xA = 11240, xB = 10600; - const fx = (type, unitId, x, z, rot, size, opts) => { - const f = FixtureModel.create(st, type, unitId, x, z, rot); - if (size && size.anchor === 'oost') f.x = Math.round((unitId === 'A' ? xA : xB) - x); - Object.assign(f, size || {}); - Object.assign(f.opts, opts || {}); - st.fixtures.push(f); - }; fx('keuken', 'A', 4746, 556, 0, { w: 2660 }, { front: 'antraciet', hogeKast: 'links', spoelbak: 0.49 }); fx('kookeiland', 'A', 4746, 2000, 0, { w: 2660, d: 600 }, { front: 'antraciet', kookplaat: true }); fx('eettafel', 'A', 1600, 1820, 90, { w: 2000, d: 1100 }, { vorm: 'ovaal', stoelen: 6 }); @@ -220,10 +230,80 @@ window.HouseModel = (() => { return st; } + // Model Wetering (examples/ontwerp_wetering.pdf): 10,5 × 4,8 m, één rechthoek zonder naadwand (open woonkamer + // over de volle diepte). 4,8 m is te diep voor één unit, dus twee units (2,6 + 2,2 m). Alles vanaf de westgevel en + // de noordgevel gemaat (buitenwerks); de entreegevel met openslaande deuren en voordeur is het zuiden. + function createWetering() { + const st = newModelState('Wetering'); + const S = st.settings, H = 2800, W = 10500, DA = 2600, DB = 2200; + Object.assign(S.facade, { type: 'rabat', rabatColor: 'grijs' }); + S.cladding = 'verticaal'; + S.trasraam.enabled = false; + S.roof.fasciaColor = 'zwart'; + S.lighting.mode = 'gevel'; + S.lighting.direction = 'both'; + if (S.elektra && S.elektra.apparaten) Object.assign(S.elektra.apparaten, { koken: true, oven: true, vaatwasser: true }); + + const A = createUnitEntry('A', null, { width: W, depth: DA, height: H, windowOn: [] }); + const B = createUnitEntry('B', { to: 'A', side: 'achter', align: 'start', offset: 0 }, { width: W, depth: DB, height: H, windowOn: [] }); + st.units.push(A, B); + setHouseRoof(st, true); + Object.assign(S.roof.house, { form: { type: 'plat', low: H, high: H } }); + + const { K, binnen, ops, iw, room, fx } = modelKit(st, H, 90); + // Noord 'right' en zuid 'left' = vanaf de westhoek; west 'left' en oost 'right' = vanaf de noordkant van de wand. + ops(A.unit.walls.voor, [['raam', 500, 700, 1400, 'right', 5670, K('raam')]]); + ops(A.unit.walls.links, [['raam', 700, 2100, 0, 'left', 300, K('raam')], ['raam', 700, 2100, 0, 'left', 1500, K('raam')]]); + ops(A.unit.walls.rechts, [['raam', 500, 1400, 700, 'right', 270, K('raam')]]); + ops(B.unit.walls.rechts, [['raam', 500, 1400, 700, 'right', 540, K('raam')]]); + ops(B.unit.walls.achter, [ + ['deur', 824, 2100, 0, 'left', 1200, K('deur', { hinge: 'links', swing: 'buiten', volglas: true })], + ['deur', 824, 2100, 0, 'left', 2176, K('deur', { hinge: 'rechts', swing: 'buiten', volglas: true })], + ['voordeur', 1100, 2100, 0, 'left', 5350, K('deur', { hinge: 'rechts', swing: 'buiten', volglas: true })]]); + B.lighting.walls.achter = 'openingen'; + + const D = 2115; + iw(4675, 190, 4675, 2210); // keuken | badkamer + iw(4630, 2255, 7120, 2255, [['deur', 930, D, 'left', 1390, binnen({ hinge: 'rechts', swing: 'buiten' })]]); // badkamer | entree + iw(7165, 190, 7165, 4610, [ // slaapkamer + ['deur', 980, 2130, 'left', 360, Object.assign(binnen(), { type: 'schuifdeur', leaves: 1, mount: 'opbouw', side: 'binnen', dir: 'rechts' })], + ['deur', 930, D, 'left', 2277, binnen({ hinge: 'links', swing: 'buiten' })]]); + iw(6455, 3765, 7120, 3765); // kapstoknis entree + + // Geen naadwand: woonkamer en slaapkamer lopen over beide units door (Unit › Naadwand toevoegen zet hem terug). + + room('A', 2000, 1500, 'woonkeuken', 'Woonkamer/keuken'); + room('A', 5900, 1500, 'badkamer'); + room('A', 8700, 1500, 'slaapkamer', 'Slaapkamer'); + + const zB = (z) => z - DA; + fx('keuken', 'A', 4330, 1245, 90, { w: 2110 }, { front: 'antraciet', hogeKast: 'geen', spoelbak: 0.85, kookplaat: true, kookplaatPlek: 0.2 }); + fx('keuken', 'A', 4630, 2600, 0, { w: 1200 }, { front: 'antraciet', hogeKast: 'rechts', spoel: false, vaatwasser: false }); + fx('boiler', 'A', 5515, 2525, 0); + fx('eettafel', 'A', 1620, 1160, 0, { w: 1000, d: 1000 }, { vorm: 'ovaal', stoelen: 4 }); + fx('bank', 'B', 615, zB(3640), 270, { w: 1600, d: 850 }, { chaise: 'geen' }); + fx('salontafel', 'B', 1770, zB(3510), 0, { w: 600, d: 600 }, { vorm: 'rond' }); + fx('fauteuil', 'B', 2760, zB(4060), 150); + fx('douche', 'A', 5245, 640, 270, { w: 900, d: 1050 }, { glas: 'links', glasLengte: 800 }); + fx('toilet', 'A', 5065, 1760, 270, { w: 900 }); + fx('wastafel', 'A', 6150, 420, 0, { w: 600 }, { spiegel: false }); + fx('bed', 'A', 9260, 2050, 90, { w: 1600, d: 2100, anchor: 'oost' }, { nachtkastjes: false }); + fx('kledingkast', 'B', 8760, zB(4310), 180, { w: 3100 }); + + 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 }, + wetering: { label: 'Woning Wetering', icon: 'house-door', create: createWetering } + }; + function createDefaultState(template) { - if (template === 'amstel') return createAmstel(); + if (MODELS[template]) return MODELS[template].create(); if (TEST_DEFAULT && (!template || template === 'single')) return createVlinderwoning(); - const state = { project: createEmptyProject(), settings: createDefaultSettings(), units: [], interiorWalls: [], rooms: [], fixtures: [] }; + const state = newModelState(); state.units.push(createUnitEntry('A', null, template === 'L' ? { width: 8000, depth: 3600 } : { width: 6000, depth: 3000 })); if (template === 'L') { state.units.push(createUnitEntry('B', { to: 'A', side: 'achter', align: 'start', offset: 0 }, { width: 4500, depth: 3600, windowOn: [] })); @@ -1371,7 +1451,7 @@ window.HouseModel = (() => { return { ALIGN_LABELS, SIDE_LABELS, DEFAULT_INTERIOR_THICKNESS, FACADE_TYPES, facadeFor, facadeMaterials, - clone, createEmptyProject, createDefaultSettings, createUnitEntry, applyGlobalSettings, nextUnitId, createDefaultState, unitRects, computeSeams, overlappingPairs, + clone, createEmptyProject, createDefaultSettings, createUnitEntry, applyGlobalSettings, nextUnitId, MODELS, createDefaultState, unitRects, computeSeams, overlappingPairs, interiorWallDefaults, setThickness, thicknessOf, interiorLayout, ensureSeamWalls, syncSeamWalls, missingSeamWalls, houseRoofShape, houseRoofFits, setHouseRoof, coverExtensions, computeParts, allWallPieces, computeTotals, computeDimensions, dimensionRows, TRANSPORT, TRANSPORT_CLASSES, computeTransport, validateState, fromUnitDesign, normalize diff --git a/js/house/house.js b/js/house/house.js index b2cc8f8..c03e731 100644 --- a/js/house/house.js +++ b/js/house/house.js @@ -1522,11 +1522,13 @@ function mountHouseEditor(container, callbacks) { } const FX_OPT_UI = { front: { label: 'Front', kind: 'sel', options: () => Object.fromEntries(Object.entries(FixtureModel.FRONTS).map(([k, f]) => [k, f.label])) }, - spoelbak: { label: 'Spoelbak', kind: 'range', min: 0, max: 1, step: 0.01, unit: '', hint: '0 = links, 1 = rechts (van voren gezien)' }, + spoel: { label: 'Spoelbak', kind: 'toggle' }, + spoelbak: { label: 'Plek spoelbak', kind: 'range', min: 0, max: 1, step: 0.01, unit: '', hint: '0 = links, 1 = rechts (van voren gezien)' }, hogeKast: { label: 'Hoge kast', kind: 'sel', options: () => ({ geen: 'Geen', links: 'Links', rechts: 'Rechts' }), hint: 'Inbouwkoelkast + oven, 600 mm' }, vaatwasser: { label: 'Vaatwasser', kind: 'toggle' }, bovenkasten: { label: 'Bovenkasten', kind: 'toggle' }, kookplaat: { label: 'Kookplaat', kind: 'toggle', hint: 'Inductie, 4 zones' }, + kookplaatPlek: { label: 'Plek kookplaat', kind: 'range', min: 0, max: 1, step: 0.01, unit: '', hint: '0 = links, 1 = rechts (van voren gezien)' }, glas: { label: 'Glaswand', kind: 'sel', options: () => ({ links: 'Links', rechts: 'Rechts', geen: 'Geen' }), hint: 'Van voren gezien' }, glasLengte: { label: 'Lengte glas', kind: 'num', step: 50 }, spiegel: { label: 'Spiegel', kind: 'toggle' }, diff --git a/js/interior/fixture-model.js b/js/interior/fixture-model.js index 39ecb87..b9cea4b 100644 --- a/js/interior/fixture-model.js +++ b/js/interior/fixture-model.js @@ -162,8 +162,8 @@ window.FixtureModel = (() => { }, keuken: { label: 'Keukenblok', cat: 'keuken', w: 2660, d: 600, h: 910, resize: ['w', 'd', 'h'], minW: 600, wall: true, cost: 'keuken', - opts: { front: 'antraciet', spoelbak: 0.6, hogeKast: 'links', vaatwasser: true, bovenkasten: false }, - hint: 'Onderkasten met werkblad en spoelbak (plek: 0 = links, 1 = rechts, van voren gezien); hoge kast 600 mm met inbouwkoelkast en oven', + opts: { front: 'antraciet', spoel: true, spoelbak: 0.6, kookplaat: false, kookplaatPlek: 0.2, hogeKast: 'links', vaatwasser: true, bovenkasten: false }, + hint: 'Onderkasten met werkblad, spoelbak en eventueel kookplaat (plek: 0 = links, 1 = rechts, van voren gezien); hoge kast 600 mm met inbouwkoelkast en oven', build(f, g) { kitchenRun(g, f, false); }, plan(f) { return kitchenPlan(f, false); } }, @@ -386,25 +386,32 @@ window.FixtureModel = (() => { boxAt(g, front, a + 2, b - 2, 1790, 2195, zf - 22, zf - 2); } if (!island) { - // Spoelbak + kraan - const sx = spoelX(f, K); - boxAt(g, MAT.chroom(), sx - 380, sx + 380, top - 2, top + 0.5, zb + 90, zf - 70); - boxAt(g, MAT.rubber(), sx - 360, sx + 360, top - 1, top + 1, zb + 105, zf - 85); - ecyl(g, MAT.zwartMetaal(), sx + 200, zb + 55, 22, 22, top, top + 300); - boxAt(g, MAT.zwartMetaal(), sx + 190, sx + 210, top + 280, top + 300, zb + 55, zb + 280); + if (f.opts.spoel !== false) { + const sx = spoelX(f, K); + boxAt(g, MAT.chroom(), sx - 380, sx + 380, top - 2, top + 0.5, zb + 90, zf - 70); + boxAt(g, MAT.rubber(), sx - 360, sx + 360, top - 1, top + 1, zb + 105, zf - 85); + ecyl(g, MAT.zwartMetaal(), sx + 200, zb + 55, 22, 22, top, top + 300); + boxAt(g, MAT.zwartMetaal(), sx + 190, sx + 210, top + 280, top + 300, zb + 55, zb + 280); + } if (f.opts.bovenkasten) boxAt(g, front, K.x0, K.x1, 1450, 2150, zb, zb + 350); - } else if (f.opts.kookplaat) { - const hw = Math.min(780, K.x1 - K.x0 - 100), hd = Math.min(520, f.d - 80); - boxAt(g, MAT.kookplaat(), -hw / 2, hw / 2, top, top + 4, -hd / 2, hd / 2); - hobZones(hw, hd).forEach(z => ecyl(g, MAT.ring(), z.x, z.z, z.r, z.r, top + 4, top + 5)); + } + if (f.opts.kookplaat) { + const { hx, hw, hd } = hobOf(f, K, island); + boxAt(g, MAT.kookplaat(), hx - hw / 2, hx + hw / 2, top, top + 4, -hd / 2, hd / 2); + hobZones(hw, hd).forEach(z => ecyl(g, MAT.ring(), hx + z.x, z.z, z.r, z.r, top + 4, top + 5)); } } const hobZones = (hw, hd) => [[-0.25, -0.25, 0.19], [0.25, -0.25, 0.14], [-0.25, 0.25, 0.14], [0.25, 0.25, 0.19]] .map(([a, b, r]) => ({ x: a * hw, z: b * hd, r: r * Math.min(hw, hd * 1.4) })); - function spoelX(f, K) { - const t = Math.max(0, Math.min(1, f.opts.spoelbak === undefined ? 0.6 : f.opts.spoelbak)); + function plekX(K, v, def) { + const t = Math.max(0, Math.min(1, v === undefined ? def : v)); return Math.max(K.x0 + 400, Math.min(K.x1 - 400, K.x0 + t * (K.x1 - K.x0))); } + const spoelX = (f, K) => plekX(K, f.opts.spoelbak, 0.6); + // Kookeiland: kookplaat in het midden; keukenblok: op kookplaatPlek. + const hobOf = (f, K, island) => ({ + hx: island ? 0 : plekX(K, f.opts.kookplaatPlek, 0.2), hw: Math.min(780, K.x1 - K.x0 - 100), hd: Math.min(520, f.d - 80) + }); function kitchenPlan(f, island) { const zb = -f.d / 2, zf = f.d / 2, K = kitchenModules(f, island), out = []; out.push(rectP(K.x0, K.x1, zb, zf)); @@ -413,13 +420,14 @@ window.FixtureModel = (() => { const a = K.tall === 'links' ? -f.w / 2 : f.w / 2 - 600; out.push(rectP(a, a + 600, zb, zf), lineP(a, zb, a + 600, zf, 'thin'), lineP(a, zf, a + 600, zb, 'thin')); } - if (!island) { + if (!island && f.opts.spoel !== false) { const sx = spoelX(f, K); out.push(rrectP(sx - 360, sx + 360, zb + 105, zf - 85, 40, 'thin'), circP(sx + 200, zb + 55, 22, 'thin')); - } else if (f.opts.kookplaat) { - const hw = Math.min(780, K.x1 - K.x0 - 100), hd = Math.min(520, f.d - 80); - out.push(rectP(-hw / 2, hw / 2, -hd / 2, hd / 2, 'thin')); - hobZones(hw, hd).forEach(z => out.push(circP(z.x, z.z, z.r, 'thin'))); + } + if (f.opts.kookplaat) { + const { hx, hw, hd } = hobOf(f, K, island); + out.push(rectP(hx - hw / 2, hx + hw / 2, -hd / 2, hd / 2, 'thin')); + hobZones(hw, hd).forEach(z => out.push(circP(hx + z.x, z.z, z.r, 'thin'))); } return out; }