@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
@@ -0,0 +1,274 @@
1
+ import * as z from "zod"
2
+ import { PluginApiReturn } from "../../types"
3
+ import {
4
+ PluginAnnotateFill,
5
+ PluginAnnotateGeoKind,
6
+ PluginAnnotateTextColor,
7
+ PluginAnnotateTextSize,
8
+ } from "./annotate"
9
+
10
+ /**
11
+ * Presentation shapes — a stable, plugin-owned shape lifecycle on Present-mode
12
+ * sheets.
13
+ *
14
+ * Where {@linkcode PluginPresentationAnnotateApi} is fire-and-forget (every call
15
+ * creates a new shape), this namespace keys each shape by a caller-chosen
16
+ * string, so rerunning an analysis **updates** its sheet output in place instead
17
+ * of duplicating it:
18
+ *
19
+ * - {@linkcode PluginPresentationShapesApi.upsert} — create or update the shape for a key
20
+ * - {@linkcode PluginPresentationShapesApi.remove} — delete the shape for a key
21
+ * - {@linkcode PluginPresentationShapesApi.removeAll} — delete every shape owned by the calling plugin
22
+ * - {@linkcode PluginPresentationShapesApi.list} — the calling plugin's shapes (key, shapeId, type)
23
+ *
24
+ * Shape ids are derived deterministically from the plugin id + key, so upserts
25
+ * converge across collaborators. Mutations require **Present mode to be open**
26
+ * (`list` is a never-throw read); each plugin sees and mutates only its own
27
+ * shapes.
28
+ *
29
+ * Accessed via `snaptrude.presentation.shapes`.
30
+ */
31
+ export abstract class PluginPresentationShapesApi {
32
+ constructor() {}
33
+
34
+ /**
35
+ * Create or update the shape for a key.
36
+ *
37
+ * The `shape` spec is a discriminated union over the same four annotation
38
+ * kinds as `presentation.annotate` (`text`, `note`, `arrow`, `geo`), with the
39
+ * same option shapes. On first call for a key the shape is created on the
40
+ * target sheet (`options.sheetId`, default: the first sheet) and nested under
41
+ * the sheet frame; on later calls the existing shape is updated in place —
42
+ * same shape id, same sheet, one undo step. Positions/bounds are relative to
43
+ * the sheet's top-left, in canvas units. Requires Present mode to be open.
44
+ *
45
+ * @param key - Caller-chosen stable identifier for the shape (per plugin).
46
+ * @param shape - The desired shape ({@linkcode PluginPresentationShapeSpec}):
47
+ * `{ type: "text" | "note" | "arrow" | "geo", ... }` with the matching
48
+ * annotate options. Changing `type` for an existing key replaces the shape
49
+ * (same shape id).
50
+ * @param options - Optional `sheetId` — the sheet to create the shape on
51
+ * (defaults to the first sheet; ignored on update, the shape stays on its
52
+ * sheet).
53
+ * @returns A {@linkcode PluginPresentationShapesUpsertResult} — the stable
54
+ * `shapeId` and `created` (`true` on first upsert, `false` on update).
55
+ * @throws If Present mode is not open, the args are invalid, or the sheet id
56
+ * is unknown.
57
+ *
58
+ * @examplePrompt Update the analysis label on the sheet instead of adding another one
59
+ * @examplePrompt Rerun the daylight report and refresh its annotations
60
+ * @examplePrompt Keep one "FAR: 2.4" text on Sheet 1 that updates on each run
61
+ * @examplePrompt Draw a highlight rectangle the plugin can move on the next run
62
+ *
63
+ * # Example
64
+ * ```ts
65
+ * const { shapeId, created } = await snaptrude.presentation.shapes.upsert("far-label", {
66
+ * type: "text",
67
+ * text: "FAR: 2.4",
68
+ * position: { x: 40, y: 40 },
69
+ * color: "blue",
70
+ * })
71
+ * ```
72
+ */
73
+ public abstract upsert(
74
+ key: string,
75
+ shape: PluginPresentationShapeSpec,
76
+ options?: { sheetId?: string },
77
+ ): PluginApiReturn<PluginPresentationShapesUpsertResult>
78
+
79
+ /**
80
+ * Delete the shape for a key.
81
+ *
82
+ * Removes the calling plugin's shape registered under `key`, if it exists.
83
+ * Requires Present mode to be open.
84
+ *
85
+ * @param key - The key passed to {@linkcode PluginPresentationShapesApi.upsert}.
86
+ * @returns `true` if a shape was deleted, `false` if no shape exists for the
87
+ * key.
88
+ * @throws If Present mode is not open or the key is invalid.
89
+ *
90
+ * @examplePrompt Remove the FAR label from the sheet
91
+ * @examplePrompt Delete the highlight the plugin added earlier
92
+ * @examplePrompt Clear the "far-label" annotation
93
+ *
94
+ * # Example
95
+ * ```ts
96
+ * const removed = await snaptrude.presentation.shapes.remove("far-label")
97
+ * ```
98
+ */
99
+ public abstract remove(key: string): PluginApiReturn<boolean>
100
+
101
+ /**
102
+ * Delete every shape owned by the calling plugin.
103
+ *
104
+ * Removes all shapes the calling plugin has upserted (across all sheets) in
105
+ * one undo step. Requires Present mode to be open.
106
+ *
107
+ * @returns The number of shapes deleted.
108
+ * @throws If Present mode is not open.
109
+ *
110
+ * @examplePrompt Clear all annotations my plugin added
111
+ * @examplePrompt Reset the sheet output before rerunning the analysis
112
+ * @examplePrompt Remove every plugin-drawn shape
113
+ *
114
+ * # Example
115
+ * ```ts
116
+ * const deleted = await snaptrude.presentation.shapes.removeAll()
117
+ * ```
118
+ */
119
+ public abstract removeAll(): PluginApiReturn<number>
120
+
121
+ /**
122
+ * List the calling plugin's shapes.
123
+ *
124
+ * Returns one entry per live shape the calling plugin has upserted — its
125
+ * `key`, the underlying canvas `shapeId`, and the shape `type`. A never-throw
126
+ * read: returns an empty list when Present mode is closed.
127
+ *
128
+ * @returns A {@linkcode PluginPresentationShapesListResult} with the `shapes`
129
+ * array (empty when Present mode is closed).
130
+ *
131
+ * @examplePrompt Which annotations has my plugin placed on the sheets?
132
+ * @examplePrompt List the plugin's shapes before refreshing them
133
+ * @examplePrompt Check whether the FAR label already exists
134
+ *
135
+ * # Example
136
+ * ```ts
137
+ * const { shapes } = await snaptrude.presentation.shapes.list()
138
+ * const hasLabel = shapes.some((s) => s.key === "far-label")
139
+ * ```
140
+ */
141
+ public abstract list(): PluginApiReturn<PluginPresentationShapesListResult>
142
+ }
143
+
144
+ /**
145
+ * Desired shape for {@linkcode PluginPresentationShapesApi.upsert} — a
146
+ * discriminated union over the four annotate kinds, with the same option shapes
147
+ * as the matching `presentation.annotate` call:
148
+ *
149
+ * | `type` | Payload | Mirrors |
150
+ * |---|---|---|
151
+ * | `"text"` | `text`, `position?`, `size?`, `color?` | `annotate.text` |
152
+ * | `"note"` | `text`, `position?`, `color?`, `size?` | `annotate.note` |
153
+ * | `"arrow"` | `start`, `end`, `color?`, `size?` | `annotate.arrow` |
154
+ * | `"geo"` | `kind`, `bounds`, `color?`, `fill?` | `annotate.shape` |
155
+ *
156
+ * Positions/bounds are relative to the sheet's top-left, in canvas units.
157
+ */
158
+ export const PluginPresentationShapeSpec = z.discriminatedUnion("type", [
159
+ z.object({
160
+ type: z.literal("text"),
161
+ text: z.string(),
162
+ position: z.object({ x: z.number(), y: z.number() }).optional(),
163
+ size: PluginAnnotateTextSize.optional(),
164
+ color: PluginAnnotateTextColor.optional(),
165
+ }),
166
+ z.object({
167
+ type: z.literal("note"),
168
+ text: z.string(),
169
+ position: z.object({ x: z.number(), y: z.number() }).optional(),
170
+ color: PluginAnnotateTextColor.optional(),
171
+ size: PluginAnnotateTextSize.optional(),
172
+ }),
173
+ z.object({
174
+ type: z.literal("arrow"),
175
+ start: z.object({ x: z.number(), y: z.number() }),
176
+ end: z.object({ x: z.number(), y: z.number() }),
177
+ color: PluginAnnotateTextColor.optional(),
178
+ size: PluginAnnotateTextSize.optional(),
179
+ }),
180
+ z.object({
181
+ type: z.literal("geo"),
182
+ kind: PluginAnnotateGeoKind,
183
+ bounds: z.object({
184
+ x: z.number(),
185
+ y: z.number(),
186
+ w: z.number().positive(),
187
+ h: z.number().positive(),
188
+ }),
189
+ color: PluginAnnotateTextColor.optional(),
190
+ fill: PluginAnnotateFill.optional(),
191
+ }),
192
+ ])
193
+ export type PluginPresentationShapeSpec = z.infer<
194
+ typeof PluginPresentationShapeSpec
195
+ >
196
+
197
+ /** The shape kind of a plugin-owned shape (the `type` discriminant of {@linkcode PluginPresentationShapeSpec}). */
198
+ export const PluginPresentationShapeType = z.enum([
199
+ "text",
200
+ "note",
201
+ "arrow",
202
+ "geo",
203
+ ])
204
+ export type PluginPresentationShapeType = z.infer<
205
+ typeof PluginPresentationShapeType
206
+ >
207
+
208
+ /**
209
+ * Arguments for {@linkcode PluginPresentationShapesApi.upsert}.
210
+ *
211
+ * | Property | Type | Description |
212
+ * |---|---|---|
213
+ * | `key` | `string` | Caller-chosen stable identifier (non-empty) |
214
+ * | `shape` | {@linkcode PluginPresentationShapeSpec} | The desired shape |
215
+ * | `sheetId` | `string \| undefined` | Sheet to create on (default: first sheet; ignored on update) |
216
+ */
217
+ export const PluginPresentationShapesUpsertArgs = z.object({
218
+ key: z.string().min(1),
219
+ shape: PluginPresentationShapeSpec,
220
+ sheetId: z.string().optional(),
221
+ })
222
+ export type PluginPresentationShapesUpsertArgs = z.infer<
223
+ typeof PluginPresentationShapesUpsertArgs
224
+ >
225
+
226
+ /**
227
+ * Result of {@linkcode PluginPresentationShapesApi.upsert}.
228
+ *
229
+ * | Property | Type | Description |
230
+ * |---|---|---|
231
+ * | `shapeId` | `string` | Stable id of the canvas shape for the key |
232
+ * | `created` | `boolean` | `true` if the shape was created, `false` if updated |
233
+ */
234
+ export const PluginPresentationShapesUpsertResult = z.object({
235
+ shapeId: z.string(),
236
+ created: z.boolean(),
237
+ })
238
+ export type PluginPresentationShapesUpsertResult = z.infer<
239
+ typeof PluginPresentationShapesUpsertResult
240
+ >
241
+
242
+ /**
243
+ * Arguments for {@linkcode PluginPresentationShapesApi.remove}.
244
+ *
245
+ * | Property | Type | Description |
246
+ * |---|---|---|
247
+ * | `key` | `string` | The key the shape was upserted under (non-empty) |
248
+ */
249
+ export const PluginPresentationShapesRemoveArgs = z.object({
250
+ key: z.string().min(1),
251
+ })
252
+ export type PluginPresentationShapesRemoveArgs = z.infer<
253
+ typeof PluginPresentationShapesRemoveArgs
254
+ >
255
+
256
+ /**
257
+ * Result of {@linkcode PluginPresentationShapesApi.list}.
258
+ *
259
+ * | Property | Type | Description |
260
+ * |---|---|---|
261
+ * | `shapes` | `{ key: string; shapeId: string; type: `{@linkcode PluginPresentationShapeType}` }[]` | One entry per live plugin-owned shape |
262
+ */
263
+ export const PluginPresentationShapesListResult = z.object({
264
+ shapes: z.array(
265
+ z.object({
266
+ key: z.string(),
267
+ shapeId: z.string(),
268
+ type: PluginPresentationShapeType,
269
+ }),
270
+ ),
271
+ })
272
+ export type PluginPresentationShapesListResult = z.infer<
273
+ typeof PluginPresentationShapesListResult
274
+ >
@@ -186,6 +186,56 @@ export abstract class PluginProgramSiteApi {
186
186
  * ```
187
187
  */
188
188
  public abstract getNorthAngle(): PluginApiReturn<PluginProgramSiteNorthAngleResult>
189
+
190
+ /**
191
+ * Get the IANA timezone of the project's geographic location.
192
+ *
193
+ * The timezone identifier (e.g. `"America/New_York"`, `"Asia/Kolkata"`)
194
+ * looked up from the same geo-location
195
+ * {@linkcode PluginProgramSiteApi.getLocation} returns. Returns `null` when
196
+ * the project is not geo-located on terrain.
197
+ *
198
+ * @returns A {@linkcode PluginProgramSiteTimezoneResult} — the IANA timezone
199
+ * id string, or `null` when the project has no geo-located site.
200
+ *
201
+ * @examplePrompt What timezone is this project in?
202
+ * @examplePrompt Get the site's IANA timezone
203
+ * @examplePrompt What's the local time zone at the project location?
204
+ * @examplePrompt Which timezone should I use for the sun study schedule?
205
+ *
206
+ * # Example
207
+ * ```ts
208
+ * const timezone = await snaptrude.program.site.getTimezone()
209
+ * if (timezone) console.log(`Project timezone: ${timezone}`)
210
+ * ```
211
+ */
212
+ public abstract getTimezone(): PluginApiReturn<PluginProgramSiteTimezoneResult>
213
+
214
+ /**
215
+ * Get the weather file resolved for the project site.
216
+ *
217
+ * A lightweight read of the EPW weather file the `analysis.*` sun and
218
+ * daylight studies compute against — its catalog id, station name, content
219
+ * checksum, and how it was selected. This is the same file (same
220
+ * `weatherFileId`) that {@linkcode PluginAnalysisWeatherApi.getSeries} reads
221
+ * rows from and that a daylight run echoes in its provenance. Returns `null`
222
+ * when the project has no geo-located site (no weather file to resolve).
223
+ *
224
+ * @returns A {@linkcode PluginProgramSiteWeatherResult} —
225
+ * `{ weatherFileId, station, checksum, selectionMethod }`, or `null` when
226
+ * the project has no geo-located site.
227
+ *
228
+ * @examplePrompt Which weather file is my project using?
229
+ * @examplePrompt What EPW station was resolved for this site?
230
+ * @examplePrompt Get the weather file id for the daylight analysis
231
+ *
232
+ * # Example
233
+ * ```ts
234
+ * const weather = await snaptrude.program.site.getWeather()
235
+ * if (weather) console.log(weather.station, weather.selectionMethod)
236
+ * ```
237
+ */
238
+ public abstract getWeather(): PluginApiReturn<PluginProgramSiteWeatherResult>
189
239
  }
190
240
 
191
241
  /**
@@ -420,3 +470,48 @@ export const PluginProgramSiteNorthAngleResult = z.number().nullable()
420
470
  export type PluginProgramSiteNorthAngleResult = z.infer<
421
471
  typeof PluginProgramSiteNorthAngleResult
422
472
  >
473
+
474
+ /**
475
+ * Result of {@linkcode PluginProgramSiteApi.getTimezone} — the IANA timezone
476
+ * id of the project's geo-location (e.g. `"Europe/London"`), or `null` when
477
+ * the project is not geo-located.
478
+ */
479
+ export const PluginProgramSiteTimezoneResult = z.string().nullable()
480
+ export type PluginProgramSiteTimezoneResult = z.infer<
481
+ typeof PluginProgramSiteTimezoneResult
482
+ >
483
+
484
+ /**
485
+ * A summary of the weather file resolved for the project site. Matches the
486
+ * relevant fields of `analysis.weather`'s `PluginWeatherSourceMeta`;
487
+ * `selectionMethod` is one of `"nearest-station"`, `"user-upload"`, or
488
+ * `"project-override"`.
489
+ *
490
+ * | Property | Type | Description |
491
+ * |---|---|---|
492
+ * | `weatherFileId` | `string` | Catalog id (same key as `core.io.import.epw`) |
493
+ * | `station` | `string` | Station/file name |
494
+ * | `checksum` | `string` | sha256 of the EPW file |
495
+ * | `selectionMethod` | `"nearest-station" \| "user-upload" \| "project-override"` | How the file was matched |
496
+ */
497
+ export const PluginProgramSiteWeather = z.object({
498
+ weatherFileId: z.string(),
499
+ station: z.string(),
500
+ checksum: z.string(),
501
+ selectionMethod: z.enum([
502
+ "nearest-station",
503
+ "user-upload",
504
+ "project-override",
505
+ ]),
506
+ })
507
+ export type PluginProgramSiteWeather = z.infer<typeof PluginProgramSiteWeather>
508
+
509
+ /**
510
+ * Result of {@linkcode PluginProgramSiteApi.getWeather} — the resolved weather
511
+ * file summary, or `null` when the project has no geo-located site.
512
+ */
513
+ export const PluginProgramSiteWeatherResult =
514
+ PluginProgramSiteWeather.nullable()
515
+ export type PluginProgramSiteWeatherResult = z.infer<
516
+ typeof PluginProgramSiteWeatherResult
517
+ >
@@ -68,6 +68,12 @@ export abstract class PluginProgramSpreadsheetApi {
68
68
  */
69
69
  public abstract bindings: PluginProgramSpreadsheetBindingsApi
70
70
 
71
+ /**
72
+ * Plugin datasets — named, plugin-supplied row sets that bindings can render
73
+ * and auto-refresh on rerun. See {@linkcode PluginProgramSpreadsheetDatasetsApi}.
74
+ */
75
+ public abstract datasets: PluginProgramSpreadsheetDatasetsApi
76
+
71
77
  /**
72
78
  * Render a titled, formatted table onto a named sheet (create-or-replace).
73
79
  *
@@ -204,6 +210,9 @@ export abstract class PluginProgramSpreadsheetApi {
204
210
  * single-sheet: it serializes one sheet's used range — pass `options.sheetName`
205
211
  * to choose which sheet, or omit it to serialize the active sheet.
206
212
  *
213
+ * For a different export intent, see `core.io.export.model` (the 3D model
214
+ * file) and `presentation.export` (Present-mode layout sheets to PDF/PNG/JPG).
215
+ *
207
216
  * @param format - The file format to export to.
208
217
  * @param options - Optional `fileName` (without extension; a default is used
209
218
  * when omitted) and `sheetName` — the sheet to serialize for `csv`
@@ -315,6 +324,55 @@ export abstract class PluginProgramSpreadsheetApi {
315
324
  },
316
325
  ): PluginApiReturn<PluginProgramSpreadsheetAddChartResult>
317
326
 
327
+ /**
328
+ * Place a floating image on a workbook sheet from a base64 data URI.
329
+ *
330
+ * The image is anchored at a cell's top-left corner (default `"A1"`) and
331
+ * floats over the grid (a SpreadJS floating picture — it does not occupy
332
+ * cells). Only base64 `data:image/png|jpeg|svg+xml` URIs are accepted —
333
+ * never URLs — and the URI is capped at 2 MB. Passing a `name` gives the
334
+ * picture a stable id: re-running with the same `name` replaces the previous
335
+ * picture (rerun-friendly); when omitted a name is generated.
336
+ *
337
+ * For images on Present-mode sheets (the tldraw canvas), use
338
+ * `presentation.shapes` instead — this verb places floating pictures in the
339
+ * SpreadJS workbook.
340
+ *
341
+ * @param sheetName - Sheet to place the image on.
342
+ * @param image - The image payload: a base64 `dataUri`
343
+ * (`data:image/png|jpeg|svg+xml;base64,…`, max 2 MB).
344
+ * @param options - Optional `anchorCell` (A1 notation, default `"A1"`),
345
+ * `size` in px (intrinsic size when omitted), and stable `name`
346
+ * (replace-on-rerun).
347
+ * @returns A {@linkcode PluginProgramSpreadsheetAddImageResult} with the sheet
348
+ * and the picture's name.
349
+ * @throws If the sheet does not exist, or the data URI is not an accepted
350
+ * image type or exceeds 2 MB.
351
+ *
352
+ * @examplePrompt Add my logo image to the report sheet
353
+ * @examplePrompt Place a PNG at cell B2 on the Takeoff sheet
354
+ * @examplePrompt Put an SVG diagram on a spreadsheet sheet
355
+ * @examplePrompt Insert an image into the workbook
356
+ *
357
+ * # Example
358
+ * ```ts
359
+ * const { name } = await snaptrude.program.spreadsheet.addImage(
360
+ * "Report",
361
+ * { dataUri: "data:image/png;base64,iVBORw0..." },
362
+ * { anchorCell: "B2", size: { width: 240, height: 120 }, name: "logo" },
363
+ * )
364
+ * ```
365
+ */
366
+ public abstract addImage(
367
+ sheetName: string,
368
+ image: { dataUri: string },
369
+ options?: {
370
+ anchorCell?: string
371
+ size?: { width: number; height: number }
372
+ name?: string
373
+ },
374
+ ): PluginApiReturn<PluginProgramSpreadsheetAddImageResult>
375
+
318
376
  /**
319
377
  * Highlight a range of cells (background color and optional emphasis).
320
378
  *
@@ -911,6 +969,76 @@ export abstract class PluginProgramSpreadsheetBindingsApi {
911
969
  public abstract list(): PluginApiReturn<PluginProgramSpreadsheetBindingsListResult>
912
970
  }
913
971
 
972
+ /**
973
+ * Program-spreadsheet plugin datasets — named, plugin-supplied row sets stored
974
+ * alongside the workbook so bindings can render and re-render them.
975
+ *
976
+ * A dataset is uploaded with {@linkcode PluginProgramSpreadsheetDatasetsApi.set}
977
+ * (upsert by name). A binding whose source is `{ dataset: "plugin", name }`
978
+ * renders the dataset at its target anchor, and re-renders automatically every
979
+ * time the dataset is set again — the "refresh on rerun" behaviour. Accessed
980
+ * via `snaptrude.program.spreadsheet.datasets`.
981
+ */
982
+ export abstract class PluginProgramSpreadsheetDatasetsApi {
983
+ constructor() {}
984
+
985
+ /**
986
+ * Store (upsert) a named plugin dataset.
987
+ *
988
+ * Re-using a `name` replaces the dataset and automatically refreshes every
989
+ * binding whose source is `{ dataset: "plugin", name }` — no extra plugin
990
+ * calls needed to keep bound ranges current. Limits: at most 10,000 rows and
991
+ * 2 MB serialized.
992
+ *
993
+ * @param name - Dataset name (re-using a name overwrites it).
994
+ * @param columns - Column definitions (see {@linkcode PluginSpreadsheetColumn});
995
+ * their order is the column order bindings render.
996
+ * @param rows - Row objects keyed by column `key`.
997
+ * @returns A {@linkcode PluginProgramSpreadsheetDatasetsSetResult} with the
998
+ * stored row count.
999
+ * @throws If the dataset exceeds 10,000 rows or 2 MB serialized.
1000
+ *
1001
+ * @examplePrompt Store my plugin's rows as a named dataset
1002
+ * @examplePrompt Upload data the sheets can bind to and refresh
1003
+ * @examplePrompt Save a dataset for a live sheet binding
1004
+ * @examplePrompt Push my computed table so a binding keeps it fresh
1005
+ *
1006
+ * # Example
1007
+ * ```ts
1008
+ * await snaptrude.program.spreadsheet.datasets.set(
1009
+ * "costs",
1010
+ * [
1011
+ * { key: "item", header: "Item" },
1012
+ * { key: "cost", header: "Cost", align: "right" },
1013
+ * ],
1014
+ * [{ item: "Concrete", cost: 120 }],
1015
+ * )
1016
+ * ```
1017
+ */
1018
+ public abstract set(
1019
+ name: string,
1020
+ columns: PluginSpreadsheetColumn[],
1021
+ rows: Record<string, PluginSpreadsheetCell>[],
1022
+ ): PluginApiReturn<PluginProgramSpreadsheetDatasetsSetResult>
1023
+
1024
+ /**
1025
+ * List the stored plugin datasets.
1026
+ *
1027
+ * @returns A {@linkcode PluginProgramSpreadsheetDatasetsListResult} with each
1028
+ * dataset's `name`, `rowCount`, and `updatedAt` timestamp.
1029
+ *
1030
+ * @examplePrompt List my plugin datasets
1031
+ * @examplePrompt What datasets are stored for the sheets?
1032
+ * @examplePrompt Show the data sets bindings can use
1033
+ *
1034
+ * # Example
1035
+ * ```ts
1036
+ * const { datasets } = await snaptrude.program.spreadsheet.datasets.list()
1037
+ * ```
1038
+ */
1039
+ public abstract list(): PluginApiReturn<PluginProgramSpreadsheetDatasetsListResult>
1040
+ }
1041
+
914
1042
  /** A single spreadsheet cell value. */
915
1043
  export const PluginSpreadsheetCell = z.union([
916
1044
  z.string(),
@@ -2011,16 +2139,18 @@ export type PluginProgramSpreadsheetTemplatesApplyResult = z.infer<
2011
2139
  // --- bindings.* ------------------------------------------------------------
2012
2140
 
2013
2141
  /**
2014
- * A binding's data source. Currently only the takeoff dataset; `program` is
2015
- * reserved for a future source.
2142
+ * A binding's data source the built-in takeoff dataset, or a named plugin
2143
+ * dataset stored via {@linkcode PluginProgramSpreadsheetDatasetsApi.set}.
2016
2144
  *
2017
2145
  * | Property | Type | Description |
2018
2146
  * |---|---|---|
2019
- * | `dataset` | `"takeoff"` | The dataset the binding pulls from |
2147
+ * | `dataset` | `"takeoff" \| "plugin"` | The dataset kind the binding pulls from |
2148
+ * | `name` | `string` | (`"plugin"` only) The stored dataset's name |
2020
2149
  */
2021
- export const PluginSpreadsheetBindingSource = z.object({
2022
- dataset: z.enum(["takeoff"]),
2023
- })
2150
+ export const PluginSpreadsheetBindingSource = z.discriminatedUnion("dataset", [
2151
+ z.object({ dataset: z.literal("takeoff") }),
2152
+ z.object({ dataset: z.literal("plugin"), name: z.string() }),
2153
+ ])
2024
2154
  export type PluginSpreadsheetBindingSource = z.infer<
2025
2155
  typeof PluginSpreadsheetBindingSource
2026
2156
  >
@@ -2169,3 +2299,118 @@ export const PluginProgramSpreadsheetBindingsListResult = z.object({
2169
2299
  export type PluginProgramSpreadsheetBindingsListResult = z.infer<
2170
2300
  typeof PluginProgramSpreadsheetBindingsListResult
2171
2301
  >
2302
+
2303
+ // --- datasets.* ------------------------------------------------------------
2304
+
2305
+ /**
2306
+ * Arguments for {@linkcode PluginProgramSpreadsheetDatasetsApi.set}.
2307
+ *
2308
+ * | Property | Type | Description |
2309
+ * |---|---|---|
2310
+ * | `name` | `string` | Dataset name (re-using a name overwrites it) |
2311
+ * | `columns` | {@linkcode PluginSpreadsheetColumn}`[]` | Column definitions (at least one) |
2312
+ * | `rows` | `Record<string, cell>[]` | Row objects keyed by column `key` (max 10,000) |
2313
+ */
2314
+ export const PluginProgramSpreadsheetDatasetsSetArgs = z.object({
2315
+ name: z.string(),
2316
+ columns: z.array(PluginSpreadsheetColumn).min(1),
2317
+ rows: z.array(z.record(z.string(), PluginSpreadsheetCell)).max(10_000),
2318
+ })
2319
+ export type PluginProgramSpreadsheetDatasetsSetArgs = z.infer<
2320
+ typeof PluginProgramSpreadsheetDatasetsSetArgs
2321
+ >
2322
+
2323
+ /**
2324
+ * Result of {@linkcode PluginProgramSpreadsheetDatasetsApi.set}.
2325
+ *
2326
+ * | Property | Type | Description |
2327
+ * |---|---|---|
2328
+ * | `name` | `string` | The stored dataset's name |
2329
+ * | `rowCount` | `number` | Number of rows stored |
2330
+ */
2331
+ export const PluginProgramSpreadsheetDatasetsSetResult = z.object({
2332
+ name: z.string(),
2333
+ rowCount: z.number(),
2334
+ })
2335
+ export type PluginProgramSpreadsheetDatasetsSetResult = z.infer<
2336
+ typeof PluginProgramSpreadsheetDatasetsSetResult
2337
+ >
2338
+
2339
+ /**
2340
+ * A stored plugin dataset's summary, for
2341
+ * {@linkcode PluginProgramSpreadsheetDatasetsApi.list}.
2342
+ *
2343
+ * | Property | Type | Description |
2344
+ * |---|---|---|
2345
+ * | `name` | `string` | Dataset name |
2346
+ * | `rowCount` | `number` | Number of rows stored |
2347
+ * | `updatedAt` | `string` | ISO timestamp of the last `set` |
2348
+ */
2349
+ export const PluginSpreadsheetDataset = z.object({
2350
+ name: z.string(),
2351
+ rowCount: z.number(),
2352
+ updatedAt: z.string(),
2353
+ })
2354
+ export type PluginSpreadsheetDataset = z.infer<typeof PluginSpreadsheetDataset>
2355
+
2356
+ /**
2357
+ * Result of {@linkcode PluginProgramSpreadsheetDatasetsApi.list}.
2358
+ *
2359
+ * | Property | Type | Description |
2360
+ * |---|---|---|
2361
+ * | `datasets` | {@linkcode PluginSpreadsheetDataset}`[]` | Every stored dataset |
2362
+ */
2363
+ export const PluginProgramSpreadsheetDatasetsListResult = z.object({
2364
+ datasets: z.array(PluginSpreadsheetDataset),
2365
+ })
2366
+ export type PluginProgramSpreadsheetDatasetsListResult = z.infer<
2367
+ typeof PluginProgramSpreadsheetDatasetsListResult
2368
+ >
2369
+
2370
+ // --- addImage --------------------------------------------------------------
2371
+
2372
+ /**
2373
+ * Arguments for {@linkcode PluginProgramSpreadsheetApi.addImage}.
2374
+ *
2375
+ * | Property | Type | Description |
2376
+ * |---|---|---|
2377
+ * | `sheetName` | `string` | Sheet to place the image on |
2378
+ * | `image` | `{ dataUri: string }` | Base64 `data:image/png\|jpeg\|svg+xml` URI (max 2 MB; URLs rejected) |
2379
+ * | `anchorCell` | `string?` | Anchor cell in A1 notation (default `"A1"`) |
2380
+ * | `size` | `{ width; height }?` | Size in px; intrinsic size when omitted |
2381
+ * | `name` | `string?` | Stable picture id — same name replaces on rerun; generated when omitted |
2382
+ */
2383
+ export const PluginProgramSpreadsheetAddImageArgs = z.object({
2384
+ sheetName: z.string(),
2385
+ image: z.object({
2386
+ dataUri: z
2387
+ .string()
2388
+ .max(2_097_152, "image.dataUri exceeds the 2 MB limit")
2389
+ .regex(
2390
+ /^data:image\/(png|jpeg|svg\+xml);base64,/,
2391
+ "image.dataUri must be a base64 data:image/png, jpeg, or svg+xml URI",
2392
+ ),
2393
+ }),
2394
+ anchorCell: z.string().optional(),
2395
+ size: z.object({ width: z.number(), height: z.number() }).optional(),
2396
+ name: z.string().optional(),
2397
+ })
2398
+ export type PluginProgramSpreadsheetAddImageArgs = z.infer<
2399
+ typeof PluginProgramSpreadsheetAddImageArgs
2400
+ >
2401
+
2402
+ /**
2403
+ * Result of {@linkcode PluginProgramSpreadsheetApi.addImage}.
2404
+ *
2405
+ * | Property | Type | Description |
2406
+ * |---|---|---|
2407
+ * | `sheetName` | `string` | The sheet the image was placed on |
2408
+ * | `name` | `string` | The picture's (stable or generated) name |
2409
+ */
2410
+ export const PluginProgramSpreadsheetAddImageResult = z.object({
2411
+ sheetName: z.string(),
2412
+ name: z.string(),
2413
+ })
2414
+ export type PluginProgramSpreadsheetAddImageResult = z.infer<
2415
+ typeof PluginProgramSpreadsheetAddImageResult
2416
+ >