Tectum v0.4.0 — mijlpaal gevelopbouw

Configurator voor Van Emous Mantelzorgwoningen: wanden, vloeren, units,
woningen (L-vorm), plat dak met dakrand, buitenverlichting, latwerk op
vochtdoek, trasraam, sandwichpanelen en PDF-tekenbladen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Machiel
2026-09-30 11:38:54 +02:00
co-authored by Claude Opus 5.5
commit c690e72d13
36 changed files with 9954 additions and 0 deletions
+237
View File
@@ -0,0 +1,237 @@
# Tectum — configurator Van Emous Mantelzorgwoningen (intern)
Losse, meerdere-bestanden-opzet van de eerdere single-file wand-tool, nu met
een menubalk om tussen objecttypen (muur/vloer) te schakelen.
Copyright © 2026 Machiel Heinen. Alle rechten
voorbehouden; gebruik uitsluitend met schriftelijke toestemming. Zie
[LICENTIE.md](LICENTIE.md). Elk bronbestand begint met deze copyright-kop.
## Draaien
**Optie 1 — direct openen (geen server nodig):**
Dubbelklik `index.html`. Alles werkt met gewone `<script src="...">`-tags
(geen ES-modules), dus dit werkt ook via `file://`.
**Optie 2 — lokale server (optioneel):**
```
cd project
python3 -m http.server 8000
```
en open `http://localhost:8000`.
## Structuur
```
index.html app-shell: menubalk, splashscreen, over-deze-software
css/
app.css alle stijl, in één bestand
js/
vendor/
three.min.js three.js r128, lokaal gevendord (geen CDN meer)
OrbitControls.js eigen, compacte OrbitControls-vervanging (zie
"Bewuste keuzes" hieronder) — leunt op three.min.js
core/
geometry.js gedeelde, pure functies (regelwerk-verdeling,
polygon-oppervlaktes, clip-functies) — gebruikt
door zowel wall.js als floor.js, niet gedupliceerd
canvas-labels.js gedeelde stijl voor maatlabels ("X: 6000mm") in de
2D-canvassen van wand, vloer en unit
pdf-sheet.js gedeelde PDF-bouwstenen: titelblok, maatlijn, tabel
camera-memory.js onthoudt de 3D-camerastand per tabblad + onderdeel
view-filter.js "Weergave"-dropdown met vinkjes voor de 3D-weergaven
license.js licentietekst voor "Over deze software" (kopie van
LICENTIE.md, dat bestand is leidend)
logo-data.js logo als PNG-data voor de PDF-titelblokken
(opnieuw genereren uit assets/logo.svg, zie kop)
wall/
wall-model.js WallModel: ENIGE bron van de wand-logica (anker-
resolutie openingen, regelwerk, 3D-opbouw, specs,
validatie) — geen DOM
wall-pdf.js WallPdf: het wand-tekenblad (aanzicht, boven- en
zijaanzicht, materiaallijst) — losse wand én unit
wall.js wand-editor (UI: invoer, 2D, interactie, JSON/PDF)
floor/
floor-model.js FloorModel: ENIGE bron van de vloer-logica (balken,
doorvoeren, chassis, 3D-opbouw, specs, validatie)
floor-pdf.js FloorPdf: het vloer-tekenblad — losse vloer én unit
floor.js vloer-editor (UI, uitsluitend cirkelvormige
openingen/doorvoeren)
roof/
roof-model.js RoofModel: plat dak = vloer (FloorModel) 180°
gespiegeld + dakleer; dakrand (boeidelen met spots
of daktrim) als één doorlopende rand om de woning
lighting/
lighting-model.js LightingModel: buitenverlichting per unit (geen /
boeideel-spots / gevellampen), gevellampen per
wandstuk met terugval naar spots, override per wand
unit/
unit-model.js UnitModel: 1 vloer + 4 wanden, hoekaansluiting,
naden (wand vervalt deels/geheel) en wandstukken
house/
house-model.js HouseModel: woning = gekoppelde units + binnenwanden;
posities (koppelen aan zijde + uitlijning), naden,
totalen
house-pdf.js HousePdf: samenhangende PDF (plattegrond, per unit
vloer + wandstukken, binnenwanden, totalen)
house.js woning-editor (UI): navigatieboom, plattegrond met
selecteren/binnenwand tekenen, eigenschappen, 3D;
mount de echte wand-/vloer-editor per onderdeel
app.js menubalk-logica, versienummer, mount/unmount van
de actieve editor
json/
defaults/ referentie-JSON's die tonen wat "Nieuw" oplevert
(worden NIET ingeladen via fetch(), zie hieronder)
premade/ bestemmingsmap voor opgeslagen/geëxporteerde designs
assets/ logo (preloader) + bestemmingsmap voor toekomstige
GLB's/texturen
```
## Bewuste keuzes / bekende beperkingen
- **Volledig CDN-vrij.** three.js (r128) staat lokaal in `js/vendor/three.min.js`.
OrbitControls is een eigen, compacte implementatie (`js/vendor/OrbitControls.js`)
— het originele addon-bestand was niet beschikbaar om te vendoren, dus is de
gebruikte subset (roteren via slepen, zoomen via scrollwiel, pannen via
rechts-slepen, `target`/`update()`) opnieuw geschreven en losstaand getest
(rotatie/zoom-richting/grenzen/pannen/dispose). Functioneel gelijkwaardig
voor wat deze tool nodig heeft, maar geen 1-op-1 kopie van alle opties van
de officiële addon (bijv. geen damping/inertia).
- **Geen ES-modules.** Alle JS-bestanden zijn gewone `<script>`-tags, geladen
in een vaste volgorde (`geometry.js` → `canvas-labels.js` → jsPDF →
`pdf-sheet.js` → `wall-model.js` → `wall-pdf.js` → `floor-model.js` →
`floor-pdf.js` → `roof-model.js` → `lighting-model.js` → `wall.js` → `floor.js` → `unit-model.js` → `house-model.js`
→ `house-pdf.js` → `house.js` → `app.js`).
Reden: ES-module-imports (`import ... from`) breken bij rechtstreeks openen
via `file://` door browser-CORS-beleid. Dit was expliciet een vereiste
("gewoon via een index file"), dus is hier bewust voor gekozen.
- **`json/defaults/*.json` worden niet gefetcht.** Om dezelfde reden (fetch()
van lokale bestanden werkt ook niet betrouwbaar via `file://`): de
standaardwaarden van "Nieuw" staan hardcoded in `wall.js`/`floor.js` zelf,
en komen overeen met deze JSON-bestanden. De JSON's zijn dus referentie/
documentatie, en (voor wanden) een bestand dat je via "Importeer JSON" kan
inladen — geen runtime-configuratiebron.
- **Alleen wall.js heeft Exporteer/Importeer JSON.** floor.js nog niet.
- **Floor.js heeft nu dezelfde soorten logica als wall.js.** 2D-bovenaanzicht
+ doorsnede, regelwerk (vloerbalken), cirkelvormige doorvoeren, een
onderlaag van OSB-plaat (standaard 22mm, dikte + oppervlak + volume +
gewicht), een isolatielaag (leegte tussen de balken, analytisch bepaald —
net als bij de wand krijgt isolatie zelf bewust geen dichtheid/gewicht),
een volwaardig 3D-perspectief (balken/isolatie/OSB met doorvoer-gaten/
chassis-wielen, met aan/uit-toggles voor OSB en isolatie, plus fullscreen-
2D/3D) en een specificaties-paneel met een gewichtstotaal. Losse,
optionele chassis-laag (4 transportwielen in een "dobbelsteen 4"-indeling,
exact gecentreerd op de vloer — spoorbreedte/wielbasis/wieldiameter
instelbaar, geen interactie met de balkposities).
- **Single point of truth wand/vloer/unit.** Alle reken- en 3D-logica zit in
`WallModel`/`FloorModel`. De losse editors én de unit roepen die aan, dus
een wijziging in de wand- of vloerlogica geldt automatisch overal. De unit
bewaart per onderdeel een gewone wand-/vloer-state; wandbreedtes worden
afgeleid uit vloermaat + wanddiktes + hoekaansluiting, openingen houden
hun anker (links/midden/rechts). "Opslaan als" bij een unit geeft JSON +
één samenhangende PDF: bovenaanzicht unit (U-1), vloer (V-1), elke wand
(W-1…W-4, via WallPdf/FloorPdf) en een totalenblad (balken, zaaglijst,
glaswol, plaatwerk, gewichten). Projectgegevens van de unit staan in
`state.project` en gelden voor alle bladen.
- **Standaarden (WallModel).** Muurbalk 38 x 235 mm (stijlen én regels),
ingekocht in 3600/4200/4800/5400 mm; een zaagplan (`planStockCutting` in
geometry.js) kiest per staaf de lengte met het minste snijverlies — bij een
unit over alle wanden samen. Stukken langer dan 5400 worden opgeknipt.
Elke wandkop heeft een dubbele hoekstijl (2 x 38 mm). De app start met een
unit.
- **Woning (v0.2).** Bestand → Nieuw → Woning (1 unit) of Woning L-vorm. Een
unit koppel je aan een zijde van een andere unit (uitlijning start/midden/
eind + verschuiving). Op de naad vervalt de wand van beide units over het
gedeelde stuk; een wand kan daardoor in meerdere stukken uiteenvallen
(bijv. A-W2a/b), haakse wanden lopen dan door tot de vloerrand. Binnen-
wanden teken je in de plattegrond (standaard 100 mm, spaanplaat beide
zijden) en bewerk je met de gewone wand-editor. Een wandstuk dat aan een
naad eindigt loopt de naad in door met de dikte van de haakse buurwand,
zodat binnenhoeken (L-vorm) dicht zijn. Zijden heten naar windrichting
t.o.v. het bovenaanzicht (boven = noord): voor = noord, achter = zuid,
links = west, rechts = oost (intern blijven de oude sleutels). Oude unit-JSON's kunnen in
een woning worden geïmporteerd. Units kunnen (nog) niet gedraaid worden.
- **Mijlpaal v0.4 — gevelopbouw.** Samenvatting van de v0.3.11–0.3.19-iteraties:
kruislat-latwerk op vochtdoek (richting gevelbekleding globaal), lampen op de
afgewerkte gevel (+52 mm), logo-watermerk op het doek, trasraam (cement-
gebonden plaat, 350 mm vanaf het loopvlak), sandwichpanelen rondom (600 mm de
grond in), hoekdoorloop van de buitenlagen over de hoekstijlen, en latwerk/
doek per doorlopende gevel over unit-naden heen. Details per onderdeel hieronder.
- **Plat dak (v0.3).** Elke unit heeft een dak (tweede laag) dat standaard
de vloermaat volgt, maar een eigen maat/verschuiving mag krijgen. Opbouw =
vloer 180° gespiegeld (OSB als plafond onder, balken, dakleer bovenop); het
dak wordt bewerkt met de vloer-editor in dakmodus. De dakrand is woning-
breed: boeidelen (overstek 200–400 mm vanaf de dakrand = standaard de
gevel, 300 mm hoog, spots op 40% van de overstek, op elke (unit-)hoek en
daartussen verdeeld met min/max h.o.h.) of een daktrim van 80 mm. De rand
loopt als één geheel over de naden tussen units. Het dak ligt op de hoogste
wandbovenkant van de unit; bij units met verschillende hoogtes ligt de
dakrand op het hoogste dak.
- **Dakrand & buitenverlichting (v0.3.3).** Boeidelen aan/uit per unit
(anders daktrim); de rand wordt per ribbe opgebouwd (in verstek, geen
zichtbare naad). Verlichting is woning-breed: type (geen / boeideel-spots /
gevellampen), lichtrichting en alle afstanden. Gevellampen worden per
GEVEL verdeeld (aansluitende wandstukken in hetzelfde vlak, ook over een
naad tussen units), niet per wand: op lamphoogte (standaard 1950) alleen op
vrije stukken >= 2000 mm (openingen incl. raamwerk eraf), eerste/laatste
300 mm van hoek/opening, daartussen min/max h.o.h.; smallere stukken krijgen
geen lampen. Gevellampen en boeideel-spots sluiten elkaar uit. De min.
h.o.h. geldt over alle lampen van een gevel samen (ook bij afstand hoek/
opening en overrides): te dicht bij elkaar = samengevoegd tot één lamp in
het midden. Per wandzijde blijft een override: automatisch / 500 mm
naast elke opening / geen. Standaard unithoogte 2600 mm.
- **3D-weergave (v0.3.5).** Filters als dropdown met vinkjes. Woning:
Beplating (wandplaten, vloer- en plafondplaten, dakleer, boeideel/daktrim),
Isolatie, Dak, Lampen. "Volle render": avondscène met maaiveld, elke
gevellamp (per lichtrichting) en boeideel-spot is een echte SpotLight
(zonder schaduwen, om het licht te houden); de nep-lichtkegels zijn dan
verborgen.
- **Latwerk gevelbekleding (v0.3.11).** Bij vochtdoek komt er een kruislat
op (WallModel.computeBattens): latten 20x30 mm, handelslengte 4000, 400 mm
h.o.h. in beide richtingen, uitgespaard rond openingen en afgekapt op de
(schuine) bovenrand. De bovenste laag staat haaks op de gevelbekleding
(WEO/Novalit, 12 mm; richting per losse wand, in een woning voorlopig
globaal als woning-instelling). Gevellampen hangen op de afgewerkte gevel:
wandvlak + kruislat (40) + bekleding (12) = 52 mm naar buiten. Cement-gebonden
platen (steenstrips) krijgen geen latwerk. Latten tellen mee in gewicht,
specificaties, wand-PDF en woningtotalen; zichtbaar met "Beplating".
- **Vochtdoek-bedrukking (v0.3.13).** Net als op de echte doeken staat het logo
als medium-grijs watermerk (geen kleur) onder 45° op het vochtdoek, 1,5 m
breed, herhaald per tegel van 2 m (WallModel.createMaterials, CanvasTexture).
- **Trasraam (v0.3.14).** Woning-instelling (aan/uit + hoogte, standaard 350 mm
vanaf Y=0 van de vloer = het beloopbare vlak / bovenkant vloer). Onderaan
elke vochtdoek-gevel komt een strook cement-gebonden plaat (montage-
achterkant), vóór het doek; het doek loopt er ongewijzigd achter door en
het latwerk begint erboven. Zelfde plaat- en uitsparingslogica als de
buitenplaten, dus openingen blijven gerespecteerd (WallModel.trasraamFor,
build3D opts.trasraam, computeBattens). Telt mee in gewicht, wand-PDF en de
woningtotalen ("Cementplaat (trasraam)").
- **Hoekdoorloop buitenlagen (v0.3.18).** Een wand die tussen twee doorlopende
wanden past, bedekt met zijn buitenlagen ook de kopse kant (hoekstijlen) van
die wanden: UnitModel geeft per wand(stuk) skinExt { left, right } (= dikte
van de doorlopende wand, 0 bij naden/vrije einden), WallModel.cornerExt telt
per laag de eigen diepte erbij. Latwerk en trasraam lopen door; vochtdoek
en cementplaat (liggen in de beplatingsruimte) krijgen een strook net vóór
het wandvlak.
- **Latwerk en doek per gevel (v0.3.19).** HouseModel.assignFacades groepeert
collineaire, aansluitende buitenwandstukken over alle units tot gevels
(groupFacadeRuns in core/geometry.js, ook gebruikt door de buitenverlichting)
en geeft elk stuk p.facade { u0, extStart, isEnd }. Het verticale
latten-raster (400 h.o.h.) en het watermerk op het doek lopen daardoor over
unit-naden door; een eindlat komt alleen aan het einde van de gevel.
- **Sandwichpanelen (v0.3.16).** Woning-instelling aan/uit. SandwichModel
(js/sandwich/sandwich-model.js) legt één doorlopende rand (in verstek) om de
vereniging van alle unit-vloeren: 40 mm dik, van het loopvlak (bovenkant
vloer) 600 mm naar beneden, de grond in. Plaat 495 x 1000 x 40 (9 kg),
verticaal: één plaat per 495 mm gevel, 1000 afgezaagd op 600. Naden per
495 mm; zichtbaar met "Beplating"; m2, aantal platen en gewicht (hele
platen) in de woningtotalen.
- **Openingtypes.** Raam (500x1900), deur (900x2150) en voordeur (1150x2150);
type kiezen zet de standaardmaat, daarna vrij aan te passen. De app start
met een L-woning met een voordeur (noord, unit A) en 3 ramen (zuid, unit B).
- **Woning-instellingen.** Regelwerk-h.o.h. (min/max) geldt globaal voor alle
buitenwanden resp. alle binnenwanden; in de wand-editor zijn die velden
binnen een woning vergrendeld.
- **Versienummer** staat als enkele constante (`APP_VERSION`) in `app.js`.
Conventie: laatste cijfer per kleine iteratie, middelste per mijlpaal
(aangegeven in chat), eerste bij live-gang.