@snaptrude/plugin-core 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/api-manifest.full.json +1350 -89
  3. package/api-manifest.json +826 -40
  4. package/dist/api/analysis/daylight.d.ts +603 -0
  5. package/dist/api/analysis/daylight.d.ts.map +1 -0
  6. package/dist/api/analysis/heatmaps.d.ts +438 -14
  7. package/dist/api/analysis/heatmaps.d.ts.map +1 -1
  8. package/dist/api/analysis/index.d.ts +15 -0
  9. package/dist/api/analysis/index.d.ts.map +1 -1
  10. package/dist/api/analysis/solar.d.ts +249 -0
  11. package/dist/api/analysis/solar.d.ts.map +1 -0
  12. package/dist/api/analysis/weather.d.ts +193 -0
  13. package/dist/api/analysis/weather.d.ts.map +1 -0
  14. package/dist/api/core/camera/index.d.ts +37 -0
  15. package/dist/api/core/camera/index.d.ts.map +1 -1
  16. package/dist/api/core/geom/create/index.d.ts +318 -1
  17. package/dist/api/core/geom/create/index.d.ts.map +1 -1
  18. package/dist/api/core/index.d.ts +9 -0
  19. package/dist/api/core/index.d.ts.map +1 -1
  20. package/dist/api/core/io/export/index.d.ts +3 -1
  21. package/dist/api/core/io/export/index.d.ts.map +1 -1
  22. package/dist/api/core/io/import/index.d.ts +61 -0
  23. package/dist/api/core/io/import/index.d.ts.map +1 -1
  24. package/dist/api/core/mode/index.d.ts +99 -0
  25. package/dist/api/core/mode/index.d.ts.map +1 -0
  26. package/dist/api/core/storeys/index.d.ts +251 -0
  27. package/dist/api/core/storeys/index.d.ts.map +1 -0
  28. package/dist/api/core/tags.d.ts +24 -0
  29. package/dist/api/core/tags.d.ts.map +1 -1
  30. package/dist/api/core/zoom/index.d.ts +4 -0
  31. package/dist/api/core/zoom/index.d.ts.map +1 -1
  32. package/dist/api/design/create/index.d.ts +115 -4
  33. package/dist/api/design/create/index.d.ts.map +1 -1
  34. package/dist/api/design/delete/index.d.ts +3 -0
  35. package/dist/api/design/delete/index.d.ts.map +1 -1
  36. package/dist/api/design/query/geometry/index.d.ts +112 -0
  37. package/dist/api/design/query/geometry/index.d.ts.map +1 -1
  38. package/dist/api/design/query/index.d.ts +4 -0
  39. package/dist/api/design/query/index.d.ts.map +1 -1
  40. package/dist/api/design/query/referenceLines.d.ts +45 -0
  41. package/dist/api/design/query/referenceLines.d.ts.map +1 -0
  42. package/dist/api/design/query/spaces.d.ts +173 -0
  43. package/dist/api/design/query/spaces.d.ts.map +1 -1
  44. package/dist/api/design/transform/index.d.ts +12 -0
  45. package/dist/api/design/transform/index.d.ts.map +1 -1
  46. package/dist/api/design/update/index.d.ts +51 -1
  47. package/dist/api/design/update/index.d.ts.map +1 -1
  48. package/dist/api/entity/buildableEnvelope.d.ts +4 -0
  49. package/dist/api/entity/buildableEnvelope.d.ts.map +1 -1
  50. package/dist/api/entity/referenceLine.d.ts +8 -0
  51. package/dist/api/entity/referenceLine.d.ts.map +1 -1
  52. package/dist/api/entity/story.d.ts +20 -0
  53. package/dist/api/entity/story.d.ts.map +1 -1
  54. package/dist/api/index.d.ts +5 -0
  55. package/dist/api/index.d.ts.map +1 -1
  56. package/dist/api/presentation/export.d.ts +4 -0
  57. package/dist/api/presentation/export.d.ts.map +1 -1
  58. package/dist/api/presentation/import.d.ts +6 -0
  59. package/dist/api/presentation/import.d.ts.map +1 -1
  60. package/dist/api/presentation/index.d.ts +9 -0
  61. package/dist/api/presentation/index.d.ts.map +1 -1
  62. package/dist/api/presentation/placedViews.d.ts +370 -0
  63. package/dist/api/presentation/placedViews.d.ts.map +1 -0
  64. package/dist/api/presentation/shapes.d.ts +481 -0
  65. package/dist/api/presentation/shapes.d.ts.map +1 -0
  66. package/dist/api/program/site.d.ts +94 -0
  67. package/dist/api/program/site.d.ts.map +1 -1
  68. package/dist/api/program/spreadsheet.d.ts +260 -28
  69. package/dist/api/program/spreadsheet.d.ts.map +1 -1
  70. package/dist/api/workspace/index.d.ts +460 -0
  71. package/dist/api/workspace/index.d.ts.map +1 -0
  72. package/dist/index.cjs +2625 -1758
  73. package/dist/index.cjs.map +1 -1
  74. package/dist/index.js +2515 -1758
  75. package/dist/index.js.map +1 -1
  76. package/package.json +3 -2
  77. package/scripts/generate-manifest.mjs +45 -0
  78. package/src/api/analysis/daylight.ts +470 -0
  79. package/src/api/analysis/heatmaps.ts +444 -17
  80. package/src/api/analysis/index.ts +15 -0
  81. package/src/api/analysis/solar.ts +237 -0
  82. package/src/api/analysis/weather.ts +179 -0
  83. package/src/api/core/camera/index.ts +39 -0
  84. package/src/api/core/geom/create/index.ts +344 -1
  85. package/src/api/core/index.ts +9 -0
  86. package/src/api/core/io/export/index.ts +3 -1
  87. package/src/api/core/io/import/index.ts +64 -0
  88. package/src/api/core/mode/index.ts +96 -0
  89. package/src/api/core/storeys/index.ts +279 -0
  90. package/src/api/core/tags.ts +27 -0
  91. package/src/api/core/zoom/index.ts +4 -0
  92. package/src/api/design/create/index.ts +138 -1
  93. package/src/api/design/delete/index.ts +3 -0
  94. package/src/api/design/query/geometry/index.ts +125 -3
  95. package/src/api/design/query/index.ts +4 -0
  96. package/src/api/design/query/referenceLines.ts +52 -0
  97. package/src/api/design/query/spaces.ts +143 -0
  98. package/src/api/design/transform/index.ts +12 -0
  99. package/src/api/design/update/index.ts +66 -1
  100. package/src/api/entity/buildableEnvelope.ts +4 -0
  101. package/src/api/entity/referenceLine.ts +8 -0
  102. package/src/api/entity/story.ts +20 -0
  103. package/src/api/index.ts +5 -0
  104. package/src/api/presentation/export.ts +4 -0
  105. package/src/api/presentation/import.ts +6 -0
  106. package/src/api/presentation/index.ts +9 -0
  107. package/src/api/presentation/placedViews.ts +363 -0
  108. package/src/api/presentation/shapes.ts +274 -0
  109. package/src/api/program/site.ts +95 -0
  110. package/src/api/program/spreadsheet.ts +251 -6
  111. package/src/api/workspace/index.ts +516 -0
@@ -16,15 +16,25 @@ import { ComponentHandle, Vec3Components } from "../../handles"
16
16
  * - {@linkcode PluginAnalysisHeatmapsApi.renderGrid} — a coloured grid mesh
17
17
  * from point samples in world coordinates (wind, noise, microclimate — any
18
18
  * field sampled over an area).
19
+ * - {@linkcode PluginAnalysisHeatmapsApi.renderField} — arbitrary planar
20
+ * polygon cells (or pre-tessellated meshes), one scalar value each — for
21
+ * non-square analysis meshes straight from a solver.
19
22
  *
20
23
  * Shared semantics:
21
24
  *
22
- * - **Single active heatmap** — a render call **replaces** whatever heatmap
23
- * is currently showing (from either mode).
25
+ * - **Named overlays** — every render call targets a named overlay
26
+ * (`options.name`; a shared default overlay when omitted). Rendering an
27
+ * existing name replaces that overlay only; the newly rendered overlay
28
+ * becomes the visible one — **exactly one overlay is visible at a time**.
29
+ * Up to 16 overlays can be registered; manage them (list / show / hide /
30
+ * remove) via {@linkcode PluginAnalysisHeatmapsApi.overlays}.
24
31
  * - **Legend** — the host renders a legend card from
25
32
  * {@linkcode PluginAnalysisHeatmapOptions} (title, unit, min/max, colour
26
33
  * segments). Values outside `[min, max]` **clamp to the end colours** and
27
34
  * the legend shows `<` / `>` markers at the clamped ends.
35
+ * - **Hover tooltip** — pass `options.hover: true` to any render call and,
36
+ * while that overlay is visible, hovering a cell shows a tooltip with the
37
+ * cell's `value` (and its `meta` payload, when the cell carries one).
28
38
  * - **Ephemeral** — the heatmap is a transient overlay: never persisted,
29
39
  * never saved with views, and auto-cleared when a scene-mutating edit
30
40
  * invalidates it. Re-render after changing the model.
@@ -32,6 +42,13 @@ import { ComponentHandle, Vec3Components } from "../../handles"
32
42
  * Accessed via `snaptrude.analysis.heatmaps`.
33
43
  */
34
44
  export abstract class PluginAnalysisHeatmapsApi {
45
+ /**
46
+ * Manage the named heatmap overlays created by the render calls —
47
+ * list / show / hide / remove. See
48
+ * {@linkcode PluginAnalysisHeatmapOverlaysApi}.
49
+ */
50
+ public abstract overlays: PluginAnalysisHeatmapOverlaysApi
51
+
35
52
  constructor() {}
36
53
 
37
54
  /**
@@ -39,8 +56,9 @@ export abstract class PluginAnalysisHeatmapsApi {
39
56
  *
40
57
  * Each entry maps a space to a scalar value; the value is mapped through
41
58
  * the colour ramp (see {@linkcode PluginAnalysisHeatmapOptions}) and the
42
- * space's top face is painted that single flat colour. Replaces any
43
- * heatmap currently showing (single active heatmap). Spaces whose top
59
+ * space's top face is painted that single flat colour. Renders into the
60
+ * overlay named `options.name` (the default overlay when omitted),
61
+ * replacing that overlay only and making it the visible one. Spaces whose top
44
62
  * face is not flat (pitched/sloped tops) are **skipped with a console
45
63
  * warning** — the rest of the heatmap still renders.
46
64
  *
@@ -59,6 +77,8 @@ export abstract class PluginAnalysisHeatmapsApi {
59
77
  * skipped).
60
78
  * @throws When `options.min` is greater than `options.max`.
61
79
  * @throws When a colour in `options.colors` is not a 6-digit hex colour.
80
+ * @throws When `options.name` is a new overlay name and the 16-overlay
81
+ * cap is reached.
62
82
  * @throws When plugin writes are disabled.
63
83
  *
64
84
  * @examplePrompt Color each room by its occupancy count
@@ -89,8 +109,9 @@ export abstract class PluginAnalysisHeatmapsApi {
89
109
  * {@linkcode PluginUnitsApi.convert}) — coloured by mapping the value
90
110
  * through the colour ramp
91
111
  * (see {@linkcode PluginAnalysisHeatmapOptions}). This is the mode for
92
- * field data — wind, noise, microclimate — sampled over an area. Replaces
93
- * any heatmap currently showing (single active heatmap).
112
+ * field data — wind, noise, microclimate — sampled over an area. Renders
113
+ * into the overlay named `options.name` (the default overlay when
114
+ * omitted), replacing that overlay only and making it the visible one.
94
115
  *
95
116
  * The heatmap is ephemeral: it is auto-cleared by scene-mutating edits
96
117
  * and is never persisted or saved with views.
@@ -108,6 +129,8 @@ export abstract class PluginAnalysisHeatmapsApi {
108
129
  * @throws When `cells` is empty or `cellSize` is not a positive, finite number.
109
130
  * @throws When `options.min` is greater than `options.max`.
110
131
  * @throws When a colour in `options.colors` is not a 6-digit hex colour.
132
+ * @throws When `options.name` is a new overlay name and the 16-overlay
133
+ * cap is reached.
111
134
  * @throws When plugin writes are disabled.
112
135
  *
113
136
  * @examplePrompt Overlay my CFD wind simulation results on the site
@@ -131,15 +154,152 @@ export abstract class PluginAnalysisHeatmapsApi {
131
154
  ): PluginApiReturn<PluginAnalysisHeatmapsRenderResult>
132
155
 
133
156
  /**
134
- * Clear the plugin heatmap from the scene.
157
+ * Render a grid heatmap on an **arbitrarily oriented plane** — a façade,
158
+ * a section cut, any flat surface.
159
+ *
160
+ * The same point-sample model as
161
+ * {@linkcode PluginAnalysisHeatmapsApi.renderGrid}, generalised: cells lie
162
+ * on the plane whose direction is given by `normal` (for a façade, the
163
+ * outward wall normal). Each cell is a square of edge `cellSize` centred
164
+ * on its **world-coordinate** `position` and oriented in that plane; the
165
+ * host derives the in-plane axes from the normal and offsets each cell a
166
+ * hair along it so the heatmap never z-fights the surface it annotates.
167
+ * Cell positions should lie on (or very near) the surface being
168
+ * annotated — the host does not project them. Renders into the overlay
169
+ * named `options.name` (the default overlay when omitted), replacing
170
+ * that overlay only and making it the visible one.
171
+ *
172
+ * This is the mode for per-façade data — solar irradiance, glare, panel
173
+ * yield, wind pressure — sampled over a vertical or sloped face. For
174
+ * horizontal ground fields prefer
175
+ * {@linkcode PluginAnalysisHeatmapsApi.renderGrid}, which is this call
176
+ * with `normal = { x: 0, y: 1, z: 0 }`.
135
177
  *
136
- * Un-applies the rendered heatmap (either mode) and removes its legend.
137
- * A no-op (returns `false`) when no plugin heatmap is showing. Does not
138
- * touch the built-in study heatmaps (`sunlightHours` / `illuminance`) —
139
- * they have their own `reset`.
178
+ * The heatmap is ephemeral: it is auto-cleared by scene-mutating edits
179
+ * and is never persisted or saved with views.
140
180
  *
141
- * @returns `true` when a heatmap was cleared, `false` when none was
142
- * showing.
181
+ * @param cells - One {@linkcode PluginAnalysisHeatmapGridCell} per
182
+ * sample: a world-coordinate `position` on the surface plane (y up)
183
+ * and its scalar `value`.
184
+ * @param cellSize - Edge length of each (square) cell, in the same
185
+ * Snaptrude internal units as `position` (see
186
+ * {@linkcode PluginUnitsApi.convert}). Must be a positive, finite number.
187
+ * @param normal - The plane's normal direction as `{ x, y, z }` (for a
188
+ * façade, the outward wall normal — read it via
189
+ * `core.geom.query.face.getNormal` on the wall's face). Any non-zero
190
+ * length; the host normalises it.
191
+ * @param options - {@linkcode PluginAnalysisHeatmapOptions} — legend
192
+ * title/unit, explicit min/max range (auto-derived from the data when
193
+ * omitted), and the colour ramp.
194
+ * @returns A {@linkcode PluginAnalysisHeatmapsRenderResult} —
195
+ * `{ success: true }` once the heatmap is rendered.
196
+ * @throws When `cells` is empty, `cellSize` is not a positive finite
197
+ * number, or `normal` has zero length.
198
+ * @throws When `options.min` is greater than `options.max`.
199
+ * @throws When a colour in `options.colors` is not a 6-digit hex colour.
200
+ * @throws When `options.name` is a new overlay name and the 16-overlay
201
+ * cap is reached.
202
+ * @throws When plugin writes are disabled.
203
+ *
204
+ * @examplePrompt Paint the solar irradiance results on the south façade
205
+ * @examplePrompt Show my daylight values as a heatmap on this wall
206
+ * @examplePrompt Render panel-level PV yield on the building face
207
+ * @examplePrompt Overlay wind pressure on the tower's west elevation
208
+ *
209
+ * # Example
210
+ * ```ts
211
+ * // Samples lie on a wall plane facing +X; colour each 1-unit cell by irradiance.
212
+ * await snaptrude.analysis.heatmaps.renderSurfaceGrid(
213
+ * samples.map((s) => ({ position: { x: wallX, y: s.y, z: s.z }, value: s.irradiance })),
214
+ * 1,
215
+ * { x: 1, y: 0, z: 0 }, // façade normal
216
+ * { title: "Irradiance", unit: "kWh/m²", colors: ["#0000ff", "#ffff00", "#ff0000"] },
217
+ * )
218
+ * ```
219
+ */
220
+ public abstract renderSurfaceGrid(
221
+ cells: PluginAnalysisHeatmapGridCell[],
222
+ cellSize: number,
223
+ normal: Vec3Components,
224
+ options?: PluginAnalysisHeatmapOptions,
225
+ ): PluginApiReturn<PluginAnalysisHeatmapsRenderResult>
226
+
227
+ /**
228
+ * Render a heatmap from **arbitrary cell geometry** — planar polygons or
229
+ * pre-tessellated meshes, one scalar value each.
230
+ *
231
+ * The free-form sibling of the grid calls: instead of square cells, each
232
+ * {@linkcode PluginAnalysisHeatmapFieldCell} brings its own footprint in
233
+ * **world coordinates** (y up) — either a planar `polygon` ring (3+
234
+ * points, any orientation; the host triangulates it) or a pre-tessellated
235
+ * `vertices` + `indices` mesh (triangle list). This is the mode for
236
+ * analysis meshes straight from a solver: FEA/CFD cells, Voronoi regions,
237
+ * per-panel façade patches — anything not on a square grid. Each cell's
238
+ * `value` is mapped through the colour scale
239
+ * (see {@linkcode PluginAnalysisHeatmapOptions}, including
240
+ * `options.scale`); an optional `meta` payload is stored with the overlay
241
+ * for later features. Renders into the overlay named `options.name` (the
242
+ * default overlay when omitted), replacing that overlay only and making
243
+ * it the visible one.
244
+ *
245
+ * Degenerate polygon cells (collinear points, zero area) are **skipped
246
+ * with a console warning** — the rest of the heatmap still renders.
247
+ *
248
+ * The heatmap is ephemeral: it is auto-cleared by scene-mutating edits
249
+ * and is never persisted or saved with views.
250
+ *
251
+ * @param cells - One {@linkcode PluginAnalysisHeatmapFieldCell} per cell:
252
+ * a planar `polygon` ring OR a `vertices`/`indices` triangle mesh, plus
253
+ * its scalar `value` and optional `meta`.
254
+ * @param options - {@linkcode PluginAnalysisHeatmapOptions} — legend
255
+ * title/unit, min/max range, colour ramp, and the colour `scale`
256
+ * (bands / threshold / categorical).
257
+ * @returns A {@linkcode PluginAnalysisHeatmapsRenderResult} —
258
+ * `{ success: true }` once the heatmap is rendered.
259
+ * @throws When `cells` is empty, a polygon has fewer than 3 points, a
260
+ * pre-tessellated cell's `indices` length is not a multiple of 3, or an
261
+ * index is out of range of its `vertices`.
262
+ * @throws When every polygon cell was degenerate (nothing renderable).
263
+ * @throws When a `categorical` scale has no class for a cell's value.
264
+ * @throws When `options.min` is greater than `options.max`, or a colour
265
+ * is not a 6-digit hex colour.
266
+ * @throws When `options.name` is a new overlay name and the 16-overlay
267
+ * cap is reached.
268
+ * @throws When plugin writes are disabled.
269
+ *
270
+ * @examplePrompt Overlay my FEA stress results on their solver mesh
271
+ * @examplePrompt Color these Voronoi regions by footfall
272
+ * @examplePrompt Paint each façade panel polygon by its PV yield
273
+ * @examplePrompt Render pass/fail daylight compliance per zone polygon
274
+ *
275
+ * # Example
276
+ * ```ts
277
+ * // Two triangular cells at ground level, coloured pass/fail at 300 lux.
278
+ * await snaptrude.analysis.heatmaps.renderField(
279
+ * [
280
+ * { polygon: [{ x: 0, y: 0, z: 0 }, { x: 4, y: 0, z: 0 }, { x: 0, y: 0, z: 4 }], value: 420 },
281
+ * { polygon: [{ x: 4, y: 0, z: 4 }, { x: 0, y: 0, z: 4 }, { x: 4, y: 0, z: 0 }], value: 180 },
282
+ * ],
283
+ * { title: "Daylight", unit: "lux", scale: { type: "threshold", threshold: 300 } },
284
+ * )
285
+ * ```
286
+ */
287
+ public abstract renderField(
288
+ cells: PluginAnalysisHeatmapFieldCell[],
289
+ options?: PluginAnalysisHeatmapOptions,
290
+ ): PluginApiReturn<PluginAnalysisHeatmapsRenderResult>
291
+
292
+ /**
293
+ * Clear every plugin heatmap overlay from the scene.
294
+ *
295
+ * Removes **all** registered overlays (equivalent to
296
+ * {@linkcode PluginAnalysisHeatmapOverlaysApi.removeAll}) and closes the
297
+ * legend. A no-op (returns `false`) when no plugin heatmap exists. Does
298
+ * not touch the built-in study heatmaps (`sunlightHours` /
299
+ * `illuminance`) — they have their own `reset`.
300
+ *
301
+ * @returns `true` when at least one overlay was cleared, `false` when
302
+ * none existed.
143
303
  * @throws When plugin writes are disabled.
144
304
  *
145
305
  * @examplePrompt Clear my custom heatmap
@@ -176,6 +336,138 @@ export abstract class PluginAnalysisHeatmapsApi {
176
336
  public abstract isActive(): PluginApiReturn<boolean>
177
337
  }
178
338
 
339
+ /**
340
+ * Heatmap overlay management — the named overlays created by the
341
+ * `analysis.heatmaps` render calls.
342
+ *
343
+ * Every render call registers (or replaces) an overlay under
344
+ * `options.name` — a shared default overlay when omitted. Overlays are
345
+ * in-session only: never persisted, never saved with views, and **all**
346
+ * of them are cleared together when a scene-mutating edit invalidates
347
+ * them. At most 16 overlays can be registered at once, and **exactly one
348
+ * overlay is visible at a time** — showing one hides the others, and the
349
+ * legend always describes the visible overlay.
350
+ *
351
+ * Accessed via `snaptrude.analysis.heatmaps.overlays`.
352
+ */
353
+ export abstract class PluginAnalysisHeatmapOverlaysApi {
354
+ constructor() {}
355
+
356
+ /**
357
+ * List the registered heatmap overlays.
358
+ *
359
+ * Returns every overlay's `name`, `kind` (which render call produced
360
+ * it: `"spaces"`, `"grid"`, `"surfaceGrid"` or `"field"`) and whether it is the
361
+ * currently visible one. At most one overlay has `visible: true`.
362
+ *
363
+ * @returns A {@linkcode PluginAnalysisHeatmapOverlaysListResult} —
364
+ * `{ overlays: [{ name, visible, kind }] }`, empty when nothing is
365
+ * registered.
366
+ *
367
+ * @examplePrompt Which heatmap overlays do I have?
368
+ * @examplePrompt List my rendered analysis overlays
369
+ * @examplePrompt Check which heatmap is currently visible
370
+ *
371
+ * # Example
372
+ * ```ts
373
+ * const { overlays } = await snaptrude.analysis.heatmaps.overlays.list()
374
+ * const visible = overlays.find((o) => o.visible)
375
+ * ```
376
+ */
377
+ public abstract list(): PluginApiReturn<PluginAnalysisHeatmapOverlaysListResult>
378
+
379
+ /**
380
+ * Make a registered overlay the visible one.
381
+ *
382
+ * Enables the named overlay's meshes, hides whichever overlay was
383
+ * visible before, and swaps the legend to the shown overlay's. A no-op
384
+ * returning `false` when no overlay of that name exists.
385
+ *
386
+ * @param name - The overlay's name (`options.name` at render time).
387
+ * @returns `true` when the overlay is now visible, `false` when no
388
+ * overlay of that name exists.
389
+ * @throws When plugin writes are disabled.
390
+ *
391
+ * @examplePrompt Switch back to my wind heatmap
392
+ * @examplePrompt Show the occupancy overlay
393
+ * @examplePrompt Flip between my two analysis overlays
394
+ *
395
+ * # Example
396
+ * ```ts
397
+ * await snaptrude.analysis.heatmaps.overlays.show("wind")
398
+ * ```
399
+ */
400
+ public abstract show(name: string): PluginApiReturn<boolean>
401
+
402
+ /**
403
+ * Hide an overlay without removing it.
404
+ *
405
+ * Disables the named overlay's meshes; when it was the visible one its
406
+ * legend closes too (nothing visible → no legend). The overlay stays
407
+ * registered and can be brought back with
408
+ * {@linkcode PluginAnalysisHeatmapOverlaysApi.show}. A no-op returning
409
+ * `false` when no overlay of that name exists.
410
+ *
411
+ * @param name - The overlay's name (`options.name` at render time).
412
+ * @returns `true` when the overlay exists (and is now hidden), `false`
413
+ * when no overlay of that name exists.
414
+ * @throws When plugin writes are disabled.
415
+ *
416
+ * @examplePrompt Hide the wind overlay but keep it around
417
+ * @examplePrompt Temporarily hide my heatmap
418
+ * @examplePrompt Turn off the occupancy colours without deleting them
419
+ *
420
+ * # Example
421
+ * ```ts
422
+ * await snaptrude.analysis.heatmaps.overlays.hide("wind")
423
+ * ```
424
+ */
425
+ public abstract hide(name: string): PluginApiReturn<boolean>
426
+
427
+ /**
428
+ * Remove an overlay — dispose its meshes and forget it.
429
+ *
430
+ * Frees the overlay's slot toward the 16-overlay cap; when it was the
431
+ * visible one its legend closes. A no-op returning `false` when no
432
+ * overlay of that name exists.
433
+ *
434
+ * @param name - The overlay's name (`options.name` at render time).
435
+ * @returns `true` when the overlay was removed, `false` when no overlay
436
+ * of that name exists.
437
+ * @throws When plugin writes are disabled.
438
+ *
439
+ * @examplePrompt Delete the wind overlay
440
+ * @examplePrompt Remove my occupancy heatmap for good
441
+ * @examplePrompt Free up an overlay slot
442
+ *
443
+ * # Example
444
+ * ```ts
445
+ * await snaptrude.analysis.heatmaps.overlays.remove("wind")
446
+ * ```
447
+ */
448
+ public abstract remove(name: string): PluginApiReturn<boolean>
449
+
450
+ /**
451
+ * Remove every registered overlay and close the legend.
452
+ *
453
+ * The bulk form of {@linkcode PluginAnalysisHeatmapOverlaysApi.remove};
454
+ * `analysis.heatmaps.reset()` is this call under its historical name.
455
+ *
456
+ * @returns The number of overlays removed (`0` when none existed).
457
+ * @throws When plugin writes are disabled.
458
+ *
459
+ * @examplePrompt Clear all my heatmap overlays
460
+ * @examplePrompt Remove every analysis overlay from the scene
461
+ * @examplePrompt Start over with a clean set of overlays
462
+ *
463
+ * # Example
464
+ * ```ts
465
+ * const removed = await snaptrude.analysis.heatmaps.overlays.removeAll()
466
+ * ```
467
+ */
468
+ public abstract removeAll(): PluginApiReturn<number>
469
+ }
470
+
179
471
  /**
180
472
  * One space's entry in {@linkcode PluginAnalysisHeatmapsApi.renderSpaces}.
181
473
  *
@@ -199,15 +491,115 @@ export type PluginAnalysisHeatmapSpaceEntry = z.infer<
199
491
  * |---|---|---|
200
492
  * | `position` | `{ x, y, z }` | Cell centre in **world coordinates** (y is up) |
201
493
  * | `value` | `number` | The scalar value mapped to a colour |
494
+ * | `meta` | `unknown \| undefined` | Optional payload stored with the overlay — shown in the hover tooltip when `options.hover` is on |
202
495
  */
203
496
  export const PluginAnalysisHeatmapGridCell = z.object({
204
497
  position: Vec3Components,
205
498
  value: z.number().finite(),
499
+ meta: z.unknown().optional(),
206
500
  })
207
501
  export type PluginAnalysisHeatmapGridCell = z.infer<
208
502
  typeof PluginAnalysisHeatmapGridCell
209
503
  >
210
504
 
505
+ /** A 6-digit hex colour like `"#0000ff"` (the `#` is optional). */
506
+ const HexColor = z
507
+ .string()
508
+ .regex(/^#?[0-9a-fA-F]{6}$/, 'expected a 6-digit hex colour like "#0000ff"')
509
+
510
+ /**
511
+ * One cell in {@linkcode PluginAnalysisHeatmapsApi.renderField} — either a
512
+ * planar polygon ring the host triangulates, or a pre-tessellated triangle
513
+ * mesh. All coordinates are **world coordinates** (y up).
514
+ *
515
+ * Polygon form:
516
+ *
517
+ * | Property | Type | Description |
518
+ * |---|---|---|
519
+ * | `polygon` | `{ x, y, z }[]` | Planar ring of 3+ points (any orientation; no self-intersections) |
520
+ * | `value` | `number` | The scalar value mapped to a colour |
521
+ * | `meta` | `unknown \| undefined` | Optional payload stored with the overlay |
522
+ *
523
+ * Pre-tessellated form:
524
+ *
525
+ * | Property | Type | Description |
526
+ * |---|---|---|
527
+ * | `vertices` | `{ x, y, z }[]` | Mesh vertices (3+) |
528
+ * | `indices` | `number[]` | Triangle list into `vertices` — length a multiple of 3, every index in range |
529
+ * | `value` | `number` | The scalar value mapped to a colour |
530
+ * | `meta` | `unknown \| undefined` | Optional payload stored with the overlay |
531
+ */
532
+ export const PluginAnalysisHeatmapFieldCell = z.union([
533
+ z.object({
534
+ polygon: z.array(Vec3Components).min(3),
535
+ value: z.number().finite(),
536
+ meta: z.unknown().optional(),
537
+ }),
538
+ z.object({
539
+ vertices: z.array(Vec3Components).min(3),
540
+ indices: z.array(z.number().int().nonnegative()).min(3),
541
+ value: z.number().finite(),
542
+ meta: z.unknown().optional(),
543
+ }),
544
+ ])
545
+ export type PluginAnalysisHeatmapFieldCell = z.infer<
546
+ typeof PluginAnalysisHeatmapFieldCell
547
+ >
548
+
549
+ /**
550
+ * The colour scale for a heatmap render — how values become colours and how
551
+ * the legend reads. Three kinds (discriminated by `type`):
552
+ *
553
+ * - **`bands`** — the default: the colour ramp quantised into `count`
554
+ * discrete bands (2–64, default 11). The rendered mesh and legend always
555
+ * use discrete bands; "continuous" is simply a high `count`.
556
+ * - **`threshold`** — binary pass/fail at a cut-off `threshold`: values
557
+ * `>= threshold` get `passColor`, the rest `failColor`; the legend shows
558
+ * the two labelled swatches.
559
+ * - **`categorical`** — exact-match classes: every cell value must equal
560
+ * one class's `value` (anything else is a validation error); the legend
561
+ * shows one labelled swatch per class.
562
+ *
563
+ * | Kind | Property | Type | Description |
564
+ * |---|---|---|---|
565
+ * | `bands` | `count` | `number \| undefined` | Number of discrete bands, 2–64 (default 11) |
566
+ * | `threshold` | `threshold` | `number` | Values `>= threshold` pass |
567
+ * | `threshold` | `passColor` | `string \| undefined` | Hex colour for passing values (host default green) |
568
+ * | `threshold` | `failColor` | `string \| undefined` | Hex colour for failing values (host default red) |
569
+ * | `threshold` | `passLabel` | `string \| undefined` | Legend label for the pass swatch (default `≥ threshold`) |
570
+ * | `threshold` | `failLabel` | `string \| undefined` | Legend label for the fail swatch (default `< threshold`) |
571
+ * | `categorical` | `classes` | `{ value, label, color }[]` | Exact-match classes: scalar `value`, legend `label`, hex `color` |
572
+ */
573
+ export const PluginAnalysisHeatmapScale = z.discriminatedUnion("type", [
574
+ z.object({
575
+ type: z.literal("bands"),
576
+ count: z.number().int().min(2).max(64).optional(),
577
+ }),
578
+ z.object({
579
+ type: z.literal("threshold"),
580
+ threshold: z.number().finite(),
581
+ passColor: HexColor.optional(),
582
+ failColor: HexColor.optional(),
583
+ passLabel: z.string().optional(),
584
+ failLabel: z.string().optional(),
585
+ }),
586
+ z.object({
587
+ type: z.literal("categorical"),
588
+ classes: z
589
+ .array(
590
+ z.object({
591
+ value: z.number().finite(),
592
+ label: z.string(),
593
+ color: HexColor,
594
+ }),
595
+ )
596
+ .min(1),
597
+ }),
598
+ ])
599
+ export type PluginAnalysisHeatmapScale = z.infer<
600
+ typeof PluginAnalysisHeatmapScale
601
+ >
602
+
211
603
  /**
212
604
  * Display options shared by both heatmap render modes.
213
605
  *
@@ -219,21 +611,24 @@ export type PluginAnalysisHeatmapGridCell = z.infer<
219
611
  *
220
612
  * | Property | Type | Description |
221
613
  * |---|---|---|
614
+ * | `name` | `string \| undefined` | Overlay name to render into (see {@linkcode PluginAnalysisHeatmapOverlaysApi}); a shared default overlay when omitted |
222
615
  * | `title` | `string \| undefined` | Legend title |
223
616
  * | `unit` | `string \| undefined` | Unit label shown on the legend (e.g. `"m/s"`) |
224
617
  * | `min` | `number \| undefined` | Value mapped to the first colour stop; data minimum when omitted |
225
618
  * | `max` | `number \| undefined` | Value mapped to the last colour stop; data maximum when omitted |
226
619
  * | `colors` | `string[] \| undefined` | Ordered 6-digit hex colour stops (e.g. `"#0000ff"`), low → high; host blue → cyan → green → yellow → red default |
620
+ * | `scale` | {@linkcode PluginAnalysisHeatmapScale}` \| undefined` | Colour scale: discrete `bands` (count 2–64, default 11), binary `threshold`, or exact-match `categorical` |
621
+ * | `hover` | `boolean \| undefined` | Show a tooltip with the hovered cell's `value` (+ its `meta`, when given) while this overlay is visible. Default `false` |
227
622
  */
228
623
  export const PluginAnalysisHeatmapOptions = z.object({
624
+ name: z.string().min(1).max(64).optional(),
229
625
  title: z.string().optional(),
230
626
  unit: z.string().optional(),
231
627
  min: z.number().finite().optional(),
232
628
  max: z.number().finite().optional(),
233
- colors: z
234
- .array(z.string().regex(/^#?[0-9a-fA-F]{6}$/, "expected a 6-digit hex colour like \"#0000ff\""))
235
- .min(1)
236
- .optional(),
629
+ colors: z.array(HexColor).min(1).optional(),
630
+ scale: PluginAnalysisHeatmapScale.optional(),
631
+ hover: z.boolean().optional(),
237
632
  })
238
633
  export type PluginAnalysisHeatmapOptions = z.infer<
239
634
  typeof PluginAnalysisHeatmapOptions
@@ -254,3 +649,35 @@ export const PluginAnalysisHeatmapsRenderResult = z.object({
254
649
  export type PluginAnalysisHeatmapsRenderResult = z.infer<
255
650
  typeof PluginAnalysisHeatmapsRenderResult
256
651
  >
652
+
653
+ /**
654
+ * One overlay row in {@linkcode PluginAnalysisHeatmapOverlaysApi.list}.
655
+ *
656
+ * | Property | Type | Description |
657
+ * |---|---|---|
658
+ * | `name` | `string` | The overlay's name (`options.name` at render time) |
659
+ * | `visible` | `boolean` | Whether this is the (single) visible overlay |
660
+ * | `kind` | `"spaces" \| "grid" \| "surfaceGrid" \| "field"` | Which render call produced it |
661
+ */
662
+ export const PluginAnalysisHeatmapOverlayInfo = z.object({
663
+ name: z.string(),
664
+ visible: z.boolean(),
665
+ kind: z.enum(["spaces", "grid", "surfaceGrid", "field"]),
666
+ })
667
+ export type PluginAnalysisHeatmapOverlayInfo = z.infer<
668
+ typeof PluginAnalysisHeatmapOverlayInfo
669
+ >
670
+
671
+ /**
672
+ * Result of {@linkcode PluginAnalysisHeatmapOverlaysApi.list}.
673
+ *
674
+ * | Property | Type | Description |
675
+ * |---|---|---|
676
+ * | `overlays` | {@linkcode PluginAnalysisHeatmapOverlayInfo}`[]` | Every registered overlay; at most one has `visible: true` |
677
+ */
678
+ export const PluginAnalysisHeatmapOverlaysListResult = z.object({
679
+ overlays: z.array(PluginAnalysisHeatmapOverlayInfo),
680
+ })
681
+ export type PluginAnalysisHeatmapOverlaysListResult = z.infer<
682
+ typeof PluginAnalysisHeatmapOverlaysListResult
683
+ >
@@ -3,6 +3,9 @@ import { PluginAnalysisShadowsApi } from "./shadows"
3
3
  import { PluginAnalysisSunlightHoursApi } from "./sunlightHours"
4
4
  import { PluginAnalysisIlluminanceApi } from "./illuminance"
5
5
  import { PluginAnalysisHeatmapsApi } from "./heatmaps"
6
+ import { PluginAnalysisWeatherApi } from "./weather"
7
+ import { PluginAnalysisSolarApi } from "./solar"
8
+ import { PluginAnalysisDaylightApi } from "./daylight"
6
9
 
7
10
  /**
8
11
  * Sustainability-analysis APIs.
@@ -21,6 +24,9 @@ import { PluginAnalysisHeatmapsApi } from "./heatmaps"
21
24
  * - {@linkcode PluginAnalysisApi.sunlightHours} — Direct-sunlight-hours heatmap job (start / poll / cancel / clear)
22
25
  * - {@linkcode PluginAnalysisApi.illuminance} — Daylight-illuminance heatmap job, Pro-gated (start / poll / cancel / clear)
23
26
  * - {@linkcode PluginAnalysisApi.heatmaps} — Render plugin-computed scalar data as a heatmap (per-space / grid)
27
+ * - {@linkcode PluginAnalysisApi.weather} — Read the site's typical-year weather series (numeric, paged)
28
+ * - {@linkcode PluginAnalysisApi.solar} — Sample solar exposure at caller-chosen points (numeric)
29
+ * - {@linkcode PluginAnalysisApi.daylight} — Standards-grade annual daylight metrics (IES LM-83, numeric)
24
30
  *
25
31
  * Accessed via `snaptrude.analysis`.
26
32
  */
@@ -35,6 +41,12 @@ export abstract class PluginAnalysisApi {
35
41
  public abstract illuminance: PluginAnalysisIlluminanceApi
36
42
  /** Plugin-supplied scalar-data heatmaps. See {@linkcode PluginAnalysisHeatmapsApi}. */
37
43
  public abstract heatmaps: PluginAnalysisHeatmapsApi
44
+ /** Site typical-year weather series (numeric, paged). See {@linkcode PluginAnalysisWeatherApi}. */
45
+ public abstract weather: PluginAnalysisWeatherApi
46
+ /** Per-point solar exposure sampling (numeric). See {@linkcode PluginAnalysisSolarApi}. */
47
+ public abstract solar: PluginAnalysisSolarApi
48
+ /** IES LM-83 annual daylight metrics (numeric). See {@linkcode PluginAnalysisDaylightApi}. */
49
+ public abstract daylight: PluginAnalysisDaylightApi
38
50
 
39
51
  constructor() {}
40
52
  }
@@ -44,3 +56,6 @@ export * from "./shadows"
44
56
  export * from "./sunlightHours"
45
57
  export * from "./illuminance"
46
58
  export * from "./heatmaps"
59
+ export * from "./weather"
60
+ export * from "./solar"
61
+ export * from "./daylight"