Skip to content

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 synchronouslycompute 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

MethodWhat it doesMutates?
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:

CategoryDefaultPropertyCovers
wall0.5reflectanceWalls, columns, beams, doors, frames
ceiling0.7reflectanceDown-facing slab/roof faces
floor0.2reflectanceUp-facing slab/roof faces
furniture0.5reflectanceFurniture
glazing0.65transmittance (Tvis)Window/door glass panes
context0.2reflectanceTerrain, context masses, context buildings
blinds (closed)0.05diffuse transmittanceClosed-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.

PropertyTypeDescription
spacingMnumberSensor spacing
workplaneHeightMnumberSensor height above the floor
boundaryOffsetMnumberInset from the space boundary

PluginDaylightOptics

PropertyTypeDescription
materialsRecord<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

PropertyTypeDescription
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)
gridPluginDaylightGridSensor-grid spec
opticsPluginDaylightOptics?Surface-optics overrides (material → category → LM-83 defaults)

PluginDaylightPollArgs

PropertyTypeDescription
runIdstringThe run id returned by compute
cursorstring?Sensor-page cursor from a previous page's nextCursor

PluginDaylightSensorRecord

Metric fields are present when the corresponding metric was requested.

PropertyTypeDescription
sensorIdstringSensor id
position[number, number, number]World plan-space [x, y, z]
spaceIdstringComponentHandle id of the containing space
storeyIdstringStorey the sensor is on
areaWeightM2numberFloor area this sensor represents (m²)
occupiedHoursnumberOccupied hours per the schedule
directSunHoursAtOrAbove1000Luxnumber?ASE numerator (shades ignored per LM-83); present when ASE requested
aseFailboolean?Direct-sun hours > 250 (fails ASE); present when ASE requested
da300Percentnumber?% of occupied hours ≥ 300 lux, blinds operated; present when sDA requested
sdaPassboolean?da300Percent >= 50; present when sDA requested

PluginDaylightAggregate

An area-weighted rollup for a scope. Metric fields are present when the corresponding metric was requested.

PropertyTypeDescription
scope"project" | "storey" | "space"Rollup scope
idstring | nullScope id; null for project scope
analysisAreaM2numberAnalysed floor area (m²)
asePercentnumber?Area-weighted % of the analysis area failing ASE; when ASE requested
sdaPercentnumber?Area-weighted % of the analysis area passing sDA300/50%; when sDA requested

PluginDaylightResults

PropertyTypeDescription
status"running" | "complete" | "cancelled" | "failed""complete" on synchronous (ASE-only) runs; "running" then "complete"/"failed" on sDA runs
runIdstring?Run id for poll; present on asynchronous (sDA) runs
sensorsPluginDaylightSensorRecord[]Per-sensor records (paged; empty while "running")
aggregatesPluginDaylightAggregate[]Scope rollups (all scopes, not paged; empty while "running")
provenanceobjectFull run provenance echo (weather, thresholds, grid, location, resolved optics, blinds, warnings, excluded spaces)
nextCursorstring | nullNext 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 — the standard, the metrics, an optional occupancy schedule, optional surface optics overrides, and the sensor grid spec.
  • Returns: PluginDaylightResults — per-sensor sensors, scope-rolled aggregates, the full provenance echo, nextCursor, and runId on 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_FAILED with details.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 — the runId returned by compute, plus an optional sensor-page cursor.
  • Returns: PluginDaylightResults for the run.
  • Throws: When the runId is unknown, or when the run failed (surfaced as a typed OPERATION_FAILED).
ts
const polled = await snaptrude.analysis.daylight.poll({ runId });
if (polled.status === "complete") {
  const passing = polled.sensors.filter((s) => s.sdaPass).length;
}