@snaptrude/plugin-core 0.7.1 → 0.8.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 (130) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/api-manifest.full.json +6442 -0
  3. package/api-manifest.json +2029 -128
  4. package/dist/api/core/camera/index.d.ts +208 -0
  5. package/dist/api/core/camera/index.d.ts.map +1 -0
  6. package/dist/api/core/comment/index.d.ts +105 -2
  7. package/dist/api/core/comment/index.d.ts.map +1 -1
  8. package/dist/api/core/geom/create/index.d.ts +13 -13
  9. package/dist/api/core/geom/delete/index.d.ts +8 -2
  10. package/dist/api/core/geom/delete/index.d.ts.map +1 -1
  11. package/dist/api/core/geom/query/arc.d.ts +5 -5
  12. package/dist/api/core/geom/query/brep.d.ts +18 -18
  13. package/dist/api/core/geom/query/circle.d.ts +18 -18
  14. package/dist/api/core/geom/query/contour.d.ts +20 -20
  15. package/dist/api/core/geom/query/curve.d.ts +49 -49
  16. package/dist/api/core/geom/query/edge.d.ts +5 -5
  17. package/dist/api/core/geom/query/face.d.ts +16 -16
  18. package/dist/api/core/geom/query/halfedge.d.ts +8 -8
  19. package/dist/api/core/geom/query/profile.d.ts +19 -19
  20. package/dist/api/core/geom/query/vertex.d.ts +8 -8
  21. package/dist/api/core/geom/update/contour.d.ts +14 -14
  22. package/dist/api/core/geom/update/curve.d.ts +7 -7
  23. package/dist/api/core/geom/update/profile.d.ts +16 -16
  24. package/dist/api/core/handles/index.d.ts +210 -0
  25. package/dist/api/core/handles/index.d.ts.map +1 -0
  26. package/dist/api/core/index.d.ts +15 -0
  27. package/dist/api/core/index.d.ts.map +1 -1
  28. package/dist/api/core/io/export/index.d.ts +132 -0
  29. package/dist/api/core/io/export/index.d.ts.map +1 -0
  30. package/dist/api/core/io/import/index.d.ts +1 -1
  31. package/dist/api/core/io/index.d.ts +5 -0
  32. package/dist/api/core/io/index.d.ts.map +1 -1
  33. package/dist/api/core/layers.d.ts +7 -7
  34. package/dist/api/core/proposals/index.d.ts +65 -9
  35. package/dist/api/core/proposals/index.d.ts.map +1 -1
  36. package/dist/api/core/user.d.ts +44 -0
  37. package/dist/api/core/user.d.ts.map +1 -0
  38. package/dist/api/design/boolean/index.d.ts +4 -4
  39. package/dist/api/design/create/index.d.ts +139 -44
  40. package/dist/api/design/create/index.d.ts.map +1 -1
  41. package/dist/api/design/doors/index.d.ts +36 -0
  42. package/dist/api/design/doors/index.d.ts.map +1 -1
  43. package/dist/api/design/edit/index.d.ts +1 -1
  44. package/dist/api/design/erase/index.d.ts +2 -2
  45. package/dist/api/design/furniture/index.d.ts +114 -3
  46. package/dist/api/design/furniture/index.d.ts.map +1 -1
  47. package/dist/api/design/index.d.ts +10 -0
  48. package/dist/api/design/index.d.ts.map +1 -1
  49. package/dist/api/design/materials/index.d.ts +111 -14
  50. package/dist/api/design/materials/index.d.ts.map +1 -1
  51. package/dist/api/design/query/index.d.ts +31 -1
  52. package/dist/api/design/query/index.d.ts.map +1 -1
  53. package/dist/api/design/query/spaces.d.ts +5 -5
  54. package/dist/api/design/transform/index.d.ts +83 -14
  55. package/dist/api/design/transform/index.d.ts.map +1 -1
  56. package/dist/api/design/types/index.d.ts +181 -0
  57. package/dist/api/design/types/index.d.ts.map +1 -0
  58. package/dist/api/design/update/index.d.ts +335 -2
  59. package/dist/api/design/update/index.d.ts.map +1 -1
  60. package/dist/api/design/visibility.d.ts +98 -0
  61. package/dist/api/design/visibility.d.ts.map +1 -0
  62. package/dist/api/entity/referenceLine.d.ts +2 -2
  63. package/dist/api/entity/space.d.ts +19 -19
  64. package/dist/api/entity/story.d.ts +128 -15
  65. package/dist/api/entity/story.d.ts.map +1 -1
  66. package/dist/api/presentation/annotate.d.ts +448 -0
  67. package/dist/api/presentation/annotate.d.ts.map +1 -0
  68. package/dist/api/presentation/diagrams.d.ts +49 -8
  69. package/dist/api/presentation/diagrams.d.ts.map +1 -1
  70. package/dist/api/presentation/export.d.ts +104 -0
  71. package/dist/api/presentation/export.d.ts.map +1 -0
  72. package/dist/api/presentation/index.d.ts +38 -0
  73. package/dist/api/presentation/index.d.ts.map +1 -1
  74. package/dist/api/presentation/sheets.d.ts +410 -13
  75. package/dist/api/presentation/sheets.d.ts.map +1 -1
  76. package/dist/api/presentation/views.d.ts +165 -10
  77. package/dist/api/presentation/views.d.ts.map +1 -1
  78. package/dist/api/program/areas.d.ts +63 -3
  79. package/dist/api/program/areas.d.ts.map +1 -1
  80. package/dist/api/program/cores.d.ts +3 -99
  81. package/dist/api/program/cores.d.ts.map +1 -1
  82. package/dist/api/program/index.d.ts +2 -2
  83. package/dist/api/program/index.d.ts.map +1 -1
  84. package/dist/api/program/layout.d.ts +172 -12
  85. package/dist/api/program/layout.d.ts.map +1 -1
  86. package/dist/api/program/site.d.ts +11 -8
  87. package/dist/api/program/site.d.ts.map +1 -1
  88. package/dist/api/program/spreadsheet.d.ts +105 -13
  89. package/dist/api/program/spreadsheet.d.ts.map +1 -1
  90. package/dist/handles.d.ts +64 -25
  91. package/dist/handles.d.ts.map +1 -1
  92. package/dist/index.cjs +2316 -1644
  93. package/dist/index.cjs.map +1 -1
  94. package/dist/index.js +2224 -1639
  95. package/dist/index.js.map +1 -1
  96. package/package.json +1 -1
  97. package/src/api/core/camera/index.ts +212 -0
  98. package/src/api/core/comment/index.ts +120 -2
  99. package/src/api/core/geom/delete/index.ts +6 -0
  100. package/src/api/core/handles/index.ts +233 -0
  101. package/src/api/core/index.ts +15 -0
  102. package/src/api/core/io/export/index.ts +124 -0
  103. package/src/api/core/io/index.ts +5 -0
  104. package/src/api/core/proposals/index.ts +71 -11
  105. package/src/api/core/user.ts +46 -0
  106. package/src/api/design/create/index.ts +166 -36
  107. package/src/api/design/doors/index.ts +40 -0
  108. package/src/api/design/furniture/index.ts +127 -3
  109. package/src/api/design/index.ts +10 -0
  110. package/src/api/design/materials/index.ts +157 -30
  111. package/src/api/design/query/index.ts +33 -7
  112. package/src/api/design/transform/index.ts +89 -12
  113. package/src/api/design/types/index.ts +156 -0
  114. package/src/api/design/update/index.ts +402 -6
  115. package/src/api/design/visibility.ts +109 -0
  116. package/src/api/entity/story.ts +141 -15
  117. package/src/api/presentation/annotate.ts +360 -0
  118. package/src/api/presentation/diagrams.ts +53 -8
  119. package/src/api/presentation/export.ts +104 -0
  120. package/src/api/presentation/index.ts +46 -0
  121. package/src/api/presentation/sheets.ts +346 -13
  122. package/src/api/presentation/views.ts +164 -12
  123. package/src/api/program/areas.ts +57 -6
  124. package/src/api/program/cores.ts +3 -91
  125. package/src/api/program/index.ts +2 -2
  126. package/src/api/program/layout.ts +182 -12
  127. package/src/api/program/site.ts +11 -8
  128. package/src/api/program/spreadsheet.ts +125 -29
  129. package/src/handles.ts +77 -13
  130. package/tsconfig.json +7 -2
@@ -19,10 +19,21 @@ import { PluginApiReturn } from "../../types"
19
19
  * longer `"running"` — a run can take up to 20 minutes.
20
20
  * 3. {@linkcode PluginProgramLayoutApi.cancel} aborts an in-flight run.
21
21
  *
22
- * `arrange` produces several candidate layouts and **auto-commits the first
23
- * solution** as one undoable edit (there is no interactive solution-review
24
- * over the plugin boundary); `pack` re-shapes and applies its single result
25
- * directly. Both are **Pro-plan-gated**, matching the product UI.
22
+ * `arrange` produces several candidate layouts. By default it **auto-commits
23
+ * the first solution** as one undoable edit; pass `autoCommit: false` to hold
24
+ * the candidates for review instead `getState` then reports
25
+ * `status: "pendingReview"` with `totalSolutions`, and
26
+ * {@linkcode PluginProgramLayoutApi.applySolution} commits the one you pick
27
+ * (the headless counterpart of the product's "Solution N of M → Apply" bar).
28
+ * `pack` re-shapes and applies its single result directly. Both are
29
+ * **Pro-plan-gated**, matching the product UI.
30
+ *
31
+ * {@linkcode PluginProgramLayoutApi.stack} is the **cross-storey** action —
32
+ * the product's *Pack in envelope* / auto-stack. It splits the building
33
+ * envelope by storey and packs each storey's departments into its slice as one
34
+ * undoable edit. Unlike `arrange` / `pack`, it is **not** the async-job model:
35
+ * it resolves inline when the whole multi-storey pack is done (no `getState`
36
+ * polling) and returns the storeys it had to skip. It is Pro-gated too.
26
37
  *
27
38
  * Accessed via `snaptrude.program.layout`.
28
39
  */
@@ -35,9 +46,16 @@ export abstract class PluginProgramLayoutApi {
35
46
  * Starts the backend job and **returns immediately** — poll
36
47
  * {@linkcode PluginProgramLayoutApi.getState} until `status` leaves
37
48
  * `"running"` (a run can take up to 20 minutes). The solver produces
38
- * several candidate layouts; this **auto-commits the first solution** as one
39
- * undoable edit (no interactive review over the plugin boundary). Starting a
40
- * run while one is in flight replaces it.
49
+ * several candidate layouts; by default this **auto-commits the first
50
+ * solution** as one undoable edit. Pass `autoCommit: false` to review them
51
+ * instead: the run finishes with `status: "pendingReview"` and
52
+ * `totalSolutions` in `getState`, and
53
+ * {@linkcode PluginProgramLayoutApi.applySolution} commits the candidate you
54
+ * pick ({@linkcode PluginProgramLayoutApi.cancel} discards them). An
55
+ * arrange **without an envelope** applies directly (there is nothing to
56
+ * review) regardless of `autoCommit`. Starting a run while one is in flight
57
+ * replaces it; while solutions are pending review, new runs are refused —
58
+ * apply or cancel first.
41
59
  *
42
60
  * With no `options`, operates on the eligible Room/Department masses on the
43
61
  * active storey and auto-detects the single buildable envelope there.
@@ -53,11 +71,14 @@ export abstract class PluginProgramLayoutApi {
53
71
  * @throws When a `spaceId` / `departmentId` / `envelopeId` does not resolve.
54
72
  * @throws When more than one envelope is on the active storey and none was
55
73
  * given (ambiguous).
74
+ * @throws When arrange solutions are pending review (apply or cancel them
75
+ * first).
56
76
  * @throws When plugin writes are disabled.
57
77
  *
58
78
  * @examplePrompt Arrange the rooms in the envelope
59
79
  * @examplePrompt Lay out the departments inside the building envelope
60
80
  * @examplePrompt Auto-arrange the program spaces
81
+ * @examplePrompt Arrange the rooms but let me pick the solution
61
82
  *
62
83
  * # Example
63
84
  * ```ts
@@ -98,6 +119,8 @@ export abstract class PluginProgramLayoutApi {
98
119
  * @throws When a `spaceId` / `departmentId` / `envelopeId` does not resolve.
99
120
  * @throws When more than one envelope is on the active storey and none was
100
121
  * given (ambiguous).
122
+ * @throws When arrange solutions are pending review (apply or cancel them
123
+ * first).
101
124
  * @throws When plugin writes are disabled.
102
125
  *
103
126
  * @examplePrompt Pack the rooms into the envelope
@@ -118,6 +141,92 @@ export abstract class PluginProgramLayoutApi {
118
141
  options?: PluginProgramLayoutRunArgs,
119
142
  ): PluginApiReturn<PluginProgramLayoutRunResult>
120
143
 
144
+ /**
145
+ * Apply one of the pending arrange solutions.
146
+ *
147
+ * The headless counterpart of the product's **"Solution N of M → Apply"**
148
+ * review bar: after an {@linkcode PluginProgramLayoutApi.arrange} run
149
+ * started with `autoCommit: false` finishes with `status: "pendingReview"`,
150
+ * this commits the candidate at `index` (0-based,
151
+ * `0 ≤ index < totalSolutions` from
152
+ * {@linkcode PluginProgramLayoutApi.getState}) as **one undoable edit** and
153
+ * discards the other candidates. `getState` then reports
154
+ * `status: "active"`.
155
+ *
156
+ * @param index - 0-based index of the pending solution to apply.
157
+ * @returns A {@linkcode PluginProgramLayoutApplyResult} —
158
+ * `{ success: true }` when the solution was applied.
159
+ * @throws When no arrange solutions are pending review (no
160
+ * `arrange({ autoCommit: false })` run has finished, or its solutions were
161
+ * already applied / cancelled).
162
+ * @throws When `index` is out of range for the pending solutions.
163
+ * @throws When plugin writes are disabled.
164
+ *
165
+ * @examplePrompt Apply the second arrange solution
166
+ * @examplePrompt Pick layout solution 3 and commit it
167
+ * @examplePrompt Apply the arrange solution I chose
168
+ *
169
+ * # Example
170
+ * ```ts
171
+ * await snaptrude.program.layout.arrange({ autoCommit: false })
172
+ * let job = await snaptrude.program.layout.getState()
173
+ * while (job?.status === "running") {
174
+ * await new Promise((r) => setTimeout(r, 5000))
175
+ * job = await snaptrude.program.layout.getState()
176
+ * }
177
+ * if (job?.status === "pendingReview") {
178
+ * console.log(`${job.totalSolutions} candidate layouts`)
179
+ * await snaptrude.program.layout.applySolution(1) // commit the second one
180
+ * }
181
+ * ```
182
+ */
183
+ public abstract applySolution(
184
+ index: number,
185
+ ): PluginApiReturn<PluginProgramLayoutApplyResult>
186
+
187
+ /**
188
+ * Stack the program across **every storey** of the building — the product's
189
+ * *Pack in envelope* / auto-stack.
190
+ *
191
+ * Splits the building envelope massing into per-storey envelope slices, then
192
+ * packs each storey's departments into its slice, applying the whole
193
+ * multi-storey result as **one undoable edit**.
194
+ *
195
+ * Unlike {@linkcode PluginProgramLayoutApi.arrange} /
196
+ * {@linkcode PluginProgramLayoutApi.pack}, this **is not** the async-job
197
+ * model: `await` it and it resolves when the stack is done — there is no
198
+ * `getState` polling. It operates on the whole active model (no id / envelope
199
+ * arguments): it auto-discovers the departments and envelope on each storey.
200
+ *
201
+ * Storeys that have departments but **no envelope** are reported in
202
+ * `skippedStoreys` (nothing was packed there). A storey's departments must
203
+ * already be on that storey — `stack` does not move departments between
204
+ * floors, it lays out each floor's program into that floor's envelope.
205
+ *
206
+ * @returns A {@linkcode PluginProgramLayoutStackResult} — `success: true`
207
+ * with the (possibly empty) `skippedStoreys` when the stack was applied, or
208
+ * `success: false` with an `error` when the run failed (e.g. the adjacency
209
+ * service was unreachable).
210
+ * @throws When the project is not on a Pro plan.
211
+ * @throws When plugin writes are disabled.
212
+ * @throws When an `arrange` / `pack` run, or another `stack`, is already in
213
+ * flight, or arrange solutions are pending review (finish, apply, or
214
+ * `cancel` first).
215
+ *
216
+ * @examplePrompt Stack the program across all floors
217
+ * @examplePrompt Auto-stack the departments into the envelope on every storey
218
+ * @examplePrompt Pack the whole building's program into its envelope
219
+ *
220
+ * # Example
221
+ * ```ts
222
+ * const { success, skippedStoreys, error } = await snaptrude.program.layout.stack()
223
+ * if (!success) throw new Error(error)
224
+ * if (skippedStoreys.length)
225
+ * console.warn(`No envelope on storeys: ${skippedStoreys.join(", ")}`)
226
+ * ```
227
+ */
228
+ public abstract stack(): PluginApiReturn<PluginProgramLayoutStackResult>
229
+
121
230
  /**
122
231
  * Get the state of the layout run.
123
232
  *
@@ -130,6 +239,10 @@ export abstract class PluginProgramLayoutApi {
130
239
  * | `"running"` | A run is in flight — keep polling |
131
240
  * | `"active"` | The run finished and its layout is applied to the model |
132
241
  * | `"inactive"` | A run finished without applying a layout (the solver failed or found no solution) |
242
+ * | `"pendingReview"` | An `arrange({ autoCommit: false })` run finished and its candidates await {@linkcode PluginProgramLayoutApi.applySolution} / {@linkcode PluginProgramLayoutApi.cancel} |
243
+ *
244
+ * While `status` is `"pendingReview"` the state also carries
245
+ * `totalSolutions` — the number of candidate layouts to pick from.
133
246
  *
134
247
  * @returns A {@linkcode PluginProgramLayoutStateResult} with the run
135
248
  * `status`, or `null` when no run result is available (never ran or was
@@ -150,11 +263,13 @@ export abstract class PluginProgramLayoutApi {
150
263
  /**
151
264
  * Cancel the in-flight layout run.
152
265
  *
153
- * Aborts the backend job and discards any pending solution. A no-op
154
- * (returns `false`) when nothing is running.
266
+ * Aborts the backend job and discards any pending solution. Also discards
267
+ * a finished run's **pending-review** solutions (after
268
+ * `arrange({ autoCommit: false })`) without applying one. A no-op (returns
269
+ * `false`) when nothing is running and nothing is pending.
155
270
  *
156
- * @returns `true` when a run was cancelled, `false` when nothing was
157
- * running.
271
+ * @returns `true` when a run was cancelled or pending solutions were
272
+ * discarded, `false` when there was nothing to cancel.
158
273
  * @throws When plugin writes are disabled.
159
274
  *
160
275
  * @examplePrompt Cancel the arrange
@@ -180,11 +295,13 @@ export abstract class PluginProgramLayoutApi {
180
295
  * | `spaceIds` | `string[] \| undefined` | Component ids of the Room masses to lay out |
181
296
  * | `departmentIds` | `string[] \| undefined` | Component ids of the Department masses to lay out |
182
297
  * | `envelopeId` | `string \| undefined` | The envelope to fit into — a mass component id or a buildable-envelope handle (`be_…`); omit to auto-detect the single envelope on the active storey |
298
+ * | `autoCommit` | `boolean \| undefined` | `arrange` only — `true` / omitted (default) auto-commits the first candidate solution; `false` holds the candidates for review via {@linkcode PluginProgramLayoutApi.applySolution}. Ignored by `pack` (it applies its single result directly) |
183
299
  */
184
300
  export const PluginProgramLayoutRunArgs = z.object({
185
301
  spaceIds: z.array(z.string()).optional(),
186
302
  departmentIds: z.array(z.string()).optional(),
187
303
  envelopeId: z.string().optional(),
304
+ autoCommit: z.boolean().optional(),
188
305
  })
189
306
  export type PluginProgramLayoutRunArgs = z.infer<
190
307
  typeof PluginProgramLayoutRunArgs
@@ -217,11 +334,13 @@ export type PluginProgramLayoutRunResult = z.infer<
217
334
  * | `"running"` | A run is in flight — keep polling |
218
335
  * | `"active"` | The run finished and its layout is applied to the model |
219
336
  * | `"inactive"` | A run finished without applying a layout (the solver failed or found no solution) |
337
+ * | `"pendingReview"` | An `arrange({ autoCommit: false })` run finished and its candidate solutions await {@linkcode PluginProgramLayoutApi.applySolution} / {@linkcode PluginProgramLayoutApi.cancel} |
220
338
  */
221
339
  export const PluginProgramLayoutJobStatus = z.enum([
222
340
  "running",
223
341
  "active",
224
342
  "inactive",
343
+ "pendingReview",
225
344
  ])
226
345
  export type PluginProgramLayoutJobStatus = z.infer<
227
346
  typeof PluginProgramLayoutJobStatus
@@ -232,10 +351,12 @@ export type PluginProgramLayoutJobStatus = z.infer<
232
351
  *
233
352
  * | Property | Type | Description |
234
353
  * |---|---|---|
235
- * | `status` | {@linkcode PluginProgramLayoutJobStatus} | `"running"` \| `"active"` \| `"inactive"` |
354
+ * | `status` | {@linkcode PluginProgramLayoutJobStatus} | `"running"` \| `"active"` \| `"inactive"` \| `"pendingReview"` |
355
+ * | `totalSolutions` | `number \| undefined` | Number of candidate solutions awaiting review — present only while `status` is `"pendingReview"` |
236
356
  */
237
357
  export const PluginProgramLayoutJobState = z.object({
238
358
  status: PluginProgramLayoutJobStatus,
359
+ totalSolutions: z.number().int().optional(),
239
360
  })
240
361
  export type PluginProgramLayoutJobState = z.infer<
241
362
  typeof PluginProgramLayoutJobState
@@ -250,3 +371,52 @@ export const PluginProgramLayoutStateResult =
250
371
  export type PluginProgramLayoutStateResult = z.infer<
251
372
  typeof PluginProgramLayoutStateResult
252
373
  >
374
+
375
+ /**
376
+ * Arguments for {@linkcode PluginProgramLayoutApi.applySolution}.
377
+ *
378
+ * | Property | Type | Description |
379
+ * |---|---|---|
380
+ * | `index` | `number` | 0-based index of the pending solution to apply (`0 ≤ index < totalSolutions`) |
381
+ */
382
+ export const PluginProgramLayoutApplySolutionArgs = z.object({
383
+ index: z.number().int().min(0),
384
+ })
385
+ export type PluginProgramLayoutApplySolutionArgs = z.infer<
386
+ typeof PluginProgramLayoutApplySolutionArgs
387
+ >
388
+
389
+ /**
390
+ * Result of {@linkcode PluginProgramLayoutApi.applySolution}.
391
+ *
392
+ * | Property | Type | Description |
393
+ * |---|---|---|
394
+ * | `success` | `boolean` | Whether the chosen solution was applied |
395
+ * | `error` | `string \| undefined` | Failure reason when `success` is `false` |
396
+ */
397
+ export const PluginProgramLayoutApplyResult = z.object({
398
+ success: z.boolean(),
399
+ error: z.string().optional(),
400
+ })
401
+ export type PluginProgramLayoutApplyResult = z.infer<
402
+ typeof PluginProgramLayoutApplyResult
403
+ >
404
+
405
+ /**
406
+ * Result of {@linkcode PluginProgramLayoutApi.stack} — reports whether the
407
+ * cross-storey stack was applied and which storeys were skipped.
408
+ *
409
+ * | Property | Type | Description |
410
+ * |---|---|---|
411
+ * | `success` | `boolean` | Whether the stack was applied |
412
+ * | `skippedStoreys` | `number[]` | Storey values that had departments but no envelope, so nothing was packed there |
413
+ * | `error` | `string \| undefined` | Failure reason when `success` is `false` |
414
+ */
415
+ export const PluginProgramLayoutStackResult = z.object({
416
+ success: z.boolean(),
417
+ skippedStoreys: z.array(z.number()),
418
+ error: z.string().optional(),
419
+ })
420
+ export type PluginProgramLayoutStackResult = z.infer<
421
+ typeof PluginProgramLayoutStackResult
422
+ >
@@ -6,8 +6,9 @@ import { PluginAreaUnit } from "./metrics"
6
6
  * Program site — read the planning context of the project's site/plot.
7
7
  *
8
8
  * The **site** is the set of plot/parcel footprints the program is planned
9
- * against: their total area, per-parcel footprints (in scene XZ coordinates),
10
- * andwhen the project is geo-located on terrain their geographic
9
+ * against: their total area, per-parcel footprints (world XZ plan coordinates
10
+ * in Snaptrude units — the same plan space as a space's `planPoints`), and —
11
+ * when the project is geo-located on terrain — their geographic
11
12
  * (latitude/longitude) rings. This is the program-planning view of the site;
12
13
  * zoning numbers (setbacks, FAR/FSI, height limits) live in the site-analysis
13
14
  * sheet and are not read here.
@@ -64,7 +65,8 @@ export abstract class PluginProgramSiteApi {
64
65
  public abstract getArea(): PluginApiReturn<PluginProgramSiteAreaResult>
65
66
 
66
67
  /**
67
- * List the site parcels with their footprints (in scene coordinates).
68
+ * List the site parcels with their footprints (world XZ plan coordinates,
69
+ * Snaptrude units).
68
70
  *
69
71
  * @returns A {@linkcode PluginProgramSiteListPolygonsResult} with each parcel's
70
72
  * id, area, label, and 2D footprint (XZ ground-plane points). Empty when the
@@ -88,7 +90,7 @@ export abstract class PluginProgramSiteApi {
88
90
  *
89
91
  * Only available when the project is geo-located on terrain; returns `[]` (no
90
92
  * rings) otherwise. Use {@linkcode PluginProgramSiteApi.listPolygons} for the
91
- * scene-coordinate footprints that are always available.
93
+ * world-coordinate footprints that are always available.
92
94
  *
93
95
  * @returns A {@linkcode PluginProgramSiteListGeoPolygonsResult} with one ring
94
96
  * of `{ lat, lng, alt }` points per parcel.
@@ -187,13 +189,14 @@ export abstract class PluginProgramSiteApi {
187
189
  }
188
190
 
189
191
  /**
190
- * A 2D ground-plane point of a site parcel footprint, in scene coordinates
191
- * (metres on the project's XZ plane).
192
+ * A 2D ground-plane point of a site parcel footprint, in world XZ plan
193
+ * coordinates (Snaptrude units) the same plan space as a space's
194
+ * `planPoints`, so site parcels overlay space footprints directly.
192
195
  *
193
196
  * | Property | Type | Description |
194
197
  * |---|---|---|
195
- * | `x` | `number` | X coordinate (scene metres) |
196
- * | `z` | `number` | Z coordinate (scene metres) |
198
+ * | `x` | `number` | X coordinate (world, Snaptrude units) |
199
+ * | `z` | `number` | Z coordinate (world, Snaptrude units) |
197
200
  */
198
201
  export const PluginSiteFootprintPoint = z.object({
199
202
  x: z.number(),
@@ -23,7 +23,8 @@ import { PluginAreaUnit } from "./metrics"
23
23
  *
24
24
  * Plus dataset reads ({@linkcode PluginProgramSpreadsheetApi.getProgram} /
25
25
  * {@linkcode PluginProgramSpreadsheetApi.getTakeoff} — the data the render* methods
26
- * draw), sheet reads ({@linkcode PluginProgramSpreadsheetApi.listSheets} /
26
+ * draw and {@linkcode PluginProgramSpreadsheetApi.getMaterialTakeoff}, surface area
27
+ * per material), sheet reads ({@linkcode PluginProgramSpreadsheetApi.listSheets} /
27
28
  * {@linkcode PluginProgramSpreadsheetApi.getSheetData}), charts + emphasis
28
29
  * ({@linkcode PluginProgramSpreadsheetApi.addChart} /
29
30
  * {@linkcode PluginProgramSpreadsheetApi.highlightCells}), workbook file export
@@ -128,13 +129,15 @@ export abstract class PluginProgramSpreadsheetApi {
128
129
  * A specialization of the render* family: the **host pulls and projects the
129
130
  * current takeoff dataset itself** — the plugin supplies only the target
130
131
  * `sheetName`, an optional `title`, and an optional `filter`; there is **no
131
- * data argument**. Returns the same rendered-artifact record as
132
+ * data argument**. The `filter` is applied host-side to the projected rows
133
+ * before rendering. Returns the same rendered-artifact record as
132
134
  * {@linkcode PluginProgramSpreadsheetApi.render}.
133
135
  * _(Authored ahead — optional via the `spreadsheet?` mount until the host
134
136
  * round-trip is live.)_
135
137
  *
136
- * @param options - Optional `sheetName`, `title`, and `filter` (a
137
- * {@linkcode PluginProgramSpreadsheetRenderDatasetArgs}).
138
+ * @param options - Optional `sheetName`, `title`, and `filter` a
139
+ * case-insensitive substring kept when any of a row's cell values contains
140
+ * it (a {@linkcode PluginProgramSpreadsheetRenderDatasetArgs}).
138
141
  * @returns A {@linkcode PluginProgramSpreadsheetRenderResult} with the written
139
142
  * ranges and counts.
140
143
  * @throws If the takeoff cannot be rendered.
@@ -149,9 +152,11 @@ export abstract class PluginProgramSpreadsheetApi {
149
152
  * await snaptrude.program.spreadsheet.renderTakeoff({ sheetName: "Takeoff" })
150
153
  * ```
151
154
  */
152
- public abstract renderTakeoff(
153
- options?: { sheetName?: string; title?: string; filter?: string },
154
- ): PluginApiReturn<PluginProgramSpreadsheetRenderResult>
155
+ public abstract renderTakeoff(options?: {
156
+ sheetName?: string
157
+ title?: string
158
+ filter?: string
159
+ }): PluginApiReturn<PluginProgramSpreadsheetRenderResult>
155
160
 
156
161
  /**
157
162
  * Render the area program onto a sheet.
@@ -159,13 +164,15 @@ export abstract class PluginProgramSpreadsheetApi {
159
164
  * A specialization of the render* family: the **host pulls and projects the
160
165
  * current program dataset itself** (departments with area targets vs allocated)
161
166
  * — the plugin supplies only the target `sheetName`, an optional `title`, and an
162
- * optional `filter`; there is **no data argument**. Returns the same
167
+ * optional `filter`; there is **no data argument**. The `filter` is applied
168
+ * host-side to the projected rows before rendering. Returns the same
163
169
  * rendered-artifact record as {@linkcode PluginProgramSpreadsheetApi.render}.
164
170
  * _(Authored ahead — optional via the `spreadsheet?` mount until the host
165
171
  * round-trip is live.)_
166
172
  *
167
- * @param options - Optional `sheetName`, `title`, and `filter` (a
168
- * {@linkcode PluginProgramSpreadsheetRenderDatasetArgs}).
173
+ * @param options - Optional `sheetName`, `title`, and `filter` a
174
+ * case-insensitive substring kept when any of a row's cell values contains
175
+ * it (a {@linkcode PluginProgramSpreadsheetRenderDatasetArgs}).
169
176
  * @returns A {@linkcode PluginProgramSpreadsheetRenderResult} with the written
170
177
  * ranges and counts.
171
178
  * @throws If the program cannot be rendered.
@@ -180,20 +187,27 @@ export abstract class PluginProgramSpreadsheetApi {
180
187
  * await snaptrude.program.spreadsheet.renderProgram({ sheetName: "Program" })
181
188
  * ```
182
189
  */
183
- public abstract renderProgram(
184
- options?: { sheetName?: string; title?: string; filter?: string },
185
- ): PluginApiReturn<PluginProgramSpreadsheetRenderResult>
190
+ public abstract renderProgram(options?: {
191
+ sheetName?: string
192
+ title?: string
193
+ filter?: string
194
+ }): PluginApiReturn<PluginProgramSpreadsheetRenderResult>
186
195
 
187
196
  /**
188
- * Export the whole report workbook to a file (xlsx, pdf, or csv).
197
+ * Export the report workbook to a file (xlsx, pdf, or csv).
189
198
  *
190
199
  * Returns the file content as base64 so the plugin can download, upload, or
191
200
  * attach it. This is the workbook **file** export; to read cell **values** back
192
201
  * as data, use {@linkcode PluginProgramSpreadsheetApi.getSheetData} instead.
193
202
  *
194
- * @param format - The file format to export the workbook to.
195
- * @param options - Optional `fileName` (without extension); a default is used
196
- * when omitted.
203
+ * `xlsx` and `pdf` export the **whole** workbook. `csv` is inherently
204
+ * single-sheet: it serializes one sheet's used range pass `options.sheetName`
205
+ * to choose which sheet, or omit it to serialize the active sheet.
206
+ *
207
+ * @param format - The file format to export to.
208
+ * @param options - Optional `fileName` (without extension; a default is used
209
+ * when omitted) and `sheetName` — the sheet to serialize for `csv`
210
+ * (ignored by `xlsx`/`pdf`, which always export the whole workbook).
197
211
  * @returns A {@linkcode PluginProgramSpreadsheetExportResult} with the file's
198
212
  * `fileName`, `mimeType`, and base64 `dataBase64`.
199
213
  * @throws If the workbook cannot be serialized to the requested format.
@@ -202,16 +216,20 @@ export abstract class PluginProgramSpreadsheetApi {
202
216
  * @examplePrompt Download the whole workbook as a PDF
203
217
  * @examplePrompt Export the program workbook to xlsx
204
218
  * @examplePrompt Save the report as a csv file
219
+ * @examplePrompt Export the Takeoff sheet as CSV
205
220
  *
206
221
  * # Example
207
222
  * ```ts
208
223
  * const file = await snaptrude.program.spreadsheet.export("xlsx")
209
224
  * // file.fileName, file.mimeType, file.dataBase64
225
+ *
226
+ * // CSV of a specific sheet:
227
+ * const csv = await snaptrude.program.spreadsheet.export("csv", { sheetName: "Takeoff" })
210
228
  * ```
211
229
  */
212
230
  public abstract export(
213
231
  format: PluginSpreadsheetExportFormat,
214
- options?: { fileName?: string },
232
+ options?: { fileName?: string; sheetName?: string },
215
233
  ): PluginApiReturn<PluginProgramSpreadsheetExportResult>
216
234
 
217
235
  /**
@@ -378,6 +396,38 @@ export abstract class PluginProgramSpreadsheetApi {
378
396
  */
379
397
  public abstract getTakeoff(): PluginApiReturn<PluginProgramSpreadsheetGetTakeoffResult>
380
398
 
399
+ /**
400
+ * Read the material take-off dataset (surface area per material, per element type).
401
+ *
402
+ * The native "Material Take-Off" quantities — the same figures the BIM Take-Off's
403
+ * *Material Take-Off* sheet lists — as render-ready rows: for each material applied
404
+ * to a wall/floor/ceiling/slab/roof family, its rolled-up **surface area** in the
405
+ * project's display units. This is the material-centric complement to
406
+ * {@linkcode PluginProgramSpreadsheetApi.getTakeoff} (which rolls up by element
407
+ * family). A read only — nothing is written and no file is downloaded.
408
+ *
409
+ * When no materials are applied (or the model is empty) the result is simply an
410
+ * empty `rows` array — not an error.
411
+ *
412
+ * @returns A {@linkcode PluginProgramSpreadsheetGetMaterialTakeoffResult} with the
413
+ * material take-off `rows`, a `rowCount`, and the `generatedAt` timestamp.
414
+ *
415
+ * @examplePrompt Get the material take-off
416
+ * @examplePrompt How much of each material is used?
417
+ * @examplePrompt Read the surface area by material
418
+ * @examplePrompt Give me the material quantities before I render them
419
+ * @examplePrompt What's in the material take-off right now?
420
+ *
421
+ * # Example
422
+ * ```ts
423
+ * const mto = await snaptrude.program.spreadsheet.getMaterialTakeoff()
424
+ * for (const row of mto.rows) {
425
+ * console.log(row.objectType, row.family, row.material, row.quantity, row.unit)
426
+ * }
427
+ * ```
428
+ */
429
+ public abstract getMaterialTakeoff(): PluginApiReturn<PluginProgramSpreadsheetGetMaterialTakeoffResult>
430
+
381
431
  // --- Sheet construction (lower-level cell writes; render is the high-level path) ---
382
432
 
383
433
  /**
@@ -597,9 +647,9 @@ export abstract class PluginProgramSpreadsheetApi {
597
647
  * await snaptrude.program.spreadsheet.recalculate()
598
648
  * ```
599
649
  */
600
- public abstract recalculate(
601
- options?: { sheetName?: string },
602
- ): PluginApiReturn<PluginProgramSpreadsheetRecalculateResult>
650
+ public abstract recalculate(options?: {
651
+ sheetName?: string
652
+ }): PluginApiReturn<PluginProgramSpreadsheetRecalculateResult>
603
653
 
604
654
  /**
605
655
  * Run a sequence of sheet operations as one atomic, undoable transaction.
@@ -689,9 +739,9 @@ export abstract class PluginProgramSpreadsheetApi {
689
739
  * const { entries } = await snaptrude.program.spreadsheet.getAuditLog({ limit: 10 })
690
740
  * ```
691
741
  */
692
- public abstract getAuditLog(
693
- options?: { limit?: number },
694
- ): PluginApiReturn<PluginProgramSpreadsheetGetAuditLogResult>
742
+ public abstract getAuditLog(options?: {
743
+ limit?: number
744
+ }): PluginApiReturn<PluginProgramSpreadsheetGetAuditLogResult>
695
745
 
696
746
  /**
697
747
  * Ping the program-mode spreadsheet bridge to check it is reachable.
@@ -839,9 +889,9 @@ export abstract class PluginProgramSpreadsheetBindingsApi {
839
889
  * await snaptrude.program.spreadsheet.bindings.refresh({ name: "takeoff" })
840
890
  * ```
841
891
  */
842
- public abstract refresh(
843
- options?: { name?: string },
844
- ): PluginApiReturn<PluginProgramSpreadsheetBindingsRefreshResult>
892
+ public abstract refresh(options?: {
893
+ name?: string
894
+ }): PluginApiReturn<PluginProgramSpreadsheetBindingsRefreshResult>
845
895
 
846
896
  /**
847
897
  * List all data bindings in the workbook.
@@ -977,7 +1027,7 @@ export type PluginProgramSpreadsheetRenderResult = z.infer<
977
1027
  * |---|---|---|
978
1028
  * | `sheetName` | `string?` | Target sheet name (a default is used when omitted) |
979
1029
  * | `title` | `string?` | Optional title rendered above the table |
980
- * | `filter` | `string?` | Optional dataset-filter expression to narrow the rows |
1030
+ * | `filter` | `string?` | Case-insensitive substring a row is kept when any of its cell values contains it (blank/omitted keeps every row). Not a query language. |
981
1031
  */
982
1032
  export const PluginProgramSpreadsheetRenderDatasetArgs = z.object({
983
1033
  sheetName: z.string().optional(),
@@ -999,12 +1049,14 @@ export type PluginSpreadsheetExportFormat = z.infer<
999
1049
  *
1000
1050
  * | Property | Type | Description |
1001
1051
  * |---|---|---|
1002
- * | `format` | `"xlsx" \| "pdf" \| "csv"` | The file format to export the workbook to |
1052
+ * | `format` | `"xlsx" \| "pdf" \| "csv"` | The file format to export to |
1003
1053
  * | `fileName` | `string?` | Desired file name (without extension); a default is used when omitted |
1054
+ * | `sheetName` | `string?` | For `csv`: the sheet to serialize (the active sheet when omitted). Ignored by `xlsx`/`pdf`. |
1004
1055
  */
1005
1056
  export const PluginProgramSpreadsheetExportArgs = z.object({
1006
1057
  format: PluginSpreadsheetExportFormat,
1007
1058
  fileName: z.string().optional(),
1059
+ sheetName: z.string().optional(),
1008
1060
  })
1009
1061
  export type PluginProgramSpreadsheetExportArgs = z.infer<
1010
1062
  typeof PluginProgramSpreadsheetExportArgs
@@ -1286,6 +1338,50 @@ export type PluginProgramSpreadsheetGetTakeoffResult = z.infer<
1286
1338
  typeof PluginProgramSpreadsheetGetTakeoffResult
1287
1339
  >
1288
1340
 
1341
+ /**
1342
+ * One row in the material take-off dataset. The take-off is grouped by element
1343
+ * object type + family + applied material, so each row is a rolled-up surface
1344
+ * area for that material on that family.
1345
+ *
1346
+ * | Property | Type | Description |
1347
+ * |---|---|---|
1348
+ * | `objectType` | `string` | Element object type the material sits on (`"wall"`, `"floor"`, `"ceiling"`, `"slab"`, `"roof"`) |
1349
+ * | `family` | `string` | Element family the quantity is aggregated under |
1350
+ * | `material` | `string` | Applied material name |
1351
+ * | `quantity` | `number` | Rolled-up surface area in `unit` |
1352
+ * | `unit` | `string` | Area unit (e.g. `"sq.ft"` or `"sq.m"`) |
1353
+ */
1354
+ export const PluginSpreadsheetMaterialTakeoffRow = z.object({
1355
+ objectType: z.string(),
1356
+ family: z.string(),
1357
+ material: z.string(),
1358
+ quantity: z.number(),
1359
+ unit: z.string(),
1360
+ })
1361
+ export type PluginSpreadsheetMaterialTakeoffRow = z.infer<
1362
+ typeof PluginSpreadsheetMaterialTakeoffRow
1363
+ >
1364
+
1365
+ /**
1366
+ * Result of {@linkcode PluginProgramSpreadsheetApi.getMaterialTakeoff} — the native
1367
+ * material take-off dataset (surface area per material). Empty when no materials are
1368
+ * applied.
1369
+ *
1370
+ * | Property | Type | Description |
1371
+ * |---|---|---|
1372
+ * | `rows` | {@linkcode PluginSpreadsheetMaterialTakeoffRow}`[]` | One row per object-type/family/material group |
1373
+ * | `rowCount` | `number` | Number of rows |
1374
+ * | `generatedAt` | `string` | ISO timestamp of when the dataset was read |
1375
+ */
1376
+ export const PluginProgramSpreadsheetGetMaterialTakeoffResult = z.object({
1377
+ rows: z.array(PluginSpreadsheetMaterialTakeoffRow),
1378
+ rowCount: z.number(),
1379
+ generatedAt: z.string(),
1380
+ })
1381
+ export type PluginProgramSpreadsheetGetMaterialTakeoffResult = z.infer<
1382
+ typeof PluginProgramSpreadsheetGetMaterialTakeoffResult
1383
+ >
1384
+
1289
1385
  // --- createSheet -----------------------------------------------------------
1290
1386
 
1291
1387
  /** Behaviour when {@linkcode PluginProgramSpreadsheetApi.createSheet} targets an existing sheet name. */