v0.4.15: gevelbekleding per woning (rabat/steen, override per wand), stenen trasraam (110 mm, 181 texturen), riet per unit, vloerafwerking, verfkleur binnen, boeideelkleur, gevel tot onder dakrand, binnenhoek-fixes; testontwerp als standaard
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7d91416004
commit
12c2579471
+234
-34
@@ -56,8 +56,10 @@ window.WallModel = (() => {
|
||||
// Hoe ver de afgewerkte gevel buiten het wandvlak ligt (mm): bij vochtdoek
|
||||
// de kruislat (2 lagen) + de gevelbekleding. Gebruikt o.a. voor de
|
||||
// positie van gevellampen.
|
||||
function claddingOffset(buitenMateriaal) {
|
||||
return buitenMateriaal === 'vochtdoek' ? 2 * BATTEN.thick + CLADDING_THICK_MM : 0;
|
||||
// Bij cementplaat met steen (stone): de steenstrip.
|
||||
function claddingOffset(buitenMateriaal, stone) {
|
||||
if (buitenMateriaal === 'vochtdoek') return 2 * BATTEN.thick + CLADDING_THICK_MM;
|
||||
return buitenMateriaal === 'cementplaat' && stone ? STONE.THICK_MM : 0;
|
||||
}
|
||||
|
||||
// Trasraam: strook cement-gebonden plaat (montage-achterkant voor de
|
||||
@@ -69,9 +71,50 @@ window.WallModel = (() => {
|
||||
// Geeft null als hij uit staat. Alleen van toepassing op vochtdoek-wanden
|
||||
// (cement-gebonden buitenzijde is al plaat).
|
||||
const TRASRAAM_DEFAULT_MM = 350;
|
||||
function trasraamFor(heightMM) {
|
||||
// finish: 'cementplaat' of een steencode uit TRASRAAM_STONES (steenstrip
|
||||
// op een isolatieblok, zie STONE).
|
||||
function trasraamFor(heightMM, finish) {
|
||||
if (!(heightMM > 0)) return null;
|
||||
return { bottom: 0, top: heightMM };
|
||||
return { bottom: 0, top: heightMM, finish: finish || 'cementplaat' };
|
||||
}
|
||||
|
||||
// Steen (trasraam en steengevel): isolatieblok met steenstrip ervoor,
|
||||
// samen THICK_MM dik (vóór het doek resp. de cementplaat). Texturen: assets/textures/buitenmuur/
|
||||
// "<code> - Witte voeg.jpg", 2062 x 1125 px op 2 mm/px (steen + voeg
|
||||
// ~62 mm per laag), naadloos herhalend. Voorvertoning in thumb/<px>/ (.png).
|
||||
// JOINT_MM: hart van de onderste voeg boven de onderrand van de textuur
|
||||
// (gemeten: 1-2 px); COURSE_MM: steen + voeg (31 px). De bovenrand van de
|
||||
// textuur snijdt door een steen (1125 px is geen veelvoud van 31).
|
||||
const STONE = { THICK_MM: 110, STRIP_MM: 20, TILE_W_MM: 4124, TILE_H_MM: 2250, JOINT_MM: 3, COURSE_MM: 62, DIR: 'assets/textures/buitenmuur/' };
|
||||
const TRASRAAM_STONES = ('K490 K518 K560 K662 K706 K739 K752 K764 K773 K911P K911 K914 K915 K919 K923P K923 K941P K941 K947 K948 K960 K991 ' +
|
||||
'R100 R116 R140 R200 R206 R214 R216 R220 R227 R228 R240 R268 R286 R287 R303 R307 R332 R335 R343 R356 R381 R382 R384 R385 R386 R391 ' +
|
||||
'R400 R401 R435 R436 R440 R480 R483 R484 R487 R488 R489 R500 R510 R518 R535 R540 R550 R555 R560 R561 R563 R564 R565 R572DF14_64 R572DF ' +
|
||||
'R580 R581 R582 R658 R659 R663 R664 R665 R669 R680 R682 R684 R685 R686 R687 R688 R689 R690 R691 R692 R-693 R694 R695 R696 R697 R698 ' +
|
||||
'R700 R714 R715 R717 R719 R720 R721 R725 R726 R727 R729 R730 R731 R732 R733 R734 R735 R736 R737 R738 R739 R740 R742 R743 R744 R745X737 ' +
|
||||
'R746 R747 R748 R748X764X749 R749 R750 R751 R751X759 R752 R754 R756 R757 R758 R759 R760 R761 R762 R763 R764 R764X756 R766 R767 R768 ' +
|
||||
'R769 R770 R771 R772 R773 R775 R777 R788 R800 R835 R840 R911P R911 R913 R916 R917 R918 R921 R923P R931 R932 R941P R941 R942 R943 R944 ' +
|
||||
'R960DF14_70 R960DF R961 R970 R980 R985 R991').split(' ');
|
||||
const isStoneFinish = (finish) => TRASRAAM_STONES.includes(finish);
|
||||
const stoneTextureUrl = (code) => STONE.DIR + encodeURIComponent(code + ' - Witte voeg.jpg');
|
||||
const stoneThumbUrl = (code, px) => STONE.DIR + 'thumb/' + (px || 64) + '/' + encodeURIComponent(code + ' - Witte voeg.png');
|
||||
// Eén materiaal per steen (gedeeld, pas geladen als hij gebruikt wordt).
|
||||
// UV's van de steenblokken zijn in meters, dus repeat = 1/tegel.
|
||||
const stoneMaterials = {};
|
||||
function stoneMaterial(code) {
|
||||
if (stoneMaterials[code]) return stoneMaterials[code];
|
||||
const mat = new THREE.MeshStandardMaterial({ color: 0xb0a090, roughness: 0.9 });
|
||||
if (typeof document !== 'undefined' && THREE.TextureLoader) {
|
||||
new THREE.TextureLoader().load(stoneTextureUrl(code), (tex) => {
|
||||
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
|
||||
tex.repeat.set(1000 / STONE.TILE_W_MM, 1000 / STONE.TILE_H_MM);
|
||||
tex.anisotropy = 8;
|
||||
mat.map = tex;
|
||||
mat.color.set(0xffffff);
|
||||
mat.needsUpdate = true;
|
||||
});
|
||||
}
|
||||
stoneMaterials[code] = mat;
|
||||
return mat;
|
||||
}
|
||||
// Hoekdoorloop van de buitenlagen. Een wand die tussen twee doorlopende
|
||||
// wanden past (unit-hoek) moet met zijn buitenlagen ook de kopse kant
|
||||
@@ -79,9 +122,13 @@ window.WallModel = (() => {
|
||||
// right } = dikte van die wand (mm, 0 = vrij uiteinde). Elke laag loopt
|
||||
// daarbovenop nog door tot zijn eigen buitenvlak, zodat de hoek dicht is.
|
||||
// depth = afstand van het buitenvlak van de laag tot het wandvlak (mm).
|
||||
// Negatief (binnenhoek): het stuk loopt zoveel de naad in, binnen de wand
|
||||
// van de buur-unit. Geen doorloop; de buitenlagen krijgen daar een
|
||||
// uitsparing (build3D: trim, tot opts.trimTop).
|
||||
function cornerExt(skinExt, depth) {
|
||||
const e = skinExt || {};
|
||||
return { left: e.left > 0 ? e.left + depth : 0, right: e.right > 0 ? e.right + depth : 0 };
|
||||
const one = (v) => v > 0 ? v + depth : 0;
|
||||
return { left: one(e.left), right: one(e.right) };
|
||||
}
|
||||
|
||||
// Onderkant van vochtdoek en latwerk (wand-coördinaten, mm).
|
||||
@@ -479,13 +526,13 @@ window.WallModel = (() => {
|
||||
// Stukken van een horizontale lijn op hoogte y (mm) die OP de beplating
|
||||
// liggen: binnen de wand-omtrek (incl. schuine top) en buiten de
|
||||
// uitsparingen. Geeft [[x0, x1], ...] terug (mm, t.o.v. het wandmidden).
|
||||
function skinSegmentsAtY(CONFIG, y) {
|
||||
function skinSegmentsAtY(CONFIG, y, extraHoles) {
|
||||
if (y <= 0) return [];
|
||||
// Deel van de breedte waar de daklijn boven y ligt.
|
||||
const range = topLine(CONFIG).rangeAbove(y);
|
||||
if (!range) return [];
|
||||
let segments = [range];
|
||||
sheathingHoles(CONFIG).filter(h => y > h.hy0 && y < h.hy1).forEach(h => {
|
||||
sheathingHoles(CONFIG).concat(extraHoles || []).filter(h => y > h.hy0 && y < h.hy1).forEach(h => {
|
||||
segments = segments.flatMap(([a, b]) => {
|
||||
if (h.hx1 <= a || h.hx0 >= b) return [[a, b]];
|
||||
const out = [];
|
||||
@@ -513,7 +560,9 @@ window.WallModel = (() => {
|
||||
// hoger begint (opbouw, blootliggende naadwand) volgt zo hetzelfde raster.
|
||||
// skinFrom (optioneel, mm): buitenafwerking pas vanaf deze hoogte (het
|
||||
// blootliggende deel van een naadwand boven het dak van de andere unit).
|
||||
function computeBattens(CONFIG, cladding, trasraam, skinExt, facade, skinFrom) {
|
||||
// extraHoles (optioneel, mm, zoals sheathingHoles): extra uitsparingen
|
||||
// (binnenhoek-doorloop, zie build3D).
|
||||
function computeBattens(CONFIG, cladding, trasraam, skinExt, facade, skinFrom, extraHoles) {
|
||||
const y0 = Math.max(skinStartY(trasraam), skinFrom || 0); // boven het trasraam
|
||||
const ext = cornerExt(skinExt, 2 * BATTEN.thick); // over de hoekstijlen van de doorlopende wand
|
||||
const xL = -CONFIG.wallWidth / 2 - ext.left, xR = CONFIG.wallWidth / 2 + ext.right;
|
||||
@@ -521,7 +570,7 @@ window.WallModel = (() => {
|
||||
const isEnd = facade ? facade.isEnd : true;
|
||||
const halfW = CONFIG.wallWidth / 2;
|
||||
const tl = topLine(CONFIG), topYAt = tl.at;
|
||||
const holes = sheathingHoles(CONFIG);
|
||||
const holes = sheathingHoles(CONFIG).concat(extraHoles || []);
|
||||
const w = BATTEN.width, sp = BATTEN.spacing;
|
||||
const top = (cladding === 'verticaal') ? 'horizontaal' : 'verticaal';
|
||||
|
||||
@@ -533,7 +582,7 @@ window.WallModel = (() => {
|
||||
yStart = g + Math.max(0, Math.ceil((yStart - g) / sp - 1e-9)) * sp;
|
||||
}
|
||||
for (let y = yStart; y < tl.maxY - w / 2; y += sp) {
|
||||
skinSegmentsAtY(CONFIG, y).forEach(([x0, x1]) => {
|
||||
skinSegmentsAtY(CONFIG, y, extraHoles).forEach(([x0, x1]) => {
|
||||
if (x0 <= -halfW + 0.5) x0 = xL;
|
||||
if (x1 >= halfW - 0.5) x1 = xR;
|
||||
if (x1 - x0 > w) horizontaal.push({ x0, x1, y });
|
||||
@@ -627,6 +676,7 @@ window.WallModel = (() => {
|
||||
? new THREE.MeshStandardMaterial({ color: 0xffffff, map: doekMap, roughness: 0.7, side: THREE.DoubleSide })
|
||||
: new THREE.MeshStandardMaterial({ color: DOEK_COLOR, roughness: 0.7, side: THREE.DoubleSide }),
|
||||
cementplaatMaterial: new THREE.MeshStandardMaterial({ color: 0x808285, roughness: 0.8, side: THREE.DoubleSide }),
|
||||
isolatieMaterial: new THREE.MeshStandardMaterial({ color: 0xf4f4f2, roughness: 0.95 }),
|
||||
battenMaterial: new THREE.MeshStandardMaterial({ color: 0xd9b77e, roughness: 0.7 })
|
||||
};
|
||||
}
|
||||
@@ -644,10 +694,14 @@ window.WallModel = (() => {
|
||||
const spaanplaatVisible = opts.spaanplaatVisible !== false;
|
||||
const buitenVisible = opts.buitenVisible !== false;
|
||||
const cladding = opts.cladding || 'horizontaal';
|
||||
const trasraam = buitenMateriaal === 'vochtdoek' ? (opts.trasraam || null) : null;
|
||||
// Trasraam: bij rabat (vochtdoek) en bij een steengevel (cementplaat + stone).
|
||||
const trasraam = buitenMateriaal === 'vochtdoek' || (buitenMateriaal === 'cementplaat' && isStoneFinish(opts.stone)) ? (opts.trasraam || null) : null;
|
||||
const skinExt = opts.skinExt || null;
|
||||
const facade = opts.facade || null;
|
||||
const skinFrom = opts.skinFrom > 0 ? opts.skinFrom : 0; // buitenafwerking vanaf (mm), zie computeBattens
|
||||
const painted = !!opts.painted; // spaanplaat geschilderd: zonder plaatnaden
|
||||
// stone: steencode (TRASRAAM_STONES) voor steenstrips op een cementplaat-gevel.
|
||||
const stone = buitenMateriaal === 'cementplaat' && isStoneFinish(opts.stone) ? opts.stone : null;
|
||||
// spaanplaatBelow: onder skinFrom aan de buitenzijde spaanplaat (naadwand
|
||||
// geheel in één unit: onder het dak van de andere unit is het een binnenwand).
|
||||
const spaanplaatBelow = !!opts.spaanplaatBelow && skinFrom > 0 && buitenMateriaal === 'vochtdoek';
|
||||
@@ -680,6 +734,8 @@ window.WallModel = (() => {
|
||||
let spaanplaatBuitenVolumeM3 = 0;
|
||||
let battenLengthM = 0, battenVolumeM3 = 0, battenCount = 0; // kruislat (alleen vochtdoek)
|
||||
let trasraamAreaM2 = 0, trasraamVolumeM3 = 0; // cement-gebonden plaat onderaan (alleen vochtdoek)
|
||||
let stoneAreaM2 = 0, stoneIsolatieM3 = 0; // steengevel (cementplaat + stone): strip + isolatieblok
|
||||
let trasraamStoneAreaM2 = 0, trasraamIsolatieM3 = 0; // stenen trasraam: steenstrip (gevelvlak) + isolatieblok
|
||||
let horizBeamCount = 0; // onderregel-segmenten + topbalk + dorpels per opening
|
||||
|
||||
function createExtrudedBeam(shape, depth, positionZ) {
|
||||
@@ -709,6 +765,16 @@ window.WallModel = (() => {
|
||||
const tl = topLine(CONFIG, toM);
|
||||
const startXM = -halfW;
|
||||
const topYAt = tl.at;
|
||||
// Gevel doorgetrokken tot onder de dakrand (opts.skinAbove, mm): de
|
||||
// buitenlagen (doek, latwerk, steen, cementplaat) volgen een bovenrand
|
||||
// die zoveel hoger ligt, met dezelfde helling; regelwerk en binnenzijde
|
||||
// blijven op de wandhoogte.
|
||||
const skinAbove = opts.skinAbove > 0 ? opts.skinAbove : 0;
|
||||
const CONFIG_OUT = skinAbove ? Object.assign({}, CONFIG, {
|
||||
wallHeightLeft: CONFIG.wallHeightLeft + skinAbove, wallHeightRight: CONFIG.wallHeightRight + skinAbove,
|
||||
wallPeak: CONFIG.wallPeak ? { x: CONFIG.wallPeak.x, h: CONFIG.wallPeak.h + skinAbove } : null
|
||||
}) : CONFIG;
|
||||
const tlOut = skinAbove ? topLine(CONFIG_OUT, toM) : tl;
|
||||
// Onderkant bovenregel van xLeft naar xRight (incl. verstek bij de nok).
|
||||
const underPts = tl.underPts;
|
||||
|
||||
@@ -858,6 +924,89 @@ window.WallModel = (() => {
|
||||
const holeExtents = sheathingHoles(CONFIG).map(hole => ({
|
||||
hx0: hole.hx0 * toM, hx1: hole.hx1 * toM, hy0: hole.hy0 * toM, hy1: hole.hy1 * toM
|
||||
}));
|
||||
// Binnenhoek-doorloop (negatieve skinExt): dat stuk zit binnen de wand
|
||||
// en het dak van de buur-unit; daar geen buitenlagen, tot opts.trimTop
|
||||
// ({ left, right }, mm; bovenkant dak van de buur). Daarboven loopt de
|
||||
// gevel gewoon door. Als uitsparing (zoals een opening) in alle
|
||||
// buitenlagen.
|
||||
const trimL = skinExt && skinExt.left < 0 ? -skinExt.left * toM : 0;
|
||||
const trimR = skinExt && skinExt.right < 0 ? -skinExt.right * toM : 0;
|
||||
const trimTop = opts.trimTop || {};
|
||||
const topOf = (v) => (v > 0 ? v : 1e7) * toM;
|
||||
const trimHoles = [];
|
||||
if (trimL > 0) trimHoles.push({ hx0: -halfW - 1, hx1: -halfW + trimL, hy0: -1, hy1: topOf(trimTop.left) });
|
||||
if (trimR > 0) trimHoles.push({ hx0: halfW - trimR, hx1: halfW + 1, hy0: -1, hy1: topOf(trimTop.right) });
|
||||
const outerHoles = holeExtents.concat(trimHoles);
|
||||
const trimHolesMM = trimHoles.map(h => ({ hx0: h.hx0 / toM, hx1: h.hx1 / toM, hy0: h.hy0 / toM, hy1: h.hy1 / toM }));
|
||||
|
||||
// Steenblok (trasraam en steengevel): isolatieblok met een steenstrip
|
||||
// ervoor, samen STONE.THICK_MM dik, vóór het wandvlak (doek resp.
|
||||
// cementplaat). Van hoogte y0 tot yCap (m) of tot de daklijn, met de
|
||||
// openingen uitgespaard. Hoeken: doorloop zoals de andere buitenlagen
|
||||
// (cornerExt); het laatste stuk van die doorloop is de hoek zelf,
|
||||
// waarvan de kopse kant zichtbaar is: dat stuk is over de volle dikte
|
||||
// steen. UV's in meters in gevel-coördinaten, zodat het metselwerk
|
||||
// over wandstukken en naden doorloopt; per vlakrichting gezet (ook de
|
||||
// kopse kanten horizontaal, niet de standaard van ExtrudeGeometry).
|
||||
// Verticaal: anchor = { y (m), down } — op hoogte y ligt een voeg;
|
||||
// de gevel telt vanaf daar omhoog (hele stenen boven de naad), het
|
||||
// trasraam vanaf zijn bovenkant omlaag (hele stenen onder de naad).
|
||||
// Geeft { areaM2, isoM3 }.
|
||||
function stoneBlock(code, y0, yCap, anchor) {
|
||||
// v (m) op hoogte y: omhoog vanaf de onderste voeg van de textuur;
|
||||
// omlaag vanaf een voeg een aantal hele lagen hoger in de textuur,
|
||||
// zodat de strook eronder nog binnen de textuur valt.
|
||||
const jointM = STONE.JOINT_MM * toM, courseM = STONE.COURSE_MM * toM;
|
||||
const vOff = anchor.down
|
||||
? jointM + Math.ceil(Math.max(0, anchor.y - y0) / courseM) * courseM - anchor.y
|
||||
: jointM - anchor.y;
|
||||
const setUV = (geo) => {
|
||||
const pos = geo.attributes.position, nrm = geo.attributes.normal, uv = geo.attributes.uv;
|
||||
for (let k = 0; k < pos.count; k++) {
|
||||
const x = pos.getX(k) + uvShiftM, y = pos.getY(k) + vOff, z = pos.getZ(k);
|
||||
const nx = Math.abs(nrm.getX(k)), ny = Math.abs(nrm.getY(k));
|
||||
if (nx > 0.5) uv.setXY(k, z, y); else if (ny > 0.5) uv.setXY(k, x, z); else uv.setXY(k, x, y);
|
||||
}
|
||||
uv.needsUpdate = true;
|
||||
return geo;
|
||||
};
|
||||
const S = STONE;
|
||||
const zB = (maxDepth / 2) + skinGapM + sThickM + skinGapM;
|
||||
const tIso = (S.THICK_MM - S.STRIP_MM) * toM, tAll = S.THICK_MM * toM;
|
||||
const ext = extM(CONFIG.sheathingThick + 2 * SKIN_GAP_MM + S.THICK_MM);
|
||||
const xL = -halfW - ext.left, xR = halfW + ext.right;
|
||||
const capL = ext.left > 0 ? xL + tAll : xL, capR = ext.right > 0 ? xR - tAll : xR;
|
||||
const topClamped = (x) => tlOut.at(Math.max(-halfW, Math.min(halfW, x)));
|
||||
const rects = outerHoles.map(h => ({ x0: h.hx0, x1: h.hx1, y0: h.hy0, y1: h.hy1 }));
|
||||
const shapesIn = (a, b) => {
|
||||
if (b - a <= edgeEps) return [];
|
||||
const capped = yCap === Infinity;
|
||||
const pieces = columnMinusRects(a, b, y0, capped ? topClamped : (x) => Math.min(yCap, topClamped(x)), rects,
|
||||
capped ? { extraXs: tlOut.kinks } : { yCap, extraXs: tlOut.kinks.concat(tlOut.crossings(yCap)) });
|
||||
return (pieces || []).map(piece => {
|
||||
const shape = new THREE.Shape(piece.outer.map(q => new THREE.Vector2(q.x, q.y)));
|
||||
piece.holes.forEach(hole => shape.holes.push(new THREE.Path(hole.map(q => new THREE.Vector2(q.x, q.y)))));
|
||||
return shape;
|
||||
});
|
||||
};
|
||||
const add = (shapes, depth, z, mat) => {
|
||||
if (!shapes.length) return;
|
||||
const mesh = new THREE.Mesh(setUV(new THREE.ExtrudeGeometry(shapes, { steps: 1, depth, bevelEnabled: false })), mat);
|
||||
mesh.position.set(0, 0, z);
|
||||
mesh.visible = buitenVisible;
|
||||
mesh.castShadow = true;
|
||||
wallGroup3D.add(mesh);
|
||||
};
|
||||
const stoneMat = stoneMaterial(code);
|
||||
const isoMat = opts.materials.isolatieMaterial || cementplaatMaterial;
|
||||
const mid = shapesIn(capL, capR);
|
||||
const caps = shapesIn(xL, capL).concat(shapesIn(capR, xR));
|
||||
add(mid, tIso, zB, isoMat);
|
||||
add(mid, tAll - tIso, zB + tIso, stoneMat);
|
||||
add(caps, tAll, zB, stoneMat);
|
||||
const area = (list) => list.reduce((sum, shape) => sum + shapeAreaWithHoles(shape), 0);
|
||||
return { areaM2: area(mid) + area(caps), isoM3: area(mid) * tIso };
|
||||
}
|
||||
|
||||
// side: 'front' (buitenzijde/zuid) of 'back' (binnenzijde/noord).
|
||||
// range (optioneel, m): { y0, y1, zExtra } = alleen de strook daartussen
|
||||
@@ -867,11 +1016,15 @@ window.WallModel = (() => {
|
||||
// komt er een losse strook zOut verder naar buiten (laag in de
|
||||
// beplatingsruimte, anders zouden de hoekstijlen erdoor steken).
|
||||
function buildVerticalBoardSkin(material, side, visible, range, ext) {
|
||||
const bounds = boardBoundaries.slice();
|
||||
// Geschilderde spaanplaat (opts.painted): één egaal vlak, geen naden.
|
||||
const seamless = painted && material === spaanplaatMaterial;
|
||||
const bounds = seamless ? [boardBoundaries[0], boardBoundaries[boardBoundaries.length - 1]] : boardBoundaries.slice();
|
||||
if (ext && !ext.zOut) { bounds[0] -= ext.left; bounds[bounds.length - 1] += ext.right; }
|
||||
const yB = range ? range.y0 : 0;
|
||||
const yCap = range ? range.y1 : Infinity;
|
||||
const skinTopAt = (x) => Math.min(yCap, topYAt(x));
|
||||
const TL = side === 'front' && !range ? tlOut : tl; // buitenplaat: tot onder de dakrand
|
||||
const HX = side === 'front' ? outerHoles : holeExtents;
|
||||
const skinTopAt = (x) => Math.min(yCap, TL.at(x));
|
||||
let areaM2 = 0, volumeM3 = 0;
|
||||
const z = (side === 'front' ? (maxDepth / 2) + skinGapM : -(maxDepth / 2) - sThickM - skinGapM) + ((range && range.zExtra) || 0);
|
||||
// Duidelijke naad tussen platen: dunne rand rond elke plaat, 50%
|
||||
@@ -894,8 +1047,8 @@ window.WallModel = (() => {
|
||||
// over een plaatnaad wordt een inkeping in de plaatrand, zodat er
|
||||
// geen reepje plaat (met randlijnen) door de opening blijft lopen.
|
||||
const pieces = columnMinusRects(ex0, ex1, yB, skinTopAt,
|
||||
holeExtents.map(h => ({ x0: h.hx0, x1: h.hx1, y0: h.hy0, y1: h.hy1 })),
|
||||
{ yCap: range ? yCap : undefined, extraXs: range ? tl.kinks.concat(tl.crossings(yCap)) : tl.kinks });
|
||||
HX.map(h => ({ x0: h.hx0, x1: h.hx1, y0: h.hy0, y1: h.hy1 })),
|
||||
{ yCap: range ? yCap : undefined, extraXs: range ? TL.kinks.concat(TL.crossings(yCap)) : TL.kinks });
|
||||
let boardShapes;
|
||||
if (pieces) {
|
||||
boardShapes = pieces.map(piece => {
|
||||
@@ -910,7 +1063,7 @@ window.WallModel = (() => {
|
||||
boardShape.moveTo(ex0, yB);
|
||||
boardShape.lineTo(ex1, yB);
|
||||
boardShape.lineTo(ex1, skinTopAt(ex1));
|
||||
tl.kinks.filter(x => x > ex0 && x < ex1).forEach(x => boardShape.lineTo(x, skinTopAt(x)));
|
||||
TL.kinks.filter(x => x > ex0 && x < ex1).forEach(x => boardShape.lineTo(x, skinTopAt(x)));
|
||||
boardShape.lineTo(ex0, skinTopAt(ex0));
|
||||
boardShape.closePath();
|
||||
holeExtents.forEach(h => {
|
||||
@@ -941,11 +1094,13 @@ window.WallModel = (() => {
|
||||
board.visible = visible;
|
||||
wallGroup3D.add(board);
|
||||
|
||||
const edgesGeo = new THREE.EdgesGeometry(boardGeo);
|
||||
const edgeLines = new THREE.LineSegments(edgesGeo, edgeMaterial);
|
||||
edgeLines.position.set(0, 0, z);
|
||||
edgeLines.visible = visible;
|
||||
wallGroup3D.add(edgeLines);
|
||||
if (!seamless) {
|
||||
const edgesGeo = new THREE.EdgesGeometry(boardGeo);
|
||||
const edgeLines = new THREE.LineSegments(edgesGeo, edgeMaterial);
|
||||
edgeLines.position.set(0, 0, z);
|
||||
edgeLines.visible = visible;
|
||||
wallGroup3D.add(edgeLines);
|
||||
}
|
||||
|
||||
areaM2 += boardAreaM2;
|
||||
volumeM3 += boardAreaM2 * sThickM;
|
||||
@@ -975,6 +1130,18 @@ window.WallModel = (() => {
|
||||
|
||||
// Binnenzijde (noord): altijd spaanplaat.
|
||||
const spaanplaatResult = buildVerticalBoardSkin(spaanplaatMaterial, 'back', spaanplaatVisible);
|
||||
// Kopse kant van een binnenhoek-doorloop (trimL/trimR): die staat open
|
||||
// naar de kamer van de buur-unit; binnenplaat erop (over regelwerk +
|
||||
// beplating), net buiten het stijlvlak tegen flikkeren.
|
||||
[[trimL, -halfW, -1], [trimR, halfW, 1]].forEach(([trim, x, dir]) => {
|
||||
if (!(trim > 0)) return;
|
||||
const h = Math.min(topYAt(x), topOf(dir < 0 ? trimTop.left : trimTop.right)), depth = maxDepth + 2 * (skinGapM + sThickM);
|
||||
const cap = new THREE.Mesh(new THREE.PlaneGeometry(depth, h), spaanplaatMaterial);
|
||||
cap.rotation.y = dir * Math.PI / 2;
|
||||
cap.position.set(x + dir * 0.0003, h / 2, 0);
|
||||
cap.visible = spaanplaatVisible;
|
||||
wallGroup3D.add(cap);
|
||||
});
|
||||
spaanplaatAreaM2 = spaanplaatResult.areaM2;
|
||||
spaanplaatVolumeM3 = spaanplaatResult.volumeM3;
|
||||
|
||||
@@ -997,6 +1164,31 @@ window.WallModel = (() => {
|
||||
Object.assign(extM(0), { zOut: sThickM + 2 * skinGapM }));
|
||||
cementplaatAreaM2 = cementResult.areaM2;
|
||||
cementplaatVolumeM3 = cementResult.volumeM3;
|
||||
// Steen (opts.stone): steenblok (isolatie + strip) over de hele gevel,
|
||||
// vóór de cement-gebonden plaat; onderaan het trasraam (eigen steen
|
||||
// of cementplaat), de gevelsteen begint erboven.
|
||||
if (stone) {
|
||||
let y0 = 0;
|
||||
if (trasraam && isStoneFinish(trasraam.finish)) {
|
||||
const r = stoneBlock(trasraam.finish, trasraam.bottom * toM, trasraam.top * toM, { y: trasraam.top * toM, down: true });
|
||||
trasraamStoneAreaM2 = r.areaM2;
|
||||
trasraamIsolatieM3 = r.isoM3;
|
||||
y0 = trasraam.top * toM;
|
||||
} else if (trasraam) {
|
||||
const tras = buildVerticalBoardSkin(cementplaatMaterial, 'front', buitenVisible, {
|
||||
y0: trasraam.bottom * toM, y1: trasraam.top * toM, zExtra: sThickM + skinGapM
|
||||
}, extM(CONFIG.sheathingThick + 2 * SKIN_GAP_MM));
|
||||
trasraamAreaM2 = tras.areaM2;
|
||||
trasraamVolumeM3 = tras.volumeM3;
|
||||
y0 = trasraam.top * toM;
|
||||
}
|
||||
// Anker op de gevel-hoogte van de naad (facade.gridY0 = bovenkant
|
||||
// trasraam t.o.v. dit stuk), zodat ook een opbouw aansluit.
|
||||
const anchorY = facade && facade.gridY0 !== undefined ? facade.gridY0 * toM : y0;
|
||||
const r = stoneBlock(stone, y0, Infinity, { y: anchorY, down: false });
|
||||
stoneAreaM2 = r.areaM2;
|
||||
stoneIsolatieM3 = r.isoM3;
|
||||
}
|
||||
} else {
|
||||
// F. VOCHTDOEK BUITENZIJDE (zuid) — horizontale banen van 1500mm
|
||||
// (rolbreedte), "oneindig" strekkend in de breedte (dus geen
|
||||
@@ -1011,9 +1203,10 @@ window.WallModel = (() => {
|
||||
// ligt — zoals bij een echte shingle-overlap — i.p.v. exact co-planair
|
||||
// (wat flikkering/z-fighting zou geven nu de platen niet meer
|
||||
// transparant zijn).
|
||||
const doekZStepM = 0.0002; // 0.2 mm per baan, verwaarloosbaar t.o.v. de spouwdiepte
|
||||
const wallOutlinePts = [{ x: -halfW, y: 0 }, { x: halfW, y: 0 }, ...tl.pts.slice().reverse()];
|
||||
const maxRoofY = tl.maxY;
|
||||
const doekZStepM = 0.0002;
|
||||
const DOEK_OUT_M = 0.001; // 0.2 mm per baan, verwaarloosbaar t.o.v. de spouwdiepte
|
||||
const wallOutlinePts = [{ x: -halfW, y: 0 }, { x: halfW, y: 0 }, ...tlOut.pts.slice().reverse()];
|
||||
const maxRoofY = tlOut.maxY;
|
||||
|
||||
let rowY0 = skinFrom * toM;
|
||||
let rowIndex = 0;
|
||||
@@ -1023,11 +1216,11 @@ window.WallModel = (() => {
|
||||
// Baan = strook [rowY0, min(rowY1, daklijn)] min uitsparingen
|
||||
// (columnMinusRects, zelfde als de platen): een opening over de
|
||||
// baanrand wordt een inkeping, geen reepje doek door de opening.
|
||||
const roofAt = topYAt;
|
||||
const [rx0, rx1] = tl.rangeAbove(rowY0) || [0, 0];
|
||||
const extraXs = tl.crossings(rowY1).concat(tl.kinks);
|
||||
const roofAt = tlOut.at;
|
||||
const [rx0, rx1] = tlOut.rangeAbove(rowY0) || [0, 0];
|
||||
const extraXs = tlOut.crossings(rowY1).concat(tlOut.kinks);
|
||||
const pieces = rx1 - rx0 > edgeEps ? columnMinusRects(rx0, rx1, rowY0, (x) => Math.min(rowY1, roofAt(x)),
|
||||
holeExtents.map(h => ({ x0: h.hx0, x1: h.hx1, y0: h.hy0, y1: h.hy1 })), { yCap: rowY1, extraXs }) : [];
|
||||
outerHoles.map(h => ({ x0: h.hx0, x1: h.hx1, y0: h.hy0, y1: h.hy1 })), { yCap: rowY1, extraXs }) : [];
|
||||
|
||||
let rowShapes;
|
||||
if (pieces) {
|
||||
@@ -1068,7 +1261,10 @@ window.WallModel = (() => {
|
||||
const rowAreaM2 = rowShapes.reduce((sum, shape) => sum + shapeAreaWithHoles(shape), 0);
|
||||
|
||||
const doekRow = new THREE.Mesh(rowGeo, doekMaterial);
|
||||
doekRow.position.set(0, 0, (maxDepth / 2) + skinGapM + rowIndex * doekZStepM);
|
||||
// 1 mm naar buiten: niet in hetzelfde vlak als de dakrand (dak op
|
||||
// de wand), anders flikkert het dak door het doek heen waar de
|
||||
// gevel tot onder de dakrand doorloopt (skinAbove).
|
||||
doekRow.position.set(0, 0, (maxDepth / 2) + skinGapM + rowIndex * doekZStepM + DOEK_OUT_M);
|
||||
doekRow.visible = buitenVisible;
|
||||
wallGroup3D.add(doekRow);
|
||||
|
||||
@@ -1101,7 +1297,11 @@ window.WallModel = (() => {
|
||||
// uitsparingen als de buitenplaten), vóór het doek
|
||||
// (zelfde vlak als de latten). Het doek loopt
|
||||
// er ongewijzigd achter door; het latwerk begint erboven.
|
||||
if (trasraam) {
|
||||
if (trasraam && isStoneFinish(trasraam.finish)) {
|
||||
const r = stoneBlock(trasraam.finish, trasraam.bottom * toM, trasraam.top * toM, { y: trasraam.top * toM, down: true });
|
||||
trasraamStoneAreaM2 = r.areaM2;
|
||||
trasraamIsolatieM3 = r.isoM3;
|
||||
} else if (trasraam) {
|
||||
const tras = buildVerticalBoardSkin(cementplaatMaterial, 'front', buitenVisible, {
|
||||
y0: trasraam.bottom * toM, y1: trasraam.top * toM, zExtra: sThickM + (rowIndex + 1) * doekZStepM + skinGapM
|
||||
}, extM(CONFIG.sheathingThick + 2 * SKIN_GAP_MM));
|
||||
@@ -1111,9 +1311,9 @@ window.WallModel = (() => {
|
||||
|
||||
// G. KRUISLAT op het vochtdoek (computeBattens): onderste laag direct op
|
||||
// het doek, bovenste laag (haaks op de gevelbekleding) daaroverheen.
|
||||
const bat = computeBattens(CONFIG, cladding, trasraam, skinExt, facade, skinFrom);
|
||||
const bat = computeBattens(CONFIG_OUT, cladding, trasraam, skinExt, facade, skinFrom, trimHolesMM);
|
||||
const bT = BATTEN.thick * toM, bW = BATTEN.width * toM;
|
||||
const zDoek = (maxDepth / 2) + skinGapM + sThickM + rowIndex * doekZStepM + skinGapM;
|
||||
const zDoek = (maxDepth / 2) + skinGapM + sThickM + rowIndex * doekZStepM + skinGapM + DOEK_OUT_M;
|
||||
const addLayer = (orientation, z) => {
|
||||
bat.layers[orientation].forEach(b => {
|
||||
const horiz = orientation === 'horizontaal';
|
||||
@@ -1136,7 +1336,7 @@ window.WallModel = (() => {
|
||||
stats: {
|
||||
woodVolumeM3, spaanplaatVolumeM3, spaanplaatAreaM2, cementplaatVolumeM3, cementplaatAreaM2, doekAreaM2,
|
||||
spaanplaatBuitenAreaM2, spaanplaatBuitenVolumeM3, horizBeamCount,
|
||||
battenLengthM, battenVolumeM3, battenCount, trasraamAreaM2, trasraamVolumeM3
|
||||
battenLengthM, battenVolumeM3, battenCount, trasraamAreaM2, trasraamVolumeM3, trasraamStoneAreaM2, trasraamIsolatieM3, stoneAreaM2, stoneIsolatieM3
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1346,7 +1546,7 @@ window.WallModel = (() => {
|
||||
}
|
||||
|
||||
return {
|
||||
STANDARD_BEAM, STOCK_LENGTHS_MM, CORNER_STUDS, EXTERIOR_MATERIALS, OPENING_TYPES, BATTEN, CLADDING_DIRECTIONS, CLADDING_THICK_MM, claddingOffset, cornerExt, TRASRAAM_DEFAULT_MM, trasraamFor, skinStartY, computeBattens, planPurchase, purchaseText,
|
||||
STANDARD_BEAM, STOCK_LENGTHS_MM, CORNER_STUDS, EXTERIOR_MATERIALS, OPENING_TYPES, BATTEN, CLADDING_DIRECTIONS, CLADDING_THICK_MM, claddingOffset, cornerExt, TRASRAAM_DEFAULT_MM, STONE, TRASRAAM_STONES, isStoneFinish, stoneThumbUrl, trasraamFor, skinStartY, computeBattens, planPurchase, purchaseText,
|
||||
sheathingHoles, skinSegmentsAtY, topLine, topPlateOutlines,
|
||||
validateState, resolveOpenings, computeBOM, groupBOM, buildConfig, configFromState, thicknessOf, computeFrame,
|
||||
createMaterials, build3D, computeSpecs
|
||||
|
||||
+4
-1
@@ -88,7 +88,10 @@ window.WallPdf = (() => {
|
||||
doc.text(`Bovenste lat ${bat.top}, gevelbekleding ${opts.cladding || 'horizontaal'} (WEO/Novalit)`, tblX[0], my);
|
||||
if (opts.trasraam) {
|
||||
my += 4;
|
||||
doc.text(`Trasraam: cement-gebonden plaat tot ${opts.trasraam.top} mm boven bovenkant vloer`, tblX[0], my);
|
||||
const finish = WallModel.isStoneFinish(opts.trasraam.finish)
|
||||
? `steenstrip ${opts.trasraam.finish} op isolatieblok (${WallModel.STONE.THICK_MM} mm)`
|
||||
: 'cement-gebonden plaat';
|
||||
doc.text(`Trasraam: ${finish} tot ${opts.trasraam.top} mm boven bovenkant vloer`, tblX[0], my);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-2
@@ -253,6 +253,8 @@ function mountWallEditor(container, callbacks) {
|
||||
}
|
||||
// Binnen een woning is de richting van de gevelbekleding (voorlopig) globaal.
|
||||
const lockCladding = callbacks && callbacks.lockCladding;
|
||||
// ... en de buitenzijde (gevelbekleding per woning, override per wand).
|
||||
const lockExterior = callbacks && callbacks.lockExterior;
|
||||
|
||||
// Binnen een woning is de h.o.h. globaal (woning-instellingen), niet per wand.
|
||||
if (callbacks && callbacks.lockSpacing) {
|
||||
@@ -566,7 +568,7 @@ function mountWallEditor(container, callbacks) {
|
||||
function getPureState() {
|
||||
return {
|
||||
wall: collectWallParams(),
|
||||
materials: { interior: 'spaanplaat', exterior: buitenMateriaal, cladding, trasraam },
|
||||
materials: { interior: 'spaanplaat', exterior: buitenMateriaal, cladding, trasraam, stone },
|
||||
openings: openings.map(o => ({ id: o.id, type: o.type || 'raam', w: o.w, h: o.h, y: o.y, anchor: o.anchor, offset: o.offset }))
|
||||
};
|
||||
}
|
||||
@@ -591,6 +593,7 @@ function mountWallEditor(container, callbacks) {
|
||||
buitenMateriaal = (state.materials && state.materials.exterior) || 'vochtdoek';
|
||||
cladding = (state.materials && state.materials.cladding) || 'horizontaal';
|
||||
trasraam = (state.materials && state.materials.trasraam) || null; // woning-instelling
|
||||
stone = (state.materials && state.materials.stone) || null; // woning-instelling (steen)
|
||||
syncMaterialButtons();
|
||||
|
||||
openings = state.openings.map(o => ({ id: o.id, type: o.type || 'raam', w: o.w, h: o.h, y: o.y, anchor: o.anchor, offset: o.offset }));
|
||||
@@ -1244,11 +1247,13 @@ function mountWallEditor(container, callbacks) {
|
||||
let buitenMateriaal = 'vochtdoek';
|
||||
let cladding = 'horizontaal'; // richting gevelbekleding (alleen bij vochtdoek)
|
||||
let trasraam = null; // { bottom, top } mm, komt uit de woning-instellingen (WallModel.trasraamFor)
|
||||
let stone = null; // steencode bij cementplaat, komt uit de woning-instellingen
|
||||
function syncMaterialButtons() {
|
||||
document.querySelectorAll('.material-toggle-btn[data-material]').forEach(b => b.classList.toggle('active', b.dataset.material === buitenMateriaal));
|
||||
document.querySelectorAll('.material-toggle-btn[data-cladding]').forEach(b => b.classList.toggle('active', b.dataset.cladding === cladding));
|
||||
document.getElementById('cladding-section').style.display = buitenMateriaal === 'vochtdoek' ? '' : 'none';
|
||||
if (lockCladding) document.querySelectorAll('.material-toggle-btn[data-cladding]').forEach(b => { b.disabled = true; b.title = lockCladding; });
|
||||
if (lockExterior) document.querySelectorAll('.material-toggle-btn[data-material]').forEach(b => { b.disabled = true; b.title = lockExterior; });
|
||||
}
|
||||
document.querySelectorAll('.material-toggle-btn[data-material]').forEach(btn => {
|
||||
btn.addEventListener('click', () => { buitenMateriaal = btn.dataset.material; syncMaterialButtons(); updateAll(); });
|
||||
@@ -1284,7 +1289,7 @@ function mountWallEditor(container, callbacks) {
|
||||
function update3DScene() {
|
||||
if (wallGroup3D) scene.remove(wallGroup3D);
|
||||
const result = WallModel.build3D(CONFIG, calcData, {
|
||||
materials: wallMaterials, buitenMateriaal, cladding, trasraam, spaanplaatVisible, buitenVisible
|
||||
materials: wallMaterials, buitenMateriaal, cladding, trasraam, stone, spaanplaatVisible, buitenVisible
|
||||
});
|
||||
wallGroup3D = result.group;
|
||||
lastWallStats = result.stats;
|
||||
|
||||
Reference in New Issue
Block a user