A local-first IES lighting simulator for the browser: import real photometry, model a room, aim your
luminaires, or just describe the space — and read the work plane as a heatmap with engineering metrics.
Direct light plus up to four diffuse bounces, calculated on your machine.
Free account · No project uploads · Installable offline PWA
LM-63 · Type C
TILT=NONE photometry
Direct + up to 4
Diffuse bounces
Local-first
IndexedDB · offline PWA
Plate 01 / PlanAtelier · 6.00 × 4.00 × 3.00 m · as new
Average
467 lx
Uniformity
23%
Target
79% ≥ 300 lx
Fig. 01 — Demo atelier: six 2,400 lm downlights, a 64 × 43 grid and 450 surface patches.
Mean 467 lx as new. At the default 0.80 maintenance factor the same
room reads 373 lx maintained.
01What it does
A lighting study, not a render.
Photometry, geometry and result stay visible and editable. No hidden defaults, no black-box solver.
01 / Input
Import real photometry
Drop in an LM-63 Type C file. The parser reads the angle tables, symmetry and candela, draws a polar
distribution, and surfaces the lumens, watts and maximum candela behind it.
LM-63 · Type C · TILT=NONE
02 / Model
Build the room, aim the light
Set a rectangular room, work-plane height and surface reflectances. Place luminaires by hand, or stamp a
block of them with grid placement, then tune mounting height, aim, tilt and dimming in the inspector.
X/Y · Mount · Aim · Tilt · Dim
03 / Result
Read the work plane
Switch between total, direct and reflected illuminance. Track average, minimum, maximum and uniformity,
check a target-lux threshold, and hover the grid for a point reading. Up to three labelled isolux
contours trace the levels that matter, and a maintenance factor turns as-new figures into the
maintained values you specify against.
Average · Min · Max · U0 · Target
04 / Ask
Describe it, then calculate it
Type the room and the light you want — "20 × 15 m office, 500 lx" — and LuxLab builds the
scene, sizes the layout by simulation and reports the result. The provider only reads the
brief; geometry, photometry and every lux value stay in the engine. Administrators choose
DeepSeek or OpenCode Go and manage the encrypted key in Settings; each person can still
hide the bar in their own browser.
A short loop: edit, debounce, calculate. The worker keeps the interface responsive while the grid runs.
01Parse
Import and validate
Open the IES library and load a Type C file. LuxLab checks the header and angle tables, expands symmetry,
and builds a bilinear candela sampler.
Rejects TILT=INCLUDE · Type A/B
02Model
Place and aim
Set the room and work plane, then place luminaires in plan. Use the inspector for mounting height, aim,
tilt and dimming. The 3D view is read-only.
Walls block light · No shadows yet
03Read
Calculate and inspect
Recompute and read the false-colour map. Compare total, direct and reflected views, then check the
metrics and the target threshold.
Direct + up to four bounces
03Local-first
The calculation never leaves the tab.
No project uploads and no server-side render. An account gates the tool; the geometry, photometry
and results stay in the tab. If you use Ask LuxLab, the sentence and a list of fixture names go to
the configured AI provider; turning the assistant off in Settings sends nothing. Projects and IES source text live in
IndexedDB, and the service worker keeps the tool available offline. Close the lid and the room is
still there.
Private by default
No project uploads and no tracking cookies. Your project stays browser state, not a
server record; page views are counted anonymously. The assistant is the one network
feature, and it is opt-out.
Instant feedback
The engine runs in a Web Worker. Edits debounce for 360 ms; only the latest calculation survives.
Install it and unplug
An offline PWA. The app shell, fonts and the lazy 3D preview are precached after one visit.
Portable projects
Exports are self-contained .luxlab.json files with every referenced IES profile embedded.
IES→Parser→Worker→Canvas→IndexedDB
Plate 02 / 3D previewProjects stay in the browser
6 luminaires450 patches467 lx mean as new
Fig. 02 — Work-plane illuminance in the read-only 3D preview. Same 450 patches; rendered locally.
04The plan
Credible studies, at browser speed.
v0.12.0 is tagged and in use. Describe a space in the Ask bar and LuxLab builds, solves
and calculates it; administrators choose DeepSeek or OpenCode Go and manage the key from the
admin settings. The scene and results panels now resize, and the widths are remembered in
the browser. Geometry is here too: rooms are polygons on a shared floor, walls block light,
and a stepped outline is something you draw. Shadows and reporting are next, in the order
the method can support honestly.
The goal
Make a credible IES illumination study as quick as opening a tab: fast enough to iterate, private enough
for client work, and accessible enough that photometry isn't locked inside desktop software.
Shipped now
Shipped
One or more rooms on a shared floor plan, each with its own ceiling
Polygon room outlines, with walls that block light
Stepped and L-shaped rooms you can draw and reshape
Two workspaces: warm paper, or the OrbitX dark palette
A floor summary, area-weighted across every room
Type C, TILT=NONE IES import with polar preview
OrbitX catalogue: 97 IES profiles across 20 families
Direct illuminance plus up to four diffuse bounces
Multi-select on the plan, to move or delete a group at once
2D plan editor and read-only 3D preview
Average, min, max, U0 and target compliance
Up to three labelled isolux contours
Maintenance factor: fixed, or the CIE 97 four-factor method
Grid placement for blocks of luminaires
Accounts with roles, an admin dashboard and first-run password change
IndexedDB projects with self-contained JSON export
Installable, fully offline PWA
Next
Unscheduled
→Freeform outlines at any angle, and rooms that share a doorway
→Openings, furniture and shadow rays
→CSV and PDF report export
→Glare metrics, including UGR
→Type A/B photometry and TILT data
→Multi-bounce or path-traced verification
No dates. Scope follows what the method can support honestly.
05Method
A short, inspectable pipeline.
Parse, interpolate, integrate, display. Every result is reproducible from the inputs, in the browser.
01LM-63
02Bilinear
03Worker
04Typed arrays
05Canvas
06IndexedDB
01
LM-63 parser
Type C, TILT=NONE, header variants and symmetry expansion. Unsupported photometry is rejected, not approximated.
02
Bilinear candela
Vertical and horizontal interpolation with 360° wrap-around. Dimming and the maintenance factor scale candela linearly, per luminaire.
03
Web Worker
The pure engine runs off the main thread. Edits debounce for 360 ms and the latest request wins.
04
Deterministic heatmaps
The inferno scale is driven by the data's min and max. No random dithering, no per-frame noise.
05
Local IndexedDB
Projects and IES source text persist in the browser. Exports embed every referenced profile.
06
Offline PWA
The app shell, fonts and lazy 3D chunk are precached after the first visit. No CDN in the path.
Calculation
DirectE = I(γ, C) · cos ε / r²Up to four bouncesBᵢ¹ = ρᵢ · EᵢBᵢk+1 = ρᵢ · Σⱼ Bⱼk Aⱼ cos θⱼ cos θᵢ / (π rᵢⱼ²)Eref(p) = Σₖ Σᵢ Bᵢk Aᵢ cos θᵢ cos θₚ / (π rᵢ²)MaintainedEm = MF · (Ed + Eref), MF = LLMF · LSF · LMF · RSMF
Bilinear candela in γ and C. One bounce by default and up to four; still an estimate, not a full
radiosity solve.
Fixture check
750 lm declared→748 lm integrated
A public Cooper Lighting MRZ Type C profile was integrated at 1°. The check exercises angle handling and
flux integration — it is not a certification.
Third-party fixture · Not a compliance claim
06Changelog
What shipped, in order.
Generated from the project changelog on every deploy, so the page cannot fall behind the release.
v0.12.0
Added
Resizable workspace panels. Drag the divider beside the scene panel or the results panel to give the plan and 3D view more room, or widen the calculation panel. The centre viewport re-fits live; the plan, the heatmap and the 3D canvas stay sharp at any width.
Keyboard resizing and reset. Focus a divider and use the arrow keys (Shift for larger steps, Home/End for the limits); double-click it or use Settings → Layout → Reset panel widths to restore the defaults. Each divider reports its position to assistive technology.
Per-browser layout memory. Panel widths are stored in this browser, survive reloads and are clamped on smaller windows. The preferred width returns when the window grows again.
Changed
The fixed scene/results panel borders are replaced by slim draggable dividers with the same hairline look. The scene panel defaults to 280 px at every breakpoint and the results panel to 320 px; both are adjustable.
Arrow keys on a focused divider stop at the divider, so they no longer nudge a selected luminaire.
v0.11.0
Added
Admin-managed AI provider settings. Administrators can choose the assistant's provider from either Settings surface — the admin dashboard's Settings tab or the in-app Settings modal (shown only to admins) — between DeepSeek and OpenCode Go. They can set the model, enter or replace the API key, test the connection and revert to the server environment. The choice is stored on the server and applies to every account; there is no per-user provider, model or key configuration.
Encrypted key storage. Provider keys entered in the dashboard are encrypted with AES-256-GCM using a key derived from LUXLAB_APP_SECRET and stored in the SQLite settings table. The API never returns a key, only a masked last-four hint, and save/clear actions are written to the audit log.
Environment fallback and safe failure. With no saved override the existing environment configuration keeps working exactly as before. A saved override wins; a key that can no longer be decrypted or a corrupt settings row fails closed instead of silently switching provider. Reverting deletes the override and restores the environment.
Changed
The assistant client is built per request from the resolved provider settings rather than at service start, so an administrator's change applies immediately.
The in-app Assistant toggle is now clearly a per-browser privacy choice ("Ask LuxLab in this browser"); the provider, model and key are administrator-owned.
Notes
Rotating LUXLAB_APP_SECRET invalidates saved provider keys; the admin panel says so and asks for the key again. Backups of the database now contain a ciphertext that needs the same secret.
The current live deployment keeps its environment OpenCode Go key until an administrator saves an override; no migration is required.
v0.10.1
Added
OpenCode Go as the assistant provider. The bar now works with any OpenAI-compatible chat-completions gateway. Point LUXLAB_AI_BASE_URL at OpenCode Go's endpoint and use deepseek-v4.1-flash with your Go API key; the service identifies itself, sends a stable x-opencode-session header, and keeps JSON mode and the low-latency thinking toggle.
A shared daily budget.LUXLAB_AI_GLOBAL_DAILY_LIMIT (80 by default) caps the assistant across every account, not just per account. With open sign-up, that is what stops a personal provider subscription being drained by new accounts.
Changed
Provider-neutral environment names: LUXLAB_AI_API_KEY and LUXLAB_AI_BASE_URL now sit beside the original DEEPSEEK_* names, which remain supported aliases. The status endpoint and the transcript name the configured provider instead of always saying DeepSeek.
Notes
OpenCode Go is sold for coding-agent traffic and monitors abuse. A public lighting app with open sign-up is not its intended workload: use a dedicated key or OpenCode Zen for a public deployment, consider approval-only sign-up, and keep the shared daily cap.
v0.10.0
Added
Ask LuxLab: a sentence becomes a calculated study. A prompt bar sits under the header on the project list and in the workspace. Describe the space and the light — "20 × 15 m open-plan office, 500 lx" — and LuxLab builds the project, resolves a supported IES profile, places the luminaires, runs the engine and reports the result with every assumption. DeepSeek reads the brief; geometry, photometry and every lux value are calculated locally.
Fixture choice that names its substitutes. The brief is matched against the 95 supported OrbitX profiles and your own library. An exact catalogue or library ref is honoured; anything else resolves to a real profile, and the transcript names the one it used and why.
Counts solved by simulation. Say "500 lx" without a count: LuxLab estimates with the lumen method, then simulates a ladder of layouts to find the smallest count that meets the target, with a 3 % design margin. Say "12 downlights" and it places exactly twelve.
Follow-ups and undo. A second sentence refines the AI-created project in place; changing the room renames it, and one click restores the previous version. The transcript keeps the brief, assumptions, compliance and metrics for every turn.
An opt-out and a visible disclosure. The assistant is on by default and can be turned off in Settings. While it is on, the prompt and a list of fixture names go to DeepSeek; projects, photometry and results stay in the browser. The key lives only in the accounts service.
Changed
The accounts service gains /api/ai/interpret and /api/ai/status: session and CSRF protected, rate limited per account (six prompts per ten minutes, 40 a day by default) and capped at three concurrent calls by default.
The landing page's privacy copy now states plainly what the assistant sends and what it never sends.
v0.9.0
Added
Up to four diffuse bounces. The engine could solve one bounce of interreflection; it now iterates, so light that bounces from a wall onto the ceiling and back down is part of the answer rather than being dropped after the first hop. One to four, chosen per project, with the default left at one so every saved design keeps the numbers it was calculated with.
Select several luminaires and delete them together. Shift- or Cmd-click in the plan or the list builds a selection; the inspector still edits one fixture, and Delete (or the "Delete N" button) removes the whole set in a single update — one autosave, one recalculation. Select all scopes to the active room.
A 100 % maintenance preset. As-new is a legitimate comparison, so it is a button beside 70, 80 and 90 rather than only the far end of the slider.
Changed
Bounce count shares the patch budget: four bounces use about a quarter of the patches of one, and the solve stops early when a layer adds less than a thousandth of the previous one.
The results panel footnote states the number of bounces actually solved, and the landing's method card now shows the iterative form rather than a single pass.
07Ready when you are
Start with a room. Leave with a number.
One click from the project list: 6 × 4 × 3 m, six 2,400 lm downlights, 373 lx maintained at the default
0.80 factor. A free account keeps it yours; nothing you draw is uploaded.