v0.6.3: modellen Waal (12,85 × 7 m, flauw zadeldak dat aan de oostkop 1,5 m doorloopt als veranda), Maas (9,2 × 9,2 m, woningdak met inpandige veranda in de noordwesthoek) en Rijn (L-vorm 13 × 10 m, zadeldak als twee lessenaars op de naad, vleugel met plat dak, steenstrips met schoorsteenstrook, kookeiland, logeerkamer, spots in de boeidelen); overkapping per unit (dak verlengen voorbij de gevel) met palen (PostModel: hout, maat, h.o.h., op de plattegrond/PDF en in de kosten); kleur schakelaars/wcd's (wit, antraciet, zwart); gevellampen naast openingen en per vrij wandstuk gelijk verdeeld, nooit in een opening; logobord niet meer onder een unit bij een voordeur in een binnenhoek
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b137a458d4
commit
86fcd57b96
@@ -26,6 +26,7 @@ js/core/ gedeelde helpers: geometrie, bouwfasen, ribbon, UI-kit, PDF-
|
|||||||
js/wall/ WallModel (logica) + wand-PDF + wand-editor
|
js/wall/ WallModel (logica) + wand-PDF + wand-editor
|
||||||
js/floor/ FloorModel (logica) + vloer-PDF + vloer-editor
|
js/floor/ FloorModel (logica) + vloer-PDF + vloer-editor
|
||||||
js/roof/ RoofModel: dak (plat/lessenaar/zadel) + dakrand (boeidelen/daktrim)
|
js/roof/ RoofModel: dak (plat/lessenaar/zadel) + dakrand (boeidelen/daktrim)
|
||||||
|
PostModel: palen onder een overkapping/veranda (dak per unit te verlengen)
|
||||||
js/lighting/ LightingModel: gevellampen, boeideel-spots, binnenspots
|
js/lighting/ LightingModel: gevellampen, boeideel-spots, binnenspots
|
||||||
js/elektra/ ElektraModel + ElektraPdf: meterkast, schakelaars (letters), wcd, groepen, leidingen
|
js/elektra/ ElektraModel + ElektraPdf: meterkast, schakelaars (letters), wcd, groepen, leidingen
|
||||||
js/sandwich/ SandwichModel: sandwichpanelen rondom
|
js/sandwich/ SandwichModel: sandwichpanelen rondom
|
||||||
@@ -292,7 +293,18 @@ hout.balk.38x235;Constructiehout vuren C24 38×235;€/m;4,91;Houthandel Y;;2026
|
|||||||
(`HouseModel.createIJssel`, naar `examples/ontwerp_ijssel.pdf`): L-vorm
|
(`HouseModel.createIJssel`, naar `examples/ontwerp_ijssel.pdf`): L-vorm
|
||||||
14 × 4,6 m met een vleugel van 4,6 × 3 m, twee units met zadeldak en
|
14 × 4,6 m met een vleugel van 4,6 × 3 m, twee units met zadeldak en
|
||||||
riet die met een kil en een hoekkeper op elkaar aansluiten, stucwerk wit,
|
riet die met een kil en een hoekkeper op elkaar aansluiten, stucwerk wit,
|
||||||
openslaande deuren in de westgevel. De standaardmodellen staan in
|
openslaande deuren in de westgevel. Model **Waal**
|
||||||
|
(`HouseModel.createWaal`, naar `examples/ontwerp_waal.pdf`): 12,85 × 7 m,
|
||||||
|
flauw zadeldak dat aan de oostkop 1,5 m doorloopt als veranda op eiken
|
||||||
|
palen (Overkapping per unit). Model **Maas** (`HouseModel.createMaas`,
|
||||||
|
naar `examples/ontwerp_maas.pdf`): vierkant 9,2 × 9,2 m, flauw zadeldak
|
||||||
|
over de hele woning; de overkapping maakt van de noordwesthoek een
|
||||||
|
inpandige veranda (2,5 × 4,6 m) op eiken palen. Model **Rijn**
|
||||||
|
(`HouseModel.createRijn`, naar `examples/ontwerp_rijn.pdf`): L-vorm
|
||||||
|
13 × 10 m, hoofdvolume met flauw zadeldak (twee lessenaars die op de naad
|
||||||
|
de nok vormen) en een vleugel met plat dak, steenstrips rondom met een
|
||||||
|
donkere schoorsteenstrook in de oostgevel, kookeiland, logeerkamer en
|
||||||
|
apart toilet in de vleugel. De standaardmodellen staan in
|
||||||
`HouseModel.MODELS`; Bestand › Snel nieuw en het venster Nieuw worden
|
`HouseModel.MODELS`; Bestand › Snel nieuw en het venster Nieuw worden
|
||||||
daaruit opgebouwd (nieuw model = één regel plus een `create`-functie die
|
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
|
`modelKit` gebruikt). De L-woning (8 × 3,6 m + 4,5 × 3,6 m) blijft
|
||||||
|
|||||||
@@ -97,6 +97,7 @@
|
|||||||
<script src="js/floor/floor-model.js"></script>
|
<script src="js/floor/floor-model.js"></script>
|
||||||
<script src="js/floor/floor-pdf.js"></script>
|
<script src="js/floor/floor-pdf.js"></script>
|
||||||
<script src="js/roof/roof-model.js"></script>
|
<script src="js/roof/roof-model.js"></script>
|
||||||
|
<script src="js/roof/post-model.js"></script>
|
||||||
<script src="js/lighting/lighting-model.js"></script>
|
<script src="js/lighting/lighting-model.js"></script>
|
||||||
<script src="js/elektra/elektra-model.js"></script>
|
<script src="js/elektra/elektra-model.js"></script>
|
||||||
<script src="js/sandwich/sandwich-model.js"></script>
|
<script src="js/sandwich/sandwich-model.js"></script>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming.
|
* Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming.
|
||||||
* Zie LICENTIE.md.
|
* Zie LICENTIE.md.
|
||||||
*/
|
*/
|
||||||
const APP_VERSION = '0.6.2';
|
const APP_VERSION = '0.6.3';
|
||||||
// 0.0.X per kleine iteratie, 0.X.0 per mijlpaal, X.0.0 bij live-gang.
|
// 0.0.X per kleine iteratie, 0.X.0 per mijlpaal, X.0.0 bij live-gang.
|
||||||
|
|
||||||
let tabs = [];
|
let tabs = [];
|
||||||
|
|||||||
@@ -120,7 +120,10 @@ window.COST_DEFAULTS = {
|
|||||||
nokOplegging: { label: 'Oplegging nokbalk (stalen schoen + bouten), 2 per balk', unit: '€/st.', value: 45 },
|
nokOplegging: { label: 'Oplegging nokbalk (stalen schoen + bouten), 2 per balk', unit: '€/st.', value: 45 },
|
||||||
nokPlaatsen: { label: 'Stalen nokbalk plaatsen, stellen en vastzetten', unit: 's', value: 2700 },
|
nokPlaatsen: { label: 'Stalen nokbalk plaatsen, stellen en vastzetten', unit: 's', value: 2700 },
|
||||||
nokVulhoutMontage: { label: 'Vulhout in de holling van de nokbalk aanbrengen (beide zijden)', unit: 's/m', value: 240 },
|
nokVulhoutMontage: { label: 'Vulhout in de holling van de nokbalk aanbrengen (beide zijden)', unit: 's/m', value: 240 },
|
||||||
nokPersoneel: { label: 'Personeel bij plaatsen nokbalk (i.p.v. de bezetting)', unit: 'man', value: 3 }
|
nokPersoneel: { label: 'Personeel bij plaatsen nokbalk (i.p.v. de bezetting)', unit: 'man', value: 3 },
|
||||||
|
paalHoutM3: { label: 'Paalhout overkapping (eiken, geschaafd)', unit: '€/m³', value: 2400 },
|
||||||
|
paalvoet: { label: 'Paalvoet verzinkt, verstelbaar (incl. ankers)', unit: '€/st.', value: 40 },
|
||||||
|
paalMontage: { label: 'Paal plaatsen: stellen, aftoppen, vastzetten aan het dak', unit: 's/st.', value: 2700 }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ window.CostModel = (() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const wo = newEl('WON', 'Woning (dakrand, fundering, verlichting)', null);
|
const wo = newEl('WON', 'Woning (dakrand, overkapping, fundering, verlichting)', null);
|
||||||
const D = S.dak;
|
const D = S.dak;
|
||||||
if (totals.roofEdge) {
|
if (totals.roofEdge) {
|
||||||
const e = totals.roofEdge;
|
const e = totals.roofEdge;
|
||||||
@@ -238,6 +238,11 @@ window.CostModel = (() => {
|
|||||||
add(wo, 'Dak', 'Dak', 'Onderplaat boeideel', e.soffitM2, 'm²', e.soffitM2 * D.onderplaatM2, e.soffitM2 * D.onderplaatMontage);
|
add(wo, 'Dak', 'Dak', 'Onderplaat boeideel', e.soffitM2, 'm²', e.soffitM2 * D.onderplaatM2, e.soffitM2 * D.onderplaatMontage);
|
||||||
add(wo, 'Dak', 'Dak', `Daktrim ${e.trimHeight} mm`, e.trimLengthM, 'm', e.trimLengthM * D.daktrimM, e.trimLengthM * D.daktrimMontage);
|
add(wo, 'Dak', 'Dak', `Daktrim ${e.trimHeight} mm`, e.trimLengthM, 'm', e.trimLengthM * D.daktrimM, e.trimLengthM * D.daktrimMontage);
|
||||||
}
|
}
|
||||||
|
if (totals.posts) {
|
||||||
|
const pp = totals.posts;
|
||||||
|
add(wo, 'Hout', 'Dak', `${pp.wood} palen ${pp.size}×${pp.size} mm (${pp.count} st.)`, pp.lengthM, 'm', pp.m3 * D.paalHoutM3, pp.count * D.paalMontage);
|
||||||
|
add(wo, 'Staal', 'Dak', 'Paalvoeten', pp.count, 'st.', pp.count * D.paalvoet, 0);
|
||||||
|
}
|
||||||
if (parts.sandwich) {
|
if (parts.sandwich) {
|
||||||
const sw = parts.sandwich;
|
const sw = parts.sandwich;
|
||||||
add(wo, 'Vloer', 'Vloer', `Sandwichpanelen (${sw.panelCount} platen)`, sw.areaM2, 'm²', sw.areaM2 * S.vloer.sandwichM2, sw.panelCount * S.vloer.sandwichMontage);
|
add(wo, 'Vloer', 'Vloer', `Sandwichpanelen (${sw.panelCount} platen)`, sw.areaM2, 'm²', sw.areaM2 * S.vloer.sandwichM2, sw.panelCount * S.vloer.sandwichMontage);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ window.ElektraModel = (() => {
|
|||||||
switchHeight: 1500,
|
switchHeight: 1500,
|
||||||
wcdHeight: 400,
|
wcdHeight: 400,
|
||||||
wcdPerUnit: 2,
|
wcdPerUnit: 2,
|
||||||
|
plateColor: 'wit', // schakelaars, wcd's, apparaat-wcd's en vloerdozen
|
||||||
slack: 10, // %
|
slack: 10, // %
|
||||||
reserve: 300,
|
reserve: 300,
|
||||||
meterkastReserve: 500,
|
meterkastReserve: 500,
|
||||||
@@ -32,6 +33,11 @@ window.ElektraModel = (() => {
|
|||||||
droger: { label: 'Droger', code: 'DR', breaker: 'B16', cable: 'wcd', height: 1100, zone: 'techniek', material: 'Wandcontactdoos enkel met randaarde' },
|
droger: { label: 'Droger', code: 'DR', breaker: 'B16', cable: 'wcd', height: 1100, zone: 'techniek', material: 'Wandcontactdoos enkel met randaarde' },
|
||||||
warmtepomp: { label: 'Warmtepomp', code: 'WP', breaker: 'B16', cable: 'wcd', height: 1200, zone: 'techniek', material: 'Werkschakelaar 2-polig (warmtepomp)' }
|
warmtepomp: { label: 'Warmtepomp', code: 'WP', breaker: 'B16', cable: 'wcd', height: 1200, zone: 'techniek', material: 'Werkschakelaar 2-polig (warmtepomp)' }
|
||||||
};
|
};
|
||||||
|
const PLATE_COLORS = {
|
||||||
|
wit: { label: 'Wit', hex: 0xf6f6f2 },
|
||||||
|
antraciet: { label: 'Antraciet', hex: 0x3a3d41 },
|
||||||
|
zwart: { label: 'Zwart', hex: 0x161718 }
|
||||||
|
};
|
||||||
const BOX_PITCH = 71;
|
const BOX_PITCH = 71;
|
||||||
const PLATE = 80;
|
const PLATE = 80;
|
||||||
const WCD_W = 150;
|
const WCD_W = 150;
|
||||||
@@ -62,6 +68,7 @@ window.ElektraModel = (() => {
|
|||||||
s.removed = Array.isArray(i.removed) ? i.removed.slice() : [];
|
s.removed = Array.isArray(i.removed) ? i.removed.slice() : [];
|
||||||
s.vloerWcd = Array.isArray(i.vloerWcd) ? i.vloerWcd.filter(o => o && o.unitId).map(o => Object.assign({}, o)) : [];
|
s.vloerWcd = Array.isArray(i.vloerWcd) ? i.vloerWcd.filter(o => o && o.unitId).map(o => Object.assign({}, o)) : [];
|
||||||
s.enabled = s.enabled !== false;
|
s.enabled = s.enabled !== false;
|
||||||
|
if (!PLATE_COLORS[s.plateColor]) s.plateColor = DEFAULT_SETTINGS.plateColor;
|
||||||
// v2: schakelaars 1050 -> 1500 mm, wcd 300 -> 400 mm (oude standaard).
|
// v2: schakelaars 1050 -> 1500 mm, wcd 300 -> 400 mm (oude standaard).
|
||||||
if (!i.v) {
|
if (!i.v) {
|
||||||
if (i.switchHeight === 1050) s.switchHeight = DEFAULT_SETTINGS.switchHeight;
|
if (i.switchHeight === 1050) s.switchHeight = DEFAULT_SETTINGS.switchHeight;
|
||||||
@@ -561,6 +568,7 @@ window.ElektraModel = (() => {
|
|||||||
out.combos.forEach(c => { frames[c.switches.length] = (frames[c.switches.length] || 0) + 1; });
|
out.combos.forEach(c => { frames[c.switches.length] = (frames[c.switches.length] || 0) + 1; });
|
||||||
const m = out.materials;
|
const m = out.materials;
|
||||||
const add = (naam, aantal, eenheid) => { if (aantal > 0) m.push({ naam, aantal, eenheid: eenheid || 'st.' }); };
|
const add = (naam, aantal, eenheid) => { if (aantal > 0) m.push({ naam, aantal, eenheid: eenheid || 'st.' }); };
|
||||||
|
const kleur = ', ' + PLATE_COLORS[s.plateColor].label.toLowerCase();
|
||||||
if (out.meterkast) {
|
if (out.meterkast) {
|
||||||
add('Groepenkast 3-fase (aansluiting 3×25 A)', 1);
|
add('Groepenkast 3-fase (aansluiting 3×25 A)', 1);
|
||||||
add('Hoofdschakelaar 4-polig 40 A', 1);
|
add('Hoofdschakelaar 4-polig 40 A', 1);
|
||||||
@@ -571,13 +579,13 @@ window.ElektraModel = (() => {
|
|||||||
add('Installatieautomaat B16 1P+N (wcd)', nWcdG);
|
add('Installatieautomaat B16 1P+N (wcd)', nWcdG);
|
||||||
add('Installatieautomaat B16 1P+N (apparaten)', out.apps.filter(a => (APPLIANCES[a.app].poles || 1) === 1).length);
|
add('Installatieautomaat B16 1P+N (apparaten)', out.apps.filter(a => (APPLIANCES[a.app].poles || 1) === 1).length);
|
||||||
add('Installatieautomaat 2× B16, 2-fase (kookgroep)', out.apps.filter(a => APPLIANCES[a.app].poles === 2).length);
|
add('Installatieautomaat 2× B16, 2-fase (kookgroep)', out.apps.filter(a => APPLIANCES[a.app].poles === 2).length);
|
||||||
add('Schakelaar enkelpolig (inbouw)', out.switches.length);
|
add('Schakelaar enkelpolig (inbouw)' + kleur, out.switches.length);
|
||||||
Object.keys(frames).forEach(n => add(`Afdekraam ${n}-voudig`, frames[n]));
|
Object.keys(frames).forEach(n => add(`Afdekraam ${n}-voudig` + kleur, frames[n]));
|
||||||
add('Wandcontactdoos dubbel met randaarde', out.wcds.length);
|
add('Wandcontactdoos dubbel met randaarde' + kleur, out.wcds.length);
|
||||||
add('Vloerdoos met dubbele wcd (randaarde)', out.floorWcds.length);
|
add('Vloerdoos met dubbele wcd (randaarde)' + kleur, out.floorWcds.length);
|
||||||
const appMat = {};
|
const appMat = {};
|
||||||
out.apps.forEach(a => { const n = APPLIANCES[a.app].material; appMat[n] = (appMat[n] || 0) + 1; });
|
out.apps.forEach(a => { const n = APPLIANCES[a.app].material; appMat[n] = (appMat[n] || 0) + 1; });
|
||||||
Object.keys(appMat).forEach(n => add(n, appMat[n]));
|
Object.keys(appMat).forEach(n => add(n + kleur, appMat[n]));
|
||||||
add('Luchtdichte hollewanddoos (schakelaar/wcd/apparaat)', out.switches.length + 2 * out.wcds.length + out.apps.length);
|
add('Luchtdichte hollewanddoos (schakelaar/wcd/apparaat)', out.switches.length + 2 * out.wcds.length + out.apps.length);
|
||||||
add('Luchtdichte verdeeldoos (plafond)', out.boxes.length);
|
add('Luchtdichte verdeeldoos (plafond)', out.boxes.length);
|
||||||
add('Lasklem 3-voudig (schatting)', out.boxes.length * 3 + out.switches.length + out.wcds.length);
|
add('Lasklem 3-voudig (schatting)', out.boxes.length * 3 + out.switches.length + out.wcds.length);
|
||||||
@@ -731,6 +739,7 @@ window.ElektraModel = (() => {
|
|||||||
const toM = 0.001, group = new THREE.Group();
|
const toM = 0.001, group = new THREE.Group();
|
||||||
if (!el || !el.enabled) return group;
|
if (!el || !el.enabled) return group;
|
||||||
const mats = materials || createMaterials();
|
const mats = materials || createMaterials();
|
||||||
|
mats.plateMaterial.color.setHex(PLATE_COLORS[el.settings.plateColor].hex);
|
||||||
const st = BuildStages.clamp(stage), show = (key) => BuildStages.shows(st, key);
|
const st = BuildStages.clamp(stage), show = (key) => BuildStages.shows(st, key);
|
||||||
const rough = show('elektra') && !show('beplating');
|
const rough = show('elektra') && !show('beplating');
|
||||||
const box = (w, h, d, c, rotY, mat) => {
|
const box = (w, h, d, c, rotY, mat) => {
|
||||||
@@ -864,5 +873,5 @@ window.ElektraModel = (() => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return { DEFAULT_SETTINGS, CABLES, CORE_COLORS, APPLIANCES, PLATE, WCD_W, cableInfo, normalizeSettings, compute, snapToWall, wallCoords, clipPlan, roofDeckMap, createMaterials, build3D };
|
return { DEFAULT_SETTINGS, PLATE_COLORS, CABLES, CORE_COLORS, APPLIANCES, PLATE, WCD_W, cableInfo, normalizeSettings, compute, snapToWall, wallCoords, clipPlan, roofDeckMap, createMaterials, build3D };
|
||||||
})();
|
})();
|
||||||
|
|||||||
+309
-5
@@ -306,7 +306,7 @@ window.HouseModel = (() => {
|
|||||||
Object.assign(S.facade, { type: 'stuc', stucColor: 'wit' });
|
Object.assign(S.facade, { type: 'stuc', stucColor: 'wit' });
|
||||||
Object.assign(S.trasraam, { enabled: true, height: 600, finish: 'R510' });
|
Object.assign(S.trasraam, { enabled: true, height: 600, finish: 'R510' });
|
||||||
S.roof.fasciaColor = 'zwart';
|
S.roof.fasciaColor = 'zwart';
|
||||||
S.lighting.mode = 'gevel';
|
S.lighting.mode = 'spots';
|
||||||
S.lighting.direction = 'both';
|
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.apparaten) Object.assign(S.elektra.apparaten, { koken: true, oven: true, vaatwasser: true });
|
||||||
|
|
||||||
@@ -368,11 +368,298 @@ window.HouseModel = (() => {
|
|||||||
return st;
|
return st;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Model Waal (examples/ontwerp_waal.pdf): 12,85 × 7 m, twee units van 3,5 m met één flauw zadeldak over de
|
||||||
|
// woning (nok west–oost op de naad). Het dak loopt aan de oostkop 1,5 m door als veranda (10,5 m²) op drie
|
||||||
|
// eiken palen (PostModel). Geen naadwand: slaapkamer en woonkamer/keuken lopen over beide units door; bij de
|
||||||
|
// naad staat aan elke kant een binnenwand (badkamer/keuken | hal). Alles buitenwerks vanaf de noordwesthoek.
|
||||||
|
function createWaal() {
|
||||||
|
const st = newModelState('Waal');
|
||||||
|
const S = st.settings, LO = 2800, HI = 3400, W = 12850, D = 3500;
|
||||||
|
Object.assign(S.facade, { type: 'rabat', rabatColor: 'grijs' });
|
||||||
|
S.cladding = 'verticaal';
|
||||||
|
Object.assign(S.trasraam, { enabled: true, height: 600, finish: 'R510' });
|
||||||
|
Object.assign(S.roof, { fasciaColor: 'wit', overhang: RoofModel.OVERHANG_MAX, fasciaHeight: 350 });
|
||||||
|
S.roof.posts = PostModel.normalizeSettings({ enabled: true, size: 200, wood: 'eiken' });
|
||||||
|
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 });
|
||||||
|
|
||||||
|
const A = createUnitEntry('A', null, { width: W, depth: D, height: LO, windowOn: [] });
|
||||||
|
const B = createUnitEntry('B', { to: 'A', side: 'achter', align: 'start', offset: 0 }, { width: W, depth: D, height: LO, windowOn: [] });
|
||||||
|
st.units.push(A, B);
|
||||||
|
setHouseRoof(st, true);
|
||||||
|
Object.assign(S.roof.house, { form: { type: 'zadel', ridge: 'x', low: LO, high: HI } });
|
||||||
|
[A, B].forEach(u => { u.roof.extend = { f: 0, r: 1500, b: 0, l: 0 }; });
|
||||||
|
|
||||||
|
const { K, binnen, ops, iw, room, fx } = modelKit(st, LO, 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', 800, 500, 1600, 'right', 3490, K('raam')],
|
||||||
|
['raam', 800, 1100, 1000, 'right', 6530, K('raam')],
|
||||||
|
['raam', 1400, 1100, 1000, 'right', 8510, K('raam')]]);
|
||||||
|
ops(A.unit.walls.links, [['raam', 500, 1400, 700, 'left', 1170, K('raam')]]);
|
||||||
|
ops(B.unit.walls.links, [['raam', 500, 1400, 700, 'left', 390, K('raam')], ['raam', 1400, 1100, 1000, 'left', 1570, K('raam')]]);
|
||||||
|
// Oostkop onder de veranda: schuifdeur bij de keuken, openslaande deuren bij de woonkamer.
|
||||||
|
ops(A.unit.walls.rechts, [['deur', 1800, 2100, 0, 'right', 880,
|
||||||
|
K('deur', { type: 'schuifdeur', leaves: 2, mount: 'opbouw', side: 'binnen', glas: true })]]);
|
||||||
|
ops(B.unit.walls.rechts, [
|
||||||
|
['deur', 900, 2100, 0, 'right', 860, K('deur', { leaf: 'rechts', hinge: 'rechts', swing: 'buiten', volglas: true })],
|
||||||
|
['deur', 900, 2100, 0, 'right', 1760, K('deur', { leaf: 'links', hinge: 'links', swing: 'buiten', volglas: true })]]);
|
||||||
|
ops(B.unit.walls.achter, [
|
||||||
|
['raam', 500, 1400, 700, 'left', 3530, K('raam')],
|
||||||
|
['voordeur', 1100, 2100, 0, 'left', 4310, K('deur', { hinge: 'links', swing: 'buiten', glas: true })],
|
||||||
|
...[7130, 8230, 10470, 11570].map(x => ['raam', 500, 2100, 0, 'left', x, K('raam')])]);
|
||||||
|
B.lighting.walls.achter = 'openingen';
|
||||||
|
|
||||||
|
const DH = 2115;
|
||||||
|
// Noord–zuid: 'buiten' = west, 'left' = vanaf de noordkant; west–oost: 'buiten' = zuid, 'left' = vanaf de westkant.
|
||||||
|
iw(3235, 190, 3235, 3430, [['deur', 980, 2130, 'left', 1530, // slaapkamer | badkamer
|
||||||
|
Object.assign(binnen(), { type: 'schuifdeur', leaves: 1, mount: 'opbouw', side: 'buiten', dir: 'links' })]]);
|
||||||
|
iw(5325, 190, 5325, 3430); // badkamer | keuken
|
||||||
|
iw(3280, 3510, 6535, 3510, [], 160); // badkamer, keuken | hal (over de naad)
|
||||||
|
iw(3235, 3590, 3235, 4655, [['deur', 930, DH, 'left', 75, binnen({ hinge: 'links', swing: 'buiten' })]]); // hal → slaapkamer
|
||||||
|
iw(6490, 3590, 6490, 6810, [['deur', 930, DH, 'left', 72, binnen({ hinge: 'links', swing: 'binnen' })]]); // hal, entree | woonkamer
|
||||||
|
iw(190, 4700, 6445, 4700, [ // slaapkamer, hal | berging, toilet, entree
|
||||||
|
['deur', 930, DH, 'left', 120, binnen({ hinge: 'links', swing: 'binnen' })],
|
||||||
|
['deur', 930, DH, 'left', 4200, binnen({ hinge: 'rechts', swing: 'buiten' })]]);
|
||||||
|
iw(2248, 4745, 2248, 5819); // berging | toilet
|
||||||
|
iw(2203, 5864, 3607, 5864); // toilet | berging, entree
|
||||||
|
iw(2938, 5909, 2938, 6810); // berging | entree
|
||||||
|
iw(3562, 4745, 3562, 5819, [['deur', 930, DH, 'left', 70, binnen({ hinge: 'links', swing: 'binnen' })]]); // toilet → entree
|
||||||
|
|
||||||
|
const plafond = { ceiling: LO };
|
||||||
|
room('A', 9500, 1800, 'woonkeuken', 'Woonkamer/keuken', plafond);
|
||||||
|
room('A', 1500, 1800, 'slaapkamer', 'Slaapkamer', plafond);
|
||||||
|
room('A', 4300, 2200, 'badkamer', '', plafond);
|
||||||
|
room('B', 4800, 600, 'hal', 'Hal', plafond);
|
||||||
|
room('B', 2900, 1800, 'toilet', '', plafond);
|
||||||
|
room('B', 4700, 2700, 'hal', 'Entree', plafond);
|
||||||
|
room('B', 1200, 2400, 'berging', 'Berging', plafond);
|
||||||
|
|
||||||
|
const zB = (z) => z - D;
|
||||||
|
fx('kledingkast', 'A', 1690, 490, 0, { w: 3000 });
|
||||||
|
fx('bed', 'A', 1240, 2770, 270, { w: 1600, d: 2100 }, { nachtkastjes: false });
|
||||||
|
fx('douche', 'A', 4585, 640, 90, { w: 900, d: 1390 }, { glas: 'rechts', glasLengte: 700 });
|
||||||
|
fx('wastafel', 'A', 5050, 1920, 90, { w: 800 });
|
||||||
|
fx('toilet', 'A', 3790, 3085, 180, { w: 1000 });
|
||||||
|
fx('keuken', 'A', 5670, 1810, 270, { w: 3240 }, { front: 'antraciet', hogeKast: 'links', spoel: false, vaatwasser: false, kookplaat: true, kookplaatPlek: 0.45 });
|
||||||
|
fx('keuken', 'A', 7025, 490, 0, { w: 2110 }, { front: 'antraciet', hogeKast: 'geen', spoelbak: 0.5, kookplaat: false });
|
||||||
|
fx('eettafel', 'A', 10230, 1730, 0, { w: 2000, d: 1100 }, { vorm: 'ovaal', stoelen: 6 });
|
||||||
|
fx('bank', 'B', 9820, zB(5365), 90, { w: 2500, d: 900 }, { chaise: 'rechts', chaiseLengte: 1300 });
|
||||||
|
fx('fauteuil', 'B', 8420, zB(3910), 330);
|
||||||
|
fx('salontafel', 'B', 8180, zB(5180), 0, { w: 800, d: 800 }, { vorm: 'rond' });
|
||||||
|
fx('tvmeubel', 'B', 6735, zB(5815), 270, { w: 1500, d: 400 });
|
||||||
|
fx('kledingkast', 'B', 6145, zB(5777), 90, { w: 2060 });
|
||||||
|
fx('toilet', 'B', 2638, zB(5312), 270, { w: 1000 });
|
||||||
|
fx('boiler', 'B', 1810, zB(4970), 0);
|
||||||
|
fx('wasmachine', 'B', 2500, zB(6510), 180);
|
||||||
|
|
||||||
|
computeParts(st, { skip3D: true });
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Model Maas (examples/ontwerp_maas.pdf): vierkant 9,2 × 9,2 m onder één flauw zadeldak (nok west–oost op de
|
||||||
|
// naad). Twee units van 4,6 m diep: noord 6,7 m breed (oost uitgelijnd), zuid 9,2 m. De overkapping van het
|
||||||
|
// woningdak maakt van de noordwesthoek een inpandige veranda (2,5 × 4,6 m) op eiken palen. Maten buitenwerks vanaf
|
||||||
|
// de noordwesthoek van de woning; unit A begint 2,5 m oostelijker (X = interieur-coördinaten).
|
||||||
|
function createMaas() {
|
||||||
|
const st = newModelState('Maas');
|
||||||
|
const S = st.settings, LO = 2800, HI = 3500, D = 4600, V = 2500;
|
||||||
|
Object.assign(S.facade, { type: 'rabat', rabatColor: 'naturel' });
|
||||||
|
S.cladding = 'verticaal';
|
||||||
|
S.trasraam.enabled = false;
|
||||||
|
Object.assign(S.roof, { fasciaColor: 'antraciet', overhang: 300, fasciaHeight: 300 });
|
||||||
|
S.roof.posts = PostModel.normalizeSettings({ enabled: true, size: 200, wood: 'eiken' });
|
||||||
|
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 });
|
||||||
|
|
||||||
|
const A = createUnitEntry('A', null, { width: 9200 - V, depth: D, height: LO, windowOn: [] });
|
||||||
|
const B = createUnitEntry('B', { to: 'A', side: 'achter', align: 'end', offset: 0 }, { width: 9200, depth: D, height: LO, windowOn: [] });
|
||||||
|
st.units.push(A, B);
|
||||||
|
setHouseRoof(st, true);
|
||||||
|
Object.assign(S.roof.house, { form: { type: 'zadel', ridge: 'x', low: LO, high: HI }, cover: true });
|
||||||
|
|
||||||
|
const { K, binnen, ops, iw, room, fx } = modelKit(st, LO, 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', 800, 1500, 600, 'right', 970, K('raam')],
|
||||||
|
['raam', 800, 1500, 600, 'right', 2715, K('raam')],
|
||||||
|
['raam', 800, 1100, 1000, 'right', 4815, K('raam')]]);
|
||||||
|
// Westgevel onder de veranda: openslaande deuren met een vast zijlicht aan beide kanten (samen 3000 × 2100).
|
||||||
|
ops(A.unit.walls.links, [
|
||||||
|
['raam', 600, 2100, 0, 'left', 770, K('raam')],
|
||||||
|
['deur', 900, 2100, 0, 'left', 1370, K('deur', { leaf: 'rechts', hinge: 'links', swing: 'buiten', volglas: true })],
|
||||||
|
['deur', 900, 2100, 0, 'left', 2270, K('deur', { leaf: 'links', hinge: 'rechts', swing: 'buiten', volglas: true })],
|
||||||
|
['raam', 600, 2100, 0, 'left', 3170, K('raam')]]);
|
||||||
|
ops(B.unit.walls.voor, [['raam', 1100, 1500, 600, 'right', 315, K('raam')]]);
|
||||||
|
ops(B.unit.walls.links, [['raam', 800, 1500, 600, 'left', 200, K('raam')]]);
|
||||||
|
ops(B.unit.walls.rechts, [['raam', 800, 1100, 1000, 'right', 210, K('raam')]]);
|
||||||
|
ops(B.unit.walls.achter, [
|
||||||
|
['raam', 500, 700, 1400, 'left', 3525, K('raam')],
|
||||||
|
['voordeur', 1100, 2100, 0, 'left', 5675, K('deur', { hinge: 'links', swing: 'buiten' })],
|
||||||
|
['raam', 500, 1500, 600, 'left', 8375, K('raam')]]);
|
||||||
|
|
||||||
|
ensureSeamWalls(st);
|
||||||
|
const seam = st.interiorWalls.find(w => w.seam);
|
||||||
|
if (seam) {
|
||||||
|
const tA = WallModel.thicknessOf(WallModel.configFromState(A.unit.walls.links));
|
||||||
|
seam.wall.openings = [{ id: 1, type: 'deur', w: 930, h: 2115, y: 0, anchor: 'left', offset: Math.round(5790 - V - tA),
|
||||||
|
fill: binnen({ hinge: 'rechts', swing: 'binnen' }) }];
|
||||||
|
}
|
||||||
|
|
||||||
|
const DH = 2115, X = (x) => x - V;
|
||||||
|
// Noord–zuid: 'buiten' = west, 'left' = vanaf de noordkant; west–oost: 'buiten' = zuid, 'left' = vanaf de westkant.
|
||||||
|
iw(X(3250), 4700, X(3250), 9010, [ // slaapkamer | hal, toilet, badkamer
|
||||||
|
['deur', 930, DH, 'left', 70, binnen({ hinge: 'links', swing: 'binnen' })],
|
||||||
|
['deur', 980, 2130, 'left', 2780, Object.assign(binnen(), { type: 'schuifdeur', leaves: 1, mount: 'opbouw', side: 'buiten', dir: 'links' })]]);
|
||||||
|
iw(X(3295), 5740, X(5620), 5740, [['deur', 930, DH, 'left', 665, binnen({ hinge: 'links', swing: 'binnen' })]]); // hal | toilet, schacht
|
||||||
|
iw(X(5075), 5785, X(5075), 6865); // toilet | schacht
|
||||||
|
iw(X(3295), 6910, X(5530), 6910); // toilet, schacht | badkamer
|
||||||
|
iw(X(5575), 5785, X(5575), 9010); // schacht, badkamer | entree
|
||||||
|
iw(X(6865), 4700, X(6865), 9010, [ // entree | logeerkamer, berging
|
||||||
|
['deur', 930, DH, 'left', 70, binnen({ hinge: 'links', swing: 'buiten' })],
|
||||||
|
['deur', 930, DH, 'left', 3185, binnen({ hinge: 'rechts', swing: 'buiten' })]]);
|
||||||
|
iw(X(6910), 6915, X(9010), 6915); // logeerkamer | berging
|
||||||
|
|
||||||
|
const plafond = { ceiling: LO };
|
||||||
|
room('A', 2500, 2000, 'woonkeuken', 'Woonkamer/keuken');
|
||||||
|
room('B', 1500, 2000, 'slaapkamer', 'Slaapkamer', plafond);
|
||||||
|
room('B', 4100, 1700, 'toilet');
|
||||||
|
room('B', 4000, 3300, 'badkamer');
|
||||||
|
room('B', 6200, 2500, 'hal', 'Entree', plafond);
|
||||||
|
room('B', 7900, 1100, 'slaapkamer', 'Logeerkamer', plafond);
|
||||||
|
room('B', 7900, 3300, 'berging', 'Berging', plafond);
|
||||||
|
|
||||||
|
const zB = (z) => z - D;
|
||||||
|
fx('keuken', 'A', X(7620), 490, 0, { w: 1580 }, { front: 'antraciet', hogeKast: 'geen', spoelbak: 0.58, kookplaat: false });
|
||||||
|
fx('keuken', 'A', X(8710), 2290, 90, { w: 4200 }, { front: 'antraciet', hogeKast: 'rechts', spoel: false, vaatwasser: false, kookplaat: true, kookplaatPlek: 0.43 });
|
||||||
|
fx('eettafel', 'A', X(5860), 1090, 0, { w: 1000, d: 1000 }, { vorm: 'ovaal', stoelen: 4 });
|
||||||
|
fx('bank', 'A', X(4020), 3770, 180, { w: 2550, d: 900 }, { chaise: 'rechts', chaiseLengte: 1300 });
|
||||||
|
fx('salontafel', 'A', X(4200), 2710, 0, { w: 800, d: 800 }, { vorm: 'rond' });
|
||||||
|
fx('fauteuil', 'A', X(3480), 1460, 330);
|
||||||
|
fx('bed', 'B', 1240, zB(6520), 270, { w: 1600, d: 2100 }, { nachtkastjes: false });
|
||||||
|
fx('kledingkast', 'B', 1700, zB(8710), 180, { w: 3000 });
|
||||||
|
fx('toilet', 'B', 3600, zB(6325), 270, { w: 1000 });
|
||||||
|
fx('wastafel', 'B', 3760, zB(7205), 0, { w: 800 });
|
||||||
|
fx('toilet', 'B', 3760, zB(8700), 180, { w: 1000 });
|
||||||
|
fx('douche', 'B', 5090, zB(8265), 180, { w: 900, d: 1500 }, { glas: 'rechts', glasLengte: 700 });
|
||||||
|
fx('bed', 'B', 7960, zB(6430), 90, { w: 900, d: 2100 }, { nachtkastjes: false });
|
||||||
|
fx('wasmachine', 'B', 7280, zB(7260), 0);
|
||||||
|
fx('boiler', 'B', 8700, zB(7260), 0);
|
||||||
|
|
||||||
|
computeParts(st, { skip3D: true });
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Model Rijn (examples/ontwerp_rijn.pdf): L-vorm 13 × 10 m, hoofdvolume 13 × 6,54 m (twee units van 3,27 m) met
|
||||||
|
// een vleugel van 4,2 × 3,46 m aan de zuidwestkant. Flauw zadeldak over het hoofdvolume als twee lessenaars die
|
||||||
|
// op de naad de nok vormen (een woningdak zou ook de vleugel overspannen); de vleugel heeft een plat dak op
|
||||||
|
// goothoogte. Alleen een naadwand naar de vleugel; de 160 mm wand langs de nok is een gewone binnenwand.
|
||||||
|
// Steenstrips rondom, in de oostgevel een donkere strook waar de schoorsteen staat. Maten buitenwerks vanaf de
|
||||||
|
// noordwesthoek, zoals de tekening (de steenstrips komen daar in de configurator buiten de units nog bij).
|
||||||
|
function createRijn() {
|
||||||
|
const st = newModelState('Rijn');
|
||||||
|
const S = st.settings, LO = 2800, HI = 3500, D = 3270, BRICK = 'R752', SCHOORSTEEN = 'R777';
|
||||||
|
Object.assign(S.facade, { type: 'steen', stone: BRICK });
|
||||||
|
Object.assign(S.trasraam, { enabled: true, height: 500, finish: 'R669' });
|
||||||
|
Object.assign(S.roof, { fasciaColor: 'wit', overhang: 300, fasciaHeight: 350 });
|
||||||
|
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 });
|
||||||
|
|
||||||
|
const A = createUnitEntry('A', null, { width: 13000, depth: D, height: LO, windowOn: [] });
|
||||||
|
const B = createUnitEntry('B', { to: 'A', side: 'achter', align: 'start', offset: 0 }, { width: 13000, depth: D, height: LO, windowOn: [] });
|
||||||
|
const C = createUnitEntry('C', { to: 'B', side: 'achter', align: 'start', offset: 0 }, { width: 4200, depth: 3460, height: LO, windowOn: [] });
|
||||||
|
st.units.push(A, B, C);
|
||||||
|
UnitModel.applyRoofForm(A.unit, { type: 'lessenaar', dir: 'achter', low: LO, high: HI });
|
||||||
|
UnitModel.applyRoofForm(B.unit, { type: 'lessenaar', dir: 'voor', low: LO, high: HI });
|
||||||
|
|
||||||
|
const { K, binnen, ops, iw, room, fx } = modelKit(st, LO, 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, 1400, 700, 'right', 1045, K('raam')],
|
||||||
|
['raam', 500, 1400, 700, 'right', 3630, K('raam')],
|
||||||
|
['raam', 1200, 500, 1600, 'right', 4760, K('raam')],
|
||||||
|
['raam', 1400, 1400, 700, 'right', 7560, K('raam')]]);
|
||||||
|
ops(A.unit.walls.rechts, [['raam', 1800, 2100, 0, 'right', 785, K('raam')]]);
|
||||||
|
ops(B.unit.walls.rechts, [
|
||||||
|
['deur', 900, 2100, 0, 'right', 730, K('deur', { leaf: 'rechts', hinge: 'rechts', swing: 'buiten', volglas: true })],
|
||||||
|
['deur', 900, 2100, 0, 'right', 1630, K('deur', { leaf: 'links', hinge: 'links', swing: 'buiten', volglas: true })]]);
|
||||||
|
ops(B.unit.walls.links, [['raam', 1400, 1100, 1000, 'left', 465, K('raam')]]);
|
||||||
|
ops(B.unit.walls.achter, [
|
||||||
|
['raam', 1100, 2100, 0, 'left', 4680, K('raam')],
|
||||||
|
['raam', 1800, 2100, 0, 'left', 10490, K('raam')]]);
|
||||||
|
ops(C.unit.walls.links, [['raam', 500, 700, 1400, 'left', 365, K('raam')]]);
|
||||||
|
ops(C.unit.walls.rechts, [['voordeur', 1100, 2100, 0, 'right', 70, K('deur', { hinge: 'links', swing: 'buiten', glas: true })]]);
|
||||||
|
ops(C.unit.walls.achter, [['raam', 1400, 1100, 1000, 'left', 1505, K('raam')]]);
|
||||||
|
// Schoorsteen midden in de oostgevel (z 2870–3670). u loopt van buiten gezien van links (zuid) naar rechts.
|
||||||
|
A.facade.walls.rechts = { type: 'steen', stone: SCHOORSTEEN };
|
||||||
|
A.unit.walls.rechts.splits = [{ u: 400, facade: { type: 'steen', stone: BRICK } }];
|
||||||
|
B.unit.walls.rechts.splits = [{ u: 2870, facade: { type: 'steen', stone: SCHOORSTEEN } }];
|
||||||
|
|
||||||
|
const DH = 2115;
|
||||||
|
// Noord–zuid: 'buiten' = west, 'left' = vanaf de noordkant; west–oost: 'buiten' = zuid, 'left' = vanaf de westkant.
|
||||||
|
iw(190, 3266, 6513, 3266, [['deur', 930, DH, 'left', 2800, binnen({ hinge: 'links', swing: 'buiten' })]], 160); // slaapkamer, badkamer | keuken (langs de nok)
|
||||||
|
iw(4380, 190, 4380, 3186, [['deur', 980, 2130, 'left', 1136, // slaapkamer | badkamer
|
||||||
|
Object.assign(binnen(), { type: 'schuifdeur', leaves: 1, mount: 'opbouw', side: 'buiten', dir: 'links' })]]);
|
||||||
|
iw(6466, 190, 6466, 3186); // badkamer | woonkamer
|
||||||
|
iw(5800, 1135, 6421, 1135); // muurtje naast de douche
|
||||||
|
iw(2762, 3346, 2762, 6440, [['deur', 930, DH, 'left', 104, binnen({ hinge: 'links', swing: 'buiten' })]]); // berging | keuken
|
||||||
|
iw(1880, 6640, 1880, 7655, [['deur', 930, DH, 'left', 40, binnen({ hinge: 'links', swing: 'binnen' })]]); // toilet | entree
|
||||||
|
iw(190, 7700, 3107, 7700, [['deur', 930, DH, 'left', 1820, binnen({ hinge: 'links', swing: 'buiten' })]]); // toilet, entree | logeerkamer
|
||||||
|
iw(3062, 7745, 3062, 8335); // kapstoknis entree
|
||||||
|
iw(3107, 8290, 4010, 8290); // kapstoknis | kast logeerkamer
|
||||||
|
|
||||||
|
ensureSeamWalls(st, 'C');
|
||||||
|
const seam = st.interiorWalls.find(w => w.seam);
|
||||||
|
if (seam) {
|
||||||
|
const tC = WallModel.thicknessOf(WallModel.configFromState(C.unit.walls.links));
|
||||||
|
seam.wall.openings = [{ id: 1, type: 'deur', w: 930, h: DH, y: 0, anchor: 'left', offset: Math.round(2915 - tC),
|
||||||
|
fill: binnen({ hinge: 'links', swing: 'buiten' }) }];
|
||||||
|
}
|
||||||
|
|
||||||
|
const plafond = { ceiling: LO };
|
||||||
|
room('A', 9500, 1500, 'woonkeuken', 'Woonkamer/keuken');
|
||||||
|
room('A', 2600, 1700, 'slaapkamer', 'Slaapkamer', plafond);
|
||||||
|
room('A', 5400, 2000, 'badkamer', '', plafond);
|
||||||
|
room('B', 1400, 1500, 'berging', 'Berging', plafond);
|
||||||
|
room('C', 1000, 500, 'toilet');
|
||||||
|
room('C', 2400, 500, 'hal', 'Entree');
|
||||||
|
room('C', 1500, 2300, 'slaapkamer', 'Logeerkamer');
|
||||||
|
|
||||||
|
const zB = (z) => z - D, zC = (z) => z - 2 * D;
|
||||||
|
fx('kledingkast', 'A', 493, 1688, 270, { w: 2990 });
|
||||||
|
fx('bed', 'A', 2615, 1240, 0, { w: 1600, d: 2100 }, { nachtkastjes: false });
|
||||||
|
fx('douche', 'A', 5725, 640, 0, { w: 1400, d: 900 }, { glas: 'links', glasLengte: 900 });
|
||||||
|
fx('wastafel', 'A', 6191, 2000, 90, { w: 800 });
|
||||||
|
fx('toilet', 'A', 4998, 2680, 180);
|
||||||
|
fx('bank', 'A', 11195, 800, 0, { w: 3000, d: 1000 }, { chaise: 'rechts', chaiseLengte: 2100 });
|
||||||
|
fx('fauteuil', 'A', 8880, 1720, 315);
|
||||||
|
fx('salontafel', 'A', 10410, 2280, 0, { w: 800, d: 800 }, { vorm: 'rond' });
|
||||||
|
fx('keuken', 'B', 4945, zB(4350), 90, { w: 2000 }, { front: 'antraciet', hogeKast: 'geen', spoelbak: 0.15, kookplaat: true, kookplaatPlek: 0.85 });
|
||||||
|
fx('kookeiland', 'B', 5545, zB(4350), 270, { w: 2000, d: 600 }, { front: 'antraciet', kookplaat: false });
|
||||||
|
fx('keuken', 'B', 3110, zB(5080), 270, { w: 1200 }, { front: 'antraciet', hogeKast: 'rechts', spoel: false, vaatwasser: false });
|
||||||
|
fx('eettafel', 'B', 10010, zB(4990), 0, { w: 2000, d: 1100 }, { vorm: 'ovaal', stoelen: 6 });
|
||||||
|
fx('wasmachine', 'B', 620, zB(6140), 180);
|
||||||
|
fx('wasmachine', 'B', 1450, zB(6140), 180); // droger
|
||||||
|
fx('boiler', 'B', 2350, zB(6215), 180);
|
||||||
|
fx('toilet', 'C', 535, zC(7150), 270);
|
||||||
|
fx('bed', 'C', 650, zC(8745), 0, { w: 900, d: 2000 }, { nachtkastjes: false });
|
||||||
|
fx('kledingkast', 'C', 3703, zC(9072), 90, { w: 1450 });
|
||||||
|
|
||||||
|
computeParts(st, { skip3D: true });
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
|
||||||
// Standaardmodellen: Bestand › Snel nieuw en het venster Nieuw. De sleutel is de template.
|
// Standaardmodellen: Bestand › Snel nieuw en het venster Nieuw. De sleutel is de template.
|
||||||
const MODELS = {
|
const MODELS = {
|
||||||
amstel: { label: 'Woning Amstel', icon: 'house-heart', create: createAmstel },
|
amstel: { label: 'Woning Amstel', icon: 'house-heart', create: createAmstel },
|
||||||
wetering: { label: 'Woning Wetering', icon: 'house-door', create: createWetering },
|
wetering: { label: 'Woning Wetering', icon: 'house-door', create: createWetering },
|
||||||
ijssel: { label: 'Woning IJssel', icon: 'houses', create: createIJssel }
|
ijssel: { label: 'Woning IJssel', icon: 'houses', create: createIJssel },
|
||||||
|
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 }
|
||||||
};
|
};
|
||||||
|
|
||||||
function createDefaultState(template) {
|
function createDefaultState(template) {
|
||||||
@@ -598,7 +885,9 @@ window.HouseModel = (() => {
|
|||||||
ws.materials = Object.assign({}, ws.materials, facadeMaterials(state, facadeFor(state, u, side)));
|
ws.materials = Object.assign({}, ws.materials, facadeMaterials(state, facadeFor(state, u, side)));
|
||||||
});
|
});
|
||||||
if (u.roof.followFloor) {
|
if (u.roof.followFloor) {
|
||||||
const e = cover[u.id] || { l: 0, r: 0, f: 0, b: 0 };
|
// Overkapping van het woningdak plus de eigen verlenging (veranda) van deze unit.
|
||||||
|
const c = cover[u.id] || {}, x = u.roof.extend || {};
|
||||||
|
const e = Object.fromEntries(['l', 'r', 'f', 'b'].map(k => [k, (c[k] || 0) + (x[k] || 0)]));
|
||||||
u.roof.deck.floor.width = u.unit.floor.floor.width + e.l + e.r;
|
u.roof.deck.floor.width = u.unit.floor.floor.width + e.l + e.r;
|
||||||
u.roof.deck.floor.depth = u.unit.floor.floor.depth + e.f + e.b;
|
u.roof.deck.floor.depth = u.unit.floor.floor.depth + e.f + e.b;
|
||||||
u.roof.offsetX = (e.r - e.l) / 2;
|
u.roof.offsetX = (e.r - e.l) / 2;
|
||||||
@@ -610,6 +899,11 @@ window.HouseModel = (() => {
|
|||||||
applyRoofJoins(state);
|
applyRoofJoins(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EXTEND_MAX = 4000;
|
||||||
|
function normalizeExtend(e) {
|
||||||
|
return Object.fromEntries(['f', 'r', 'b', 'l'].map(k => [k, Math.max(0, Math.min(EXTEND_MAX, Math.round(Number((e || {})[k]) || 0)))]));
|
||||||
|
}
|
||||||
|
|
||||||
const houseRoofOn = (state) => !!(state.settings.roof.house && state.settings.roof.house.enabled && state.settings.roof.house.form);
|
const houseRoofOn = (state) => !!(state.settings.roof.house && state.settings.roof.house.enabled && state.settings.roof.house.form);
|
||||||
const ROOF_HEIGHT_KEYS = ['heightAll', 'heightLeft', 'heightRight', 'heightMid', 'topPts'];
|
const ROOF_HEIGHT_KEYS = ['heightAll', 'heightLeft', 'heightRight', 'heightMid', 'topPts'];
|
||||||
|
|
||||||
@@ -1338,6 +1632,10 @@ window.HouseModel = (() => {
|
|||||||
const elektraGroup = build ? ElektraModel.build3D(elektra, o.elektraMaterials, stage) : null;
|
const elektraGroup = build ? ElektraModel.build3D(elektra, o.elektraMaterials, stage) : null;
|
||||||
if (elektraGroup) elektraGroup.visible = o.elektraVisible !== false && shows('elektra');
|
if (elektraGroup) elektraGroup.visible = o.elektraVisible !== false && shows('elektra');
|
||||||
|
|
||||||
|
const groundY = units.length ? Math.max(...units.map(u => u.layout.floorTop)) - (state.settings.groundLevel || 0) : 0;
|
||||||
|
const posts = PostModel.compute(state.settings.roof.posts, units, groundY);
|
||||||
|
const postsGroup = build ? PostModel.build3D(posts, o.postMaterials) : null;
|
||||||
|
|
||||||
const sandwich = state.settings.sandwich && units.length
|
const sandwich = state.settings.sandwich && units.length
|
||||||
? SandwichModel.compute(rects, Math.max(...units.map(u => u.layout.floorTop))) : null;
|
? SandwichModel.compute(rects, Math.max(...units.map(u => u.layout.floorTop))) : null;
|
||||||
const sandwichGroup = build && sandwich ? SandwichModel.build3D(sandwich, o.sandwichMaterials || SandwichModel.createMaterials(), {
|
const sandwichGroup = build && sandwich ? SandwichModel.build3D(sandwich, o.sandwichMaterials || SandwichModel.createMaterials(), {
|
||||||
@@ -1350,7 +1648,7 @@ window.HouseModel = (() => {
|
|||||||
const fixturesGroup = build ? FixtureModel.build3D(fixtures) : null;
|
const fixturesGroup = build ? FixtureModel.build3D(fixtures) : null;
|
||||||
if (fixturesGroup) fixturesGroup.visible = shows('afwerking');
|
if (fixturesGroup) fixturesGroup.visible = shows('afwerking');
|
||||||
|
|
||||||
return { rects, seams, overlaps: overlappingPairs(rects), units, interior, roofEdge, roofEdgeGroup, lighting, lightingGroup, elektra, elektraGroup, sandwich, sandwichGroup, rooms, roomsGroup, fixtures, fixturesGroup };
|
return { rects, seams, overlaps: overlappingPairs(rects), units, interior, roofEdge, roofEdgeGroup, lighting, lightingGroup, elektra, elektraGroup, sandwich, sandwichGroup, posts, postsGroup, rooms, roomsGroup, fixtures, fixturesGroup };
|
||||||
}
|
}
|
||||||
|
|
||||||
function allWallPieces(parts) {
|
function allWallPieces(parts) {
|
||||||
@@ -1489,6 +1787,7 @@ window.HouseModel = (() => {
|
|||||||
kg: u.floorSpecs.totalKg + u.roof.specs.totalKg + u.walls.reduce((a, w) => a + w.pieces.reduce((b, p) => b + p.specs.totalKg, 0), 0)
|
kg: u.floorSpecs.totalKg + u.roof.specs.totalKg + u.walls.reduce((a, w) => a + w.pieces.reduce((b, p) => b + p.specs.totalKg, 0), 0)
|
||||||
}));
|
}));
|
||||||
if (parts.interior.length) weights.push({ label: 'Binnenwanden', element: 'BW', kg: parts.interior.reduce((a, p) => a + p.specs.totalKg, 0) });
|
if (parts.interior.length) weights.push({ label: 'Binnenwanden', element: 'BW', kg: parts.interior.reduce((a, p) => a + p.specs.totalKg, 0) });
|
||||||
|
if (parts.posts.count) weights.push({ label: 'Palen overkapping', element: 'PAAL', kg: parts.posts.kg });
|
||||||
|
|
||||||
const isolatie = parts.units.map(u => ({
|
const isolatie = parts.units.map(u => ({
|
||||||
label: `Unit ${u.id}`, element: u.id,
|
label: `Unit ${u.id}`, element: u.id,
|
||||||
@@ -1568,6 +1867,10 @@ window.HouseModel = (() => {
|
|||||||
return { unit: u.id, name: rb.profile.name, kgM: rb.profile.kgM, lengthM: rb.length / 1000, kg: rb.steelKg, fillM3: rb.fillM3, fillKg: rb.fillKg,
|
return { unit: u.id, name: rb.profile.name, kgM: rb.profile.kgM, lengthM: rb.length / 1000, kg: rb.steelKg, fillM3: rb.fillM3, fillKg: rb.fillKg,
|
||||||
util: rb.util, defl: rb.defl, deflMax: rb.deflMax, ok: rb.ok, below: rb.below, loadKNm: rb.loadKNm };
|
util: rb.util, defl: rb.defl, deflMax: rb.deflMax, ok: rb.ok, below: rb.below, loadKNm: rb.loadKNm };
|
||||||
}),
|
}),
|
||||||
|
posts: parts.posts.count ? {
|
||||||
|
count: parts.posts.count, size: parts.posts.size, wood: (PostModel.WOODS[parts.posts.wood] || {}).label || '',
|
||||||
|
lengthM: parts.posts.lengthMM / 1000, m3: parts.posts.m3, kg: parts.posts.kg
|
||||||
|
} : null,
|
||||||
roofEdge: parts.roofEdge ? {
|
roofEdge: parts.roofEdge ? {
|
||||||
fasciaLengthM: parts.roofEdge.fasciaLengthMM / 1000,
|
fasciaLengthM: parts.roofEdge.fasciaLengthMM / 1000,
|
||||||
fasciaHeight: parts.roofEdge.fasciaHeight,
|
fasciaHeight: parts.roofEdge.fasciaHeight,
|
||||||
@@ -1659,6 +1962,7 @@ window.HouseModel = (() => {
|
|||||||
if (!u.roof) { u.roof = RoofModel.createDefaultRoof(u.unit.floor); delete u.roof.fascia; }
|
if (!u.roof) { u.roof = RoofModel.createDefaultRoof(u.unit.floor); delete u.roof.fascia; }
|
||||||
if (u.roof.fascia === undefined) u.roof.fascia = oldRoof.edge !== 'daktrim';
|
if (u.roof.fascia === undefined) u.roof.fascia = oldRoof.edge !== 'daktrim';
|
||||||
if (!RoofModel.COVERINGS[u.roof.covering]) u.roof.covering = 'dakleer';
|
if (!RoofModel.COVERINGS[u.roof.covering]) u.roof.covering = 'dakleer';
|
||||||
|
u.roof.extend = normalizeExtend(u.roof.extend);
|
||||||
u.lighting = { walls: (u.lighting && u.lighting.walls) || {} };
|
u.lighting = { walls: (u.lighting && u.lighting.walls) || {} };
|
||||||
u.facade = { walls: (u.facade && u.facade.walls) || {} };
|
u.facade = { walls: (u.facade && u.facade.walls) || {} };
|
||||||
if (!hadFacade) Object.entries(u.unit.walls).forEach(([side, ws]) => {
|
if (!hadFacade) Object.entries(u.unit.walls).forEach(([side, ws]) => {
|
||||||
@@ -1672,7 +1976,7 @@ window.HouseModel = (() => {
|
|||||||
ALIGN_LABELS, SIDE_LABELS, DEFAULT_INTERIOR_THICKNESS, FACADE_TYPES, facadeFor, facadeMaterials,
|
ALIGN_LABELS, SIDE_LABELS, DEFAULT_INTERIOR_THICKNESS, FACADE_TYPES, facadeFor, facadeMaterials,
|
||||||
clone, createEmptyProject, createDefaultSettings, createUnitEntry, applyGlobalSettings, nextUnitId, MODELS, createDefaultState, unitRects, computeSeams, overlappingPairs,
|
clone, createEmptyProject, createDefaultSettings, createUnitEntry, applyGlobalSettings, nextUnitId, MODELS, createDefaultState, unitRects, computeSeams, overlappingPairs,
|
||||||
interiorWallDefaults, setThickness, thicknessOf, interiorLayout, ensureSeamWalls, syncSeamWalls, missingSeamWalls,
|
interiorWallDefaults, setThickness, thicknessOf, interiorLayout, ensureSeamWalls, syncSeamWalls, missingSeamWalls,
|
||||||
houseRoofShape, houseRoofFits, setHouseRoof, coverExtensions,
|
houseRoofShape, houseRoofFits, setHouseRoof, coverExtensions, EXTEND_MAX,
|
||||||
computeParts, allWallPieces, computeTotals, computeDimensions, dimensionRows, TRANSPORT, TRANSPORT_CLASSES, computeTransport, validateState, fromUnitDesign, normalize
|
computeParts, allWallPieces, computeTotals, computeDimensions, dimensionRows, TRANSPORT, TRANSPORT_CLASSES, computeTransport, validateState, fromUnitDesign, normalize
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -135,6 +135,10 @@ window.HousePdf = (() => {
|
|||||||
parts.roofEdge.spots.forEach(sp => doc.circle(px(sp.x), py(sp.z), 0.8, 'FD'));
|
parts.roofEdge.spots.forEach(sp => doc.circle(px(sp.x), py(sp.z), 0.8, 'FD'));
|
||||||
}
|
}
|
||||||
doc.setLineDashPattern([], 0);
|
doc.setLineDashPattern([], 0);
|
||||||
|
doc.setDrawColor(...BLACK);
|
||||||
|
doc.setLineWidth(0.2);
|
||||||
|
doc.setFillColor(160, 124, 84);
|
||||||
|
parts.posts.posts.forEach(pp => doc.rect(px(pp.x - pp.size / 2), py(pp.z - pp.size / 2), pp.size * scale, pp.size * scale, 'FD'));
|
||||||
|
|
||||||
doc.setDrawColor(...BLACK);
|
doc.setDrawColor(...BLACK);
|
||||||
doc.setFillColor(255, 211, 107);
|
doc.setFillColor(255, 211, 107);
|
||||||
@@ -586,6 +590,10 @@ window.HousePdf = (() => {
|
|||||||
rows.push(['Boeideel-spots', e.spots + ' stuks']);
|
rows.push(['Boeideel-spots', e.spots + ' stuks']);
|
||||||
rows.push(['Gevellampen', `${L.total} stuks (boven+onder ${L.both}, onder ${L.down}, boven ${L.up})`]);
|
rows.push(['Gevellampen', `${L.total} stuks (boven+onder ${L.both}, onder ${L.down}, boven ${L.up})`]);
|
||||||
rows.push(['Binnenspots', `${L.indoor} stuks (plafond)`]);
|
rows.push(['Binnenspots', `${L.indoor} stuks (plafond)`]);
|
||||||
|
if (totals.posts) {
|
||||||
|
const pp = totals.posts;
|
||||||
|
rows.push(['Palen overkapping', `${pp.count} stuks ${pp.wood.toLowerCase()} ${pp.size}x${pp.size} mm, ${fmt(pp.lengthM, 2)} m, ${fmt(pp.m3, 3)} m3, op paalvoeten`]);
|
||||||
|
}
|
||||||
PdfSheet.table(doc, 120, y, [0, 40], ['Onderdeel', 'Hoeveelheid'], rows, tableOpts('Dakrand & verlichting'));
|
PdfSheet.table(doc, 120, y, [0, 40], ['Onderdeel', 'Hoeveelheid'], rows, tableOpts('Dakrand & verlichting'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+44
-4
@@ -327,6 +327,7 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
const roofMaterials = RoofModel.createMaterials();
|
const roofMaterials = RoofModel.createMaterials();
|
||||||
const lightingMaterials = LightingModel.createMaterials();
|
const lightingMaterials = LightingModel.createMaterials();
|
||||||
const sandwichMaterials = SandwichModel.createMaterials();
|
const sandwichMaterials = SandwichModel.createMaterials();
|
||||||
|
const postMaterials = PostModel.createMaterials();
|
||||||
const elektraMaterials = ElektraModel.createMaterials();
|
const elektraMaterials = ElektraModel.createMaterials();
|
||||||
let houseGroup = null;
|
let houseGroup = null;
|
||||||
let cameraFitted = CameraMemory.restore(overviewCamKey, camera, controls);
|
let cameraFitted = CameraMemory.restore(overviewCamKey, camera, controls);
|
||||||
@@ -975,6 +976,12 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
ctx.beginPath(); ctx.arc(q.x, q.y, 3, 0, Math.PI * 2); ctx.fill();
|
ctx.beginPath(); ctx.arc(q.x, q.y, 3, 0, Math.PI * 2); ctx.fill();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
ctx.fillStyle = '#a07c54'; ctx.strokeStyle = '#e8d3b0'; ctx.lineWidth = 1;
|
||||||
|
parts.posts.posts.forEach(pp => {
|
||||||
|
const a = toPx(pp.x - pp.size / 2, pp.z - pp.size / 2), sz = pp.size * scale;
|
||||||
|
ctx.fillRect(a.x, a.y, sz, sz); ctx.strokeRect(a.x, a.y, sz, sz);
|
||||||
|
});
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
|
|
||||||
ctx.strokeStyle = '#ffe9a8'; ctx.lineWidth = 1.5;
|
ctx.strokeStyle = '#ffe9a8'; ctx.lineWidth = 1.5;
|
||||||
@@ -1238,6 +1245,12 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
UI.range('hs-fascia-h', 'Hoogte', r.fasciaHeight, { min: 150, max: 500, step: 10 }) +
|
UI.range('hs-fascia-h', 'Hoogte', r.fasciaHeight, { min: 150, max: 500, step: 10 }) +
|
||||||
sel('hs-fascia-color', 'Kleur', Object.fromEntries(Object.entries(RoofModel.FASCIA_COLORS).map(([k, c]) => [k, c.label])), r.fasciaColor) },
|
sel('hs-fascia-color', 'Kleur', Object.fromEntries(Object.entries(RoofModel.FASCIA_COLORS).map(([k, c]) => [k, c.label])), r.fasciaColor) },
|
||||||
{ label: 'Daktrim', html: num('hs-trim-h', 'Hoogte', r.trimHeight, { step: 5, hint: 'Bij units zonder boeidelen' }) },
|
{ label: 'Daktrim', html: num('hs-trim-h', 'Hoogte', r.trimHeight, { step: 5, hint: 'Bij units zonder boeidelen' }) },
|
||||||
|
{ label: 'Palen overkapping', html:
|
||||||
|
toggle('hs-posts', 'Palen', r.posts.enabled, { hint: `Onder een dak dat minstens de min. uitkraging buiten de vloer steekt (veranda); per unit: Overkapping. Nu ${parts ? parts.posts.count : 0} st.` }) +
|
||||||
|
(r.posts.enabled ? sel('hs-posts-wood', 'Hout', Object.fromEntries(Object.entries(PostModel.WOODS).map(([k, w]) => [k, w.label])), r.posts.wood) +
|
||||||
|
UI.range('hs-posts-size', 'Maat', r.posts.size, { min: 100, max: 300, step: 10, hint: 'Vierkant, mm' }) +
|
||||||
|
num('hs-posts-span', 'Max. h.o.h.', r.posts.maxSpan, { step: 100, hint: 'Grootste afstand tussen palen (of paal en gevel)' }) +
|
||||||
|
num('hs-posts-min', 'Min. uitkraging', r.posts.minExt, { step: 100, hint: 'Kleinere overstekken krijgen geen palen' }) : '') },
|
||||||
{ label: 'Daken per unit', html: `<div data-live="roof-list" class="rb-cols"></div>` });
|
{ label: 'Daken per unit', html: `<div data-live="roof-list" class="rb-cols"></div>` });
|
||||||
return groups;
|
return groups;
|
||||||
},
|
},
|
||||||
@@ -1252,6 +1265,11 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
UI.onRange('hs-fascia-h', v => { if (v > 0) r.fasciaHeight = v; }, after);
|
UI.onRange('hs-fascia-h', v => { if (v > 0) r.fasciaHeight = v; }, after);
|
||||||
UI.onSel('hs-fascia-color', v => { r.fasciaColor = v; }, rerender);
|
UI.onSel('hs-fascia-color', v => { r.fasciaColor = v; }, rerender);
|
||||||
UI.onNum('hs-trim-h', v => { r.trimHeight = v; }, after, pos);
|
UI.onNum('hs-trim-h', v => { r.trimHeight = v; }, after, pos);
|
||||||
|
UI.onToggle('hs-posts', v => { r.posts.enabled = v; }, rerender);
|
||||||
|
UI.onSel('hs-posts-wood', v => { r.posts.wood = v; }, rerender);
|
||||||
|
UI.onRange('hs-posts-size', v => { if (v > 0) r.posts.size = v; }, after);
|
||||||
|
UI.onNum('hs-posts-span', v => { if (v >= 1000) r.posts.maxSpan = v; }, after, pos);
|
||||||
|
UI.onNum('hs-posts-min', v => { if (v >= 400) r.posts.minExt = v; }, after, pos);
|
||||||
const list = () => {
|
const list = () => {
|
||||||
const el = body.querySelector('[data-live="roof-list"]');
|
const el = body.querySelector('[data-live="roof-list"]');
|
||||||
if (!el || !parts) return;
|
if (!el || !parts) return;
|
||||||
@@ -1393,7 +1411,8 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
groups.push({ label: 'Meterkast', html: num('he-mk-w', 'Breedte', e.meterkast.w, { step: 10 }) + num('he-mk-h', 'Hoogte', e.meterkast.h, { step: 10 }) +
|
groups.push({ label: 'Meterkast', html: num('he-mk-w', 'Breedte', e.meterkast.w, { step: 10 }) + num('he-mk-h', 'Hoogte', e.meterkast.h, { step: 10 }) +
|
||||||
num('he-mk-d', 'Diepte', e.meterkast.d, { step: 10 }) + num('he-mk-y', 'Onderkant', e.meterkast.y, { step: 10, hint: 'Vanaf bovenkant vloer; zo dicht mogelijk bij de voordeur' }) });
|
num('he-mk-d', 'Diepte', e.meterkast.d, { step: 10 }) + num('he-mk-y', 'Onderkant', e.meterkast.y, { step: 10, hint: 'Vanaf bovenkant vloer; zo dicht mogelijk bij de voordeur' }) });
|
||||||
groups.push({ label: 'Punten', html: num('he-sw-h', 'Schakelaar', e.switchHeight, { step: 10, hint: 'Hart, vanaf bovenkant vloer' }) +
|
groups.push({ label: 'Punten', html: num('he-sw-h', 'Schakelaar', e.switchHeight, { step: 10, hint: 'Hart, vanaf bovenkant vloer' }) +
|
||||||
num('he-wcd-h', 'Wcd', e.wcdHeight, { step: 10, hint: 'Hart, vanaf bovenkant vloer' }) + num('he-wcd-n', 'Wcd per unit', e.wcdPerUnit, { step: 1, hint: 'Dubbele wcd' }) });
|
num('he-wcd-h', 'Wcd', e.wcdHeight, { step: 10, hint: 'Hart, vanaf bovenkant vloer' }) + num('he-wcd-n', 'Wcd per unit', e.wcdPerUnit, { step: 1, hint: 'Dubbele wcd' }) +
|
||||||
|
sel('he-plate', 'Kleur', Object.fromEntries(Object.entries(ElektraModel.PLATE_COLORS).map(([k, c]) => [k, c.label])), e.plateColor || 'wit', { hint: 'Schakelaars, wcd\'s en vloerdozen' }) });
|
||||||
groups.push({ label: 'Apparaten', html: Object.entries(ElektraModel.APPLIANCES).map(([k, A]) =>
|
groups.push({ label: 'Apparaten', html: Object.entries(ElektraModel.APPLIANCES).map(([k, A]) =>
|
||||||
check(`he-app-${k}`, `${A.label} (${A.code})`, !!e.apparaten[k])).join('') });
|
check(`he-app-${k}`, `${A.label} (${A.code})`, !!e.apparaten[k])).join('') });
|
||||||
const nManual = Object.keys(e.place).length;
|
const nManual = Object.keys(e.place).length;
|
||||||
@@ -1423,6 +1442,7 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
UI.onNum('he-sw-h', v => { e.switchHeight = v; }, after, pos);
|
UI.onNum('he-sw-h', v => { e.switchHeight = v; }, after, pos);
|
||||||
UI.onNum('he-wcd-h', v => { e.wcdHeight = v; }, after, pos);
|
UI.onNum('he-wcd-h', v => { e.wcdHeight = v; }, after, pos);
|
||||||
UI.onNum('he-wcd-n', v => { e.wcdPerUnit = Math.max(0, Math.round(v)); }, after);
|
UI.onNum('he-wcd-n', v => { e.wcdPerUnit = Math.max(0, Math.round(v)); }, after);
|
||||||
|
UI.onSel('he-plate', v => { e.plateColor = v; }, rerender);
|
||||||
UI.onNum('he-slack', v => { e.slack = v; }, after);
|
UI.onNum('he-slack', v => { e.slack = v; }, after);
|
||||||
UI.onNum('he-res', v => { e.reserve = v; }, after);
|
UI.onNum('he-res', v => { e.reserve = v; }, after);
|
||||||
UI.onNum('he-mk-res', v => { e.meterkastReserve = v; }, after);
|
UI.onNum('he-mk-res', v => { e.meterkastReserve = v; }, after);
|
||||||
@@ -1860,6 +1880,9 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
(pu && pu.roof.unitRoof.ridgeBeam || u.roof.steelRidge === false && ['zadel', 'wolf'].includes(shapeNow)
|
(pu && pu.roof.unitRoof.ridgeBeam || u.roof.steelRidge === false && ['zadel', 'wolf'].includes(shapeNow)
|
||||||
? toggle('hp-steel-ridge', 'Stalen nokbalk', u.roof.steelRidge !== false, { hint: pu && pu.roof.unitRoof.ridgeBeam ? `${pu.roof.unitRoof.ridgeBeam.profile.name}, ${Math.round(pu.roof.unitRoof.ridgeBeam.steelKg)} kg; uit = verwijderen` : 'Uit = verwijderd' }) : '') +
|
? toggle('hp-steel-ridge', 'Stalen nokbalk', u.roof.steelRidge !== false, { hint: pu && pu.roof.unitRoof.ridgeBeam ? `${pu.roof.unitRoof.ridgeBeam.profile.name}, ${Math.round(pu.roof.unitRoof.ridgeBeam.steelKg)} kg; uit = verwijderen` : 'Uit = verwijderd' }) : '') +
|
||||||
toggle('hp-roof-follow', 'Maat volgt vloer', u.roof.followFloor) });
|
toggle('hp-roof-follow', 'Maat volgt vloer', u.roof.followFloor) });
|
||||||
|
if (u.roof.followFloor) groups.push({ label: 'Overkapping', html:
|
||||||
|
['f', 'r', 'b', 'l'].map(k => num('hp-ext-' + k, { f: 'Noord', r: 'Oost', b: 'Zuid', l: 'West' }[k], (u.roof.extend || {})[k] || 0,
|
||||||
|
{ hint: k === 'f' ? `Dak verlengen voorbij de gevel (veranda), max. ${HouseModel.EXTEND_MAX} mm; palen volgen vanzelf` : '' })).join('') });
|
||||||
if (!u.roof.followFloor) groups.push({ label: 'Dakmaat', html:
|
if (!u.roof.followFloor) groups.push({ label: 'Dakmaat', html:
|
||||||
num('hp-roof-w', 'Breedte', u.roof.deck.floor.width, { hint: 'West–oost' }) + num('hp-roof-d', 'Diepte', u.roof.deck.floor.depth, { hint: 'Noord–zuid' }) +
|
num('hp-roof-w', 'Breedte', u.roof.deck.floor.width, { hint: 'West–oost' }) + num('hp-roof-d', 'Diepte', u.roof.deck.floor.depth, { hint: 'Noord–zuid' }) +
|
||||||
num('hp-roof-ox', 'Versch. oost', u.roof.offsetX || 0) + num('hp-roof-oz', 'Versch. zuid', u.roof.offsetZ || 0) });
|
num('hp-roof-ox', 'Versch. oost', u.roof.offsetX || 0) + num('hp-roof-oz', 'Versch. zuid', u.roof.offsetZ || 0) });
|
||||||
@@ -1897,6 +1920,9 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
UI.onToggle('hp-fascia', v => { u.roof.fascia = v; }, rerender);
|
UI.onToggle('hp-fascia', v => { u.roof.fascia = v; }, rerender);
|
||||||
UI.onToggle('hp-steel-ridge', v => { u.roof.steelRidge = v; }, rerender);
|
UI.onToggle('hp-steel-ridge', v => { u.roof.steelRidge = v; }, rerender);
|
||||||
UI.onToggle('hp-roof-follow', v => { u.roof.followFloor = v; }, rerender);
|
UI.onToggle('hp-roof-follow', v => { u.roof.followFloor = v; }, rerender);
|
||||||
|
['f', 'r', 'b', 'l'].forEach(k => UI.onNum('hp-ext-' + k, v => {
|
||||||
|
u.roof.extend = Object.assign({ f: 0, r: 0, b: 0, l: 0 }, u.roof.extend, { [k]: Math.max(0, Math.min(HouseModel.EXTEND_MAX, v)) });
|
||||||
|
}, after));
|
||||||
UI.onNum('hp-roof-w', v => { if (v >= 500) u.roof.deck.floor.width = v; }, after);
|
UI.onNum('hp-roof-w', v => { if (v >= 500) u.roof.deck.floor.width = v; }, after);
|
||||||
UI.onNum('hp-roof-d', v => { if (v >= 500) u.roof.deck.floor.depth = v; }, after);
|
UI.onNum('hp-roof-d', v => { if (v >= 500) u.roof.deck.floor.depth = v; }, after);
|
||||||
UI.onNum('hp-roof-ox', v => { u.roof.offsetX = v; }, after);
|
UI.onNum('hp-roof-ox', v => { u.roof.offsetX = v; }, after);
|
||||||
@@ -2124,6 +2150,12 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
rows.push(['Boeideel-spots', e.spots, 'st.'], ['Gevellampen', totals.lamps.total, 'st.'], ['Binnenspots', totals.lamps.indoor, 'st.']);
|
rows.push(['Boeideel-spots', e.spots, 'st.'], ['Gevellampen', totals.lamps.total, 'st.'], ['Binnenspots', totals.lamps.indoor, 'st.']);
|
||||||
cards.push(UI.specCard('Dakrand & verlichting', 'lightbulb', rows));
|
cards.push(UI.specCard('Dakrand & verlichting', 'lightbulb', rows));
|
||||||
}
|
}
|
||||||
|
if (totals.posts) {
|
||||||
|
const pp = totals.posts;
|
||||||
|
cards.push(UI.specCard('Overkapping', 'columns', [
|
||||||
|
[`${pp.wood} palen ${pp.size}×${pp.size} mm`, pp.count, 'st.'],
|
||||||
|
['Lengte totaal', f(pp.lengthM, 2), 'm'], ['Hout', f(pp.m3, 3), 'm³'], ['Gewicht', f(pp.kg), 'kg'], ['Paalvoeten', pp.count, 'st.']]));
|
||||||
|
}
|
||||||
// TIJDELIJK (verwijderen voor productie): zaagmachine-overzicht.
|
// TIJDELIJK (verwijderen voor productie): zaagmachine-overzicht.
|
||||||
const sw = totals.saw, cs = cost.saw;
|
const sw = totals.saw, cs = cost.saw;
|
||||||
cards.push(UI.specCard('Zaagmachine (tijdelijk)', 'scissors', [
|
cards.push(UI.specCard('Zaagmachine (tijdelijk)', 'scissors', [
|
||||||
@@ -2166,7 +2198,7 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
const painted = state.settings.interiorPaint && BuildStages.shows(view3D.stage, 'afwerking');
|
const painted = state.settings.interiorPaint && BuildStages.shows(view3D.stage, 'afwerking');
|
||||||
wallMaterials.spaanplaatMaterial.color.set(painted ? state.settings.interiorColor : SPAANPLAAT_COLOR);
|
wallMaterials.spaanplaatMaterial.color.set(painted ? state.settings.interiorColor : SPAANPLAAT_COLOR);
|
||||||
roofMaterials.ceilingMaterial.color.set(state.settings.ceilingColor || '#f4f3ef');
|
roofMaterials.ceilingMaterial.color.set(state.settings.ceilingColor || '#f4f3ef');
|
||||||
parts = HouseModel.computeParts(state, { wallMaterials, floorMaterials, roofMaterials, lightingMaterials, sandwichMaterials, elektraMaterials, stage: view3D.stage, lampenVisible: view3D.lampen, elektraVisible: view3D.elektra });
|
parts = HouseModel.computeParts(state, { wallMaterials, floorMaterials, roofMaterials, lightingMaterials, sandwichMaterials, postMaterials, elektraMaterials, stage: view3D.stage, lampenVisible: view3D.lampen, elektraVisible: view3D.elektra });
|
||||||
reportDirty = true;
|
reportDirty = true;
|
||||||
|
|
||||||
if (houseGroup) { scene.remove(houseGroup); disposeGroup(houseGroup); }
|
if (houseGroup) { scene.remove(houseGroup); disposeGroup(houseGroup); }
|
||||||
@@ -2200,6 +2232,7 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
houseGroup.add(u.roof.group);
|
houseGroup.add(u.roof.group);
|
||||||
});
|
});
|
||||||
if (parts.roofEdgeGroup) houseGroup.add(parts.roofEdgeGroup);
|
if (parts.roofEdgeGroup) houseGroup.add(parts.roofEdgeGroup);
|
||||||
|
if (parts.postsGroup) houseGroup.add(parts.postsGroup);
|
||||||
}
|
}
|
||||||
if (parts.sandwichGroup) houseGroup.add(parts.sandwichGroup);
|
if (parts.sandwichGroup) houseGroup.add(parts.sandwichGroup);
|
||||||
if (parts.lightingGroup && !section) houseGroup.add(parts.lightingGroup);
|
if (parts.lightingGroup && !section) houseGroup.add(parts.lightingGroup);
|
||||||
@@ -2255,7 +2288,7 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
renderNav(parts);
|
renderNav(parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
const SIGN = { ahead: 4000, aside: 1800, poleH: 2200, poleW: 80, plateW: 1500, plateD: 30 };
|
const SIGN = { ahead: 4000, aside: 1800, clear: 1200, poleH: 2200, poleW: 80, plateW: 1500, plateD: 30 };
|
||||||
let signPos = null;
|
let signPos = null;
|
||||||
let signMaterial = null;
|
let signMaterial = null;
|
||||||
const signPoleMat = new THREE.MeshStandardMaterial({ color: 0x2b2b2b, roughness: 0.6, metalness: 0.3 });
|
const signPoleMat = new THREE.MeshStandardMaterial({ color: 0x2b2b2b, roughness: 0.6, metalness: 0.3 });
|
||||||
@@ -2284,7 +2317,14 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
function buildLogoSign(groundY) {
|
function buildLogoSign(groundY) {
|
||||||
const { d, n } = frontDoor();
|
const { d, n } = frontDoor();
|
||||||
const t = { x: n.z, z: -n.x };
|
const t = { x: n.z, z: -n.x };
|
||||||
signPos = { x: d.x + n.x * SIGN.ahead + t.x * SIGN.aside, z: d.z + n.z * SIGN.ahead + t.z * SIGN.aside };
|
// Voordeur in een binnenhoek: opzij kan het bord onder een andere unit vallen, dan verder opzij of de andere kant op.
|
||||||
|
const at = (a) => ({ x: d.x + n.x * SIGN.ahead + t.x * a, z: d.z + n.z * SIGN.ahead + t.z * a });
|
||||||
|
const free = (q) => !parts.rects.some(r => !r.error && q.x > r.x0 - SIGN.clear && q.x < r.x1 + SIGN.clear && q.z > r.z0 - SIGN.clear && q.z < r.z1 + SIGN.clear);
|
||||||
|
signPos = at(SIGN.aside);
|
||||||
|
for (let a = SIGN.aside; a <= 30000; a += 500) {
|
||||||
|
const q = [at(a), at(-a)].find(free);
|
||||||
|
if (q) { signPos = q; break; }
|
||||||
|
}
|
||||||
const toM = 0.001, plateH = SIGN.plateW / 3;
|
const toM = 0.001, plateH = SIGN.plateW / 3;
|
||||||
const g = new THREE.Group();
|
const g = new THREE.Group();
|
||||||
const pole = new THREE.Mesh(new THREE.BoxGeometry(SIGN.poleW * toM, SIGN.poleH * toM, SIGN.poleW * toM), signPoleMat);
|
const pole = new THREE.Mesh(new THREE.BoxGeometry(SIGN.poleW * toM, SIGN.poleH * toM, SIGN.poleW * toM), signPoleMat);
|
||||||
|
|||||||
@@ -39,14 +39,16 @@ window.LightingModel = (() => {
|
|||||||
calculateSegmentPositions(len, min, max).forEach(p => out.push({ x: A.x + (B.x - A.x) * p / len, z: A.z + (B.z - A.z) * p / len }));
|
calculateSegmentPositions(len, min, max).forEach(p => out.push({ x: A.x + (B.x - A.x) * p / len, z: A.z + (B.z - A.z) * p / len }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function enforceMinSpacing(positions, min) {
|
// Te dicht bij elkaar: samen naar het midden, tenzij daar een opening zit (dan vervalt de tweede).
|
||||||
|
function enforceMinSpacing(positions, min, blockedAt) {
|
||||||
let pts = positions.slice().sort((a, b) => a - b);
|
let pts = positions.slice().sort((a, b) => a - b);
|
||||||
let changed = true;
|
let changed = true;
|
||||||
while (changed && pts.length > 1) {
|
while (changed && pts.length > 1) {
|
||||||
changed = false;
|
changed = false;
|
||||||
for (let i = 0; i < pts.length - 1; i++) {
|
for (let i = 0; i < pts.length - 1; i++) {
|
||||||
if (pts[i + 1] - pts[i] < min - 0.5) {
|
if (pts[i + 1] - pts[i] < min - 0.5) {
|
||||||
pts.splice(i, 2, (pts[i] + pts[i + 1]) / 2);
|
const mid = (pts[i] + pts[i + 1]) / 2;
|
||||||
|
pts.splice(i, 2, blockedAt && blockedAt(mid) ? pts[i] : mid);
|
||||||
changed = true;
|
changed = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -55,6 +57,27 @@ window.LightingModel = (() => {
|
|||||||
return pts;
|
return pts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Naast openingen: op openingOffset van elke opening; is de tussenruimte daarvoor te klein, dan één lamp in
|
||||||
|
// het midden ervan (of geen, als die tegen de kozijnen zou hangen). Posities in u (mm vanaf de linkerkant).
|
||||||
|
function besideOpenings(cfg, s) {
|
||||||
|
const W = cfg.wallWidth, off = s.openingOffset, clear = cfg.studWidth + 150;
|
||||||
|
const ops = cfg.openings.map(op => [op.x, op.x + op.w]).sort((p, q) => p[0] - q[0]);
|
||||||
|
const out = [];
|
||||||
|
ops.forEach(([x0, x1], i) => {
|
||||||
|
const next = ops[i + 1];
|
||||||
|
if (i === 0) { const u = Math.max(x0 - off, s.endOffset); if (x0 - u >= clear) out.push(u); }
|
||||||
|
if (next) {
|
||||||
|
const gap = next[0] - x1;
|
||||||
|
if (gap >= 2 * off + 1000) out.push(x1 + off, next[0] - off);
|
||||||
|
else if (gap >= 2 * clear) out.push((x1 + next[0]) / 2);
|
||||||
|
} else {
|
||||||
|
const u = Math.min(x1 + off, W - s.endOffset);
|
||||||
|
if (u - x1 >= clear) out.push(u);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
function freeIntervals(lo, hi, blocked) {
|
function freeIntervals(lo, hi, blocked) {
|
||||||
const free = [];
|
const free = [];
|
||||||
let cursor = lo;
|
let cursor = lo;
|
||||||
@@ -113,7 +136,7 @@ window.LightingModel = (() => {
|
|||||||
};
|
};
|
||||||
if (s.mode === 'gevel') {
|
if (s.mode === 'gevel') {
|
||||||
runs.forEach(run => {
|
runs.forEach(run => {
|
||||||
const blocked = [];
|
const blocked = [], openings = [];
|
||||||
const pos = [];
|
const pos = [];
|
||||||
// Delen achter een andere wand (bijv. een stuk dat de naad in loopt) zijn geen zichtbare gevel.
|
// Delen achter een andere wand (bijv. een stuk dat de naad in loopt) zijn geen zichtbare gevel.
|
||||||
segs.filter(o => !run.segs.includes(o)).forEach(o => {
|
segs.filter(o => !run.segs.includes(o)).forEach(o => {
|
||||||
@@ -126,26 +149,29 @@ window.LightingModel = (() => {
|
|||||||
const cfg = sg.piece.config, sW = cfg.studWidth;
|
const cfg = sg.piece.config, sW = cfg.studWidth;
|
||||||
const low = Math.min(cfg.wallHeightLeft, cfg.wallHeightRight) < H + 100;
|
const low = Math.min(cfg.wallHeightLeft, cfg.wallHeightRight) < H + 100;
|
||||||
if (sg.override === 'geen' || low) { blocked.push([sg.s0, sg.s1]); return; }
|
if (sg.override === 'geen' || low) { blocked.push([sg.s0, sg.s1]); return; }
|
||||||
if (sg.override === 'openingen') {
|
const own = [];
|
||||||
blocked.push([sg.s0, sg.s1]);
|
|
||||||
cfg.openings.forEach(op => [op.x - s.openingOffset, op.x + op.w + s.openingOffset]
|
|
||||||
.filter(u => u >= 0 && u <= cfg.wallWidth).forEach(u => pos.push(uToS(sg, u))));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cfg.openings.filter(op => op.y - sW <= H && WallModel.openingTopMax(op) + sW >= H).forEach(op => {
|
cfg.openings.filter(op => op.y - sW <= H && WallModel.openingTopMax(op) + sW >= H).forEach(op => {
|
||||||
const a = uToS(sg, op.x - sW), b = uToS(sg, op.x + op.w + sW);
|
const a = uToS(sg, op.x - sW), b = uToS(sg, op.x + op.w + sW);
|
||||||
blocked.push([Math.min(a, b), Math.max(a, b)]);
|
own.push([Math.min(a, b), Math.max(a, b)]);
|
||||||
});
|
});
|
||||||
|
openings.push(...own);
|
||||||
|
if (sg.override === 'openingen') {
|
||||||
|
blocked.push([sg.s0, sg.s1]);
|
||||||
|
besideOpenings(cfg, s).forEach(u => pos.push(uToS(sg, u)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
blocked.push(...own);
|
||||||
});
|
});
|
||||||
|
// Automatisch: per vrij wandstuk gelijk verdeeld, midden in gelijke delen van ca. lampMax (één lamp
|
||||||
|
// midden tussen twee ramen), niet tegen de kozijnen of de hoek aan.
|
||||||
freeIntervals(run.s0, run.s1, blocked).forEach(([a, b]) => {
|
freeIntervals(run.s0, run.s1, blocked).forEach(([a, b]) => {
|
||||||
if (b - a < s.minWallWidth) return;
|
if (b - a < s.minWallWidth) return;
|
||||||
const first = a + s.endOffset, last = b - s.endOffset;
|
const k = Math.max(1, Math.round((b - a) / s.lampMax));
|
||||||
pos.push(first);
|
for (let i = 0; i < k; i++) pos.push(a + (b - a) * (i + 0.5) / k);
|
||||||
calculateSegmentPositions(last - first, s.lampMin, s.lampMax).forEach(p => pos.push(first + p));
|
|
||||||
pos.push(last);
|
|
||||||
});
|
});
|
||||||
// Min. h.o.h. geldt over alle lampen van de gevel samen (ook overrides en de grens tussen wanden).
|
// Min. h.o.h. geldt over alle lampen van de gevel samen (ook overrides en de grens tussen wanden).
|
||||||
enforceMinSpacing(pos, s.lampMin).forEach(v => addLamp(run, v));
|
const inOpening = (v) => openings.some(([a, b]) => v > a && v < b);
|
||||||
|
enforceMinSpacing(pos, s.lampMin, inOpening).filter(v => !inOpening(v)).forEach(v => addLamp(run, v));
|
||||||
});
|
});
|
||||||
units.forEach(u => u.walls.forEach(w => w.pieces.forEach(p => p.lamps.sort((a, b) => a.u - b.u))));
|
units.forEach(u => u.walls.forEach(w => w.pieces.forEach(p => p.lamps.sort((a, b) => a.u - b.u))));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
/*!
|
||||||
|
* Tectum
|
||||||
|
* Copyright © 2026 Machiel Heinen.
|
||||||
|
* Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming.
|
||||||
|
* Zie LICENTIE.md.
|
||||||
|
*/
|
||||||
|
// Palen onder een overkapping: steekt een dak minstens minExt buiten de vloeren uit (veranda), dan komen er
|
||||||
|
// palen onder de dakrand. Hoekpalen staan met hun buitenkant gelijk met de dakhoek; een eind dat op een gevel
|
||||||
|
// uitkomt rust op die wand. Daartussen zo weinig palen dat de h.o.h.-afstand onder maxSpan blijft.
|
||||||
|
|
||||||
|
window.PostModel = (() => {
|
||||||
|
const EPS = 0.5;
|
||||||
|
const SHOE_MM = 100; // verzinkte paalvoet tussen maaiveld en paal
|
||||||
|
const WOODS = {
|
||||||
|
eiken: { label: 'Eiken', hex: 0xa07c54, kgM3: 750 },
|
||||||
|
douglas: { label: 'Douglas', hex: 0xb98a5e, kgM3: 530 }
|
||||||
|
};
|
||||||
|
const DEFAULT_SETTINGS = { enabled: true, size: 200, wood: 'eiken', maxSpan: 3600, minExt: 1000 };
|
||||||
|
|
||||||
|
function normalizeSettings(settings) {
|
||||||
|
const s = Object.assign({}, DEFAULT_SETTINGS, settings || {});
|
||||||
|
s.enabled = s.enabled !== false;
|
||||||
|
if (!WOODS[s.wood]) s.wood = DEFAULT_SETTINGS.wood;
|
||||||
|
const num = (v, def, min, max) => (isFinite(v) ? Math.min(max, Math.max(min, Math.round(v))) : def);
|
||||||
|
s.size = num(s.size, DEFAULT_SETTINGS.size, 100, 400);
|
||||||
|
s.maxSpan = num(s.maxSpan, DEFAULT_SETTINGS.maxSpan, 1000, 8000);
|
||||||
|
s.minExt = num(s.minExt, DEFAULT_SETTINGS.minExt, 400, 5000);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// units: [{ rect (vloer), roof: { rect, ceilingAt } }] in wereld-mm; groundY = maaiveld (absoluut).
|
||||||
|
function compute(settings, units, groundY) {
|
||||||
|
const s = normalizeSettings(settings);
|
||||||
|
const out = { posts: [], count: 0, size: s.size, wood: s.wood, lengthMM: 0, m3: 0, kg: 0 };
|
||||||
|
if (!s.enabled || !units.length) return out;
|
||||||
|
const floors = units.map(u => u.rect);
|
||||||
|
const onFloor = (x, z, m) => floors.some(f => x > f.x0 - m && x < f.x1 + m && z > f.z0 - m && z < f.z1 + m);
|
||||||
|
const underRoof = (x, z) => units.some(u => { const r = u.roof.rect; return x > r.x0 && x < r.x1 && z > r.z0 && z < r.z1; });
|
||||||
|
|
||||||
|
// Vrije stukken dakrand: buitenrand van de daken samen, recht tegenover een gevel op minstens minExt (loodrecht
|
||||||
|
// op de rand gemeten); de zijkanten van een veranda krijgen alleen de hoekpalen. In een binnenhoek (veranda
|
||||||
|
// tussen twee units) loopt het stuk door tot de andere gevel en rust daar op.
|
||||||
|
const runs = [];
|
||||||
|
units.forEach(u => {
|
||||||
|
const R = u.roof.rect;
|
||||||
|
[
|
||||||
|
{ axis: 'z', at: R.x1, n: 1, s0: R.z0, s1: R.z1 }, { axis: 'z', at: R.x0, n: -1, s0: R.z0, s1: R.z1 },
|
||||||
|
{ axis: 'x', at: R.z1, n: 1, s0: R.x0, s1: R.x1 }, { axis: 'x', at: R.z0, n: -1, s0: R.x0, s1: R.x1 }
|
||||||
|
].forEach(e => {
|
||||||
|
const pt = (t, d) => (e.axis === 'z' ? { x: e.at - e.n * d, z: t } : { x: t, z: e.at - e.n * d });
|
||||||
|
const facing = (p) => Math.min(...floors.map(f => {
|
||||||
|
const [lo, hi, c0, c1, v, c] = e.axis === 'z' ? [f.z0, f.z1, f.x0, f.x1, p.z, p.x] : [f.x0, f.x1, f.z0, f.z1, p.x, p.z];
|
||||||
|
if (!(v > lo + EPS && v < hi - EPS)) return Infinity;
|
||||||
|
const d = e.n > 0 ? c - c1 : c0 - c;
|
||||||
|
return d > -EPS ? d : Infinity;
|
||||||
|
}));
|
||||||
|
const free = (t) => {
|
||||||
|
const p = pt(t, 0), o = pt(t, -1), d = facing(p);
|
||||||
|
return !underRoof(o.x, o.z) && d >= s.minExt - EPS && d < Infinity;
|
||||||
|
};
|
||||||
|
const cuts = [e.s0, e.s1].concat(floors.flatMap(f => (e.axis === 'z' ? [f.z0, f.z1] : [f.x0, f.x1]))
|
||||||
|
.filter(t => t > e.s0 + EPS && t < e.s1 - EPS)).sort((a, b) => a - b);
|
||||||
|
for (let i = 0; i < cuts.length - 1; i++) {
|
||||||
|
const a = cuts[i], b = cuts[i + 1];
|
||||||
|
if (b - a < EPS || !free((a + b) / 2)) continue;
|
||||||
|
const last = runs[runs.length - 1];
|
||||||
|
if (last && last.axis === e.axis && last.n === e.n && Math.abs(last.at - e.at) < EPS && Math.abs(last.b - a) < EPS) last.b = b;
|
||||||
|
else runs.push({ axis: e.axis, at: e.at, n: e.n, a, b });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// Aansluitende stukken van verschillende units (bijv. twee units met dezelfde veranda) samenvoegen.
|
||||||
|
runs.sort((p, q) => p.axis.localeCompare(q.axis) || p.n - q.n || Math.round(p.at) - Math.round(q.at) || p.a - q.a);
|
||||||
|
const merged = [];
|
||||||
|
runs.forEach(r => {
|
||||||
|
const m = merged.find(o => o.axis === r.axis && o.n === r.n && Math.abs(o.at - r.at) < EPS && Math.abs(o.b - r.a) < EPS);
|
||||||
|
if (m) m.b = r.b; else merged.push(Object.assign({}, r));
|
||||||
|
});
|
||||||
|
|
||||||
|
const half = s.size / 2;
|
||||||
|
const pts = [];
|
||||||
|
merged.forEach(r => {
|
||||||
|
const pos = (t) => (r.axis === 'z' ? { x: r.at - r.n * half, z: t } : { x: t, z: r.at - r.n * half });
|
||||||
|
// Een eind dat op of tegen een vloer uitkomt rust op die gevel.
|
||||||
|
const endFree = (t) => { const p = r.axis === 'z' ? { x: r.at, z: t } : { x: t, z: r.at }; return !onFloor(p.x, p.z, s.size); };
|
||||||
|
const fa = endFree(r.a), fb = endFree(r.b);
|
||||||
|
const pa = fa ? r.a + half : r.a, pb = fb ? r.b - half : r.b;
|
||||||
|
if (pb - pa < EPS) return;
|
||||||
|
const n = Math.max(1, Math.ceil((pb - pa) / s.maxSpan - 1e-9));
|
||||||
|
for (let k = 0; k <= n; k++) {
|
||||||
|
if ((k === 0 && !fa) || (k === n && !fb)) continue;
|
||||||
|
pts.push(pos(pa + (pb - pa) * k / n));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pts.forEach(p => {
|
||||||
|
if (out.posts.some(q => Math.abs(q.x - p.x) < s.size && Math.abs(q.z - p.z) < s.size)) return;
|
||||||
|
const roof = units.map(u => u.roof).find(rf => p.x > rf.rect.x0 - EPS && p.x < rf.rect.x1 + EPS && p.z > rf.rect.z0 - EPS && p.z < rf.rect.z1 + EPS);
|
||||||
|
if (!roof) return;
|
||||||
|
// Hoogste hoek: bij een hellend dak verdwijnt de paal aan de lage kant in het dakpakket, zonder kier.
|
||||||
|
const top = Math.max(...[[-1, -1], [1, -1], [-1, 1], [1, 1]].map(([a, b]) => roof.ceilingAt(p.x + a * (half - 1), p.z + b * (half - 1))));
|
||||||
|
if (!(top > groundY + SHOE_MM)) return;
|
||||||
|
out.posts.push({ x: Math.round(p.x), z: Math.round(p.z), y0: groundY, y1: Math.round(top), size: s.size });
|
||||||
|
});
|
||||||
|
out.count = out.posts.length;
|
||||||
|
out.lengthMM = out.posts.reduce((t, p) => t + p.y1 - p.y0 - SHOE_MM, 0);
|
||||||
|
out.m3 = out.lengthMM * s.size * s.size / 1e9;
|
||||||
|
out.kg = out.m3 * WOODS[s.wood].kgM3;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMaterials() {
|
||||||
|
return {
|
||||||
|
wood: Object.fromEntries(Object.entries(WOODS).map(([k, w]) => [k, new THREE.MeshStandardMaterial({ color: w.hex, roughness: 0.78 })])),
|
||||||
|
shoe: new THREE.MeshStandardMaterial({ color: 0x9ba0a4, roughness: 0.45, metalness: 0.6 })
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function build3D(data, materials) {
|
||||||
|
const toM = 0.001, group = new THREE.Group();
|
||||||
|
if (!data || !data.posts.length) return group;
|
||||||
|
const m = materials || createMaterials();
|
||||||
|
const woodMat = m.wood[data.wood] || m.wood.eiken;
|
||||||
|
data.posts.forEach(p => {
|
||||||
|
const h = p.y1 - p.y0 - SHOE_MM;
|
||||||
|
const post = new THREE.Mesh(new THREE.BoxGeometry(p.size * toM, h * toM, p.size * toM), woodMat);
|
||||||
|
post.position.set(p.x * toM, (p.y0 + SHOE_MM + h / 2) * toM, p.z * toM);
|
||||||
|
group.add(post);
|
||||||
|
const plate = new THREE.Mesh(new THREE.BoxGeometry((p.size + 40) * toM, 10 * toM, (p.size + 40) * toM), m.shoe);
|
||||||
|
plate.position.set(p.x * toM, (p.y0 + 5) * toM, p.z * toM);
|
||||||
|
group.add(plate);
|
||||||
|
const stem = new THREE.Mesh(new THREE.BoxGeometry(p.size * 0.35 * toM, (SHOE_MM - 10) * toM, p.size * 0.35 * toM), m.shoe);
|
||||||
|
stem.position.set(p.x * toM, (p.y0 + 10 + (SHOE_MM - 10) / 2) * toM, p.z * toM);
|
||||||
|
group.add(stem);
|
||||||
|
});
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { WOODS, SHOE_MM, DEFAULT_SETTINGS, normalizeSettings, compute, createMaterials, build3D };
|
||||||
|
})();
|
||||||
@@ -92,7 +92,7 @@ window.RoofModel = (() => {
|
|||||||
const deck = clone(floorState);
|
const deck = clone(floorState);
|
||||||
deck.chassis = { enabled: false, wheelDiameter: 400, trackWidth: 1200, wheelbase: 1200 };
|
deck.chassis = { enabled: false, wheelDiameter: 400, trackWidth: 1200, wheelbase: 1200 };
|
||||||
deck.openings = [];
|
deck.openings = [];
|
||||||
return { followFloor: true, offsetX: 0, offsetZ: 0, fascia: true, covering: 'dakleer', deck };
|
return { followFloor: true, offsetX: 0, offsetZ: 0, extend: { f: 0, r: 0, b: 0, l: 0 }, fascia: true, covering: 'dakleer', deck };
|
||||||
}
|
}
|
||||||
|
|
||||||
function thickness(calcData, covering) {
|
function thickness(calcData, covering) {
|
||||||
@@ -1074,6 +1074,7 @@ window.RoofModel = (() => {
|
|||||||
// v0.6.2: mansarde vervallen, wordt een zadeldak met dezelfde nok.
|
// v0.6.2: mansarde vervallen, wordt een zadeldak met dezelfde nok.
|
||||||
if (form && form.type === 'mansarde') { form.type = 'zadel'; delete form.kneeH; delete form.kneeIn; delete form.kneeRatio; }
|
if (form && form.type === 'mansarde') { form.type = 'zadel'; delete form.kneeH; delete form.kneeIn; delete form.kneeRatio; }
|
||||||
s.house = { enabled: !!h.enabled, form: form && UnitModel.ROOF_TYPES[form.type] ? form : null, cover: !!h.cover };
|
s.house = { enabled: !!h.enabled, form: form && UnitModel.ROOF_TYPES[form.type] ? form : null, cover: !!h.cover };
|
||||||
|
s.posts = PostModel.normalizeSettings(s.posts);
|
||||||
if (!FASCIA_COLORS[s.fasciaColor]) s.fasciaColor = DEFAULT_SETTINGS.fasciaColor;
|
if (!FASCIA_COLORS[s.fasciaColor]) s.fasciaColor = DEFAULT_SETTINGS.fasciaColor;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ dak.daktrimMontage;Montage daktrim;s/m;300
|
|||||||
dak.nokPlaatsen;Stalen nokbalk plaatsen, stellen en vastzetten;s;2700
|
dak.nokPlaatsen;Stalen nokbalk plaatsen, stellen en vastzetten;s;2700
|
||||||
dak.nokVulhoutMontage;Vulhout in de holling van de nokbalk aanbrengen (beide zijden);s/m;240
|
dak.nokVulhoutMontage;Vulhout in de holling van de nokbalk aanbrengen (beide zijden);s/m;240
|
||||||
dak.nokPersoneel;Personeel bij plaatsen nokbalk (i.p.v. de bezetting);man;3
|
dak.nokPersoneel;Personeel bij plaatsen nokbalk (i.p.v. de bezetting);man;3
|
||||||
|
dak.paalMontage;Paal plaatsen: stellen, aftoppen, vastzetten aan het dak;s/st.;2700
|
||||||
vloer.afwerkingMontage;Montage vloerafwerking;s/m²;1200
|
vloer.afwerkingMontage;Montage vloerafwerking;s/m²;1200
|
||||||
vloer.sandwichMontage;Montage per sandwichpaneel;s;600
|
vloer.sandwichMontage;Montage per sandwichpaneel;s;600
|
||||||
afwerking.valsPlafondMontage;Montage vals plafond;s/m²;1800
|
afwerking.valsPlafondMontage;Montage vals plafond;s/m²;1800
|
||||||
|
|||||||
|
@@ -21,6 +21,8 @@ dak.onderplaatM2;Onderplaat boeideel;€/m²;30;;;
|
|||||||
dak.daktrimM;Daktrim;€/m;12;;;
|
dak.daktrimM;Daktrim;€/m;12;;;
|
||||||
dak.staalKg;Stalen nokbalk IPE S235 (op maat gezaagd, geboord, gegrond);€/kg;2,9;;;
|
dak.staalKg;Stalen nokbalk IPE S235 (op maat gezaagd, geboord, gegrond);€/kg;2,9;;;
|
||||||
dak.nokOplegging;Oplegging nokbalk (stalen schoen + bouten), 2 per balk;€/st.;45;;;
|
dak.nokOplegging;Oplegging nokbalk (stalen schoen + bouten), 2 per balk;€/st.;45;;;
|
||||||
|
dak.paalHoutM3;Paalhout overkapping (eiken, geschaafd);€/m³;2400;;;
|
||||||
|
dak.paalvoet;Paalvoet verzinkt, verstelbaar (incl. ankers);€/st.;40;;;
|
||||||
vloer.afwerkingM2;Vloerafwerking;€/m²;35;;;
|
vloer.afwerkingM2;Vloerafwerking;€/m²;35;;;
|
||||||
vloer.sandwichM2;Sandwichpaneel;€/m²;35;;;
|
vloer.sandwichM2;Sandwichpaneel;€/m²;35;;;
|
||||||
afwerking.valsPlafondM2;Vals plafond (regelwerk + gipsplaat, afgewerkt);€/m²;38;;;
|
afwerking.valsPlafondM2;Vals plafond (regelwerk + gipsplaat, afgewerkt);€/m²;38;;;
|
||||||
|
|||||||
|
Reference in New Issue
Block a user