f163f4deb13353f15449a70b2f21798008cf4fb8
- bedekte gevelstukken (wandstuk in de naad, binnenhoek L) afgetrokken via voetafdruk - uiteinden verlengd met afwerking (+ skinExt bij tussenpassende wanden) - groupFacadeRuns accepteert overlap Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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. 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 viafile://door browser-CORS-beleid. Dit was expliciet een vereiste ("gewoon via een index file"), dus is hier bewust voor gekozen. json/defaults/*.jsonworden niet gefetcht. Om dezelfde reden (fetch() van lokale bestanden werkt ook niet betrouwbaar viafile://): de standaardwaarden van "Nieuw" staan hardcoded inwall.js/floor.jszelf, 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 instate.projecten gelden voor alle bladen. - Standaarden (WallModel). Muurbalk 38 x 235 mm (stijlen én regels),
ingekocht in 3600/4200/4800/5400 mm; een zaagplan (
planStockCuttingin 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.
- Startpunt gevellampen (v0.4.8). Een gevel telt vanaf de zichtbare, afgewerkte hoek: elk uiteinde + afwerking van de haakse wand (52 mm bij vochtdoek), plus de bedekte kopse kant (skinExt) bij tussenpassende wanden. Delen van een gevel achter een andere wand (bijv. een wandstuk dat de naad in loopt om een binnenhoek te sluiten, zoals A-W2 bij een L) worden afgetrokken via de voetafdruk van die wand; de gevel begint dan bij diens afgewerkte buitenkant.
- Lichtbronnen (v0.4.7). Geen echte schaduw per lamp (één schaduwkaart per lamp overschrijdt de browserlimiet); licht door muren wordt geometrisch voorkomen: buitenlampen en boeideel-spots zijn half hun bundelhoek naar buiten gekanteld (boeideel: normaal van de dakrand, op hoeken diagonaal), binnenspots reiken hard tot net voorbij de vloer. Buitenlampen overdag op 35% (LightingModel.DAY_OUTDOOR_FACTOR), 's nachts 100%; binnenspots feller.
- 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) inapp.js. Conventie: laatste cijfer per kleine iteratie, middelste per mijlpaal (aangegeven in chat), eerste bij live-gang.
Languages
JavaScript
94%
CSS
5.1%
HTML
0.9%