Appearance
Daylight
Standards-grade annual daylight metrics (IES LM-83). Accessed via snaptrude.analysis.daylight.
Runs an annual daylight simulation over per-space sensor grids and returns numeric per-sensor and aggregate metrics — no textures, no heatmap, no model mutation. Two metrics are supported: ASE (Annual Sunlight Exposure) and sDA (Spatial Daylight Autonomy, sDA300/50%).
Run semantics. metrics: ["ASE"] runs synchronously — compute returns the full result directly, no runId. Any request including "sDA" (alone or with ASE) runs asynchronously: compute submits the job and immediately returns { status: "running", runId, sensors: [], aggregates: [] }; call poll with the runId until status is "complete" (or "failed"). If ASE is also requested, the job runs the synchronous ASE engine internally and merges its fields into the completed result.
Blind operation. sDA is computed WITH dynamic blind operation per the IES LM-83 2% rule: windows are grouped by storey + facade orientation, and for every occupied hour blind groups close (default 5% diffuse transmittance) until no more than 2% of the analysis points receive direct sun. This API operates the blinds for you — sDA without blind operation is not LM-83-compliant, so there is no switch to turn it off. The run's provenance.blinds reports the groups, their closed hours, and any hours where no combination satisfies the rule.
Every result carries a full provenance echo (weather file, thresholds, grid, location, resolved optics, blind operation, warnings, excluded spaces) plus modelRevision + inputHash for staleness. Positions are world plan-space coordinates. The host assembles a floor polygon per space that has a footprint (the same source as design.query.spaces.getFootprint) and sends it with the sensor-grid spec.
Pro plan + engines
compute is Pro-gated and write-gated (mirrors analysis.illuminance.compute). ASE runs on the GPU host; when it is unavailable the call fails with OPERATION_FAILED and details.reason === "GPU_UNAVAILABLE". sDA runs on the Radiance engine (CPU); when it is not installed the call fails with OPERATION_FAILED and details.reason === "ENGINE_UNAVAILABLE".
At a glance
| Method | What it does | Mutates? |
|---|---|---|
compute(args) | Compute annual daylight metrics per space | — |
poll(args) | Poll an asynchronous (sDA) run for results | — |
Surface optics
The optional optics arg overrides surface optics for sDA runs. Resolution order per surface: per-material (optics.materials, keyed by material name as shown in Snaptrude — the same names design.query.geometry.getTriangulatedMeshes returns in materialIds) → per-category (optics.categories) → LM-83/convention defaults:
| Category | Default | Property | Covers |
|---|---|---|---|
wall | 0.5 | reflectance | Walls, columns, beams, doors, frames |
ceiling | 0.7 | reflectance | Down-facing slab/roof faces |
floor | 0.2 | reflectance | Up-facing slab/roof faces |
furniture | 0.5 | reflectance | Furniture |
glazing | 0.65 | transmittance (Tvis) | Window/door glass panes |
context | 0.2 | reflectance | Terrain, context masses, context buildings |
| blinds (closed) | 0.05 | diffuse transmittance | Closed-blind state (LM-83) |
Glazing takes visible transmittance (Tvis) exactly as manufacturers quote it; the backend converts it to Radiance glass transmissivity internally — never pre-convert. A glass material painted onto a non-window/door face stays opaque in the simulation, and the run's provenance.warnings says so.
Types
PluginDaylightGrid
Sensor-grid spec, all metres.
| Property | Type | Description |
|---|---|---|
spacingM | number | Sensor spacing |
workplaneHeightM | number | Sensor height above the floor |
boundaryOffsetM | number | Inset from the space boundary |
PluginDaylightOptics
| Property | Type | Description |
|---|---|---|
materials | Record<string, { reflectance } | { transmittance }>? | Keyed by material name; wins over category defaults |
categories | { wall?, ceiling?, floor?, furniture?, glazing?, context? }? | Per-category defaults override |
blinds | { transmittance }? | Closed-blind diffuse transmittance (default 0.05) |
PluginDaylightComputeArgs
| Property | Type | Description |
|---|---|---|
standard | "IES-LM-83-23" | Daylight standard; only supported value |
metrics | ("ASE" | "sDA")[] | Metrics to compute (["ASE"] synchronous; including "sDA" async) |
schedule | { startHour, endHour }? | Occupancy hours (default 8am–6pm) |
grid | PluginDaylightGrid | Sensor-grid spec |
optics | PluginDaylightOptics? | Surface-optics overrides (material → category → LM-83 defaults) |
PluginDaylightPollArgs
| Property | Type | Description |
|---|---|---|
runId | string | The run id returned by compute |
cursor | string? | Sensor-page cursor from a previous page's nextCursor |
PluginDaylightSensorRecord
Metric fields are present when the corresponding metric was requested.
| Property | Type | Description |
|---|---|---|
sensorId | string | Sensor id |
position | [number, number, number] | World plan-space [x, y, z] |
spaceId | string | ComponentHandle id of the containing space |
storeyId | string | Storey the sensor is on |
areaWeightM2 | number | Floor area this sensor represents (m²) |
occupiedHours | number | Occupied hours per the schedule |
directSunHoursAtOrAbove1000Lux | number? | ASE numerator (shades ignored per LM-83); present when ASE requested |
aseFail | boolean? | Direct-sun hours > 250 (fails ASE); present when ASE requested |
da300Percent | number? | % of occupied hours ≥ 300 lux, blinds operated; present when sDA requested |
sdaPass | boolean? | da300Percent >= 50; present when sDA requested |
PluginDaylightAggregate
An area-weighted rollup for a scope. Metric fields are present when the corresponding metric was requested.
| Property | Type | Description |
|---|---|---|
scope | "project" | "storey" | "space" | Rollup scope |
id | string | null | Scope id; null for project scope |
analysisAreaM2 | number | Analysed floor area (m²) |
asePercent | number? | Area-weighted % of the analysis area failing ASE; when ASE requested |
sdaPercent | number? | Area-weighted % of the analysis area passing sDA300/50%; when sDA requested |
PluginDaylightResults
| Property | Type | Description |
|---|---|---|
status | "running" | "complete" | "cancelled" | "failed" | "complete" on synchronous (ASE-only) runs; "running" then "complete"/"failed" on sDA runs |
runId | string? | Run id for poll; present on asynchronous (sDA) runs |
sensors | PluginDaylightSensorRecord[] | Per-sensor records (paged; empty while "running") |
aggregates | PluginDaylightAggregate[] | Scope rollups (all scopes, not paged; empty while "running") |
provenance | object | Full run provenance echo (weather, thresholds, grid, location, resolved optics, blinds, warnings, excluded spaces) |
nextCursor | string | null | Next sensor page cursor; null = last page |
Functions
compute(args)
Compute annual daylight metrics for the model's spaces. Sensors are laid out per-space from the grid spec; request the metrics you want in metrics (["ASE"], ["sDA"], or both). ASE-only runs are synchronous and return the full result directly. Any run including "sDA" is asynchronous: this returns { status: "running", runId, sensors: [], aggregates: [] } immediately — pass the runId to poll until the run completes. sDA is computed with LM-83 dynamic blind operation (see above); pass optics to override surface reflectances / glazing transmittance.
- Parameters:
args:PluginDaylightComputeArgs— thestandard, themetrics, an optional occupancyschedule, optional surfaceopticsoverrides, and the sensorgridspec.
- Returns:
PluginDaylightResults— per-sensorsensors, scope-rolledaggregates, the fullprovenanceecho,nextCursor, andrunIdon asynchronous (sDA) runs. - Throws: When the workspace is not on a Pro plan, when plugin writes are disabled, when the project has no geo-located site or no spaces to analyse, or when the required engine is unavailable (
OPERATION_FAILEDwithdetails.reason === "GPU_UNAVAILABLE"for ASE,"ENGINE_UNAVAILABLE"for sDA).
ts
let res = await snaptrude.analysis.daylight.compute({
standard: "IES-LM-83-23",
metrics: ["sDA", "ASE"],
grid: { spacingM: 0.6, workplaneHeightM: 0.76, boundaryOffsetM: 0.5 },
optics: { materials: { "Glass - Clear": { transmittance: 0.6 } } }
});
const runId = res.runId;
while (res.status === "running" && runId) {
await new Promise((resolve) => setTimeout(resolve, 5000));
res = await snaptrude.analysis.daylight.poll({ runId });
}
const failing = res.aggregates.filter((a) => a.scope === "space" && (a.sdaPercent ?? 0) < 55);poll(args)
Poll an asynchronous daylight run started by compute. Returns the same PluginDaylightResults shape as compute: status: "running" while the job is in flight (empty sensors / aggregates), "complete" with the full data once done. Poll every few seconds — an annual sDA simulation takes minutes, not milliseconds. Completed results page sensors exactly like a synchronous run: follow nextCursor via the cursor arg.
- Parameters:
args:PluginDaylightPollArgs— therunIdreturned bycompute, plus an optional sensor-pagecursor.
- Returns:
PluginDaylightResultsfor the run. - Throws: When the
runIdis unknown, or when the run failed (surfaced as a typedOPERATION_FAILED).
ts
const polled = await snaptrude.analysis.daylight.poll({ runId });
if (polled.status === "complete") {
const passing = polled.sensors.filter((s) => s.sdaPass).length;
}