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
+77
-31
@@ -38,6 +38,7 @@
|
||||
|
||||
window.RoofModel = (() => {
|
||||
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 TRIM_THICK_MM = 25; // daktrim-profiel, uitstekend buiten de dakrand
|
||||
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).
|
||||
// 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' } };
|
||||
// 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({
|
||||
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_' + 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.
|
||||
function coveringThumbUrl(key, px) {
|
||||
const c = COVERINGS[key];
|
||||
@@ -90,8 +108,13 @@ window.RoofModel = (() => {
|
||||
const coveringMaterials = {};
|
||||
function coveringMaterial(key) {
|
||||
const c = COVERINGS[key];
|
||||
if (!c || !c.file) return null;
|
||||
if (!c) return null;
|
||||
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 });
|
||||
if (typeof document !== 'undefined' && THREE.TextureLoader) {
|
||||
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 };
|
||||
}
|
||||
|
||||
// Dikte dek + bedekking (mm, haaks op het dakvlak).
|
||||
// Dikte dek (plafondplaat + sporen + dakbeschot) + bedekking (mm, haaks op het dakvlak).
|
||||
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() {
|
||||
@@ -135,15 +158,17 @@ window.RoofModel = (() => {
|
||||
// 3D van één dak (meters, gecentreerd op het dakmidden, Y = 0 onderkant
|
||||
// plafondplaat). Het vloer-3D (FloorModel.build3D) wordt gespiegeld
|
||||
// (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) {
|
||||
const m = opts.materials;
|
||||
const toM = 0.001;
|
||||
const group = new THREE.Group();
|
||||
// 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: [] }, {
|
||||
materials: m, osbVisible: opts.osbVisible !== false,
|
||||
outerJoistsOnly: opts.osbVisible !== false && opts.dakleerVisible !== false
|
||||
outerJoistsOnly: opts.osbVisible !== false
|
||||
});
|
||||
const deckH = (calcData.joistH + calcData.underlayThick) * toM;
|
||||
deck.scale.y = -1;
|
||||
@@ -168,25 +193,46 @@ window.RoofModel = (() => {
|
||||
// de textuur (vorm-coördinaten in m = UV; langs de helling = v).
|
||||
const cov = COVERINGS[opts.covering] || COVERINGS.dakleer;
|
||||
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);
|
||||
leer.rotation.x = Math.PI / 2;
|
||||
leer.position.y = deckH + cov.thickMM * toM;
|
||||
leer.castShadow = cov.thickMM > DAKLEER_MM;
|
||||
if (opts.dakleerVisible !== false) group.add(leer);
|
||||
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.position.y = deckH + bT + slabT;
|
||||
leer.castShadow = cov.thickMM > DAKLEER_MM;
|
||||
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;
|
||||
}
|
||||
|
||||
// Specificaties: die van het dek (FloorModel) + de bedekking (dakleer of
|
||||
// bijv. riet: coveringM2/Kg, dakleer dan 0).
|
||||
// Specificaties: die van het dek (FloorModel; osb = plafondplaat) + het
|
||||
// dakbeschot (beschot*) + de bedekking (coverSpecs: dakleer/riet/fels).
|
||||
function computeSpecs(calcData, resolvedOpenings, covering) {
|
||||
const deck = FloorModel.computeSpecs(calcData, resolvedOpenings);
|
||||
const areaM2 = deck.osbAreaM2; // zelfde vlak minus doorvoeren
|
||||
const isLeer = !COVERINGS[covering] || covering === 'dakleer';
|
||||
const kg = areaM2 * (COVERINGS[covering] || COVERINGS.dakleer).kgM2;
|
||||
return Object.assign({}, deck, {
|
||||
dakleerM2: isLeer ? areaM2 : 0, dakleerKg: isLeer ? kg : 0,
|
||||
rietM2: isLeer ? 0 : areaM2, rietKg: isLeer ? 0 : kg,
|
||||
totalKg: deck.totalKg + kg
|
||||
const cs = coverSpecs(covering, areaM2);
|
||||
const bVol = areaM2 * BESCHOT_MM * 1e-3, bKg = bVol * DENSITY_OSB_KGM3;
|
||||
return Object.assign({}, deck, cs, {
|
||||
beschotM2: areaM2, beschotVolM3: bVol, beschotKg: bKg,
|
||||
totalKg: deck.totalKg + bKg + cs.coverKg
|
||||
});
|
||||
}
|
||||
|
||||
@@ -385,7 +431,7 @@ window.RoofModel = (() => {
|
||||
const inset = (top - hipH) / tH; // nokeinde vanaf de kopgevel
|
||||
const plane = (key, slope, f, along) => {
|
||||
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 = [
|
||||
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)); });
|
||||
// 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; });
|
||||
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);
|
||||
|
||||
// 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 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 isLeer = !COVERINGS[covering] || covering === 'dakleer';
|
||||
const covKg = areaM2 * cov.kgM2, osbVol = areaM2 * underlay * 1e-3;
|
||||
const specs = {
|
||||
const cs = coverSpecs(covering, areaM2), osbVol = areaM2 * underlay * 1e-3, bVol = areaM2 * BESCHOT_MM * 1e-3;
|
||||
const specs = Object.assign({
|
||||
joistWoodM3: woodM3, joistWoodKg: woodM3 * DENSITY_WOOD_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),
|
||||
dakleerM2: isLeer ? areaM2 : 0, dakleerKg: isLeer ? covKg : 0, rietM2: isLeer ? 0 : areaM2, rietKg: isLeer ? 0 : covKg,
|
||||
totalKg: woodM3 * DENSITY_WOOD_KGM3 + osbVol * DENSITY_OSB_KGM3 + covKg
|
||||
};
|
||||
totalKg: woodM3 * DENSITY_WOOD_KGM3 + (osbVol + bVol) * DENSITY_OSB_KGM3 + cs.coverKg
|
||||
}, cs);
|
||||
const maxTop = top + Math.max(...regions.map(rg => rg.vThick));
|
||||
return { pA, pR, pB, q0, q1, hipH, joistW, joistH, regions, beams, specs, maxTop };
|
||||
}
|
||||
@@ -625,13 +670,14 @@ window.RoofModel = (() => {
|
||||
}
|
||||
c.regions.forEach(rg => {
|
||||
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]);
|
||||
});
|
||||
// Balken: hun (afgezaagde) veelhoek in de plattegrond, onderkant op
|
||||
// bm.yAt, hoogte jV verticaal.
|
||||
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);
|
||||
});
|
||||
return group;
|
||||
@@ -882,7 +928,7 @@ window.RoofModel = (() => {
|
||||
}
|
||||
|
||||
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,
|
||||
computeShape, wallProfile, profileHeight, computeUnitRoof, buildUnit3D, capRegions,
|
||||
computeEdge, buildEdge3D, normalizeSettings
|
||||
|
||||
Reference in New Issue
Block a user