Files
Tectum/README.md
T
MachielandClaude Opus 5.5 7ae76fe390 v0.4.5: tegelvloer met witte rand i.p.v. gras, Lucht geen/dag/nacht, Render met daglicht + lampen
- core/skybox.js: tegelvloer (tiles.jpg) passend om het object, rand 100x25 mm wit
- nacht-panorama; nacht = 15% omgevingslicht + lampen 100%
- Render-knop met camera-icoon: echte lampen in het gekozen licht
- gras verwijderd (bestanden lokaal bewaard, uit git)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:34:19 +02:00

16 KiB
Raw Blame History

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 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.5). Knop "Lucht" in elke 3D-werkbalk wisselt geen -> 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), groot genoeg voor het hele object, 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 (100%).
  • 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.