v0.5.3: elektra-buizen min. 50 mm uit elkaar (gebundeld alleen naar de meterkast), vloer-wcd met leiding door de vloer, elektra op de PDF-bladen per wand/vloer/dakvlak, kosten elektra (buis + draad per meter, boorgaten 90 s geteld op stijlen/regels/balken, dozen, groepenkast, keuring), dakbeschot OSB 18 mm bovenop de sporen, felsplaten als dakbedekking
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e1a688728c
commit
b5510d56c7
@@ -934,7 +934,7 @@ window.HouseModel = (() => {
|
||||
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 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);
|
||||
// Inhoud (bruto): per unit gemiddelde hoogte bovenkant dak boven de
|
||||
// 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: '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: `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),
|
||||
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 costOf = (element) => cost.elements.find(el => el.element === element);
|
||||
const drawWallWithCost = (sheet, opts) => {
|
||||
WallPdf.drawSheet(doc, sheet.piece.config, sheet.piece.frame, Object.assign({ buitenMateriaal: sheet.piece.buitenMateriaal, cladding: sheet.piece.cladding, trasraam: sheet.piece.trasraam, skinExt: sheet.piece.skinExt, facade: sheet.piece.facade, skinFrom: sheet.piece.skinFrom, lamps: sheet.piece.lamps || [] }, opts));
|
||||
WallPdf.drawSheet(doc, sheet.piece.config, sheet.piece.frame, Object.assign({ buitenMateriaal: sheet.piece.buitenMateriaal, cladding: sheet.piece.cladding, trasraam: sheet.piece.trasraam, skinExt: sheet.piece.skinExt, facade: sheet.piece.facade, skinFrom: sheet.piece.skinFrom, lamps: sheet.piece.lamps || [],
|
||||
overlay: ElektraPdf.wallOverlay(parts.elektra, sheet.piece) }, opts));
|
||||
drawElementCost(doc, costOf(sheet.element));
|
||||
};
|
||||
const detailPages = paginateCostRows(costDetailRows(cost));
|
||||
@@ -745,10 +746,11 @@ window.HousePdf = (() => {
|
||||
if (sheet.kind === 'views') drawViewsSheet(doc, parts, state, opts);
|
||||
else if (sheet.kind === 'plan') drawPlanSheet(doc, parts, sheets, opts);
|
||||
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));
|
||||
} 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.
|
||||
if (/-D1$/.test(sheet.element)) drawElementCost(doc, costOf(sheet.element.replace(/\d+$/, '')), 'alle dakvlakken');
|
||||
}
|
||||
|
||||
+62
-5
@@ -582,7 +582,10 @@ function mountHouseEditor(container, callbacks) {
|
||||
canvas.style.cursor = 'grabbing';
|
||||
const r = canvas.getBoundingClientRect();
|
||||
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();
|
||||
});
|
||||
function onElektraDrop() {
|
||||
@@ -592,8 +595,14 @@ function mountHouseEditor(container, callbacks) {
|
||||
canvas.style.cursor = tool === 'iw' ? 'crosshair' : 'default';
|
||||
if (!d.moved || !d.snap) return;
|
||||
suppressClick = true;
|
||||
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 };
|
||||
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] || {};
|
||||
pl[d.key] = { element: d.snap.element, side: d.snap.side, u: d.snap.u, y: prev.y };
|
||||
}
|
||||
selection = { type: 'elektra', id: d.key };
|
||||
refresh();
|
||||
}
|
||||
@@ -635,6 +644,8 @@ function mountHouseEditor(container, callbacks) {
|
||||
if (['INPUT', 'SELECT', 'TEXTAREA'].includes(document.activeElement.tagName)) return;
|
||||
if (e.key === 'Escape') {
|
||||
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') {
|
||||
state.interiorWalls = state.interiorWalls.filter(w => w.id !== selection.id);
|
||||
selection = null;
|
||||
@@ -834,7 +845,16 @@ function mountHouseEditor(container, callbacks) {
|
||||
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();
|
||||
});
|
||||
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);
|
||||
ctx.strokeStyle = '#4da6ff'; ctx.lineWidth = 2;
|
||||
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]) =>
|
||||
check(`he-app-${k}`, `${A.label} (${A.code})`, !!e.apparaten[k])).join('') });
|
||||
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.') +
|
||||
(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' }) +
|
||||
@@ -1158,6 +1180,15 @@ function mountHouseEditor(container, callbacks) {
|
||||
const cb = document.getElementById(`he-app-${k}`);
|
||||
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]');
|
||||
if (rs) rs.addEventListener('click', () => { e.place = {}; refresh(); });
|
||||
const show = () => {
|
||||
@@ -1220,10 +1251,36 @@ function mountHouseEditor(container, callbacks) {
|
||||
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
|
||||
// de plattegrond of hier invullen maakt de plek handmatig.
|
||||
function elektraItemTab(it) {
|
||||
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 cur = e.place[it.key] || { element: it.element, side: it.side, u: Math.round(it.u) };
|
||||
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:
|
||||
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:
|
||||
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)) +
|
||||
toggle('hp-fascia', 'Boeidelen', u.roof.fascia !== false, { hint: 'Uit = daktrim' }) +
|
||||
toggle('hp-roof-follow', 'Maat volgt vloer', u.roof.followFloor) });
|
||||
|
||||
Reference in New Issue
Block a user