- LightingModel.computeIndoor: plafondraster per unit (800-1500 h.o.h., 600 van wand/naad) - 3D, plattegrond, render, totalen en PDF - tegelvloer 20% groter; gras volledig verwijderd Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
260 lines
16 KiB
Markdown
260 lines
16 KiB
Markdown
# 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
|
||
|
||
Tectum draait via een (lokale) webserver; direct openen via `file://` wordt
|
||
niet meer ondersteund (texturen/lucht laden dan niet, de app toont een melding).
|
||
|
||
```
|
||
./start.sh # of: python3 -m http.server 8000
|
||
```
|
||
en open `http://localhost:8000`. Elke statische webserver volstaat (geen
|
||
build-stap; gewone `<script src>`-tags, geen ES-modules).
|
||
|
||
## 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.
|
||
- **UI-opschoning (v0.4.1).** Gedeelde UI-kit (js/core/ui-kit.js, window.UI):
|
||
velden, schakelaars, secties, tabbladen, specificatie-kaarten en iconen
|
||
(Bootstrap Icons 1.11.3, MIT, lokaal in css/vendor met ingebed lettertype,
|
||
werkt via file://). Woning-instellingen staan in tabbladen (Algemeen, Gevel,
|
||
Regelwerk, Dakrand, Verlichting); unit-eigenschappen ook (Afmetingen,
|
||
Koppeling, Dak). Labels: zelfstandig naamwoord, geen dubbele punt, uitleg
|
||
in een hint (i-icoon) i.p.v. tussen haakjes. Sneltoets T wisselt in drie
|
||
stappen: standaard -> 3D groot -> alleen de render -> standaard.
|
||
- **Omgeving en render (v0.4.6).** Keuzemenu in elke 3D-werkbalk: Ontwerp
|
||
(geen omgeving) / Dag / Nacht (core/skybox.js, keuze onthouden). Het
|
||
panorama (assets/skybox.webp / skybox-night.webp) wordt op de grond
|
||
geprojecteerd (port van three.js GroundedSkybox); onder het object ligt een
|
||
ronde tegelvloer (assets/tiles.jpg, 20% ruimer dan object + 1,5 m) met een
|
||
witte opstaande rand van 100 x 25 mm. Vloer en rand vangen licht en
|
||
schaduw. Woning: de tegelvloer ligt op het maaiveld (woning-instelling,
|
||
standaard 350 mm onder bovenkant vloer). Knop "Render" (camera): lampen en
|
||
spots als echte lichtbronnen in het gewone daglicht; Nacht: omgevingslicht
|
||
15%, lampen altijd aan. Buitenlampen 15% minder fel dan in v0.4.5.
|
||
- **Binnenspots (v0.4.6).** LightingModel.computeIndoor: per unit een raster
|
||
spots in het plafond (onderkant dak), min. 800 / max. 1500 mm h.o.h. in
|
||
beide richtingen, min. 600 mm van de binnenzijde van elke wand (of van de
|
||
unitrand bij een naad); spots te dicht bij een binnenwand vervallen.
|
||
Woning-instellingen › Verlichting › Binnenspots. In 3D (filter Lampen),
|
||
plattegrond (open cirkeltje), render en totalen.
|
||
- **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.
|