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:
Machiel
2026-10-06 20:54:49 +02:00
co-authored by Claude Opus 5.5
parent e1a688728c
commit b5510d56c7
13 changed files with 668 additions and 64 deletions
+62 -5
View File
@@ -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) });