v0.7.3: elektraleidingen nooit meer door een raam of deur (makeOpeningTest): een stijgleiding boven/onder een opening (wcd onder een raam) gaat eerst op dezelfde hoogte opzij tot naast de opening, langs dezelfde wand met een deur of raam ertussen via het plafond, gevellampen om verspringende gevelhoeken heen en het uit elkaar schuiven van buizen eindigt nooit in een opening (alle modellen 0 kruisingen); felsplaten als gevelafwerking (zink/antraciet/zwart, per woning, wand of wandstuk, opbouw als verticale rabat, kleuren gedeeld met het felsdak, in 3D, PDF en kosten); felsplaat op dak én gevel = geen boeideel, de dakplaat zet om over de rand zonder opstand, een boeideel ernaast stopt gelijk met zo'n rand (ook naast daktrim); zijwand bij een overkapping (Palen overkapping › Zijwand) in plaats van een hoekpaal, gestuukt, met de bekleding van de gevel in hetzelfde vlak aan de buitenkant (in 3D, plattegrond, PDF, DXF en kosten); model Reest (12,9 × 7,85 m, lessenaarsdak hoog aan de noordgevel over twee units in één vlak, overkapping bij de voordeur met witte zijwand, aanbouw met plat dak en daktrim, zuidgevel en dak in antraciet felsplaten, verder verticale rabat)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Machiel
2026-10-13 21:27:20 +02:00
co-authored by Claude Opus 5.5
parent 89cadbebf7
commit 51921650e3
16 changed files with 544 additions and 87 deletions
+149 -21
View File
@@ -34,7 +34,7 @@ window.HouseModel = (() => {
function createDefaultSettings() {
return {
spacing: { exterior: { min: 240, max: 480 }, interior: { min: 300, max: 600 } },
facade: { type: 'rabat', stone: 'K490', rabatColor: 'naturel', stucColor: 'wit' },
facade: { type: 'rabat', stone: 'K490', rabatColor: 'naturel', stucColor: 'wit', felsColor: 'antraciet' },
cladding: 'horizontaal',
trasraam: { enabled: true, height: WallModel.TRASRAAM_DEFAULT_MM, finish: 'cementplaat' },
interiorPaint: true,
@@ -54,7 +54,7 @@ window.HouseModel = (() => {
return { id, attach, unit, roof: RoofModel.createDefaultRoof(unit.floor), lighting: LightingModel.createDefaultUnitLighting(), facade: { walls: {} } };
}
const FACADE_TYPES = { rabat: 'Rabat', steen: 'Steen', stuc: 'Stucwerk' };
const FACADE_TYPES = { rabat: 'Rabat', steen: 'Steen', stuc: 'Stucwerk', fels: 'Felsplaten' };
function facadeFor(state, u, side, piece) {
const f = state.settings.facade;
const ow = (u.facade && u.facade.walls && u.facade.walls[side]) || null;
@@ -62,15 +62,18 @@ window.HouseModel = (() => {
const type = o && FACADE_TYPES[o.type] ? o.type : f.type;
const stone = o && o.type === 'steen' && WallModel.isStoneFinish(o.stone) ? o.stone : f.stone;
const stuc = o && o.type === 'stuc' && WallModel.isStucColor(o.stuc) ? o.stuc : f.stucColor;
return { type, stone, stuc, override: !!o };
const fels = o && o.type === 'fels' && WallModel.isFelsColor(o.fels) ? o.fels : f.felsColor;
return { type, stone, stuc, fels, override: !!o };
}
function facadeMaterials(state, fc) {
const tr = state.settings.trasraam;
const trasraam = tr.enabled ? WallModel.trasraamFor(tr.height, tr.finish) : null;
const cladding = state.settings.cladding;
if (fc.type === 'steen') return { exterior: 'cementplaat', stone: fc.stone, stuc: null, cladding, trasraam, rabatColor: null };
if (fc.type === 'stuc') return { exterior: 'cementplaat', stone: null, stuc: fc.stuc, cladding, trasraam, rabatColor: null };
return { exterior: 'vochtdoek', stone: null, stuc: null, cladding, trasraam, rabatColor: state.settings.facade.rabatColor };
if (fc.type === 'steen') return { exterior: 'cementplaat', stone: fc.stone, stuc: null, cladding, trasraam, rabatColor: null, fels: null };
if (fc.type === 'stuc') return { exterior: 'cementplaat', stone: null, stuc: fc.stuc, cladding, trasraam, rabatColor: null, fels: null };
// Felsplaten: opbouw als verticale rabat (latten liggend), de banen staan altijd.
if (fc.type === 'fels') return { exterior: 'vochtdoek', stone: null, stuc: null, cladding: 'verticaal', trasraam, rabatColor: null, fels: fc.fels };
return { exterior: 'vochtdoek', stone: null, stuc: null, cladding, trasraam, rabatColor: state.settings.facade.rabatColor, fels: null };
}
function newModelState(naam) {
@@ -660,6 +663,103 @@ window.HouseModel = (() => {
return st;
}
// Model Reest (examples/ontwerp_reest.pdf): 12,9 × 7,85 m. Hoofdvolume 11,5 × 4,8 m onder één lessenaarsdak (hoog
// aan de noordgevel) dat aan de westkop 1,4 m doorloopt als overkapping bij de voordeur, aan de zuidkant dicht met
// een witte zijwand (PostModel sideWall, geen palen). 4,8 m is te diep voor één unit: twee units (3,4 + 1,4 m, de
// naad ten zuiden van het glas in de westgevel) met elk een lessenaar in hetzelfde vlak. Aan de noordwestkant een
// aanbouw van 3,89 × 3,05 m met plat dak (toilet, berging, entree), die met een open doorgang op de woonkamer
// uitkomt; de voordeur zit onder de overkapping. Zuidgevel en dak in felsplaten (daar geen boeideel: de dakplaat
// zet om, en de felsplaten lopen over de zijwand door), de rest verticale rabat; de aanbouw heeft daktrim. De
// aanbouw is de eerste unit, zodat alle maten buitenwerks vanaf zijn noordwesthoek gelden, zoals de tekening.
// De tekening heeft de zuidgevel van de aanbouw 240 mm zuidelijker (3290); in de configurator ligt hij op de naad.
function createReest() {
const st = newModelState('Reest');
const S = st.settings, HA = 2800, HN = 3500, HM = 2820, HS = 2540, DN = 3400, DS = 1400, V = 1400, FELS = 'antraciet';
Object.assign(S.facade, { type: 'rabat', rabatColor: 'naturel', felsColor: FELS });
S.cladding = 'verticaal';
S.trasraam.enabled = false;
Object.assign(S.roof, { fasciaColor: 'wit', overhang: 300, fasciaHeight: 300 });
S.roof.posts = PostModel.normalizeSettings({ enabled: true, sideWall: true, sideColor: 'wit', maxSpan: 6000 });
S.lighting.mode = 'gevel';
S.lighting.direction = 'both';
if (S.elektra && S.elektra.apparaten) Object.assign(S.elektra.apparaten, { koken: true, oven: true, vaatwasser: true });
if (S.elektra) S.elektra.plateColor = 'antraciet';
const A = createUnitEntry('A', null, { width: 3890, depth: 3050, height: HA, windowOn: [] });
const B = createUnitEntry('B', { to: 'A', side: 'achter', align: 'start', offset: V }, { width: 11500, depth: DN, height: HM, windowOn: [] });
const C = createUnitEntry('C', { to: 'B', side: 'achter', align: 'start', offset: 0 }, { width: 11500, depth: DS, height: HS, windowOn: [] });
st.units.push(A, B, C);
UnitModel.applyRoofForm(B.unit, { type: 'lessenaar', dir: 'voor', low: HM, high: HN });
UnitModel.applyRoofForm(C.unit, { type: 'lessenaar', dir: 'voor', low: HS, high: HM });
[B, C].forEach(u => { u.roof.covering = 'fels_' + FELS; u.roof.extend = { f: 0, r: 0, b: 0, l: V }; });
A.roof.fascia = false;
C.facade.walls.achter = { type: 'fels', fels: FELS };
[A, B, C].forEach(u => ['voor', 'achter', 'links', 'rechts'].forEach(side => { u.lighting.walls[side] = 'geen'; }));
B.lighting.walls.links = 'openingen';
const { K, binnen, ops } = modelKit(st, HS, 90);
// Noord 'right' en zuid 'left' = vanaf de westhoek van de unit; west 'left' = vanaf het noordeind van de wand.
ops(A.unit.walls.voor, [['raam', 500, 700, 1400, 'right', 475, K('raam')], ['raam', 800, 1100, 1000, 'right', 2175, K('raam')]]);
ops(A.unit.walls.achter, [['voordeur', 1100, 2100, 0, 'left', 230, K('deur', { hinge: 'links', swing: 'buiten', glas: true })]]);
ops(B.unit.walls.voor, [
['raam', 600, 2100, 0, 'right', 3390, K('raam')],
['deur', 900, 2100, 0, 'right', 3990, K('deur', { leaf: 'rechts', hinge: 'links', swing: 'buiten', volglas: true })],
['deur', 900, 2100, 0, 'right', 4890, K('deur', { leaf: 'links', hinge: 'rechts', swing: 'buiten', volglas: true })],
['raam', 600, 2100, 0, 'right', 5790, K('raam')],
['raam', 500, 1400, 700, 'right', 7525, K('raam')],
['raam', 500, 1400, 700, 'right', 9975, K('raam')]]);
ops(B.unit.walls.links, [['raam', 1800, 2600, 0, 'left', 1480, K('raam')]]);
ops(C.unit.walls.achter, [['raam', 1500, 500, 1600, 'left', 2050, K('raam')], ['raam', 1500, 500, 1600, 'left', 7440, K('raam')]]);
ensureSeamWalls(st, 'A');
const seam = st.interiorWalls.find(w => w.seam);
if (seam) {
// Open doorgang entree → woonkamer (x 1855–3045), gemeten vanaf de binnenkant van de westgevel van B.
const tB = WallModel.thicknessOf(WallModel.configFromState(B.unit.walls.links));
seam.wall.openings = [{ id: 1, type: 'deur', w: 1190, h: 2300, y: 0, anchor: 'left', offset: Math.round(1855 - V - tB),
fill: Object.assign(KozijnModel.defaultFill('deur'), { type: 'geen' }) }];
}
const DH = 2115;
// Aanbouw: wanden tot het platte dak. Noord–zuid: 'buiten' = west, 'left' = vanaf de noordkant; west–oost: 'buiten' = zuid, 'left' = vanaf de westkant.
const ann = modelKit(st, HA, 90);
ann.iw(1335, 190, 1335, 1690); // toilet | berging
ann.iw(190, 1735, 3700, 1735, [ // toilet, berging | entree
['deur', 930, DH, 'left', 70, binnen({ hinge: 'links', swing: 'buiten' })],
['deur', 930, DH, 'left', 1260, binnen({ hinge: 'links', swing: 'buiten' })]]);
// Hoofdvolume: wanden onder het laagste punt van het dak, slaapkamer en badkamer met een vlak plafond.
const main = modelKit(st, HS - 40, 90);
main.iw(8715, 3240, 8715, 7660, [['deur', 930, DH, 'left', 1890, binnen({ hinge: 'rechts', swing: 'buiten' })]]); // woonkamer | slaapkamer, badkamer
main.iw(8760, 6215, 12710, 6215, [['deur', 930, DH, 'left', 1815, // slaapkamer | badkamer
Object.assign(binnen(), { type: 'schuifdeur', leaves: 1, mount: 'opbouw', side: 'binnen', dir: 'links' })]]);
const { room, fx } = main;
const plafond = { ceiling: HS - 40 };
room('A', 700, 1000, 'toilet');
room('A', 2500, 1000, 'berging', 'Berging');
room('A', 1800, 2400, 'hal', 'Entree');
room('B', 3000, 1500, 'woonkeuken', 'Woonkamer/keuken');
room('B', 9000, 1500, 'slaapkamer', 'Slaapkamer', plafond);
room('C', 9500, 600, 'badkamer', '', plafond);
const xB = (x) => x - V, zB = (z) => z - 3050, zC = (z) => z - 3050 - DN;
fx('toilet', 'A', 735, 640, 0, { w: 1100 });
fx('boiler', 'A', 3400, 1430, 90);
fx('keuken', 'C', xB(3390), zC(7360), 180, { w: 3600 }, { front: 'antraciet', hogeKast: 'geen', spoelbak: 0.29, kookplaat: true, kookplaatPlek: 0.71 });
fx('eettafel', 'B', xB(2490), zB(5160), 0, { w: 1000, d: 1000 }, { vorm: 'rond', stoelen: 4 });
fx('bank', 'B', xB(5200), zB(4360), 270, { w: 1600, d: 800 }, { chaise: 'geen' });
fx('salontafel', 'B', xB(6740), zB(4200), 0, { w: 800, d: 800 }, { vorm: 'rond' });
fx('fauteuil', 'B', xB(6940), zB(5600), 90);
fx('bed', 'B', xB(10390), zB(4320), 0, { w: 1600, d: 2100 }, { nachtkastjes: false });
fx('kledingkast', 'B', xB(12420), zB(4705), 90, { w: 2900, anchor: 'oost' });
fx('toilet', 'C', xB(9420), zC(7300), 180, { w: 1000 });
fx('wastafel', 'C', xB(10890), zC(7420), 180, { w: 800 });
fx('douche', 'C', xB(12260), zC(6960), 180, { w: 900, d: 1400 }, { glas: 'rechts', glasLengte: 800 });
computeParts(st, { skip3D: true });
return st;
}
// Standaardmodellen: Bestand › Snel nieuw en het venster Nieuw. De sleutel is de template.
const MODELS = {
amstel: { label: 'Woning Amstel', icon: 'house-heart', create: createAmstel },
@@ -667,7 +767,8 @@ window.HouseModel = (() => {
ijssel: { label: 'Woning IJssel', icon: 'houses', create: createIJssel },
waal: { label: 'Woning Waal', icon: 'house-add', create: createWaal },
maas: { label: 'Woning Maas', icon: 'house-check', create: createMaas },
rijn: { label: 'Woning Rijn', icon: 'house-gear', create: createRijn }
rijn: { label: 'Woning Rijn', icon: 'house-gear', create: createRijn },
reest: { label: 'Woning Reest', icon: 'house-up', create: createReest }
};
function createDefaultState(template) {
@@ -1259,10 +1360,10 @@ window.HouseModel = (() => {
});
const ridgePairs = houseShape ? {} : doubleLessenaarPairs(state, rects, cuts);
function buildWall(config, frame, buitenMateriaal, cladding, trasraam, skinExt, facade, skinFrom, spaanplaatBelow, stone, skinAbove, trimTop, battenAbove, stuc, skinTop) {
function buildWall(config, frame, buitenMateriaal, cladding, trasraam, skinExt, facade, skinFrom, spaanplaatBelow, stone, skinAbove, trimTop, battenAbove, stuc, skinTop, fels) {
if (!build) return { group: null, stats: null, specs: null };
const built = WallModel.build3D(config, frame, {
materials: o.wallMaterials, buitenMateriaal, cladding, trasraam, skinExt, facade, skinFrom, spaanplaatBelow, stone, skinAbove, trimTop, battenAbove, stuc, skinTop,
materials: o.wallMaterials, buitenMateriaal, cladding, trasraam, skinExt, facade, skinFrom, spaanplaatBelow, stone, skinAbove, trimTop, battenAbove, stuc, skinTop, fels,
painted: state.settings.interiorPaint && shows('afwerking'), layers, rabatColor: state.settings.facade.rabatColor
});
return { group: built.group, stats: built.stats, specs: WallModel.computeSpecs(config, built.stats, buitenMateriaal) };
@@ -1282,11 +1383,12 @@ window.HouseModel = (() => {
const cladding = m.cladding || 'horizontaal';
const stone = buitenMateriaal === 'cementplaat' ? (m.stone || null) : null;
const stuc = buitenMateriaal === 'cementplaat' && !stone ? (m.stuc || null) : null;
const fels = buitenMateriaal === 'vochtdoek' ? (m.fels || null) : null;
const trasraam = buitenMateriaal === 'vochtdoek' || stone || stuc ? (m.trasraam || null) : null;
return Object.assign(p, {
element: `${u.id}-W${wl.nr}${p.suffix}`,
label: `Unit ${u.id} ${wl.label.toLowerCase()}` + (base ? ` opbouw (vanaf ${base} mm)` : p.suffix ? ` (${p.suffix})` : ''),
buitenMateriaal, cladding, trasraam: base ? null : trasraam, stone, stuc, facadeOwn: own,
buitenMateriaal, cladding, trasraam: base ? null : trasraam, stone, stuc, fels, facadeOwn: own,
place: { cx: rect.cx + c.x, cz: rect.cz + c.z, rotY: wl.rotY, width: p.config.wallWidth, thickness: wl.thickness, normal: wl.normal, floorTop: layout.floorTop + base, baseY: base }
}); // 3D volgt na assignFacades (latwerk/doek per gevel)
});
@@ -1634,10 +1736,11 @@ window.HouseModel = (() => {
});
}));
});
const facadeDepth = (a, b, nrm) => {
// Gevelstuk onder een dakrand (grootste overlap langs de rand); dot < 0 = gevel van een hoger deel ertegenover.
const edgePiece = (a, b, nrm) => {
const horiz = Math.abs(nrm.z) > 0.5;
const line = horiz ? a.z : a.x, s0 = Math.min(horiz ? a.x : a.z, horiz ? b.x : b.z), s1 = Math.max(horiz ? a.x : a.z, horiz ? b.x : b.z);
let best = 0, bestOv = 1;
let best = null, bestOv = 1;
extPieces.forEach(p => {
const pn = { x: p.place.normal.x, z: p.place.normal.y };
const dot = pn.x * nrm.x + pn.z * nrm.z;
@@ -1647,15 +1750,20 @@ window.HouseModel = (() => {
const ov = Math.min(s1, Math.max(horiz ? q0.x : q0.z, horiz ? q1.x : q1.z)) - Math.max(s0, Math.min(horiz ? q0.x : q0.z, horiz ? q1.x : q1.z));
if (ov <= bestOv) return;
bestOv = ov;
best = Math.sign(dot) * WallModel.claddingOffset(p.buitenMateriaal, p.stone, p.stuc);
best = { p, dot };
});
return best;
};
const facadeDepth = (a, b, nrm) => {
const e = edgePiece(a, b, nrm);
return e ? Math.sign(e.dot) * WallModel.claddingOffset(e.p.buitenMateriaal, e.p.stone, e.p.stuc) : 0;
};
const facadeFels = (a, b, nrm) => { const e = edgePiece(a, b, nrm); return !!(e && e.dot > 0 && e.p.fels); };
const roofEdge = units.length ? RoofModel.computeEdge(state.settings.roof,
units.map(u => ({
unitId: u.id, rect: u.roof.rect, fascia: state.units.find(su => su.id === u.id).roof.fascia !== false,
topAt: u.roof.topAt, ridges: u.roof.ridges
})), facadeDepth) : null;
covering: u.roof.covering, topAt: u.roof.topAt, ridges: u.roof.ridges
})), facadeDepth, facadeFels) : null;
const pointInPoly = (q, poly) => {
let inside = false;
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
@@ -1671,13 +1779,13 @@ window.HouseModel = (() => {
const sg = [10, 60, 150].map(d => ({ x: m.x + nrm.x * d, z: m.z + nrm.z * d }))
.map(q => roofEdge.segments.find(e => pointInPoly(q, [e.a, e.b, e.outerB, e.outerA]))).find(Boolean);
if (!sg) return;
const h = sg.fascia ? roofEdge.fasciaHeight : roofEdge.trimHeight + roofEdge.edgeAbove;
p.battenAbove = (p.skinAbove || 0) + roofEdge.edgeAbove - h;
const above = sg.fels ? RoofModel.FELS_EDGE.aboveMM : roofEdge.edgeAbove;
p.battenAbove = (p.skinAbove || 0) + above - RoofModel.edgeHeight(roofEdge, sg);
});
assignFacades(extPieces.concat(interior.filter(p => p.skinFrom !== undefined)));
const rooms = RoomModel.compute(state.rooms || [], units, extPieces.concat(interior));
extPieces.concat(interior).forEach(p => {
const built = buildWall(p.config, p.frame, p.buitenMateriaal, p.cladding, p.trasraam, p.skinExt, p.facade, p.skinFrom, p.spaanplaatBelow, p.stone, p.skinAbove, p.trimTop, p.battenAbove, p.stuc, p.skinTop);
const built = buildWall(p.config, p.frame, p.buitenMateriaal, p.cladding, p.trasraam, p.skinExt, p.facade, p.skinFrom, p.spaanplaatBelow, p.stone, p.skinAbove, p.trimTop, p.battenAbove, p.stuc, p.skinTop, p.fels);
if (built.group && p.shiftZ) {
const outer = new THREE.Group();
built.group.position.z = p.shiftZ * 0.001;
@@ -1703,7 +1811,21 @@ window.HouseModel = (() => {
const elektraGroup = build ? ElektraModel.build3D(elektra, o.elektraMaterials, layers, o.finishedOnly ? false : o.elektraRough) : null;
if (elektraGroup) elektraGroup.visible = shows('elektra');
const posts = PostModel.compute(state.settings.roof.posts, units, groundY);
// Zijwand van een overkapping in het vlak van een gevel: die bekleding loopt erover door.
const postOutside = (w, out) => {
const line = out.x > 0 ? w.x1 : out.x < 0 ? w.x0 : out.z > 0 ? w.z1 : w.z0;
const p = extPieces.find(pc => {
const n = { x: pc.place.normal.x, z: pc.place.normal.y }, q = UnitModel.localToWorld(pc.place, 0, pc.place.thickness / 2);
return n.x * out.x + n.z * out.z > 0.5 && Math.abs((out.x ? q.x : q.z) - line) < 2 && !pc.place.baseY;
});
if (!p || (p.buitenMateriaal === 'cementplaat' && !p.stone && !p.stuc)) return null;
const kind = p.fels ? 'fels' : p.stone ? 'steen' : p.stuc ? 'stuc' : 'rabat';
return {
kind, depth: WallModel.claddingOffset(p.buitenMateriaal, p.stone, p.stuc),
finish: { fels: p.fels, stone: p.stone, stuc: p.stuc, rabatColor: kind === 'rabat' ? state.settings.facade.rabatColor : null, cladding: p.cladding }
};
};
const posts = PostModel.compute(state.settings.roof.posts, units, groundY, postOutside);
const postsGroup = build ? PostModel.build3D(posts, o.postMaterials) : null;
const chimneysGroup = build && chimneys.length ? ChimneyModel.build3D(chimneys, {
materials: o.chimneyMaterials, wallMaterial: o.wallMaterials && o.wallMaterials.spaanplaatMaterial
@@ -1957,6 +2079,10 @@ window.HouseModel = (() => {
{
naam: `Rabat ${WallModel.CLADDING_THICK_MM} mm (gevelbekleding)`, m2: byMat('vochtdoek').reduce((a, p) => a + (p.stats.rabatAreaM2 || 0), 0),
m3: null, kg: 0 // gewicht volgt
},
{
naam: 'Felsplaten gevel (staande naad)', m2: byMat('vochtdoek').reduce((a, p) => a + (p.stats.felsAreaM2 || 0), 0),
m3: null, kg: byMat('vochtdoek').reduce((a, p) => a + (p.stats.felsAreaM2 || 0), 0) * RoofModel.FELS.kgM2
}
],
weights,
@@ -1966,9 +2092,10 @@ window.HouseModel = (() => {
return { unit: u.id, name: rb.profile.name, kgM: rb.profile.kgM, lengthM: rb.length / 1000, kg: rb.steelKg, fillM3: rb.fillM3, fillKg: rb.fillKg,
util: rb.util, defl: rb.defl, deflMax: rb.deflMax, ok: rb.ok, below: rb.below, loadKNm: rb.loadKNm };
}),
posts: parts.posts.count ? {
posts: parts.posts.count || parts.posts.walls.length ? {
count: parts.posts.count, size: parts.posts.size, wood: (PostModel.WOODS[parts.posts.wood] || {}).label || '',
lengthM: parts.posts.lengthMM / 1000, m3: parts.posts.m3, kg: parts.posts.kg
lengthM: parts.posts.lengthMM / 1000, m3: parts.posts.m3, kg: parts.posts.kg,
walls: parts.posts.walls.length, wallM2: parts.posts.wallM2, outsideM2: parts.posts.outsideM2
} : null,
roofEdge: parts.roofEdge ? {
fasciaLengthM: parts.roofEdge.fasciaLengthMM / 1000,
@@ -2037,6 +2164,7 @@ window.HouseModel = (() => {
if (!WallModel.isStoneFinish(state.settings.facade.stone)) state.settings.facade.stone = def.facade.stone;
if (!WallModel.RABAT_COLORS[state.settings.facade.rabatColor]) state.settings.facade.rabatColor = def.facade.rabatColor;
if (!WallModel.isStucColor(state.settings.facade.stucColor)) state.settings.facade.stucColor = def.facade.stucColor;
if (!WallModel.isFelsColor(state.settings.facade.felsColor)) state.settings.facade.felsColor = def.facade.felsColor;
state.settings.trasraam = Object.assign(def.trasraam, state.settings.trasraam || {});
state.settings.trasraam.enabled = state.settings.trasraam.enabled !== false;
if (!(state.settings.trasraam.height > 0)) state.settings.trasraam.height = WallModel.TRASRAAM_DEFAULT_MM;