Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5510d56c7 |
@@ -69,7 +69,12 @@ assets/ logo, texturen, skybox
|
|||||||
Groepenverklaring met fasen en aardlekschakelaars, leidingen via stijlen
|
Groepenverklaring met fasen en aardlekschakelaars, leidingen via stijlen
|
||||||
en plafond naar de meterkast (strekkende meters per type, draad, buis,
|
en plafond naar de meterkast (strekkende meters per type, draad, buis,
|
||||||
steekverbinders op module-naden), materiaallijst en eigen PDF-blad.
|
steekverbinders op module-naden), materiaallijst en eigen PDF-blad.
|
||||||
Alles verplaatsbaar door te slepen in de plattegrond.
|
Buizen minstens 50 mm uit elkaar (gebundeld alleen het laatste stuk naar
|
||||||
|
de meterkast). Vloer-wcd's met leiding door de vloer. Elektra ook op de
|
||||||
|
PDF-bladen per wand, vloer en dakvlak (plafond). Alles verplaatsbaar door
|
||||||
|
te slepen in de plattegrond. Kosten (groep Elektra): buis + draad per
|
||||||
|
meter, boorgaten (geteld op stijlen, regels en balken), dozen,
|
||||||
|
afmonteren, groepenkast en keuring.
|
||||||
- **Kozijnen** (`wall/kozijn-model.js`): per opening een vulling (geen, raam
|
- **Kozijnen** (`wall/kozijn-model.js`): per opening een vulling (geen, raam
|
||||||
met vast glas en optioneel ventilatierooster, of deur met dorpel en
|
met vast glas en optioneel ventilatierooster, of deur met dorpel en
|
||||||
optionele glasstrook; zij- en bovenlicht vullen de rest van een grote
|
optionele glasstrook; zij- en bovenlicht vullen de rest van een grote
|
||||||
@@ -108,9 +113,11 @@ assets/ logo, texturen, skybox
|
|||||||
- **Dak:** per unit opgebouwd als gespiegelde vloer + dakleer, in de vorm
|
- **Dak:** per unit opgebouwd als gespiegelde vloer + dakleer, in de vorm
|
||||||
die de wanden aangeven: plat, lessenaar (schuine kopgevels) of zadel
|
die de wanden aangeven: plat, lessenaar (schuine kopgevels) of zadel
|
||||||
(nok). Elk dakvlak is een eigen dek met de balken als sporen en een eigen
|
(nok). Elk dakvlak is een eigen dek met de balken als sporen en een eigen
|
||||||
PDF-blad; doorvoeren gaan naar het vlak waarin ze vallen. Bedekking per
|
PDF-blad; doorvoeren gaan naar het vlak waarin ze vallen. Opbouw:
|
||||||
unit: dakleer, of bij een hellend dak riet (180 mm, 3,8 kg/m²); de
|
plafondplaat (OSB) binnen, sporen, dakbeschot (OSB 18 mm) bovenop en
|
||||||
dakrand schuift dan mee omhoog. De dakrand
|
daarop de bedekking per unit: dakleer, of bij een hellend dak riet
|
||||||
|
(180 mm, 3,8 kg/m²) of felsplaten (staande naad, zink/antraciet/zwart);
|
||||||
|
de dakrand schuift dan mee omhoog. De dakrand
|
||||||
(boeidelen/daktrim, spots) loopt als één rand door en volgt de helling.
|
(boeidelen/daktrim, spots) loopt als één rand door en volgt de helling.
|
||||||
Is een dak langs de naad minstens een boeideelhoogte hoger dan het
|
Is een dak langs de naad minstens een boeideelhoogte hoger dan het
|
||||||
aangrenzende, dan krijgt het een eigen rand rondom (boven het lage dak
|
aangrenzende, dan krijgt het een eigen rand rondom (boven het lage dak
|
||||||
|
|||||||
@@ -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.5.2';
|
const APP_VERSION = '0.5.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 = [];
|
||||||
|
|||||||
@@ -11,10 +11,10 @@
|
|||||||
//
|
//
|
||||||
// 0 regelwerk vloer-, wand- en dakbalken
|
// 0 regelwerk vloer-, wand- en dakbalken
|
||||||
// 1 elektra installatiebuizen, dozen en meterkast (ElektraModel)
|
// 1 elektra installatiebuizen, dozen en meterkast (ElektraModel)
|
||||||
// 2 beplating spaanplaat binnen, OSB vloer/plafond, cementplaat buiten
|
// 2 beplating spaanplaat binnen, OSB vloer/plafond/dakbeschot, cementplaat buiten
|
||||||
// 3 doek vochtdoek
|
// 3 doek vochtdoek
|
||||||
// 4 latwerk kruislat, trasraamplaat
|
// 4 latwerk kruislat, trasraamplaat
|
||||||
// 5 gevel rabat, steen, kozijnen, dakbedekking, sandwichpanelen
|
// 5 gevel rabat, steen, kozijnen, dakbedekking (dakleer, riet, felsplaten), sandwichpanelen
|
||||||
// 6 afwerking dakrand, verlichting, vloerafwerking, verf binnen
|
// 6 afwerking dakrand, verlichting, vloerafwerking, verf binnen
|
||||||
//
|
//
|
||||||
// Fasen zijn cumulatief: fase n toont alles t/m n. Een laag die in de
|
// Fasen zijn cumulatief: fase n toont alles t/m n. Een laag die in de
|
||||||
@@ -29,7 +29,7 @@ window.BuildStages = (() => {
|
|||||||
const LIST = [
|
const LIST = [
|
||||||
{ key: 'regelwerk', label: 'Regelwerk', hint: 'Vloer-, wand- en dakbalken' },
|
{ key: 'regelwerk', label: 'Regelwerk', hint: 'Vloer-, wand- en dakbalken' },
|
||||||
{ key: 'elektra', label: 'Elektra', hint: 'Installatiebuizen door wanden en plafond, dozen en meterkast' },
|
{ key: 'elektra', label: 'Elektra', hint: 'Installatiebuizen door wanden en plafond, dozen en meterkast' },
|
||||||
{ key: 'beplating', label: 'Beplating', hint: 'Spaanplaat binnen, OSB vloer en plafond, cementplaat buiten' },
|
{ key: 'beplating', label: 'Beplating', hint: 'Spaanplaat binnen, OSB vloer, plafond en dakbeschot, cementplaat buiten' },
|
||||||
{ key: 'doek', label: 'Vochtdoek', hint: 'Vochtdoek op de buitenzijde' },
|
{ key: 'doek', label: 'Vochtdoek', hint: 'Vochtdoek op de buitenzijde' },
|
||||||
{ key: 'latwerk', label: 'Latwerk', hint: 'Kruislat en trasraamplaat' },
|
{ key: 'latwerk', label: 'Latwerk', hint: 'Kruislat en trasraamplaat' },
|
||||||
{ key: 'gevel', label: 'Gevel & dak', hint: 'Rabat, steen, kozijnen, dakbedekking, sandwichpanelen' },
|
{ key: 'gevel', label: 'Gevel & dak', hint: 'Rabat, steen, kozijnen, dakbedekking, sandwichpanelen' },
|
||||||
|
|||||||
@@ -122,6 +122,10 @@ window.COST_DEFAULTS = {
|
|||||||
dakleerMontage: { label: 'Montage dakleer', unit: 's/m²', value: 600 },
|
dakleerMontage: { label: 'Montage dakleer', unit: 's/m²', value: 600 },
|
||||||
rietM2: { label: 'Riet', unit: '€/m²', value: 90 },
|
rietM2: { label: 'Riet', unit: '€/m²', value: 90 },
|
||||||
rietMontage: { label: 'Montage riet', unit: 's/m²', value: 3600 },
|
rietMontage: { label: 'Montage riet', unit: 's/m²', value: 3600 },
|
||||||
|
beschotM2: { label: 'Dakbeschot OSB 18 mm (bovenop de sporen)', unit: '€/m²', value: 14 },
|
||||||
|
beschotMontage: { label: 'Montage dakbeschot', unit: 's/m²', value: 150 },
|
||||||
|
felsM2: { label: 'Felsplaten staande naad (zink/staal, klangen, scheidingslaag)', unit: '€/m²', value: 65 },
|
||||||
|
felsMontage: { label: 'Montage felsplaten', unit: 's/m²', value: 1800 },
|
||||||
boeideelM: { label: 'Boeideel', unit: '€/m', value: 25 },
|
boeideelM: { label: 'Boeideel', unit: '€/m', value: 25 },
|
||||||
boeideelMontage: { label: 'Montage boeideel', unit: 's/m', value: 600 },
|
boeideelMontage: { label: 'Montage boeideel', unit: 's/m', value: 600 },
|
||||||
onderplaatM2: { label: 'Onderplaat boeideel', unit: '€/m²', value: 30 },
|
onderplaatM2: { label: 'Onderplaat boeideel', unit: '€/m²', value: 30 },
|
||||||
@@ -157,7 +161,55 @@ window.COST_DEFAULTS = {
|
|||||||
gevellamp: { label: 'Gevellamp', unit: '€/st.', value: 45 },
|
gevellamp: { label: 'Gevellamp', unit: '€/st.', value: 45 },
|
||||||
boeideelSpot: { label: 'Boeideel-spot', unit: '€/st.', value: 25 },
|
boeideelSpot: { label: 'Boeideel-spot', unit: '€/st.', value: 25 },
|
||||||
binnenspot: { label: 'Binnenspot', unit: '€/st.', value: 20 },
|
binnenspot: { label: 'Binnenspot', unit: '€/st.', value: 20 },
|
||||||
montage: { label: 'Montage per lamp/spot (incl. bekabeling)', unit: 's', value: 900 }
|
montage: { label: 'Montage per lamp/spot (armatuur plaatsen en aansluiten; leidingen en dozen onder Elektra)', unit: 's', value: 300 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Indicatieve groothandelsprijzen (excl. btw) en tijden; zie ElektraModel
|
||||||
|
// voor de hoeveelheden (strekkende meters per leidingtype, boorgaten, dozen).
|
||||||
|
elektra: {
|
||||||
|
label: 'Elektra',
|
||||||
|
items: {
|
||||||
|
personeel: { label: 'Personeel bij elektra (i.p.v. de bezetting)', unit: 'man', value: 1 },
|
||||||
|
boorgat: { label: 'Boorgat door stijl, regel of balk (aftekenen, boren, ontbramen)', unit: 's', value: 90 },
|
||||||
|
buisLeggen: { label: 'Installatiebuis leggen en bevestigen', unit: 's/m', value: 60 },
|
||||||
|
draadTrekken: { label: 'Draad trekken (per meter buis, alle aders)', unit: 's/m', value: 20 },
|
||||||
|
kabelLeggen: { label: 'XMvK-kabel leggen en bevestigen (buiten)', unit: 's/m', value: 40 },
|
||||||
|
doosPlaatsen: { label: 'Inbouwdoos plaatsen (luchtdicht, hollewand)', unit: 's', value: 240 },
|
||||||
|
verdeeldoos: { label: 'Verdeeldoos plaatsen en verbinden', unit: 's', value: 600 },
|
||||||
|
spotdoos: { label: 'Spotdoos plaatsen (luchtdicht plafond)', unit: 's', value: 300 },
|
||||||
|
vloerdoos: { label: 'Vloerdoos plaatsen (gat in vloer, aansluiten)', unit: 's', value: 1800 },
|
||||||
|
afmonteren: { label: 'Afmonteren schakelaar/wcd/apparaataansluiting', unit: 's', value: 300 },
|
||||||
|
steekverbinderMontage: { label: 'Steekverbinder module-naad monteren (set m/v)', unit: 's', value: 300 },
|
||||||
|
kastMontage: { label: 'Groepenkast plaatsen (kast, hoofdschakelaar, aardlek)', unit: 's', value: 3600 },
|
||||||
|
groepMontage: { label: 'Per groep: automaat plaatsen, bedraden, labelen', unit: 's', value: 900 },
|
||||||
|
keuring: { label: 'Meten, testen en opleveren (NEN 1010 / NEN 3140)', unit: 's', value: 10800 },
|
||||||
|
buis16M: { label: 'Installatiebuis flexibel Ø16', unit: '€/m', value: 0.35 },
|
||||||
|
buis19M: { label: 'Installatiebuis flexibel Ø19', unit: '€/m', value: 0.45 },
|
||||||
|
buis25M: { label: 'Installatiebuis flexibel Ø25', unit: '€/m', value: 0.8 },
|
||||||
|
draad15M: { label: 'VD-draad 1,5 mm² (per ader)', unit: '€/m', value: 0.28 },
|
||||||
|
draad25M: { label: 'VD-draad 2,5 mm² (per ader)', unit: '€/m', value: 0.45 },
|
||||||
|
xmvkM: { label: 'XMvK 3×1,5 mm²', unit: '€/m', value: 1.25 },
|
||||||
|
inbouwdoos: { label: 'Luchtdichte hollewanddoos', unit: '€/st.', value: 1.6 },
|
||||||
|
verdeeldoosSt: { label: 'Luchtdichte verdeeldoos', unit: '€/st.', value: 4.5 },
|
||||||
|
spotdoosSt: { label: 'Luchtdichte spotdoos', unit: '€/st.', value: 7.5 },
|
||||||
|
vloerdoosSt: { label: 'Vloerdoos met dubbele wcd', unit: '€/st.', value: 95 },
|
||||||
|
schakelaar: { label: 'Schakelaar enkelpolig (inbouw)', unit: '€/st.', value: 9 },
|
||||||
|
afdekraam1: { label: 'Afdekraam 1-voudig', unit: '€/st.', value: 3 },
|
||||||
|
afdekraam2: { label: 'Afdekraam 2-voudig', unit: '€/st.', value: 5 },
|
||||||
|
wcdDubbel: { label: 'Wandcontactdoos dubbel met randaarde', unit: '€/st.', value: 10 },
|
||||||
|
wcdEnkel: { label: 'Wandcontactdoos enkel met randaarde', unit: '€/st.', value: 6 },
|
||||||
|
perilex: { label: 'Perilex-wandcontactdoos (kookgroep)', unit: '€/st.', value: 18 },
|
||||||
|
werkschakelaar: { label: 'Werkschakelaar (warmtepomp)', unit: '€/st.', value: 28 },
|
||||||
|
lasklem: { label: 'Lasklem 3-voudig', unit: '€/st.', value: 0.35 },
|
||||||
|
steekverbinder3: { label: 'Steekverbinder 3-polig (set m/v)', unit: '€/set', value: 9 },
|
||||||
|
steekverbinder5: { label: 'Steekverbinder 5-polig (set m/v)', unit: '€/set', value: 14 },
|
||||||
|
doorvoer: { label: 'Luchtdichte kabeldoorvoer naar buiten', unit: '€/st.', value: 3.5 },
|
||||||
|
kast: { label: 'Groepenkast 3-fase leeg (behuizing, kammen, aansluitmateriaal)', unit: '€/st.', value: 180 },
|
||||||
|
hoofdschakelaar: { label: 'Hoofdschakelaar 4-polig 40 A', unit: '€/st.', value: 55 },
|
||||||
|
aardlek: { label: 'Aardlekschakelaar 2-polig 40 A / 30 mA', unit: '€/st.', value: 40 },
|
||||||
|
automaat: { label: 'Installatieautomaat 1P+N (B10/B16)', unit: '€/st.', value: 14 },
|
||||||
|
automaat2f: { label: 'Installatieautomaat 2-fase (kookgroep)', unit: '€/st.', value: 30 }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
+71
-2
@@ -77,8 +77,8 @@ window.CostModel = (() => {
|
|||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
// Berekening
|
// Berekening
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
const CAT_ORDER = ['Hout', 'Schroeven', 'Plaatwerk', 'Isolatie', 'Gevel', 'Dak', 'Vloer', 'Kozijnen', 'Verlichting'];
|
const CAT_ORDER = ['Hout', 'Schroeven', 'Plaatwerk', 'Isolatie', 'Gevel', 'Dak', 'Vloer', 'Kozijnen', 'Verlichting', 'Elektra'];
|
||||||
const ACT_ORDER = ['Zagen', 'Regelwerk monteren', 'Wanden plaatsen', 'Plaatwerk', 'Isoleren', 'Gevel', 'Latwerk', 'Dak', 'Vloer', 'Kozijnen', 'Verlichting'];
|
const ACT_ORDER = ['Zagen', 'Regelwerk monteren', 'Wanden plaatsen', 'Plaatwerk', 'Isoleren', 'Gevel', 'Latwerk', 'Dak', 'Vloer', 'Kozijnen', 'Verlichting', 'Elektra'];
|
||||||
const VERTICAL_KINDS = ['verticale balk', 'kreupele stijl'];
|
const VERTICAL_KINDS = ['verticale balk', 'kreupele stijl'];
|
||||||
const DORPEL_KINDS = ['bovendorpel', 'onderdorpel'];
|
const DORPEL_KINDS = ['bovendorpel', 'onderdorpel'];
|
||||||
|
|
||||||
@@ -233,9 +233,11 @@ window.CostModel = (() => {
|
|||||||
addJoints(rf, n * V.verbindingenPerVloerbalk, 'wolfseindbalken', true);
|
addJoints(rf, n * V.verbindingenPerVloerbalk, 'wolfseindbalken', true);
|
||||||
}
|
}
|
||||||
addSheet(rf, 'Plaatwerk', 'OSB plafond', rs.osbAreaM2, P.osbM2, P.osbMontage);
|
addSheet(rf, 'Plaatwerk', 'OSB plafond', rs.osbAreaM2, P.osbM2, P.osbMontage);
|
||||||
|
addSheet(rf, 'Plaatwerk', `Dakbeschot OSB ${RoofModel.BESCHOT_MM} mm`, rs.beschotM2 || 0, D.beschotM2, D.beschotMontage);
|
||||||
add(rf, 'Isolatie', 'Isoleren', 'Glaswol dak', rs.voidM3, 'm³', rs.voidM3 * S.isolatie.glaswolM3, rs.voidM3 * S.isolatie.glaswolMontage);
|
add(rf, 'Isolatie', 'Isoleren', 'Glaswol dak', rs.voidM3, 'm³', rs.voidM3 * S.isolatie.glaswolM3, rs.voidM3 * S.isolatie.glaswolMontage);
|
||||||
add(rf, 'Dak', 'Dak', 'Dakleer (EPDM)', rs.dakleerM2 || 0, 'm²', (rs.dakleerM2 || 0) * D.dakleerM2, (rs.dakleerM2 || 0) * D.dakleerMontage);
|
add(rf, 'Dak', 'Dak', 'Dakleer (EPDM)', rs.dakleerM2 || 0, 'm²', (rs.dakleerM2 || 0) * D.dakleerM2, (rs.dakleerM2 || 0) * D.dakleerMontage);
|
||||||
add(rf, 'Dak', 'Dak', 'Riet', rs.rietM2 || 0, 'm²', (rs.rietM2 || 0) * D.rietM2, (rs.rietM2 || 0) * D.rietMontage);
|
add(rf, 'Dak', 'Dak', 'Riet', rs.rietM2 || 0, 'm²', (rs.rietM2 || 0) * D.rietM2, (rs.rietM2 || 0) * D.rietMontage);
|
||||||
|
add(rf, 'Dak', 'Dak', 'Felsplaten (staande naad)', rs.felsM2 || 0, 'm²', (rs.felsM2 || 0) * D.felsM2, (rs.felsM2 || 0) * D.felsMontage);
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Woning-breed: dakrand, sandwichpanelen, verlichting ---
|
// --- Woning-breed: dakrand, sandwichpanelen, verlichting ---
|
||||||
@@ -257,6 +259,73 @@ window.CostModel = (() => {
|
|||||||
add(wo, 'Verlichting', 'Verlichting', 'Boeideel-spots', spots, 'st.', spots * L.boeideelSpot, spots * L.montage);
|
add(wo, 'Verlichting', 'Verlichting', 'Boeideel-spots', spots, 'st.', spots * L.boeideelSpot, spots * L.montage);
|
||||||
add(wo, 'Verlichting', 'Verlichting', 'Binnenspots', lm.indoor, 'st.', lm.indoor * L.binnenspot, lm.indoor * L.montage);
|
add(wo, 'Verlichting', 'Verlichting', 'Binnenspots', lm.indoor, 'st.', lm.indoor * L.binnenspot, lm.indoor * L.montage);
|
||||||
|
|
||||||
|
// --- Elektra (ElektraModel): per unit de leidingen (strekkende meters
|
||||||
|
// per type: buis + draad per ader, of XMvK), boorgaten, steekverbinders,
|
||||||
|
// dozen en aansluitingen; meterkast, groepen en keuring bij de unit waar
|
||||||
|
// de meterkast hangt. Arbeid met E.personeel man.
|
||||||
|
const elk = parts.elektra, E = S.elektra;
|
||||||
|
if (elk && elk.enabled) {
|
||||||
|
const unitIds = new Set(parts.units.map(u => u.id)), elOf = {};
|
||||||
|
const elFor = (uid) => {
|
||||||
|
const id = unitIds.has(uid) ? uid : null, k = id || '-';
|
||||||
|
return elOf[k] || (elOf[k] = newEl(id ? `${id}-EL` : 'EL', id ? `Unit ${id} elektra` : 'Elektra', id));
|
||||||
|
};
|
||||||
|
const ea = (uid, label, qty, unit, mat, sec) => add(elFor(uid), 'Elektra', 'Elektra', label, qty, unit, mat, sec, E.personeel);
|
||||||
|
const mkUnit = elk.meterkast ? elk.meterkast.unitId : null;
|
||||||
|
const groupUnit = (nr) => (elk.groups.find(g => g.nr === nr) || {}).unitId || mkUnit;
|
||||||
|
const buisPrijs = (d) => d >= 25 ? E.buis25M : d >= 19 ? E.buis19M : E.buis16M;
|
||||||
|
const agg = {};
|
||||||
|
elk.runs.forEach(r => {
|
||||||
|
const uid = groupUnit(r.group), a = agg[uid] = agg[uid] || { types: {}, holes: 0, cross: {} };
|
||||||
|
a.types[r.type] = (a.types[r.type] || 0) + r.lengthMM / 1000;
|
||||||
|
a.holes += r.holes || 0;
|
||||||
|
if (r.crossings) { const n = ElektraModel.cableInfo(r.type).cores.length; a.cross[n] = (a.cross[n] || 0) + r.crossings; }
|
||||||
|
});
|
||||||
|
Object.keys(agg).forEach(uid => {
|
||||||
|
const a = agg[uid];
|
||||||
|
Object.keys(a.types).sort().forEach(type => {
|
||||||
|
const c = ElektraModel.cableInfo(type), m = a.types[type];
|
||||||
|
if (c.conduit) {
|
||||||
|
const draad = c.mm2 >= 2.5 ? E.draad25M : E.draad15M;
|
||||||
|
ea(uid, `${c.label}: ${c.use.toLowerCase()} (buis + ${c.cores.length} aders)`, m, 'm', m * (buisPrijs(c.conduit) + c.cores.length * draad), m * (E.buisLeggen + E.draadTrekken));
|
||||||
|
} else ea(uid, `${c.label}: ${c.use.toLowerCase()}`, m, 'm', m * E.xmvkM, m * E.kabelLeggen);
|
||||||
|
});
|
||||||
|
ea(uid, `Boorgaten stijlen/regels/balken (${E.boorgat} s per gat)`, a.holes, 'st.', 0, a.holes * E.boorgat);
|
||||||
|
Object.keys(a.cross).forEach(n => {
|
||||||
|
const k = a.cross[n];
|
||||||
|
ea(uid, `Steekverbinder module-naad ${n}-polig`, k, 'set', k * (Number(n) >= 5 ? E.steekverbinder5 : E.steekverbinder3), k * E.steekverbinderMontage);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const count = (list, uid) => list.filter(x => x.unitId === uid).length;
|
||||||
|
parts.units.forEach(u => {
|
||||||
|
const uid = u.id;
|
||||||
|
const nSw = count(elk.switches, uid), combos = elk.combos.filter(c => c.unitId === uid);
|
||||||
|
const raam1 = combos.filter(c => c.switches.length === 1).length, raam2 = combos.length - raam1;
|
||||||
|
ea(uid, `Schakelaars (${raam1}× 1-voudig, ${raam2}× 2-voudig raam)`, nSw, 'st.',
|
||||||
|
nSw * (E.schakelaar + E.inbouwdoos) + raam1 * E.afdekraam1 + raam2 * E.afdekraam2, nSw * (E.doosPlaatsen + E.afmonteren));
|
||||||
|
const nW = count(elk.wcds, uid);
|
||||||
|
ea(uid, 'Wandcontactdozen dubbel (2 inbouwdozen)', nW, 'st.', nW * (E.wcdDubbel + 2 * E.inbouwdoos), nW * (2 * E.doosPlaatsen + E.afmonteren));
|
||||||
|
elk.apps.filter(a => a.unitId === uid).forEach(a => {
|
||||||
|
const price = a.app === 'koken' ? E.perilex : a.app === 'warmtepomp' ? E.werkschakelaar : E.wcdEnkel;
|
||||||
|
ea(uid, `Aansluitpunt ${a.label.toLowerCase()}`, 1, 'st.', price + E.inbouwdoos, E.doosPlaatsen + E.afmonteren);
|
||||||
|
});
|
||||||
|
const nV = count(elk.floorWcds, uid);
|
||||||
|
ea(uid, 'Vloerdoos met dubbele wcd', nV, 'st.', nV * E.vloerdoosSt, nV * (E.vloerdoos + E.afmonteren));
|
||||||
|
const nB = count(elk.boxes, uid), nKl = nB * 3 + nSw + nW;
|
||||||
|
ea(uid, `Verdeeldozen plafond (+ ${nKl} lasklemmen)`, nB, 'st.', nB * E.verdeeldoosSt + nKl * E.lasklem, nB * E.verdeeldoos);
|
||||||
|
const nSpot = elk.switches.filter(sw => sw.unitId === uid).reduce((t, sw) => t + sw.lamps.filter(l => l.kind === 'binnenspot').length, 0);
|
||||||
|
ea(uid, 'Spotdozen plafond (luchtdicht)', nSpot, 'st.', nSpot * E.spotdoosSt, nSpot * E.spotdoos);
|
||||||
|
const nDv = elk.runs.filter(r => r.type === 'buiten' && r.from.startsWith('VD') && groupUnit(r.group) === uid).length;
|
||||||
|
ea(uid, 'Kabeldoorvoer naar buiten', nDv, 'st.', nDv * E.doorvoer, 0);
|
||||||
|
});
|
||||||
|
if (elk.meterkast) {
|
||||||
|
const g1 = elk.groups.filter(g => (g.poles || 1) === 1).length, g2 = elk.groups.length - g1;
|
||||||
|
ea(mkUnit, `Groepenkast 3×25 A: kast, hoofdschakelaar, ${elk.rcdCount} aardlek`, 1, 'st.', E.kast + E.hoofdschakelaar + elk.rcdCount * E.aardlek, E.kastMontage);
|
||||||
|
ea(mkUnit, `Groepen: ${g1}× automaat 1P+N${g2 ? `, ${g2}× 2-fase` : ''}`, elk.groups.length, 'groep', g1 * E.automaat + g2 * E.automaat2f, elk.groups.length * E.groepMontage);
|
||||||
|
ea(mkUnit, 'Meten, testen en opleveren', 1, 'woning', 0, E.keuring);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Optellen ---
|
// --- Optellen ---
|
||||||
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 });
|
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 });
|
||||||
elements.forEach(el => Object.assign(el, sumLines(el.lines)));
|
elements.forEach(el => Object.assign(el, sumLines(el.lines)));
|
||||||
|
|||||||
+249
-11
@@ -31,6 +31,9 @@
|
|||||||
// Handmatige plekken gaan voor: de automatische plaatsing wijkt uit.
|
// Handmatige plekken gaan voor: de automatische plaatsing wijkt uit.
|
||||||
// Wandvlakken: binnenzijde van buitenwanden en naadwanddelen, beide
|
// Wandvlakken: binnenzijde van buitenwanden en naadwanddelen, beide
|
||||||
// zijden van binnenwanden (in de unit waar hun midden ligt).
|
// zijden van binnenwanden (in de unit waar hun midden ligt).
|
||||||
|
// - Vloer-wcd's (settings.vloerWcd = [{ id, unitId, x, z }], x/z vanaf de
|
||||||
|
// noordwesthoek van de unit): vloerdoos met dubbele wcd (bijv. bij de
|
||||||
|
// eettafel), op de wcd-groep van die unit, leiding door de vloer.
|
||||||
// - Groepen: per unit een lichtgroep (B10, 1,5 mm²) en een wcd-groep
|
// - Groepen: per unit een lichtgroep (B10, 1,5 mm²) en een wcd-groep
|
||||||
// (B16, 2,5 mm²); het buitenlicht op een eigen groep (B10, XMvK 3×1,5),
|
// (B16, 2,5 mm²); het buitenlicht op een eigen groep (B10, XMvK 3×1,5),
|
||||||
// op dezelfde aardlek als het binnenlicht van de voordeur-unit (zelfde
|
// op dezelfde aardlek als het binnenlicht van de voordeur-unit (zelfde
|
||||||
@@ -47,6 +50,16 @@
|
|||||||
// XMvK. Een leiding over een naad tussen units krijgt een steekverbinder
|
// XMvK. Een leiding over een naad tussen units krijgt een steekverbinder
|
||||||
// (modules worden los gebouwd en op de bouwplaats gekoppeld).
|
// (modules worden los gebouwd en op de bouwplaats gekoppeld).
|
||||||
// Elke leiding: + reserve (lus in de doos, meterkast extra) en + slack %.
|
// Elke leiding: + reserve (lus in de doos, meterkast extra) en + slack %.
|
||||||
|
// Boorgaten per leiding (run.holes, voor de kosten): in een wand de stijlen
|
||||||
|
// (horizontaal stuk) of de regels/dorpels (verticaal stuk) die de buis
|
||||||
|
// kruist (WallModel.computeBOM), in plafond en vloer de balken dwars op de
|
||||||
|
// buis (balkposities van het dek), een gat in de vloerplaat waar een buis
|
||||||
|
// de vloer in gaat, en een doorvoer naar buiten voor de buitenverlichting.
|
||||||
|
// Buizen houden onderling minstens CLEAR (50 mm) vrij: na het routeren
|
||||||
|
// schuift elk te dicht (evenwijdig, overlappend) padstuk naar een eigen baan
|
||||||
|
// met een korte knik ervoor en erna (separateRuns). Alleen het laatste stuk
|
||||||
|
// naar de meterkast is gebundeld: de buizen komen tegen elkaar aan de kast
|
||||||
|
// binnen. Leidingen naar een vloer-wcd lopen in de vloer (onder de OSB).
|
||||||
//
|
//
|
||||||
// Plain script, geladen na lighting-model.js en unit-model.js.
|
// Plain script, geladen na lighting-model.js en unit-model.js.
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -62,8 +75,11 @@ window.ElektraModel = (() => {
|
|||||||
reserve: 300, // mm per leiding (lussen in de dozen)
|
reserve: 300, // mm per leiding (lussen in de dozen)
|
||||||
meterkastReserve: 500, // mm extra per leiding vanuit de meterkast
|
meterkastReserve: 500, // mm extra per leiding vanuit de meterkast
|
||||||
apparaten: { koken: true, wasmachine: true, droger: true, warmtepomp: true, oven: false, vaatwasser: false },
|
apparaten: { koken: true, wasmachine: true, droger: true, warmtepomp: true, oven: false, vaatwasser: false },
|
||||||
place: {} // handmatige plekken (zie boven)
|
place: {}, // handmatige plekken (zie boven)
|
||||||
|
vloerWcd: [] // vloer-wcd's (zie boven)
|
||||||
};
|
};
|
||||||
|
const CLEAR = 50; // min. vrije ruimte tussen buizen
|
||||||
|
const FLOOR_DEPTH = 100; // buis in de vloer: zoveel onder de bovenkant vloer
|
||||||
// Apparaten met een eigen groep. code = label op de plattegrond (twee
|
// Apparaten met een eigen groep. code = label op de plattegrond (twee
|
||||||
// letters: geen verwarring met de schakelaarletters).
|
// letters: geen verwarring met de schakelaarletters).
|
||||||
const APPLIANCES = {
|
const APPLIANCES = {
|
||||||
@@ -103,6 +119,7 @@ window.ElektraModel = (() => {
|
|||||||
s.meterkast = Object.assign({}, DEFAULT_SETTINGS.meterkast, i.meterkast || {});
|
s.meterkast = Object.assign({}, DEFAULT_SETTINGS.meterkast, i.meterkast || {});
|
||||||
s.apparaten = Object.assign({}, DEFAULT_SETTINGS.apparaten, i.apparaten || {});
|
s.apparaten = Object.assign({}, DEFAULT_SETTINGS.apparaten, i.apparaten || {});
|
||||||
s.place = Object.assign({}, i.place || {});
|
s.place = Object.assign({}, i.place || {});
|
||||||
|
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;
|
||||||
// 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) {
|
||||||
@@ -205,10 +222,17 @@ window.ElektraModel = (() => {
|
|||||||
const r = u.rect;
|
const r = u.rect;
|
||||||
return u.roof.ceilingAt(Math.max(r.x0 + 1, Math.min(r.x1 - 1, x)), Math.max(r.z0 + 1, Math.min(r.z1 - 1, z)));
|
return u.roof.ceilingAt(Math.max(r.x0 + 1, Math.min(r.x1 - 1, x)), Math.max(r.z0 + 1, Math.min(r.z1 - 1, z)));
|
||||||
};
|
};
|
||||||
|
const floorAt = (x, z) => { const u = unitAt(x, z) || units[0]; return u.layout.floorTop; };
|
||||||
// Pad als 3D-punten (mm). Per punt: wall = wandvlak waar het in de wand
|
// Pad als 3D-punten (mm). Per punt: wall = wandvlak waar het in de wand
|
||||||
// ligt, ceil = boven het plafond (voor de buizen in de 3D, build3D).
|
// ligt, ceil = boven het plafond, floor = in de vloer (voor de buizen in
|
||||||
|
// de 3D en de bladen per wand/vloer/plafond).
|
||||||
function path(a, b) {
|
function path(a, b) {
|
||||||
const P = (p, wall, ceil) => ({ x: p.x, y: p.y, z: p.z, wall: wall || null, ceil: !!ceil });
|
const P = (p, wall, ceil, floor) => ({ x: p.x, y: p.y, z: p.z, wall: wall || null, ceil: !!ceil, floor: !!floor });
|
||||||
|
if (a.mount === 'vloer' || b.mount === 'vloer') { // door de vloer
|
||||||
|
const fa = floorAt(a.x, a.z) - FLOOR_DEPTH, fb = floorAt(b.x, b.z) - FLOOR_DEPTH;
|
||||||
|
return [P(a, a.face, false, a.mount === 'vloer'), P({ x: a.x, y: fa, z: a.z }, a.face, false, true), P({ x: b.x, y: fa, z: a.z }, null, false, true),
|
||||||
|
P({ x: b.x, y: fb, z: b.z }, b.face, false, true), P(b, b.face, false, b.mount === 'vloer')];
|
||||||
|
}
|
||||||
if (a.face && a.face === b.face) return [P(a, a.face), P({ x: a.x, y: b.y, z: a.z }, a.face), P(b, b.face)]; // door de stijlen
|
if (a.face && a.face === b.face) return [P(a, a.face), P({ x: a.x, y: b.y, z: a.z }, a.face), P(b, b.face)]; // door de stijlen
|
||||||
if (a.mount === 'buiten' && b.mount === 'buiten') return [P(a), P({ x: b.x, y: a.y, z: a.z }), P(b)];
|
if (a.mount === 'buiten' && b.mount === 'buiten') return [P(a), P({ x: b.x, y: a.y, z: a.z }), P(b)];
|
||||||
const top = (p) => p.mount === 'plafond' ? p.y : ceilAt(p.x, p.z);
|
const top = (p) => p.mount === 'plafond' ? p.y : ceilAt(p.x, p.z);
|
||||||
@@ -237,7 +261,7 @@ window.ElektraModel = (() => {
|
|||||||
});
|
});
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
return { unitAt, ceilAt, path, length, cost, crossings };
|
return { unitAt, ceilAt, floorAt, path, length, cost, crossings };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
@@ -250,7 +274,7 @@ window.ElektraModel = (() => {
|
|||||||
const indoor = lighting.indoor || [], gevel = lighting.lamps || [], spots = (roofEdge && roofEdge.spots) || [];
|
const indoor = lighting.indoor || [], gevel = lighting.lamps || [], spots = (roofEdge && roofEdge.spots) || [];
|
||||||
indoor.concat(gevel, spots).forEach(p => { delete p.letter; });
|
indoor.concat(gevel, spots).forEach(p => { delete p.letter; });
|
||||||
const out = {
|
const out = {
|
||||||
enabled: s.enabled, settings: s, meterkast: null, combos: [], switches: [], wcds: [], apps: [], boxes: [],
|
enabled: s.enabled, settings: s, meterkast: null, combos: [], switches: [], wcds: [], apps: [], floorWcds: [], boxes: [],
|
||||||
groups: [], runs: [], cables: [], wires: [], conduits: [], materials: [], warnings: [], items: [], faces: []
|
groups: [], runs: [], cables: [], wires: [], conduits: [], materials: [], warnings: [], items: [], faces: []
|
||||||
};
|
};
|
||||||
if (!s.enabled || !units.length) return out;
|
if (!s.enabled || !units.length) return out;
|
||||||
@@ -435,11 +459,20 @@ window.ElektraModel = (() => {
|
|||||||
out.apps.push({ key, manual: !!fx, app: k, label: A.label, code: A.code, unitId: f.unit.id, face: f, u: uu, y, rotY: f.piece.place.rotY, pos: facePoint(f, uu, y, 0), center: facePoint(f, uu, y, 6) });
|
out.apps.push({ key, manual: !!fx, app: k, label: A.label, code: A.code, unitId: f.unit.id, face: f, u: uu, y, rotY: f.piece.place.rotY, pos: facePoint(f, uu, y, 0), center: facePoint(f, uu, y, 6) });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 6c. Vloer-wcd's (handmatig toegevoegd, x/z vanaf de noordwesthoek van de unit).
|
||||||
|
s.vloerWcd.forEach(o => {
|
||||||
|
const u = units.find(x => x.id === o.unitId);
|
||||||
|
if (!u) return;
|
||||||
|
const x = u.rect.x0 + Math.max(100, Math.min(u.rect.W - 100, num(o.x, u.rect.W / 2)));
|
||||||
|
const z = u.rect.z0 + Math.max(100, Math.min(u.rect.D - 100, num(o.z, u.rect.D / 2)));
|
||||||
|
out.floorWcds.push({ key: 'vwcd:' + o.id, id: o.id, unitId: u.id, x, z, y: u.layout.floorTop });
|
||||||
|
});
|
||||||
|
|
||||||
// 7. Groepen: per unit licht (B10) + wcd (B16); fasen en aardlek om en om.
|
// 7. Groepen: per unit licht (B10) + wcd (B16); fasen en aardlek om en om.
|
||||||
units.forEach(u => {
|
units.forEach(u => {
|
||||||
const sws = out.switches.filter(x => x.unitId === u.id && x.func === 'binnen');
|
const sws = out.switches.filter(x => x.unitId === u.id && x.func === 'binnen');
|
||||||
if (sws.length) out.groups.push({ kind: 'licht', unitId: u.id, label: `Verlichting unit ${u.id}`, breaker: 'B10', cable: 'licht', switches: sws.map(x => x.letter) });
|
if (sws.length) out.groups.push({ kind: 'licht', unitId: u.id, label: `Verlichting unit ${u.id}`, breaker: 'B10', cable: 'licht', switches: sws.map(x => x.letter) });
|
||||||
if (out.wcds.some(w => w.unitId === u.id)) out.groups.push({ kind: 'wcd', unitId: u.id, label: `Wcd unit ${u.id}`, breaker: 'B16', cable: 'wcd', switches: [] });
|
if (out.wcds.some(w => w.unitId === u.id) || out.floorWcds.some(w => w.unitId === u.id)) out.groups.push({ kind: 'wcd', unitId: u.id, label: `Wcd unit ${u.id}`, breaker: 'B16', cable: 'wcd', switches: [] });
|
||||||
const outSw = out.switches.filter(x => x.unitId === u.id && x.func === 'buiten');
|
const outSw = out.switches.filter(x => x.unitId === u.id && x.func === 'buiten');
|
||||||
if (outSw.length) out.groups.push({ kind: 'buiten', unitId: u.id, label: 'Buitenverlichting', breaker: 'B10', cable: 'buiten', switches: outSw.map(x => x.letter) });
|
if (outSw.length) out.groups.push({ kind: 'buiten', unitId: u.id, label: 'Buitenverlichting', breaker: 'B10', cable: 'buiten', switches: outSw.map(x => x.letter) });
|
||||||
});
|
});
|
||||||
@@ -472,6 +505,7 @@ window.ElektraModel = (() => {
|
|||||||
}
|
}
|
||||||
out.switches.forEach(sw => { const g = groupOf(sw.func === 'buiten' ? 'buiten' : 'licht', sw.unitId); sw.group = g ? g.nr : null; });
|
out.switches.forEach(sw => { const g = groupOf(sw.func === 'buiten' ? 'buiten' : 'licht', sw.unitId); sw.group = g ? g.nr : null; });
|
||||||
out.wcds.forEach(w => { const g = groupOf('wcd', w.unitId); w.group = g ? g.nr : null; });
|
out.wcds.forEach(w => { const g = groupOf('wcd', w.unitId); w.group = g ? g.nr : null; });
|
||||||
|
out.floorWcds.forEach(w => { const g = groupOf('wcd', w.unitId); w.group = g ? g.nr : null; });
|
||||||
out.apps.forEach(a => { const g = out.groups.find(x => x.kind === 'app' && x.app === a.app); a.group = g ? g.nr : null; });
|
out.apps.forEach(a => { const g = out.groups.find(x => x.kind === 'app' && x.app === a.app); a.group = g ? g.nr : null; });
|
||||||
|
|
||||||
// 8. Leidingen.
|
// 8. Leidingen.
|
||||||
@@ -480,8 +514,7 @@ window.ElektraModel = (() => {
|
|||||||
const addRun = (type, a, b, group, letter) => {
|
const addRun = (type, a, b, group, letter) => {
|
||||||
const pts = R.path(a, b);
|
const pts = R.path(a, b);
|
||||||
const raw = R.length(pts);
|
const raw = R.length(pts);
|
||||||
const lengthMM = (raw + s.reserve + (a.mk || b.mk ? s.meterkastReserve : 0)) * (1 + s.slack / 100);
|
out.runs.push({ type, group, letter: letter || null, from: a.name || '', to: b.name || '', path: pts, mk: !!(a.mk || b.mk), outdoor: a.mount === 'buiten' || b.mount === 'buiten' });
|
||||||
out.runs.push({ type, group, letter: letter || null, from: a.name || '', to: b.name || '', path: pts, rawMM: raw, lengthMM, crossings: R.crossings(pts), outdoor: a.mount === 'buiten' || b.mount === 'buiten' });
|
|
||||||
};
|
};
|
||||||
// Minimale opspannende boom vanaf root (Prim, op de snelle schatting).
|
// Minimale opspannende boom vanaf root (Prim, op de snelle schatting).
|
||||||
const tree = (root, nodes, typeOf, group, letter) => {
|
const tree = (root, nodes, typeOf, group, letter) => {
|
||||||
@@ -514,19 +547,33 @@ window.ElektraModel = (() => {
|
|||||||
combo.vds.push(vd);
|
combo.vds.push(vd);
|
||||||
out.boxes.push({ kind: 'verdeeldoos', combo: combo.id, group: e.group, x: vd.x, y: vd.y, z: vd.z, unitId: combo.unitId });
|
out.boxes.push({ kind: 'verdeeldoos', combo: combo.id, group: e.group, x: vd.x, y: vd.y, z: vd.z, unitId: combo.unitId });
|
||||||
if (mkNode) addRun('licht', mkNode, vd, e.group); // voeding binnen: VD in buis
|
if (mkNode) addRun('licht', mkNode, vd, e.group); // voeding binnen: VD in buis
|
||||||
const swNode = Object.assign({}, combo.pos, { mount: 'wand', face: combo.face, name: e.switches.map(x => x.letter).join('') });
|
// Naar de eigen inbouwdoos (of het midden ervan) van deze groep.
|
||||||
|
const uSw = e.switches.reduce((t, x) => t + x.u, 0) / e.switches.length;
|
||||||
|
const swNode = Object.assign(facePoint(combo.face, uSw, combo.y, 0), { mount: 'wand', face: combo.face, name: e.switches.map(x => x.letter).join('') });
|
||||||
addRun('schakel' + (e.switches.length + 1), vd, swNode, e.group);
|
addRun('schakel' + (e.switches.length + 1), vd, swNode, e.group);
|
||||||
e.switches.forEach(sw => tree(vd, sw.lamps.map((l, k) => Object.assign(l, { name: `${sw.letter}${k + 1}` })),
|
e.switches.forEach(sw => tree(vd, sw.lamps.map((l, k) => Object.assign(l, { name: `${sw.letter}${k + 1}` })),
|
||||||
(a, b) => a.mount === 'buiten' || b.mount === 'buiten' ? 'buiten' : 'licht', e.group, sw.letter));
|
(a, b) => a.mount === 'buiten' || b.mount === 'buiten' ? 'buiten' : 'licht', e.group, sw.letter));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
out.groups.filter(g => g.kind === 'wcd').forEach(g => {
|
out.groups.filter(g => g.kind === 'wcd').forEach(g => {
|
||||||
const nodes = out.wcds.filter(w => w.group === g.nr).map((w, i) => Object.assign({}, w.pos, { mount: 'wand', face: w.face, name: `WCD${g.nr}.${i + 1}` }));
|
const nodes = out.wcds.filter(w => w.group === g.nr).map((w, i) => Object.assign({}, w.pos, { mount: 'wand', face: w.face, name: `WCD${g.nr}.${i + 1}` }))
|
||||||
|
.concat(out.floorWcds.filter(w => w.group === g.nr).map((w, i) => ({ x: w.x, y: w.y, z: w.z, mount: 'vloer', name: `VWCD${g.nr}.${i + 1}` })));
|
||||||
if (mkLow && nodes.length) tree(mkLow, nodes, () => 'wcd', g.nr);
|
if (mkLow && nodes.length) tree(mkLow, nodes, () => 'wcd', g.nr);
|
||||||
});
|
});
|
||||||
// Apparaten: elk een eigen leiding vanuit de meterkast.
|
// Apparaten: elk een eigen leiding vanuit de meterkast.
|
||||||
if (mkLow) out.apps.forEach(a => addRun(APPLIANCES[a.app].cable, mkLow, Object.assign({}, a.pos, { mount: 'wand', face: a.face, name: a.code }), a.group));
|
if (mkLow) out.apps.forEach(a => addRun(APPLIANCES[a.app].cable, mkLow, Object.assign({}, a.pos, { mount: 'wand', face: a.face, name: a.code }), a.group));
|
||||||
|
|
||||||
|
// 8b. Buizen uit elkaar (min. CLEAR), daarna lengtes, naad-overgangen en boorgaten.
|
||||||
|
separateRuns(out.runs);
|
||||||
|
const holesOf = makeHoleCounter(units, R);
|
||||||
|
out.runs.forEach(r => {
|
||||||
|
r.rawMM = R.length(r.path);
|
||||||
|
r.lengthMM = (r.rawMM + s.reserve + (r.mk ? s.meterkastReserve : 0)) * (1 + s.slack / 100);
|
||||||
|
r.crossings = R.crossings(r.path);
|
||||||
|
r.holes = holesOf(r);
|
||||||
|
});
|
||||||
|
out.holes = out.runs.reduce((a, r) => a + r.holes, 0);
|
||||||
|
|
||||||
// 9. Totalen: per leidingtype, draad per doorsnede/kleur, buis, groep.
|
// 9. Totalen: per leidingtype, draad per doorsnede/kleur, buis, groep.
|
||||||
const byType = {};
|
const byType = {};
|
||||||
out.runs.forEach(r => { byType[r.type] = (byType[r.type] || 0) + r.lengthMM; });
|
out.runs.forEach(r => { byType[r.type] = (byType[r.type] || 0) + r.lengthMM; });
|
||||||
@@ -578,6 +625,7 @@ window.ElektraModel = (() => {
|
|||||||
add('Schakelaar enkelpolig (inbouw)', out.switches.length);
|
add('Schakelaar enkelpolig (inbouw)', out.switches.length);
|
||||||
Object.keys(frames).forEach(n => add(`Afdekraam ${n}-voudig`, frames[n]));
|
Object.keys(frames).forEach(n => add(`Afdekraam ${n}-voudig`, frames[n]));
|
||||||
add('Wandcontactdoos dubbel met randaarde', out.wcds.length);
|
add('Wandcontactdoos dubbel met randaarde', out.wcds.length);
|
||||||
|
add('Vloerdoos met dubbele wcd (randaarde)', 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, appMat[n]));
|
||||||
@@ -600,6 +648,122 @@ window.ElektraModel = (() => {
|
|||||||
out.combos.forEach(c => item(c.key, 'sw', 'Schakelaar ' + c.switches.map(x => x.letter).join(' '), c.face, c.u, c.y, c.manual, 260, c.unitId));
|
out.combos.forEach(c => item(c.key, 'sw', 'Schakelaar ' + c.switches.map(x => x.letter).join(' '), c.face, c.u, c.y, c.manual, 260, c.unitId));
|
||||||
out.wcds.forEach(w => item(w.key, 'wcd', `Wcd ${w.key.split(':')[2]} unit ${w.unitId} (groep ${w.group})`, w.face, w.u, w.y, w.manual, 120, w.unitId));
|
out.wcds.forEach(w => item(w.key, 'wcd', `Wcd ${w.key.split(':')[2]} unit ${w.unitId} (groep ${w.group})`, w.face, w.u, w.y, w.manual, 120, w.unitId));
|
||||||
out.apps.forEach(a => item(a.key, 'app', `${a.label} (groep ${a.group})`, a.face, a.u, a.y, a.manual, 150));
|
out.apps.forEach(a => item(a.key, 'app', `${a.label} (groep ${a.group})`, a.face, a.u, a.y, a.manual, 150));
|
||||||
|
out.floorWcds.forEach(w => {
|
||||||
|
const u = units.find(x => x.id === w.unitId);
|
||||||
|
out.items.push({ key: w.key, kind: 'vwcd', label: `Vloer-wcd unit ${w.unitId} (groep ${w.group})`, unitId: w.unitId, x: w.x - u.rect.x0, z: w.z - u.rect.z0, manual: true, marker: { x: w.x, z: w.z } });
|
||||||
|
});
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Buizen uit elkaar (muteert run.path). Padstukken zijn haaks (x, y of z).
|
||||||
|
// Te dicht = evenwijdig, overlappend langs de as en minder dan CLEAR vrij
|
||||||
|
// (buitenkant tot buitenkant). Zo'n stuk schuift naar de eerste vrije baan
|
||||||
|
// (±1, ±2, ... × CLEAR + buis-Ø): in een wand langs de wand (verticaal
|
||||||
|
// stuk) of omhoog/omlaag (horizontaal stuk), in plafond/vloer horizontaal
|
||||||
|
// dwars. Een knik ervoor en erna verbindt het weer met de rest van het pad.
|
||||||
|
// Uitzondering: het laatste stuk naar de meterkast. Daar komen de buizen
|
||||||
|
// naast elkaar (tegen elkaar aan) de kast binnen.
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
function separateRuns(runs) {
|
||||||
|
const add = (p, d, k) => Object.assign({}, p, { x: p.x + d.x * k, y: p.y + d.y * k, z: p.z + d.z * k });
|
||||||
|
const axisOf = (p, q) => {
|
||||||
|
const dx = Math.abs(q.x - p.x), dy = Math.abs(q.y - p.y), dz = Math.abs(q.z - p.z);
|
||||||
|
return dx >= dy && dx >= dz ? 'x' : dz >= dy ? 'z' : 'y';
|
||||||
|
};
|
||||||
|
const lenOf = (p, q) => Math.hypot(q.x - p.x, q.y - p.y, q.z - p.z);
|
||||||
|
const seg = (run, p, q, r) => {
|
||||||
|
const axis = axisOf(p, q), perp = {};
|
||||||
|
['x', 'y', 'z'].filter(k => k !== axis).forEach(k => { perp[k] = (p[k] + q[k]) / 2; });
|
||||||
|
return { run, axis, lo: Math.min(p[axis], q[axis]), hi: Math.max(p[axis], q[axis]), perp, r };
|
||||||
|
};
|
||||||
|
const placed = [];
|
||||||
|
const conflict = (sg) => placed.some(t => t.run !== sg.run && t.axis === sg.axis && Math.min(t.hi, sg.hi) - Math.max(t.lo, sg.lo) > 1 &&
|
||||||
|
Math.hypot(...Object.keys(sg.perp).map(k => sg.perp[k] - t.perp[k])) < CLEAR + sg.r + t.r - 0.5);
|
||||||
|
const faceDir = (f) => { const L = Math.hypot(f.b.x - f.a.x, f.b.z - f.a.z) || 1; return { x: (f.b.x - f.a.x) / L, y: 0, z: (f.b.z - f.a.z) / L }; };
|
||||||
|
const shiftDir = (p, q, axis) => {
|
||||||
|
const plane = (p.ceil || p.floor) && (q.ceil || q.floor);
|
||||||
|
if (axis === 'y') { const f = p.wall || q.wall; return f ? faceDir(f) : { x: 1, y: 0, z: 0 }; }
|
||||||
|
if (!plane && (p.wall || q.wall)) return { x: 0, y: 1, z: 0 };
|
||||||
|
return axis === 'x' ? { x: 0, y: 0, z: 1 } : { x: 1, y: 0, z: 0 };
|
||||||
|
};
|
||||||
|
const conduit = runs.filter(r => cableInfo(r.type).conduit > 0);
|
||||||
|
const radius = (r) => cableInfo(r.type).conduit / 2;
|
||||||
|
|
||||||
|
// 1. Meterkast: per aansluitpunt (boven/onder) de buizen naast elkaar,
|
||||||
|
// tegen elkaar aan, gesorteerd naar de kant waar ze heen gaan.
|
||||||
|
const bundles = {};
|
||||||
|
conduit.filter(r => r.mk && r.path[0].wall).forEach(r => {
|
||||||
|
const k = Math.round(r.path[0].y);
|
||||||
|
(bundles[k] = bundles[k] || []).push(r);
|
||||||
|
});
|
||||||
|
Object.values(bundles).forEach(list => {
|
||||||
|
const d = faceDir(list[0].path[0].wall);
|
||||||
|
const along = (r) => { const q = r.path[Math.min(2, r.path.length - 1)]; return (q.x - r.path[0].x) * d.x + (q.z - r.path[0].z) * d.z; };
|
||||||
|
list.sort((a, b) => along(a) - along(b));
|
||||||
|
const total = list.reduce((a, r) => a + 2 * radius(r), 0);
|
||||||
|
let off = -total / 2;
|
||||||
|
list.forEach(r => {
|
||||||
|
const o = off + radius(r);
|
||||||
|
off += 2 * radius(r);
|
||||||
|
const orig = r.path, dAx = Math.abs(d.x) > Math.abs(d.z) ? 'x' : 'z';
|
||||||
|
const out = orig.map(p => p);
|
||||||
|
out[0] = add(orig[0], d, o);
|
||||||
|
for (let k = 0; k < orig.length - 1; k++) {
|
||||||
|
const len = lenOf(orig[k], orig[k + 1]);
|
||||||
|
if (len >= 1 && axisOf(orig[k], orig[k + 1]) === dAx) break; // loopt langs de kast: alleen langer/korter
|
||||||
|
if (k + 1 === orig.length - 1) { out.splice(k + 1, 0, add(orig[k + 1], d, o)); break; } // eindpunt blijft: knik ervoor
|
||||||
|
out[k + 1] = add(orig[k + 1], d, o);
|
||||||
|
}
|
||||||
|
r.path = out;
|
||||||
|
placed.push(seg(r, out[0], out[1], radius(r))); // gebundeld stuk: vast
|
||||||
|
r.fixed = 1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. De rest: per padstuk een vrije baan zoeken.
|
||||||
|
conduit.forEach(r => {
|
||||||
|
const rad = radius(r), step = CLEAR + 2 * rad;
|
||||||
|
let pts = r.path;
|
||||||
|
for (let i = r.fixed || 0; i < pts.length - 1; i++) {
|
||||||
|
const p = pts[i], q = pts[i + 1];
|
||||||
|
if (lenOf(p, q) < 1) continue;
|
||||||
|
const sg = seg(r, p, q, rad);
|
||||||
|
if (!conflict(sg)) { placed.push(sg); continue; }
|
||||||
|
const dir = shiftDir(p, q, sg.axis);
|
||||||
|
// Knikken tellen ook mee, behalve direct aan een doos/lamp (daar
|
||||||
|
// komen buizen samen) en aan het eind van de meterkastbundel.
|
||||||
|
const freeStart = i === 0 || (r.fixed && i === r.fixed), freeEnd = i + 1 === pts.length - 1;
|
||||||
|
let done = false;
|
||||||
|
for (let k = 1; k <= 12 && !done; k++) {
|
||||||
|
for (const sign of [1, -1]) {
|
||||||
|
const kk = sign * k * step, p2 = add(p, dir, kk), q2 = add(q, dir, kk), s2 = seg(r, p2, q2, rad);
|
||||||
|
const j1 = seg(r, p, p2, rad), j2 = seg(r, q2, q, rad);
|
||||||
|
if (conflict(s2) || (!freeStart && conflict(j1)) || (!freeEnd && conflict(j2))) continue;
|
||||||
|
pts = pts.slice(0, i + 1).concat([p2, q2], pts.slice(i + 1));
|
||||||
|
placed.push(s2, j1, j2);
|
||||||
|
i += 2; // verder vanaf de knik terug naar q
|
||||||
|
done = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!done) placed.push(sg);
|
||||||
|
}
|
||||||
|
r.path = simplifyPath(pts);
|
||||||
|
});
|
||||||
|
runs.forEach(r => { delete r.fixed; });
|
||||||
|
}
|
||||||
|
// Dubbele punten en heen-en-weer-knikken eruit.
|
||||||
|
function simplifyPath(pts) {
|
||||||
|
const same = (a, b) => Math.abs(a.x - b.x) < 0.5 && Math.abs(a.y - b.y) < 0.5 && Math.abs(a.z - b.z) < 0.5;
|
||||||
|
let out = pts.filter((p, i) => i === 0 || !same(p, pts[i - 1]));
|
||||||
|
let changed = true;
|
||||||
|
while (changed) {
|
||||||
|
changed = false;
|
||||||
|
for (let i = 1; i < out.length - 1; i++) {
|
||||||
|
if (i + 1 < out.length && same(out[i - 1], out[i + 1])) { out.splice(i, 2); changed = true; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -663,6 +827,7 @@ window.ElektraModel = (() => {
|
|||||||
el.combos.forEach(c => box(c.width, PLATE, 10, c.center, c.rotY, mats.plateMaterial));
|
el.combos.forEach(c => box(c.width, PLATE, 10, c.center, c.rotY, mats.plateMaterial));
|
||||||
el.wcds.forEach(w => box(WCD_W, PLATE, 12, w.center, w.rotY, mats.plateMaterial));
|
el.wcds.forEach(w => box(WCD_W, PLATE, 12, w.center, w.rotY, mats.plateMaterial));
|
||||||
el.apps.forEach(a => box(PLATE, PLATE, 12, a.center, a.rotY, mats.plateMaterial));
|
el.apps.forEach(a => box(PLATE, PLATE, 12, a.center, a.rotY, mats.plateMaterial));
|
||||||
|
el.floorWcds.forEach(w => box(130, 8, 130, { x: w.x, y: w.y + 4, z: w.z }, 0, mats.plateMaterial)); // deksel vloerdoos
|
||||||
}
|
}
|
||||||
if (!rough) return group;
|
if (!rough) return group;
|
||||||
|
|
||||||
@@ -702,6 +867,7 @@ window.ElektraModel = (() => {
|
|||||||
el.switches.forEach(sw => wallBox(sw.combo.face, sw.u, sw.combo.y));
|
el.switches.forEach(sw => wallBox(sw.combo.face, sw.u, sw.combo.y));
|
||||||
el.wcds.forEach(w => { wallBox(w.face, w.u - 36, w.y); wallBox(w.face, w.u + 36, w.y); });
|
el.wcds.forEach(w => { wallBox(w.face, w.u - 36, w.y); wallBox(w.face, w.u + 36, w.y); });
|
||||||
el.apps.forEach(a => wallBox(a.face, a.u, a.y));
|
el.apps.forEach(a => wallBox(a.face, a.u, a.y));
|
||||||
|
el.floorWcds.forEach(w => box(100, 80, 100, { x: w.x, y: w.y - 40, z: w.z }, 0, mats.boxMaterial)); // vloerdoos
|
||||||
// Verdeeldozen en spotdozen boven het plafond.
|
// Verdeeldozen en spotdozen boven het plafond.
|
||||||
el.boxes.forEach(b => box(90, 50, 90, { x: b.x, y: b.y + CEIL_UP + 10, z: b.z }, 0, mats.boxMaterial));
|
el.boxes.forEach(b => box(90, 50, 90, { x: b.x, y: b.y + CEIL_UP + 10, z: b.z }, 0, mats.boxMaterial));
|
||||||
el.switches.forEach(sw => sw.lamps.filter(l => l.kind === 'binnenspot').forEach(l => {
|
el.switches.forEach(sw => sw.lamps.filter(l => l.kind === 'binnenspot').forEach(l => {
|
||||||
@@ -712,5 +878,77 @@ window.ElektraModel = (() => {
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { DEFAULT_SETTINGS, CABLES, CORE_COLORS, APPLIANCES, cableInfo, normalizeSettings, compute, snapToWall, createMaterials, build3D };
|
// Plek van een punt op een wandvlak: { u, y } (wand-x en hoogte boven de vloer).
|
||||||
|
const wallCoords = (f, p) => ({ u: uAt(f, p), y: p.y - f.piece.place.floorTop });
|
||||||
|
|
||||||
|
// Lijnstuk p-q (wereld-mm) binnen grenzen b = { x0, x1, z0, z1 } (plattegrond); null = erbuiten.
|
||||||
|
function clipPlan(p, q, b) {
|
||||||
|
let t0 = 0, t1 = 1;
|
||||||
|
for (const [k, lo, hi] of [['x', b.x0, b.x1], ['z', b.z0, b.z1]]) {
|
||||||
|
const d = q[k] - p[k];
|
||||||
|
if (Math.abs(d) < 1e-9) { if (p[k] < lo - 0.5 || p[k] > hi + 0.5) return null; continue; }
|
||||||
|
let a = (lo - p[k]) / d, c = (hi - p[k]) / d;
|
||||||
|
if (a > c) [a, c] = [c, a];
|
||||||
|
t0 = Math.max(t0, a); t1 = Math.min(t1, c);
|
||||||
|
if (t0 > t1) return null;
|
||||||
|
}
|
||||||
|
const at = (t) => Object.assign({}, p, { x: p.x + (q.x - p.x) * t, y: p.y + (q.y - p.y) * t, z: p.z + (q.z - p.z) * t });
|
||||||
|
return [at(t0), at(t1)];
|
||||||
|
}
|
||||||
|
// Dakvlak pl van unit: deel van de plattegrond (bounds) en omrekening naar
|
||||||
|
// het dek zoals de dakbladen (map: { sx, sy }, mm vanaf linksvoor), gelijk
|
||||||
|
// aan de doorvoeren in RoofModel.computeUnitRoof. De balken van het dek
|
||||||
|
// staan op sx = joist + breedte/2 en lopen langs sy.
|
||||||
|
function roofDeckMap(unit, pl) {
|
||||||
|
const alongX = Math.abs(Math.sin(pl.yaw)) > 0.5, rr = unit.roof.rect, r = unit.rect;
|
||||||
|
const qOf = (p) => alongX ? p.x - r.cx : p.z - r.cz;
|
||||||
|
return {
|
||||||
|
bounds: alongX
|
||||||
|
? { x0: Math.max(r.x0, r.cx + pl.p0), x1: Math.min(r.x1, r.cx + pl.p1), z0: r.z0, z1: r.z1 }
|
||||||
|
: { x0: r.x0, x1: r.x1, z0: Math.max(r.z0, r.cz + pl.p0), z1: Math.min(r.z1, r.cz + pl.p1) },
|
||||||
|
map: (p) => ({ sx: alongX ? rr.z1 - p.z : p.x - rr.x0, sy: (qOf(p) - pl.p0) / pl.cos })
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Boorgaten van een leiding (zie kop). Telt per padstuk.
|
||||||
|
const VERTICAL_KINDS = ['verticale balk', 'kreupele stijl'];
|
||||||
|
function makeHoleCounter(units, R) {
|
||||||
|
const boms = new Map();
|
||||||
|
const bomOf = (piece) => {
|
||||||
|
if (!boms.has(piece)) boms.set(piece, piece.frame ? WallModel.computeBOM(piece.config, piece.frame) : []);
|
||||||
|
return boms.get(piece);
|
||||||
|
};
|
||||||
|
const between = (v, a, b) => v > Math.min(a, b) + 1 && v < Math.max(a, b) - 1;
|
||||||
|
const joistsBetween = (cd, a, b) => cd.joists.filter(j => between(j + cd.width / 2, a, b)).length;
|
||||||
|
return (run) => {
|
||||||
|
if (!cableInfo(run.type).conduit) return run.type === 'buiten' && run.from.startsWith('VD') ? 1 : 0; // doorvoer naar buiten
|
||||||
|
let n = 0;
|
||||||
|
run.path.slice(1).forEach((q, i) => {
|
||||||
|
const p = run.path[i];
|
||||||
|
const vertical = Math.abs(q.y - p.y) > Math.hypot(q.x - p.x, q.z - p.z);
|
||||||
|
if (p.wall && q.wall && p.wall.piece === q.wall.piece && !(p.ceil && q.ceil)) {
|
||||||
|
const f = p.wall, a = wallCoords(f, p), b = wallCoords(f, q), bom = bomOf(f.piece);
|
||||||
|
if (vertical) {
|
||||||
|
const x = a.u - f.W / 2;
|
||||||
|
n += bom.filter(bp => !VERTICAL_KINDS.includes(bp.kind) && Math.abs(x - bp.x) <= bp.length / 2 && between(bp.y, a.y, b.y)).length;
|
||||||
|
if (p.floor !== q.floor) n += 1; // door de vloerplaat/randbalk de vloer in
|
||||||
|
} else {
|
||||||
|
n += bom.filter(bp => VERTICAL_KINDS.includes(bp.kind) && between(bp.x + f.W / 2, a.u, b.u) && Math.abs(a.y - bp.y) <= bp.length / 2).length;
|
||||||
|
}
|
||||||
|
} else if (!vertical && p.ceil && q.ceil) {
|
||||||
|
const mid = { x: (p.x + q.x) / 2, z: (p.z + q.z) / 2 }, u = R.unitAt(mid.x, mid.z);
|
||||||
|
if (u) u.roof.planes.forEach(pl => {
|
||||||
|
const dm = roofDeckMap(u, pl), c = clipPlan(p, q, dm.bounds);
|
||||||
|
if (c) n += joistsBetween(pl.data.calcData, dm.map(c[0]).sx, dm.map(c[1]).sx);
|
||||||
|
});
|
||||||
|
} else if (!vertical && p.floor && q.floor) {
|
||||||
|
const u = R.unitAt((p.x + q.x) / 2, (p.z + q.z) / 2);
|
||||||
|
const c = u && clipPlan(p, q, u.rect);
|
||||||
|
if (c) n += joistsBetween(u.floorData.calcData, c[0].x - u.rect.x0, c[1].x - u.rect.x0);
|
||||||
|
} else if (vertical && (p.floor || q.floor) && !(p.wall || q.wall)) n += 1; // vloerdoos: gat in de vloerplaat
|
||||||
|
});
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { DEFAULT_SETTINGS, CABLES, CORE_COLORS, APPLIANCES, PLATE, WCD_W, cableInfo, normalizeSettings, compute, snapToWall, wallCoords, clipPlan, roofDeckMap, createMaterials, build3D };
|
||||||
})();
|
})();
|
||||||
|
|||||||
+125
-2
@@ -94,6 +94,17 @@ window.ElektraPdf = (() => {
|
|||||||
doc.text(String(a.group), px(q.x + a.face.n.x * 330), py(q.z + a.face.n.z * 330) + 0.5, { align: 'center' });
|
doc.text(String(a.group), px(q.x + a.face.n.x * 330), py(q.z + a.face.n.z * 330) + 0.5, { align: 'center' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Vloer-wcd: vierkantje met V + groepnummer.
|
||||||
|
el.floorWcds.forEach(w => {
|
||||||
|
doc.setDrawColor(...RUN_COLORS.wcd);
|
||||||
|
doc.setFillColor(255, 255, 255);
|
||||||
|
doc.setLineWidth(0.2);
|
||||||
|
doc.rect(px(w.x) - 1.1, py(w.z) - 1.1, 2.2, 2.2, 'FD');
|
||||||
|
doc.setTextColor(...RUN_COLORS.wcd);
|
||||||
|
doc.setFontSize(3.6);
|
||||||
|
doc.text('V' + w.group, px(w.x), py(w.z) + 0.5, { align: 'center' });
|
||||||
|
});
|
||||||
|
|
||||||
// Lampletters (zelfde letter als de schakelaar).
|
// Lampletters (zelfde letter als de schakelaar).
|
||||||
doc.setFontSize(3.6);
|
doc.setFontSize(3.6);
|
||||||
doc.setTextColor(...BLACK);
|
doc.setTextColor(...BLACK);
|
||||||
@@ -102,7 +113,7 @@ window.ElektraPdf = (() => {
|
|||||||
// Twee regels (doc.text met een array).
|
// Twee regels (doc.text met een array).
|
||||||
const PLAN_LEGEND = [
|
const PLAN_LEGEND = [
|
||||||
'Elektra: MK = meterkast, kadertje = schakelaar(s) met letter (dezelfde letter staat bij de lampen erop), rood rondje = dubbele wcd (groepnummer), vierkantje = verdeeldoos in plafond;',
|
'Elektra: MK = meterkast, kadertje = schakelaar(s) met letter (dezelfde letter staat bij de lampen erop), rood rondje = dubbele wcd (groepnummer), vierkantje = verdeeldoos in plafond;',
|
||||||
'rood kadertje = apparaat (KK kookgroep, WM wasmachine, DR droger, WP warmtepomp, OV oven, VW vaatwasser) met groepnummer; gestippeld = leidingen (geel licht, bruin schakeldraad, rood wcd/apparaat, groen buiten).'
|
'rood kadertje = apparaat (KK kookgroep, WM wasmachine, DR droger, WP warmtepomp, OV oven, VW vaatwasser) met groepnummer, V = vloer-wcd; gestippeld = leidingen (geel licht, bruin schakeldraad, rood wcd/apparaat, groen buiten).'
|
||||||
];
|
];
|
||||||
|
|
||||||
function drawSheet(doc, el, sheetOpts) {
|
function drawSheet(doc, el, sheetOpts) {
|
||||||
@@ -150,5 +161,117 @@ window.ElektraPdf = (() => {
|
|||||||
PdfSheet.titleBlock(doc, sheetOpts.project, sheetOpts);
|
PdfSheet.titleBlock(doc, sheetOpts.project, sheetOpts);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { RUN_COLORS, drawPlan, drawSheet, PLAN_LEGEND };
|
// ------------------------------------------------------------
|
||||||
|
// Elektra op de bladen per onderdeel. Elk geeft een overlay-functie voor
|
||||||
|
// WallPdf/FloorPdf (opts.overlay) of null als er niets te tekenen is.
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
const lineCol = (doc, type) => doc.setDrawColor(...runColor(type));
|
||||||
|
const clip = (p, q, b) => ElektraModel.clipPlan(p, q, b);
|
||||||
|
// Leidingstukken van alle buizen waarvoor test(p, q) geldt.
|
||||||
|
const conduitSegs = (el, test) => {
|
||||||
|
const out = [];
|
||||||
|
el.runs.forEach(r => {
|
||||||
|
if (!ElektraModel.cableInfo(r.type).conduit) return;
|
||||||
|
r.path.slice(1).forEach((q, i) => { const p = r.path[i]; if (test(p, q)) out.push({ type: r.type, p, q }); });
|
||||||
|
});
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
const labelBox = (doc, x, y, text, color) => {
|
||||||
|
const w = 1.6 + text.length * 1.05;
|
||||||
|
doc.setDrawColor(...color);
|
||||||
|
doc.setFillColor(255, 255, 255);
|
||||||
|
doc.setLineWidth(0.2);
|
||||||
|
doc.rect(x - w / 2, y - 1.2, w, 2.4, 'FD');
|
||||||
|
doc.setTextColor(...color);
|
||||||
|
doc.setFontSize(5);
|
||||||
|
doc.text(text, x, y + 0.7, { align: 'center' });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wand (vooraanzicht, wand-x zoals de openingen): buizen in de wand,
|
||||||
|
// schakelaars, wcd's, apparaten en de meterkast op dit wandstuk.
|
||||||
|
function wallOverlay(el, piece) {
|
||||||
|
if (!el || !el.enabled) return null;
|
||||||
|
const on = (p) => p.wall && p.wall.piece === piece;
|
||||||
|
const segs = conduitSegs(el, (p, q) => on(p) && on(q));
|
||||||
|
const combos = el.combos.filter(c => c.face.piece === piece), wcds = el.wcds.filter(w => w.face.piece === piece);
|
||||||
|
const apps = el.apps.filter(a => a.face.piece === piece), mk = el.meterkast && el.meterkast.face.piece === piece ? el.meterkast : null;
|
||||||
|
if (!segs.length && !combos.length && !wcds.length && !apps.length && !mk) return null;
|
||||||
|
const BLACK = PdfSheet.COLORS.BLACK;
|
||||||
|
return (doc, pu, py) => {
|
||||||
|
doc.setLineWidth(0.35);
|
||||||
|
segs.forEach(sg => {
|
||||||
|
const a = ElektraModel.wallCoords(sg.p.wall, sg.p), b = ElektraModel.wallCoords(sg.q.wall, sg.q);
|
||||||
|
lineCol(doc, sg.type);
|
||||||
|
doc.line(pu(a.u), py(a.y), pu(b.u), py(b.y));
|
||||||
|
});
|
||||||
|
if (mk) {
|
||||||
|
doc.setDrawColor(...BLACK);
|
||||||
|
doc.setFillColor(110, 110, 110);
|
||||||
|
doc.setLineWidth(0.25);
|
||||||
|
doc.rect(pu(mk.u - mk.w / 2), py(mk.y + mk.h), (pu(mk.u + mk.w / 2) - pu(mk.u - mk.w / 2)), py(mk.y) - py(mk.y + mk.h), 'FD');
|
||||||
|
doc.setTextColor(255, 255, 255);
|
||||||
|
doc.setFontSize(6);
|
||||||
|
doc.text('MK', pu(mk.u), py(mk.y + mk.h / 2) + 1, { align: 'center' });
|
||||||
|
}
|
||||||
|
combos.forEach(c => labelBox(doc, pu(c.u), py(c.y), c.switches.map(x => x.letter).join(' '), BLACK));
|
||||||
|
wcds.forEach(w => labelBox(doc, pu(w.u), py(w.y), `wcd ${w.group}`, RUN_COLORS.wcd));
|
||||||
|
apps.forEach(a => labelBox(doc, pu(a.u), py(a.y), `${a.code} ${a.group}`, RUN_COLORS.wcd));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vloer van een unit (bovenaanzicht vanaf linksvoor): buizen in de vloer + vloer-wcd's.
|
||||||
|
function floorOverlay(el, unit) {
|
||||||
|
if (!el || !el.enabled) return null;
|
||||||
|
const r = unit.rect, b = { x0: r.x0, x1: r.x1, z0: r.z0, z1: r.z1 };
|
||||||
|
const segs = conduitSegs(el, (p, q) => p.floor && q.floor).map(sg => Object.assign(sg, { c: clip(sg.p, sg.q, b) })).filter(sg => sg.c);
|
||||||
|
const vw = el.floorWcds.filter(w => w.unitId === unit.id);
|
||||||
|
if (!segs.length && !vw.length) return null;
|
||||||
|
return (doc, px, py) => {
|
||||||
|
doc.setLineWidth(0.35);
|
||||||
|
segs.forEach(sg => {
|
||||||
|
const [a, c] = sg.c;
|
||||||
|
if (Math.hypot(c.x - a.x, c.z - a.z) < 1) return;
|
||||||
|
lineCol(doc, sg.type);
|
||||||
|
doc.line(px(a.x - r.x0), py(a.z - r.z0), px(c.x - r.x0), py(c.z - r.z0));
|
||||||
|
});
|
||||||
|
vw.forEach(w => labelBox(doc, px(w.x - r.x0), py(w.z - r.z0), `vloer-wcd ${w.group}`, RUN_COLORS.wcd));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dakvlak (dek zoals de dakbladen, zelfde omrekening als de doorvoeren in
|
||||||
|
// RoofModel.computeUnitRoof): buizen boven het plafond, verdeeldozen en
|
||||||
|
// binnenspots (met letter) in dit vlak.
|
||||||
|
function roofOverlay(el, unit, pl) {
|
||||||
|
if (!el || !el.enabled) return null;
|
||||||
|
const dm = ElektraModel.roofDeckMap(unit, pl), map = dm.map, b = dm.bounds;
|
||||||
|
const inside = (p) => p.x >= b.x0 - 0.5 && p.x <= b.x1 + 0.5 && p.z >= b.z0 - 0.5 && p.z <= b.z1 + 0.5;
|
||||||
|
const segs = conduitSegs(el, (p, q) => p.ceil && q.ceil).map(sg => Object.assign(sg, { c: clip(sg.p, sg.q, b) })).filter(sg => sg.c);
|
||||||
|
const boxes = el.boxes.filter(bx => inside(bx));
|
||||||
|
const lamps = [];
|
||||||
|
el.switches.forEach(sw => sw.lamps.forEach(l => { if (l.kind === 'binnenspot' && inside(l)) lamps.push({ l, letter: sw.letter }); }));
|
||||||
|
if (!segs.length && !boxes.length && !lamps.length) return null;
|
||||||
|
const BLACK = PdfSheet.COLORS.BLACK;
|
||||||
|
return (doc, px, py) => {
|
||||||
|
doc.setLineWidth(0.35);
|
||||||
|
segs.forEach(sg => {
|
||||||
|
const a = map(sg.c[0]), c = map(sg.c[1]);
|
||||||
|
if (Math.hypot(c.sx - a.sx, c.sy - a.sy) < 1) return;
|
||||||
|
lineCol(doc, sg.type);
|
||||||
|
doc.line(px(a.sx), py(a.sy), px(c.sx), py(c.sy));
|
||||||
|
});
|
||||||
|
doc.setLineWidth(0.2);
|
||||||
|
boxes.forEach(bx => { const m = map(bx); lineCol(doc, 'schakel'); doc.rect(px(m.sx) - 0.9, py(m.sy) - 0.9, 1.8, 1.8, 'S'); });
|
||||||
|
doc.setFontSize(4);
|
||||||
|
lamps.forEach(({ l, letter }) => {
|
||||||
|
const m = map(l);
|
||||||
|
doc.setDrawColor(...BLACK);
|
||||||
|
doc.setFillColor(255, 233, 168);
|
||||||
|
doc.circle(px(m.sx), py(m.sy), 0.8, 'FD');
|
||||||
|
doc.setTextColor(...BLACK);
|
||||||
|
doc.text(letter, px(m.sx) + 1.1, py(m.sy) - 0.8);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { RUN_COLORS, drawPlan, drawSheet, PLAN_LEGEND, wallOverlay, floorOverlay, roofOverlay };
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -117,6 +117,10 @@ window.FloorPdf = (() => {
|
|||||||
doc.text('#' + o.id, cx, cy - o.r * scale - 2, { align: 'center' });
|
doc.text('#' + o.id, cx, cy - o.r * scale - 2, { align: 'center' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Extra laag van de aanroeper (bijv. elektra, ElektraPdf): overlay(doc, px, py),
|
||||||
|
// px/py = mm vanaf linksvoor van het dek -> blad-mm.
|
||||||
|
if (opts.overlay) opts.overlay(doc, px, py);
|
||||||
|
|
||||||
const drawDimLine = (x1, y1, x2, y2) => PdfSheet.dimLine(doc, x1, y1, x2, y2);
|
const drawDimLine = (x1, y1, x2, y2) => PdfSheet.dimLine(doc, x1, y1, x2, y2);
|
||||||
|
|
||||||
doc.setDrawColor(...BLUE);
|
doc.setDrawColor(...BLUE);
|
||||||
|
|||||||
@@ -934,7 +934,7 @@ window.HouseModel = (() => {
|
|||||||
const gevelOpen = ext.reduce((a, p) => a + openingsMM2(p), 0);
|
const gevelOpen = ext.reduce((a, p) => a + openingsMM2(p), 0);
|
||||||
const glas = ext.reduce((a, p) => a + (p.config.openings || []).filter(op => op.type === 'raam').reduce((b, op) => b + WallModel.openingArea(op), 0), 0);
|
const glas = ext.reduce((a, p) => a + (p.config.openings || []).filter(op => op.type === 'raam').reduce((b, op) => b + WallModel.openingArea(op), 0), 0);
|
||||||
const binnen = all.reduce((a, p) => a + ((p.stats && p.stats.spaanplaatAreaM2) || 0) + ((p.stats && p.stats.spaanplaatBuitenAreaM2) || 0), 0);
|
const binnen = all.reduce((a, p) => a + ((p.stats && p.stats.spaanplaatAreaM2) || 0) + ((p.stats && p.stats.spaanplaatBuitenAreaM2) || 0), 0);
|
||||||
const dakM2 = parts.units.reduce((a, u) => a + (u.roof.specs.dakleerM2 || 0) + (u.roof.specs.rietM2 || 0), 0);
|
const dakM2 = parts.units.reduce((a, u) => a + (u.roof.specs.dakleerM2 || 0) + (u.roof.specs.rietM2 || 0) + (u.roof.specs.felsM2 || 0), 0);
|
||||||
const dakPlanMM2 = parts.units.reduce((a, u) => a + u.roof.rect.W * u.roof.rect.D, 0);
|
const dakPlanMM2 = parts.units.reduce((a, u) => a + u.roof.rect.W * u.roof.rect.D, 0);
|
||||||
// Inhoud (bruto): per unit gemiddelde hoogte bovenkant dak boven de
|
// Inhoud (bruto): per unit gemiddelde hoogte bovenkant dak boven de
|
||||||
// onderkant vloer, bemonsterd op een raster.
|
// onderkant vloer, bemonsterd op een raster.
|
||||||
@@ -1089,7 +1089,9 @@ window.HouseModel = (() => {
|
|||||||
},
|
},
|
||||||
{ naam: 'OSB-plaat (vloeren + plafonds dak)', m2: sumU(u => u.floorSpecs.osbAreaM2 + u.roof.specs.osbAreaM2), m3: sumU(u => u.floorSpecs.osbVolM3 + u.roof.specs.osbVolM3), kg: sumU(u => u.floorSpecs.osbKg + u.roof.specs.osbKg) },
|
{ naam: 'OSB-plaat (vloeren + plafonds dak)', m2: sumU(u => u.floorSpecs.osbAreaM2 + u.roof.specs.osbAreaM2), m3: sumU(u => u.floorSpecs.osbVolM3 + u.roof.specs.osbVolM3), kg: sumU(u => u.floorSpecs.osbKg + u.roof.specs.osbKg) },
|
||||||
{ naam: 'Dakleer (EPDM)', m2: sumU(u => u.roof.specs.dakleerM2), m3: null, kg: sumU(u => u.roof.specs.dakleerKg) },
|
{ naam: 'Dakleer (EPDM)', m2: sumU(u => u.roof.specs.dakleerM2), m3: null, kg: sumU(u => u.roof.specs.dakleerKg) },
|
||||||
|
{ naam: `Dakbeschot OSB ${RoofModel.BESCHOT_MM} mm (bovenop de sporen)`, m2: sumU(u => u.roof.specs.beschotM2 || 0), m3: sumU(u => u.roof.specs.beschotVolM3 || 0), kg: sumU(u => u.roof.specs.beschotKg || 0) },
|
||||||
{ naam: `Riet (${RoofModel.RIET.thickMM} mm)`, m2: sumU(u => u.roof.specs.rietM2 || 0), m3: null, kg: sumU(u => u.roof.specs.rietKg || 0) },
|
{ naam: `Riet (${RoofModel.RIET.thickMM} mm)`, m2: sumU(u => u.roof.specs.rietM2 || 0), m3: null, kg: sumU(u => u.roof.specs.rietKg || 0) },
|
||||||
|
{ naam: `Felsplaten (staande naad, ${RoofModel.FELS.thickMM} mm)`, m2: sumU(u => u.roof.specs.felsM2 || 0), m3: null, kg: sumU(u => u.roof.specs.felsKg || 0) },
|
||||||
{
|
{
|
||||||
naam: 'Cementplaat (buitenzijde)', m2: byMat('cementplaat').reduce((a, p) => a + p.stats.cementplaatAreaM2, 0),
|
naam: 'Cementplaat (buitenzijde)', m2: byMat('cementplaat').reduce((a, p) => a + p.stats.cementplaatAreaM2, 0),
|
||||||
m3: byMat('cementplaat').reduce((a, p) => a + p.stats.cementplaatVolumeM3, 0), kg: byMat('cementplaat').reduce((a, p) => a + p.specs.buitenKg, 0)
|
m3: byMat('cementplaat').reduce((a, p) => a + p.stats.cementplaatVolumeM3, 0), kg: byMat('cementplaat').reduce((a, p) => a + p.specs.buitenKg, 0)
|
||||||
|
|||||||
@@ -730,7 +730,8 @@ window.HousePdf = (() => {
|
|||||||
const cost = CostModel.compute(parts, totals);
|
const cost = CostModel.compute(parts, totals);
|
||||||
const costOf = (element) => cost.elements.find(el => el.element === element);
|
const costOf = (element) => cost.elements.find(el => el.element === element);
|
||||||
const drawWallWithCost = (sheet, opts) => {
|
const drawWallWithCost = (sheet, opts) => {
|
||||||
WallPdf.drawSheet(doc, sheet.piece.config, sheet.piece.frame, Object.assign({ buitenMateriaal: sheet.piece.buitenMateriaal, cladding: sheet.piece.cladding, trasraam: sheet.piece.trasraam, skinExt: sheet.piece.skinExt, facade: sheet.piece.facade, skinFrom: sheet.piece.skinFrom, lamps: sheet.piece.lamps || [] }, opts));
|
WallPdf.drawSheet(doc, sheet.piece.config, sheet.piece.frame, Object.assign({ buitenMateriaal: sheet.piece.buitenMateriaal, cladding: sheet.piece.cladding, trasraam: sheet.piece.trasraam, skinExt: sheet.piece.skinExt, facade: sheet.piece.facade, skinFrom: sheet.piece.skinFrom, lamps: sheet.piece.lamps || [],
|
||||||
|
overlay: ElektraPdf.wallOverlay(parts.elektra, sheet.piece) }, opts));
|
||||||
drawElementCost(doc, costOf(sheet.element));
|
drawElementCost(doc, costOf(sheet.element));
|
||||||
};
|
};
|
||||||
const detailPages = paginateCostRows(costDetailRows(cost));
|
const detailPages = paginateCostRows(costDetailRows(cost));
|
||||||
@@ -745,10 +746,11 @@ window.HousePdf = (() => {
|
|||||||
if (sheet.kind === 'views') drawViewsSheet(doc, parts, state, opts);
|
if (sheet.kind === 'views') drawViewsSheet(doc, parts, state, opts);
|
||||||
else if (sheet.kind === 'plan') drawPlanSheet(doc, parts, sheets, opts);
|
else if (sheet.kind === 'plan') drawPlanSheet(doc, parts, sheets, opts);
|
||||||
else if (sheet.kind === 'floor') {
|
else if (sheet.kind === 'floor') {
|
||||||
FloorPdf.drawSheet(doc, sheet.unit.floorData.calcData, sheet.unit.floorData.resolvedOpenings, opts);
|
FloorPdf.drawSheet(doc, sheet.unit.floorData.calcData, sheet.unit.floorData.resolvedOpenings, Object.assign({ overlay: ElektraPdf.floorOverlay(parts.elektra, sheet.unit) }, opts));
|
||||||
drawElementCost(doc, costOf(sheet.element));
|
drawElementCost(doc, costOf(sheet.element));
|
||||||
} else if (sheet.kind === 'roof') {
|
} else if (sheet.kind === 'roof') {
|
||||||
FloorPdf.drawSheet(doc, sheet.plane.data.calcData, sheet.plane.data.resolvedOpenings, Object.assign({ roof: true }, opts));
|
const pu = parts.units.find(u => u.roof.planes.includes(sheet.plane));
|
||||||
|
FloorPdf.drawSheet(doc, sheet.plane.data.calcData, sheet.plane.data.resolvedOpenings, Object.assign({ roof: true, overlay: pu && ElektraPdf.roofOverlay(parts.elektra, pu, sheet.plane) }, opts));
|
||||||
// Dakkosten per unit (alle dakvlakken samen): op het eerste dakvlak.
|
// Dakkosten per unit (alle dakvlakken samen): op het eerste dakvlak.
|
||||||
if (/-D1$/.test(sheet.element)) drawElementCost(doc, costOf(sheet.element.replace(/\d+$/, '')), 'alle dakvlakken');
|
if (/-D1$/.test(sheet.element)) drawElementCost(doc, costOf(sheet.element.replace(/\d+$/, '')), 'alle dakvlakken');
|
||||||
}
|
}
|
||||||
|
|||||||
+60
-3
@@ -582,7 +582,10 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
canvas.style.cursor = 'grabbing';
|
canvas.style.cursor = 'grabbing';
|
||||||
const r = canvas.getBoundingClientRect();
|
const r = canvas.getBoundingClientRect();
|
||||||
const w = toWorld(e.clientX - r.left, e.clientY - r.top);
|
const w = toWorld(e.clientX - r.left, e.clientY - r.top);
|
||||||
elDrag.snap = ElektraModel.snapToWall(parts.elektra, w.x, w.z);
|
// Vloer-wcd: vrij in een unit (10 mm raster); de rest langs de wanden.
|
||||||
|
elDrag.snap = elDrag.key.startsWith('vwcd:')
|
||||||
|
? (parts.rects.some(rc => w.x > rc.x0 && w.x < rc.x1 && w.z > rc.z0 && w.z < rc.z1) ? { floor: true, x: Math.round(w.x / 10) * 10, z: Math.round(w.z / 10) * 10 } : null)
|
||||||
|
: ElektraModel.snapToWall(parts.elektra, w.x, w.z);
|
||||||
drawPlan();
|
drawPlan();
|
||||||
});
|
});
|
||||||
function onElektraDrop() {
|
function onElektraDrop() {
|
||||||
@@ -592,8 +595,14 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
canvas.style.cursor = tool === 'iw' ? 'crosshair' : 'default';
|
canvas.style.cursor = tool === 'iw' ? 'crosshair' : 'default';
|
||||||
if (!d.moved || !d.snap) return;
|
if (!d.moved || !d.snap) return;
|
||||||
suppressClick = true;
|
suppressClick = true;
|
||||||
|
if (d.snap.floor) {
|
||||||
|
const rc = parts.rects.find(q => d.snap.x > q.x0 && d.snap.x < q.x1 && d.snap.z > q.z0 && d.snap.z < q.z1);
|
||||||
|
const vw = state.settings.elektra.vloerWcd.find(o => 'vwcd:' + o.id === d.key);
|
||||||
|
if (rc && vw) Object.assign(vw, { unitId: rc.id, x: Math.round(d.snap.x - rc.x0), z: Math.round(d.snap.z - rc.z0) });
|
||||||
|
} else {
|
||||||
const pl = state.settings.elektra.place, prev = pl[d.key] || {};
|
const pl = state.settings.elektra.place, prev = pl[d.key] || {};
|
||||||
pl[d.key] = { element: d.snap.element, side: d.snap.side, u: d.snap.u, y: prev.y };
|
pl[d.key] = { element: d.snap.element, side: d.snap.side, u: d.snap.u, y: prev.y };
|
||||||
|
}
|
||||||
selection = { type: 'elektra', id: d.key };
|
selection = { type: 'elektra', id: d.key };
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
@@ -635,6 +644,8 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
if (['INPUT', 'SELECT', 'TEXTAREA'].includes(document.activeElement.tagName)) return;
|
if (['INPUT', 'SELECT', 'TEXTAREA'].includes(document.activeElement.tagName)) return;
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
if (drawStart) { drawStart = null; updateHint(); drawPlan(); } else if (tool === 'iw') setTool('select');
|
if (drawStart) { drawStart = null; updateHint(); drawPlan(); } else if (tool === 'iw') setTool('select');
|
||||||
|
} else if (e.key === 'Delete' && selection && selection.type === 'elektra' && String(selection.id).startsWith('vwcd:')) {
|
||||||
|
removeSelectedFloorWcd();
|
||||||
} else if (e.key === 'Delete' && selection && selection.type === 'iw') {
|
} else if (e.key === 'Delete' && selection && selection.type === 'iw') {
|
||||||
state.interiorWalls = state.interiorWalls.filter(w => w.id !== selection.id);
|
state.interiorWalls = state.interiorWalls.filter(w => w.id !== selection.id);
|
||||||
selection = null;
|
selection = null;
|
||||||
@@ -834,7 +845,16 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
const q = toPx(x.marker.x, x.marker.z);
|
const q = toPx(x.marker.x, x.marker.z);
|
||||||
ctx.fillStyle = '#4da6ff'; ctx.beginPath(); ctx.arc(q.x + 10, q.y - 9, 2.5, 0, Math.PI * 2); ctx.fill();
|
ctx.fillStyle = '#4da6ff'; ctx.beginPath(); ctx.arc(q.x + 10, q.y - 9, 2.5, 0, Math.PI * 2); ctx.fill();
|
||||||
});
|
});
|
||||||
if (elDrag && elDrag.snap) {
|
el.floorWcds.forEach(w => {
|
||||||
|
const q = toPx(w.x, w.z);
|
||||||
|
ctx.fillStyle = '#fff'; ctx.strokeStyle = rgb(RC.wcd, 1); ctx.lineWidth = 1.5;
|
||||||
|
ctx.fillRect(q.x - 7, q.y - 7, 14, 14); ctx.strokeRect(q.x - 7, q.y - 7, 14, 14);
|
||||||
|
ctx.fillStyle = rgb(RC.wcd, 1); ctx.font = 'bold 8px sans-serif'; ctx.fillText('V' + w.group, q.x, q.y + 0.5);
|
||||||
|
});
|
||||||
|
if (elDrag && elDrag.snap && elDrag.snap.floor) {
|
||||||
|
const q = toPx(elDrag.snap.x, elDrag.snap.z);
|
||||||
|
ctx.strokeStyle = '#4da6ff'; ctx.lineWidth = 2; ctx.strokeRect(q.x - 9, q.y - 9, 18, 18);
|
||||||
|
} else if (elDrag && elDrag.snap) {
|
||||||
const sn = elDrag.snap, a = toPx(sn.x, sn.z), b = toPx(sn.x + sn.n.x * 200, sn.z + sn.n.z * 200);
|
const sn = elDrag.snap, a = toPx(sn.x, sn.z), b = toPx(sn.x + sn.n.x * 200, sn.z + sn.n.z * 200);
|
||||||
ctx.strokeStyle = '#4da6ff'; ctx.lineWidth = 2;
|
ctx.strokeStyle = '#4da6ff'; ctx.lineWidth = 2;
|
||||||
ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke();
|
ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke();
|
||||||
@@ -1131,6 +1151,8 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
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;
|
||||||
|
groups.push({ label: 'Vloer', html: big('Vloer-wcd', 'plus-square', 'data-he-add-vw title="Vloerdoos met dubbele wcd in de geselecteerde (of eerste) unit"') +
|
||||||
|
note(`${e.vloerWcd.length} vloer-wcd('s); leiding door de vloer.`) });
|
||||||
groups.push({ label: 'Plaatsing', html: note('Sleep meterkast, schakelaars, wcd en apparaten in de plattegrond; selecteer er een voor positie en hoogte.') +
|
groups.push({ label: 'Plaatsing', html: note('Sleep meterkast, schakelaars, wcd en apparaten in de plattegrond; selecteer er een voor positie en hoogte.') +
|
||||||
(nManual ? big('Alles automatisch', 'arrow-counterclockwise', `data-he-reset title="${nManual} handmatige plek(ken) wissen"`) : '') });
|
(nManual ? big('Alles automatisch', 'arrow-counterclockwise', `data-he-reset title="${nManual} handmatige plek(ken) wissen"`) : '') });
|
||||||
groups.push({ label: 'Leidingen', html: num('he-slack', 'Marge %', e.slack, { step: 1 }) + num('he-res', 'Reserve', e.reserve, { step: 50, hint: 'Per leiding (lussen in de dozen), mm' }) +
|
groups.push({ label: 'Leidingen', html: num('he-slack', 'Marge %', e.slack, { step: 1 }) + num('he-res', 'Reserve', e.reserve, { step: 50, hint: 'Per leiding (lussen in de dozen), mm' }) +
|
||||||
@@ -1158,6 +1180,15 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
const cb = document.getElementById(`he-app-${k}`);
|
const cb = document.getElementById(`he-app-${k}`);
|
||||||
if (cb) cb.addEventListener('change', () => { e.apparaten[k] = cb.checked; refresh(); });
|
if (cb) cb.addEventListener('change', () => { e.apparaten[k] = cb.checked; refresh(); });
|
||||||
});
|
});
|
||||||
|
const av = body.querySelector('[data-he-add-vw]');
|
||||||
|
if (av) av.addEventListener('click', () => {
|
||||||
|
const uid = selection && selection.type === 'unit' ? selection.id : state.units[0].id;
|
||||||
|
const rc = parts && parts.rects.find(q => q.id === uid);
|
||||||
|
const id = e.vloerWcd.reduce((m, o) => Math.max(m, o.id || 0), 0) + 1;
|
||||||
|
e.vloerWcd.push({ id, unitId: uid, x: rc ? Math.round(rc.W / 2) : 1000, z: rc ? Math.round(rc.D / 2) : 1000 });
|
||||||
|
selection = { type: 'elektra', id: 'vwcd:' + id };
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
const rs = body.querySelector('[data-he-reset]');
|
const rs = body.querySelector('[data-he-reset]');
|
||||||
if (rs) rs.addEventListener('click', () => { e.place = {}; refresh(); });
|
if (rs) rs.addEventListener('click', () => { e.place = {}; refresh(); });
|
||||||
const show = () => {
|
const show = () => {
|
||||||
@@ -1220,10 +1251,36 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeSelectedFloorWcd() {
|
||||||
|
const e = state.settings.elektra;
|
||||||
|
e.vloerWcd = e.vloerWcd.filter(o => 'vwcd:' + o.id !== selection.id);
|
||||||
|
selection = null;
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
// Geselecteerd elektra-onderdeel: plek langs de wand en hoogte; slepen in
|
// Geselecteerd elektra-onderdeel: plek langs de wand en hoogte; slepen in
|
||||||
// de plattegrond of hier invullen maakt de plek handmatig.
|
// de plattegrond of hier invullen maakt de plek handmatig.
|
||||||
function elektraItemTab(it) {
|
function elektraItemTab(it) {
|
||||||
const e = state.settings.elektra;
|
const e = state.settings.elektra;
|
||||||
|
if (it.kind === 'vwcd') {
|
||||||
|
const vw = e.vloerWcd.find(o => 'vwcd:' + o.id === it.key);
|
||||||
|
return {
|
||||||
|
key: 'sel', label: it.label, icon: 'plug', contextual: true, order: 100,
|
||||||
|
groups: () => [
|
||||||
|
{ label: 'Vloer-wcd', html: kv('Unit', it.unitId) + note('Vloerdoos met dubbele wcd; leiding door de vloer naar de meterkast.') },
|
||||||
|
{ label: 'Plaats', html: num('he-vw-x', 'Vanaf west', Math.round(it.x), { step: 10, hint: 'Hart, vanaf de westrand van de unit' }) +
|
||||||
|
num('he-vw-z', 'Vanaf noord', Math.round(it.z), { step: 10, hint: 'Hart, vanaf de noordrand van de unit' }) },
|
||||||
|
{ label: 'Bewerken', html: note('Sleep de vloer-wcd in de plattegrond (ook naar een andere unit).') + big('Verwijderen', 'trash', 'data-he-vw-del title="Vloer-wcd verwijderen (Del)"') }
|
||||||
|
],
|
||||||
|
wire: (body) => {
|
||||||
|
live = [];
|
||||||
|
UI.onNum('he-vw-x', v => { if (vw) vw.x = v; }, after);
|
||||||
|
UI.onNum('he-vw-z', v => { if (vw) vw.z = v; }, after);
|
||||||
|
const b = body.querySelector('[data-he-vw-del]');
|
||||||
|
if (b) b.addEventListener('click', removeSelectedFloorWcd);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
const setPlace = (patch) => {
|
const setPlace = (patch) => {
|
||||||
const cur = e.place[it.key] || { element: it.element, side: it.side, u: Math.round(it.u) };
|
const cur = e.place[it.key] || { element: it.element, side: it.side, u: Math.round(it.u) };
|
||||||
e.place[it.key] = Object.assign({}, cur, patch);
|
e.place[it.key] = Object.assign({}, cur, patch);
|
||||||
@@ -1299,7 +1356,7 @@ function mountHouseEditor(container, callbacks) {
|
|||||||
if (form.type === 'wolf') groups.push({ label: 'Wolfsdak', html:
|
if (form.type === 'wolf') groups.push({ label: 'Wolfsdak', html:
|
||||||
UI.range('hp-roof-cut', 'Wolfseind', form.cut, { min: 2100, max: LIM.ridge, step: 10, hint: 'Hoogte waarop de kopgevels stoppen; daarboven driehoekige wolfseinden met de dakhelling tot de nok' }) });
|
UI.range('hp-roof-cut', 'Wolfseind', form.cut, { min: 2100, max: LIM.ridge, step: 10, hint: 'Hoogte waarop de kopgevels stoppen; daarboven driehoekige wolfseinden met de dakhelling tot de nok' }) });
|
||||||
groups.push({ label: 'Dak', html:
|
groups.push({ label: 'Dak', html:
|
||||||
pair(sel('hp-roof-cover', 'Bedekking', covOpts, covNow, { hint: shapeNow === 'plat' ? 'Riet kan alleen bij een hellend dak' : `${c.thickMM} mm, ${String(c.kgM2).replace('.', ',')} kg/m²` }),
|
pair(sel('hp-roof-cover', 'Bedekking', covOpts, covNow, { hint: shapeNow === 'plat' ? 'Riet en felsplaten alleen bij een hellend dak' : `${c.thickMM} mm, ${String(c.kgM2).replace('.', ',')} kg/m²` }),
|
||||||
thumb(RoofModel.coveringThumbUrl(covNow, 64), c.label)) +
|
thumb(RoofModel.coveringThumbUrl(covNow, 64), c.label)) +
|
||||||
toggle('hp-fascia', 'Boeidelen', u.roof.fascia !== false, { hint: 'Uit = daktrim' }) +
|
toggle('hp-fascia', 'Boeidelen', u.roof.fascia !== false, { hint: 'Uit = daktrim' }) +
|
||||||
toggle('hp-roof-follow', 'Maat volgt vloer', u.roof.followFloor) });
|
toggle('hp-roof-follow', 'Maat volgt vloer', u.roof.followFloor) });
|
||||||
|
|||||||
+75
-29
@@ -38,6 +38,7 @@
|
|||||||
|
|
||||||
window.RoofModel = (() => {
|
window.RoofModel = (() => {
|
||||||
const DAKLEER_MM = 2; // visuele dikte van het dakleer
|
const DAKLEER_MM = 2; // visuele dikte van het dakleer
|
||||||
|
const BESCHOT_MM = 18; // dakbeschot (OSB) bovenop de sporen, onder de bedekking
|
||||||
const DENSITY_DAKLEER_KGM2 = 1.4; // EPDM ~1,4 kg/m2
|
const DENSITY_DAKLEER_KGM2 = 1.4; // EPDM ~1,4 kg/m2
|
||||||
const TRIM_THICK_MM = 25; // daktrim-profiel, uitstekend buiten de dakrand
|
const TRIM_THICK_MM = 25; // daktrim-profiel, uitstekend buiten de dakrand
|
||||||
const EDGE_ABOVE_MM = 80; // bovenkant boeideel/daktrim ligt zoveel boven het dakleer
|
const EDGE_ABOVE_MM = 80; // bovenkant boeideel/daktrim ligt zoveel boven het dakleer
|
||||||
@@ -72,11 +73,28 @@ window.RoofModel = (() => {
|
|||||||
// hier toevoegen); zelfde opbouw (180 mm, 3,8 kg/m2).
|
// hier toevoegen); zelfde opbouw (180 mm, 3,8 kg/m2).
|
||||||
// colors: gemiddelde kleur per textuur (vlakke weergave, PDF-aanzichten).
|
// colors: gemiddelde kleur per textuur (vlakke weergave, PDF-aanzichten).
|
||||||
const RIET = { thickMM: 180, kgM2: 3.8, tileMM: 1000, nrs: ['001', '002', '003'], colors: { '001': '#947d69', '002': '#7c4c3f', '003': '#735e43' } };
|
const RIET = { thickMM: 180, kgM2: 3.8, tileMM: 1000, nrs: ['001', '002', '003'], colors: { '001': '#947d69', '002': '#7c4c3f', '003': '#735e43' } };
|
||||||
|
// Felsplaten (staande naad, zink/staal op scheidingslaag): 25 mm hoge
|
||||||
|
// felsnaden elke seamMM langs de helling; alleen hellend.
|
||||||
|
const FELS = { thickMM: 25, kgM2: 6.5, seamMM: 500, colors: { zink: ['Felsplaat zink', '#8e9497'], antraciet: ['Felsplaat antraciet', '#3d4146'], zwart: ['Felsplaat zwart', '#1f2022'] } };
|
||||||
|
// kind: soort bedekking (dakleer | riet | fels) voor specificaties en kosten.
|
||||||
const COVERINGS = Object.assign({
|
const COVERINGS = Object.assign({
|
||||||
dakleer: { label: 'Dakleer (EPDM)', color: '#2a2a2a', thickMM: DAKLEER_MM, kgM2: DENSITY_DAKLEER_KGM2 }
|
dakleer: { label: 'Dakleer (EPDM)', kind: 'dakleer', color: '#2a2a2a', thickMM: DAKLEER_MM, kgM2: DENSITY_DAKLEER_KGM2 }
|
||||||
}, ...RIET.nrs.map(nr => ({
|
}, ...RIET.nrs.map(nr => ({
|
||||||
['riet_' + nr]: { label: 'Riet ' + nr, color: RIET.colors[nr] || '#8a7560', thickMM: RIET.thickMM, kgM2: RIET.kgM2, file: `assets/textures/dak/riet_${nr}.jpg`, tileMM: RIET.tileMM, sloped: true }
|
['riet_' + nr]: { label: 'Riet ' + nr, kind: 'riet', color: RIET.colors[nr] || '#8a7560', thickMM: RIET.thickMM, kgM2: RIET.kgM2, file: `assets/textures/dak/riet_${nr}.jpg`, tileMM: RIET.tileMM, sloped: true }
|
||||||
|
})), ...Object.entries(FELS.colors).map(([k, [label, color]]) => ({
|
||||||
|
['fels_' + k]: { label, kind: 'fels', color, thickMM: FELS.thickMM, kgM2: FELS.kgM2, seamMM: FELS.seamMM, sloped: true }
|
||||||
})));
|
})));
|
||||||
|
const coverKind = (key) => (COVERINGS[key] || COVERINGS.dakleer).kind;
|
||||||
|
// Hoeveelheden per soort bedekking (zelfde sleutels in elke specs).
|
||||||
|
function coverSpecs(covering, areaM2) {
|
||||||
|
const c = COVERINGS[covering] || COVERINGS.dakleer, kg = areaM2 * c.kgM2, k = c.kind;
|
||||||
|
return {
|
||||||
|
dakleerM2: k === 'dakleer' ? areaM2 : 0, dakleerKg: k === 'dakleer' ? kg : 0,
|
||||||
|
rietM2: k === 'riet' ? areaM2 : 0, rietKg: k === 'riet' ? kg : 0,
|
||||||
|
felsM2: k === 'fels' ? areaM2 : 0, felsKg: k === 'fels' ? kg : 0,
|
||||||
|
coverKg: kg
|
||||||
|
};
|
||||||
|
}
|
||||||
// Voorvertoning (assets/textures/dak/thumb/<px>/<naam>.png) of null.
|
// Voorvertoning (assets/textures/dak/thumb/<px>/<naam>.png) of null.
|
||||||
function coveringThumbUrl(key, px) {
|
function coveringThumbUrl(key, px) {
|
||||||
const c = COVERINGS[key];
|
const c = COVERINGS[key];
|
||||||
@@ -90,8 +108,13 @@ window.RoofModel = (() => {
|
|||||||
const coveringMaterials = {};
|
const coveringMaterials = {};
|
||||||
function coveringMaterial(key) {
|
function coveringMaterial(key) {
|
||||||
const c = COVERINGS[key];
|
const c = COVERINGS[key];
|
||||||
if (!c || !c.file) return null;
|
if (!c) return null;
|
||||||
if (coveringMaterials[key]) return coveringMaterials[key];
|
if (coveringMaterials[key]) return coveringMaterials[key];
|
||||||
|
if (c.kind === 'fels') {
|
||||||
|
coveringMaterials[key] = new THREE.MeshStandardMaterial({ color: c.color, roughness: 0.4, metalness: 0.55, side: THREE.DoubleSide });
|
||||||
|
return coveringMaterials[key];
|
||||||
|
}
|
||||||
|
if (!c.file) return null;
|
||||||
const mat = new THREE.MeshStandardMaterial({ color: 0x9c8466, roughness: 0.95 });
|
const mat = new THREE.MeshStandardMaterial({ color: 0x9c8466, roughness: 0.95 });
|
||||||
if (typeof document !== 'undefined' && THREE.TextureLoader) {
|
if (typeof document !== 'undefined' && THREE.TextureLoader) {
|
||||||
new THREE.TextureLoader().load(c.file, (tex) => {
|
new THREE.TextureLoader().load(c.file, (tex) => {
|
||||||
@@ -118,9 +141,9 @@ window.RoofModel = (() => {
|
|||||||
return { followFloor: true, offsetX: 0, offsetZ: 0, fascia: true, covering: 'dakleer', deck };
|
return { followFloor: true, offsetX: 0, offsetZ: 0, fascia: true, covering: 'dakleer', deck };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dikte dek + bedekking (mm, haaks op het dakvlak).
|
// Dikte dek (plafondplaat + sporen + dakbeschot) + bedekking (mm, haaks op het dakvlak).
|
||||||
function thickness(calcData, covering) {
|
function thickness(calcData, covering) {
|
||||||
return calcData.joistH + calcData.underlayThick + (COVERINGS[covering] || COVERINGS.dakleer).thickMM;
|
return calcData.joistH + calcData.underlayThick + BESCHOT_MM + (COVERINGS[covering] || COVERINGS.dakleer).thickMM;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMaterials() {
|
function createMaterials() {
|
||||||
@@ -135,15 +158,17 @@ window.RoofModel = (() => {
|
|||||||
// 3D van één dak (meters, gecentreerd op het dakmidden, Y = 0 onderkant
|
// 3D van één dak (meters, gecentreerd op het dakmidden, Y = 0 onderkant
|
||||||
// plafondplaat). Het vloer-3D (FloorModel.build3D) wordt gespiegeld
|
// plafondplaat). Het vloer-3D (FloorModel.build3D) wordt gespiegeld
|
||||||
// (scale.y = -1; three.js corrigeert de vlakoriëntatie zelf), daarna
|
// (scale.y = -1; three.js corrigeert de vlakoriëntatie zelf), daarna
|
||||||
// komt het dakleer er bovenop.
|
// komen het dakbeschot (OSB, fase beplating) en de bedekking (fase gevel)
|
||||||
|
// er bovenop. Felsplaten: dunne plaat met staande naden langs de helling.
|
||||||
function build3D(calcData, resolvedOpenings, opts) {
|
function build3D(calcData, resolvedOpenings, opts) {
|
||||||
const m = opts.materials;
|
const m = opts.materials;
|
||||||
const toM = 0.001;
|
const toM = 0.001;
|
||||||
const group = new THREE.Group();
|
const group = new THREE.Group();
|
||||||
// Plafondplaat én bedekking zichtbaar: de sporen ertussen zijn ingesloten.
|
// Plafondplaat én bedekking zichtbaar: de sporen ertussen zijn ingesloten.
|
||||||
|
// Plafondplaat én dakbeschot zichtbaar (fase beplating): de sporen ertussen zijn ingesloten.
|
||||||
const deck = FloorModel.build3D(calcData, resolvedOpenings, { wheels: [] }, {
|
const deck = FloorModel.build3D(calcData, resolvedOpenings, { wheels: [] }, {
|
||||||
materials: m, osbVisible: opts.osbVisible !== false,
|
materials: m, osbVisible: opts.osbVisible !== false,
|
||||||
outerJoistsOnly: opts.osbVisible !== false && opts.dakleerVisible !== false
|
outerJoistsOnly: opts.osbVisible !== false
|
||||||
});
|
});
|
||||||
const deckH = (calcData.joistH + calcData.underlayThick) * toM;
|
const deckH = (calcData.joistH + calcData.underlayThick) * toM;
|
||||||
deck.scale.y = -1;
|
deck.scale.y = -1;
|
||||||
@@ -168,25 +193,46 @@ window.RoofModel = (() => {
|
|||||||
// de textuur (vorm-coördinaten in m = UV; langs de helling = v).
|
// de textuur (vorm-coördinaten in m = UV; langs de helling = v).
|
||||||
const cov = COVERINGS[opts.covering] || COVERINGS.dakleer;
|
const cov = COVERINGS[opts.covering] || COVERINGS.dakleer;
|
||||||
const covMat = coveringMaterial(opts.covering) || m.dakleerMaterial;
|
const covMat = coveringMaterial(opts.covering) || m.dakleerMaterial;
|
||||||
const leer = new THREE.Mesh(new THREE.ExtrudeGeometry(shape, { steps: 1, depth: cov.thickMM * toM, bevelEnabled: false }), covMat);
|
const bT = BESCHOT_MM * toM;
|
||||||
|
if (opts.osbVisible !== false) {
|
||||||
|
const beschot = new THREE.Mesh(new THREE.ExtrudeGeometry(shape, { steps: 1, depth: bT, bevelEnabled: false }), m.osbMaterial);
|
||||||
|
beschot.rotation.x = Math.PI / 2;
|
||||||
|
beschot.position.y = deckH + bT;
|
||||||
|
beschot.castShadow = true;
|
||||||
|
group.add(beschot);
|
||||||
|
}
|
||||||
|
if (opts.dakleerVisible !== false) {
|
||||||
|
// Felsplaat: dunne plaat + naden; anders de volle laag (dakleer, riet).
|
||||||
|
const slabT = (cov.kind === 'fels' ? 1.5 : cov.thickMM) * toM;
|
||||||
|
const leer = new THREE.Mesh(new THREE.ExtrudeGeometry(shape, { steps: 1, depth: slabT, bevelEnabled: false }), covMat);
|
||||||
leer.rotation.x = Math.PI / 2;
|
leer.rotation.x = Math.PI / 2;
|
||||||
leer.position.y = deckH + cov.thickMM * toM;
|
leer.position.y = deckH + bT + slabT;
|
||||||
leer.castShadow = cov.thickMM > DAKLEER_MM;
|
leer.castShadow = cov.thickMM > DAKLEER_MM;
|
||||||
if (opts.dakleerVisible !== false) group.add(leer);
|
group.add(leer);
|
||||||
|
if (cov.kind === 'fels') {
|
||||||
|
const n = Math.max(0, Math.floor(calcData.width / cov.seamMM));
|
||||||
|
const x0 = -(n * cov.seamMM) / 2;
|
||||||
|
for (let i = 0; i <= n; i++) {
|
||||||
|
const seam = new THREE.Mesh(new THREE.BoxGeometry(0.01, cov.thickMM * toM, dM), covMat);
|
||||||
|
seam.position.set((x0 + i * cov.seamMM) * toM, deckH + bT + cov.thickMM * toM / 2, 0);
|
||||||
|
seam.castShadow = true;
|
||||||
|
group.add(seam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specificaties: die van het dek (FloorModel) + de bedekking (dakleer of
|
// Specificaties: die van het dek (FloorModel; osb = plafondplaat) + het
|
||||||
// bijv. riet: coveringM2/Kg, dakleer dan 0).
|
// dakbeschot (beschot*) + de bedekking (coverSpecs: dakleer/riet/fels).
|
||||||
function computeSpecs(calcData, resolvedOpenings, covering) {
|
function computeSpecs(calcData, resolvedOpenings, covering) {
|
||||||
const deck = FloorModel.computeSpecs(calcData, resolvedOpenings);
|
const deck = FloorModel.computeSpecs(calcData, resolvedOpenings);
|
||||||
const areaM2 = deck.osbAreaM2; // zelfde vlak minus doorvoeren
|
const areaM2 = deck.osbAreaM2; // zelfde vlak minus doorvoeren
|
||||||
const isLeer = !COVERINGS[covering] || covering === 'dakleer';
|
const cs = coverSpecs(covering, areaM2);
|
||||||
const kg = areaM2 * (COVERINGS[covering] || COVERINGS.dakleer).kgM2;
|
const bVol = areaM2 * BESCHOT_MM * 1e-3, bKg = bVol * DENSITY_OSB_KGM3;
|
||||||
return Object.assign({}, deck, {
|
return Object.assign({}, deck, cs, {
|
||||||
dakleerM2: isLeer ? areaM2 : 0, dakleerKg: isLeer ? kg : 0,
|
beschotM2: areaM2, beschotVolM3: bVol, beschotKg: bKg,
|
||||||
rietM2: isLeer ? 0 : areaM2, rietKg: isLeer ? 0 : kg,
|
totalKg: deck.totalKg + bKg + cs.coverKg
|
||||||
totalKg: deck.totalKg + kg
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,7 +431,7 @@ window.RoofModel = (() => {
|
|||||||
const inset = (top - hipH) / tH; // nokeinde vanaf de kopgevel
|
const inset = (top - hipH) / tH; // nokeinde vanaf de kopgevel
|
||||||
const plane = (key, slope, f, along) => {
|
const plane = (key, slope, f, along) => {
|
||||||
const cos = Math.cos(Math.atan(slope));
|
const cos = Math.cos(Math.atan(slope));
|
||||||
return { key, f, along, cos, tV: underlay / cos, jV: joistH / cos, covV: cov.thickMM / cos };
|
return { key, f, along, cos, tV: underlay / cos, jV: joistH / cos, bV: BESCHOT_MM / cos, covV: cov.thickMM / cos };
|
||||||
};
|
};
|
||||||
const fns = [
|
const fns = [
|
||||||
plane('m1', sA, (p) => H(pA) + (p.x - pA) * sA, 'p'),
|
plane('m1', sA, (p) => H(pA) + (p.x - pA) * sA, 'p'),
|
||||||
@@ -399,7 +445,7 @@ window.RoofModel = (() => {
|
|||||||
fns.forEach((fj, j) => { if (j !== i && poly.length >= 3) poly = clipPoly(poly, (p) => fj.f(p) - fi.f(p)); });
|
fns.forEach((fj, j) => { if (j !== i && poly.length >= 3) poly = clipPoly(poly, (p) => fj.f(p) - fi.f(p)); });
|
||||||
// Samenvallende punten (afsnijden door de top) weg.
|
// Samenvallende punten (afsnijden door de top) weg.
|
||||||
poly = poly.filter((p, k) => { const q = poly[(k + 1) % poly.length]; return Math.hypot(q.x - p.x, q.z - p.z) > 0.5; });
|
poly = poly.filter((p, k) => { const q = poly[(k + 1) % poly.length]; return Math.hypot(q.x - p.x, q.z - p.z) > 0.5; });
|
||||||
return Object.assign({}, fi, { poly, under: fi.f, vThick: fi.tV + fi.jV + fi.covV });
|
return Object.assign({}, fi, { poly, under: fi.f, vThick: fi.tV + fi.jV + fi.bV + fi.covV });
|
||||||
}).filter(rg => rg.poly.length >= 3 && quadArea(rg.poly) > 100);
|
}).filter(rg => rg.poly.length >= 3 && quadArea(rg.poly) > 100);
|
||||||
|
|
||||||
// Snijden van een lijn (vaste p of vaste q) met een convexe veelhoek.
|
// Snijden van een lijn (vaste p of vaste q) met een convexe veelhoek.
|
||||||
@@ -499,15 +545,14 @@ window.RoofModel = (() => {
|
|||||||
const W = joistW * 1e-3, Hh = joistH * 1e-3;
|
const W = joistW * 1e-3, Hh = joistH * 1e-3;
|
||||||
const woodM3 = beams.reduce((t, bm) => t + bm.length * 1e-3 * W * Hh, 0);
|
const woodM3 = beams.reduce((t, bm) => t + bm.length * 1e-3 * W * Hh, 0);
|
||||||
const areaM2 = regions.reduce((t, rg) => t + quadArea(rg.poly) / rg.cos, 0) / 1e6;
|
const areaM2 = regions.reduce((t, rg) => t + quadArea(rg.poly) / rg.cos, 0) / 1e6;
|
||||||
const isLeer = !COVERINGS[covering] || covering === 'dakleer';
|
const cs = coverSpecs(covering, areaM2), osbVol = areaM2 * underlay * 1e-3, bVol = areaM2 * BESCHOT_MM * 1e-3;
|
||||||
const covKg = areaM2 * cov.kgM2, osbVol = areaM2 * underlay * 1e-3;
|
const specs = Object.assign({
|
||||||
const specs = {
|
|
||||||
joistWoodM3: woodM3, joistWoodKg: woodM3 * DENSITY_WOOD_KGM3,
|
joistWoodM3: woodM3, joistWoodKg: woodM3 * DENSITY_WOOD_KGM3,
|
||||||
osbAreaM2: areaM2, osbVolM3: osbVol, osbKg: osbVol * DENSITY_OSB_KGM3,
|
osbAreaM2: areaM2, osbVolM3: osbVol, osbKg: osbVol * DENSITY_OSB_KGM3,
|
||||||
|
beschotM2: areaM2, beschotVolM3: bVol, beschotKg: bVol * DENSITY_OSB_KGM3,
|
||||||
voidM3: Math.max(0, areaM2 * Hh - woodM3),
|
voidM3: Math.max(0, areaM2 * Hh - woodM3),
|
||||||
dakleerM2: isLeer ? areaM2 : 0, dakleerKg: isLeer ? covKg : 0, rietM2: isLeer ? 0 : areaM2, rietKg: isLeer ? 0 : covKg,
|
totalKg: woodM3 * DENSITY_WOOD_KGM3 + (osbVol + bVol) * DENSITY_OSB_KGM3 + cs.coverKg
|
||||||
totalKg: woodM3 * DENSITY_WOOD_KGM3 + osbVol * DENSITY_OSB_KGM3 + covKg
|
}, cs);
|
||||||
};
|
|
||||||
const maxTop = top + Math.max(...regions.map(rg => rg.vThick));
|
const maxTop = top + Math.max(...regions.map(rg => rg.vThick));
|
||||||
return { pA, pR, pB, q0, q1, hipH, joistW, joistH, regions, beams, specs, maxTop };
|
return { pA, pR, pB, q0, q1, hipH, joistW, joistH, regions, beams, specs, maxTop };
|
||||||
}
|
}
|
||||||
@@ -625,13 +670,14 @@ window.RoofModel = (() => {
|
|||||||
}
|
}
|
||||||
c.regions.forEach(rg => {
|
c.regions.forEach(rg => {
|
||||||
if (osbOn) slab(rg.poly, rg.under, rg.tV, m.osbMaterial, m.osbMaterial);
|
if (osbOn) slab(rg.poly, rg.under, rg.tV, m.osbMaterial, m.osbMaterial);
|
||||||
if (covOn) slab(rg.poly, (pt) => rg.under(pt) + rg.tV + rg.jV, rg.covV, covMat, covMat,
|
if (osbOn) slab(rg.poly, (pt) => rg.under(pt) + rg.tV + rg.jV, rg.bV, m.osbMaterial, m.osbMaterial); // dakbeschot
|
||||||
|
if (covOn) slab(rg.poly, (pt) => rg.under(pt) + rg.tV + rg.jV + rg.bV, rg.covV, covMat, covMat,
|
||||||
(pt) => rg.along === 'p' ? [pt.z * toM, pt.x * toM / rg.cos] : [pt.x * toM, pt.z * toM / rg.cos]);
|
(pt) => rg.along === 'p' ? [pt.z * toM, pt.x * toM / rg.cos] : [pt.x * toM, pt.z * toM / rg.cos]);
|
||||||
});
|
});
|
||||||
// Balken: hun (afgezaagde) veelhoek in de plattegrond, onderkant op
|
// Balken: hun (afgezaagde) veelhoek in de plattegrond, onderkant op
|
||||||
// bm.yAt, hoogte jV verticaal.
|
// bm.yAt, hoogte jV verticaal.
|
||||||
c.beams.forEach(bm => {
|
c.beams.forEach(bm => {
|
||||||
if (osbOn && covOn && bm.kind !== 'kopbalk') return;
|
if (osbOn && bm.kind !== 'kopbalk') return; // ingesloten tussen plafondplaat en dakbeschot
|
||||||
slab(bm.poly, bm.yAt, bm.jV, m.woodMaterial, m.woodMaterial);
|
slab(bm.poly, bm.yAt, bm.jV, m.woodMaterial, m.woodMaterial);
|
||||||
});
|
});
|
||||||
return group;
|
return group;
|
||||||
@@ -882,7 +928,7 @@ window.RoofModel = (() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
DAKLEER_MM, EDGE_ABOVE_MM, RIET, OVERHANG_MIN, OVERHANG_MAX, DEFAULT_SETTINGS, SPOT_RATIO, FASCIA_COLORS, COVERINGS, coveringFor, coveringThumbUrl,
|
DAKLEER_MM, BESCHOT_MM, EDGE_ABOVE_MM, RIET, FELS, coverKind, OVERHANG_MIN, OVERHANG_MAX, DEFAULT_SETTINGS, SPOT_RATIO, FASCIA_COLORS, COVERINGS, coveringFor, coveringThumbUrl,
|
||||||
createDefaultRoof, thickness, createMaterials, build3D, computeSpecs,
|
createDefaultRoof, thickness, createMaterials, build3D, computeSpecs,
|
||||||
computeShape, wallProfile, profileHeight, computeUnitRoof, buildUnit3D, capRegions,
|
computeShape, wallProfile, profileHeight, computeUnitRoof, buildUnit3D, capRegions,
|
||||||
computeEdge, buildEdge3D, normalizeSettings
|
computeEdge, buildEdge3D, normalizeSettings
|
||||||
|
|||||||
@@ -231,6 +231,10 @@ window.WallPdf = (() => {
|
|||||||
doc.text('L', lx + 2, ly + 1);
|
doc.text('L', lx + 2, ly + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Extra laag van de aanroeper (bijv. elektra, ElektraPdf):
|
||||||
|
// overlay(doc, pu, py), pu = wand-x (0..breedte) -> blad-mm.
|
||||||
|
if (opts.overlay) opts.overlay(doc, (u) => px(-wallWidth / 2 + u), py);
|
||||||
|
|
||||||
// --- Bovenaanzicht (doorsnede op stijlhoogte, zoals de voorbeeld-
|
// --- Bovenaanzicht (doorsnede op stijlhoogte, zoals de voorbeeld-
|
||||||
// tekeningen): binnenzijde boven, buitenzijde onder (= de kant van
|
// tekeningen): binnenzijde boven, buitenzijde onder (= de kant van
|
||||||
// waaruit het vooraanzicht bekeken wordt). Zelfde horizontale schaal.
|
// waaruit het vooraanzicht bekeken wordt). Zelfde horizontale schaal.
|
||||||
|
|||||||
Reference in New Issue
Block a user