01 Local-first IES simulation

Light, before
the room exists.

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.

No account for the finder Free account for the full tool No project uploads

Read the five-minute guide

LM-63 · Type C
TILT=NONE photometry
Direct + up to 4
Diffuse bounces
Local-first
IndexedDB · local projects
Plate 01 / Plan Atelier · 6.00 × 4.00 × 3.00 m · as new
LuxLab plan view: a 6 by 4 metre room with a false-colour work-plane heatmap, six downlights and a 105 to 741 lux legend.
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.

01 What 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. With a project open, ask why the result looks the way it does or request a small change; LuxLab applies it, recalculates and shows before and after. The provider reads the brief or project summary; 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. Select several lights to move them together, and export the whole study as a PDF plan, 3D view and results report.

Prompt DeepSeek parse Local solve
Try the Ask bar

02 How it works

From .ies to lux in three moves.

A short loop: edit, debounce, calculate. The worker keeps the interface responsive while the grid runs.

01 Parse

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

02 Model

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

03 Read

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

03 Local-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 installable PWA. The app shell and fonts are precached after one visit; a cold start still checks your session online.

Portable projects

Exports are self-contained .luxlab.json files with every referenced IES profile embedded.

IES Parser Worker Canvas IndexedDB
Plate 02 / 3D preview Projects stay in the browser
LuxLab 3D preview: a transparent room shell containing six luminaires above a false-colour work-plane heatmap.
6 luminaires 450 patches 467 lx mean as new
Fig. 02 — Work-plane illuminance in the read-only 3D preview. Same 450 patches; rendered locally.

04 The plan

Credible studies, at browser speed.

v0.19.0 is tagged and in use. Undo and Redo now cover the whole project: a light move, a delete, a setting change and an assistant edit are each one step, and a drag undoes as a whole. The public handbook lives at /wiki/, and the workspace has a Help menu, a shortcut sheet and a status bar that names the current tool and calculation state. Deleting a room asks first and tells you how many lights go with it. Draw a room with a rectangle drag or an orthogonal outline, edit corners and walls with snapping, type exact wall lengths and move a room with its lights. The Projects lobby remains an OrbitX portfolio. Press Select in the luminaire list to choose lights explicitly, then move or delete them together. Export a full A4 PDF with a 2D plan of every room, a 3D view, all results and the schedules. Describe a space in the Ask bar and LuxLab builds, solves and calculates it; with a project open, ask why the result looks the way it does or request a small edit, with Undo behind it.

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 PWA with local projects

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.

05 Method

A short, inspectable pipeline.

Parse, interpolate, integrate, display. Every result is reproducible from the inputs, in the browser.

  1. 01LM-63
  2. 02Bilinear
  3. 03Worker
  4. 04Typed arrays
  5. 05Canvas
  6. 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

Direct E = I(γ, C) · cos ε / r² Up to four bounces Bᵢ¹ = ρᵢ · Eᵢ Bᵢk+1 = ρᵢ · Σⱼ Bⱼk Aⱼ cos θⱼ cos θᵢ / (π rᵢⱼ²) Eref(p) = Σₖ Σᵢ Bᵢk Aᵢ cos θᵢ cos θₚ / (π rᵢ²) Maintained Em = 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

06 Changelog

What shipped, in order.

Generated from the project changelog on every deploy, so the page cannot fall behind the release.

v0.19.0

Added

  • CIE flux code, light output ratio and luminaire class in the IES library. A selected profile now shows the five flux-code figures, the downward light output ratio and its Direct/SemiDirect/GeneralDiffuse/SemiIndirect/Indirect class, using the analytic band integration from the lighting specification and checked against the Lambertian test vector.
  • U1 min/max uniformity. The result, right panel, PDF report tables and floor summary now carry both U0 = min/average and U1 = min/max, so an even room and a room with one bright spot can be told apart.
  • Correct relative LM-63 photometry. A file that declares a lamp flux and stores a per-1000-lm table is measured and scaled once to that flux. Absolute photometry and the shipped physical tables are used as stored; the IES library warns when a table's integrated flux and declared lumens disagree.
  • A lighting-specification reference and conformance audit. docs/lighting-spec.md and docs/lighting-spec-audit.md record the applicable analytic test vectors, the implemented sections, the one specification typo found and the algorithms that remain data-blocked. A new conformance test suite covers interpolation, symmetry, flux, flux code, direct illuminance and statistics.

Changed

  • The bounce count no longer re-meshes the room. One surface patch grid is used at every bounce count, so adding a bounce only adds interreflection and never darkens a cell; the work-plane near field uses the exact polygon transfer, removing the dark rim and bright halo that appeared along walls in Reflected mode.
  • LuxLab Match is part of main. The public finder at /find/, its API, its migration and its Caddy route are permanent; the service falls back to a bundled brand registry when a release has no config/ beside the server.

Fixed

  • Bounce calculations stay stable and additive when the requested count changes, and the reflected field is smooth up to the walls.
  • The IES library shows the physical maximum for a relative profile instead of its unscaled table maximum.
  • The plan hover readout is no longer a blank white box in the dark theme. The multi-select toolbar shared the same surface defect and is fixed too.

v0.18.0

Added

  • LuxLab Match: a public, AI-guided lighting finder at /find/. A visitor answers four short questions (space, size, feel, optional description) and gets three suitable fittings from the lighting company's own catalogue, each backed by a real calculation in the browser: count, average maintained lux, evenness, percentage of the floor at target and running cost. No account, no project and no lighting jargon.
  • Brand registry and embeddable page. config/advisor-brands.json selects the brand name, palette, catalogue, energy assumptions, contact and lead settings. ?brand=orbitx themes the page, and public/embed/luxlab-advisor.js mounts it in an iframe and follows its height on a customer's product page.
  • Anonymous advisor API. GET /api/advisor/status, POST /api/advisor/plan, POST /api/advisor/ask and POST /api/advisor/lead are public, same-origin and brand-validated. The model reads the description, fills only the fields the visitor marked unknown, writes the result summary and proposes typed adjustments; it never chooses a fitting and never produces a lux value. Provider failures fall back to deterministic copy so the calculation and the lead path keep working.
  • Lead hand-off. The result can be sent to the supplier with name, email, phone and an optional message, stored in advisor_leads with hashed IPs and retained for 365 days, and emailed with a Reply-To the visitor when a recipient is configured. A new admin Leads tab lists enquiries, their brief, the three matches and the chosen fitting.
  • Separate public build. vite.advisor.config.ts builds dist-find at /find/ with no accounts, IndexedDB, PWA or service worker; the tool at /tool/ is unchanged. npm run build builds both, and npm run smoke:advisor exercises the whole flow, the mobile layout, the follow-up adjustment, the lead form and the iframe embed.

Changed

  • npm run build is now the tool build followed by the advisor build; npm run check also type-checks the advisor project.
  • The Umami plugin is shared between both entries so a new page cannot silently lose analytics.
  • The Caddyfile serves /find/ without forward_auth, removes X-Frame-Options there and sets a separate frame-ancestors allowlist for customer sites.
  • The deploy script publishes dist-find as /var/www/luxlab-find/current and copies the brand registry beside the accounts service.

Notes

  • No project schema, tool API or database change; 002_advisor_leads.sql is additive.
  • The advisor reuses the existing provider and key. It has its own per-IP limit, shared daily budget and concurrency cap, so public traffic cannot exhaust the tool's assistant budget.
  • The first registered brand is OrbitX, the only catalogue with LM-63 photometry today. Adding a brand is a registry entry, a logo and its photometry.
  • Estimates only; no compliance or certification claim is made anywhere in the finder.

v0.17.1

Added

  • Delete targets the active room. With no light selected on the plan, Delete or Backspace now opens the room-delete confirmation for the active room instead of doing nothing. Escape or Cancel leaves the room in place, and Undo restores it with its luminaires. With a light selected, Delete keeps targeting the lights.

Changed

  • The room-delete dialog now lives at app level, so the shortcut shows it even on a phone with the Room and lights drawer closed.
  • The light-delete dialog no longer claims the deletion cannot be undone; Undo restores the lights.

Notes

  • No schema, database, API or engine change. The guard still refuses to delete the last room.
  • scripts/smoke-room.mjs covers Delete, Escape, the confirmation and Undo; the store tests cover request, cancel, confirm, the last-room guard and a project switch.

v0.17.0

Added

  • Project-wide Undo/Redo. Every edit to the open project is one step in a single stack: a light move, aim, tilt, dimming, mounting height, name or profile; adding, duplicating, deleting and grid placement; drawing, adding, duplicating, deleting, renaming, moving and reshaping a room and its wall dimensions; the work plane, grid detail, target lux, reflectances, reflected light, bounces, maintenance factors and isoline levels; the project name; and an assistant edit batch. The plan rail names the step ("Undo Move 2 lights"), and Cmd/Ctrl+Z, Shift+Cmd/Ctrl+Z and Ctrl+Y work from both the plan and 3D views.
  • Continuous edits collapse into one step. A pointer drag, a slider or a typing burst merges inside a sliding 700 ms window, so Undo returns to the state before the drag instead of replaying pointer events.

Changed

  • Room-scoped history is replaced by one project stack of 50 steps. Light, environment and assistant edits no longer clear the room steps.
  • Switching, creating, duplicating or importing a project starts an empty stack; the history is in memory and is not restored after a reload.
  • The room-delete dialog now says Undo restores the room instead of warning that another edit ends the window.
  • The shortcut dialog and the handbook's Undo section describe the single stack instead of room-scoped history.

Notes

  • No schema, database, API or engine change. src/lib/history.ts is pure and unit-tested.
  • New smoke: scripts/smoke-undo.mjs.
  • This ships the project-wide undo that the 0.16.0 notes listed as a follow-up.

v0.16.0

Added

  • A public handbook at /wiki/. A new static page beside the landing page explains the workspace, rooms and the plan, luminaires and IES files, the calculation and metrics, reports, Ask LuxLab, accounts, the method and limits, troubleshooting and every keyboard shortcut. It is generated from docs/wiki/index.md, linked from the landing navigation and footer, and linked contextually from every workspace panel.
  • An in-app help layer. A Help menu and a keyboard-shortcut dialog, a mobile More menu, section-level handbook links, an About and help block in Settings and a guide link in the project lobby.
  • Mobile workspace improvements. The plan rail and view controls are compact on phones, the results are one tap away, the header keeps the save state visible and the canvas has more room.
  • A maintained documentation loop. npm run wiki:build renders the handbook, npm run wiki:check catches drift, and AGENTS.md requires the matching section to be updated in the same commit. npm run check includes the wiki check.

Changed

  • The status bar is now the contextual status line: it shows the current tool hint, the derived calculation method and a live calculation state, plus a link to the handbook.
  • The left panel is called Room & lights and its draw action is Draw rectangle.
  • Toasts no longer cover the results panel, the plan hint strip no longer competes with the room chip, and the mobile layout keeps the top controls clear.
  • Functional text in the touched surfaces is larger and load-bearing muted text uses the full palette token. Hard-coded amber and red classes in the plan chrome, selection bar, scene labels and account menu now use the theme tokens.
  • The landing, README and handbook describe the offline PWA accurately: the app shell loads offline, while a cold start still checks the session online.

Fixed

  • Add-luminaire search keeps focus. Modal now keeps its close callback in a ref and supports an explicit initial focus, so typing no longer moves focus to the close button.
  • A single light uses the same delete confirmation as a set, with the correct accessible name and a phone-sized target.
  • The calculation method label matches the project. "Direct + N bounces" (or "Direct only") is derived from the environment and the solved result instead of a fixed one-bounce string.
  • The room-scoped undo is labelled honestly, and the assistant's separate Undo is documented.
  • The account menu uses a real hover surface, a 44 px phone target and Escape to close.
  • The workspace gets a skip link, the plan gets a visible focus ring, the mobile drawers trap and restore focus, and reduced-motion preferences are respected.

Notes

  • No server API, database migration or project schema change.
  • The handbook is generated with the repository's small Markdown renderer; no new dependency is shipped to the browser.
  • Project-wide undo, cached offline access, workspace modes, a command palette and a project tree remain follow-ups. The audit records why.
  • scripts/smoke-help.mjs covers the Help and More menus, the shortcut dialog, add-dialog focus, the single-light delete and the 390 px layout.

07 Ready 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.

Open LuxLab See what's next

v0.19.0 Local-first Metric

LM-63 Type C Direct + up to 4 bounces Metres · lux IndexedDB Ask LuxLab Offline PWA