@snaptrude/plugin-core 0.11.0-rc.1 → 1.0.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 (226) hide show
  1. package/AGENTS.md +4 -0
  2. package/CHANGELOG.md +176 -14
  3. package/api-manifest.json +872 -49
  4. package/dist/api/analysis/daylight.d.ts +5 -5
  5. package/dist/api/analysis/solar.d.ts +2 -2
  6. package/dist/api/core/camera/index.d.ts +447 -0
  7. package/dist/api/core/camera/index.d.ts.map +1 -1
  8. package/dist/api/core/geom/create/index.d.ts +942 -101
  9. package/dist/api/core/geom/create/index.d.ts.map +1 -1
  10. package/dist/api/core/geom/query/brep.d.ts +59 -2
  11. package/dist/api/core/geom/query/brep.d.ts.map +1 -1
  12. package/dist/api/core/geom/query/curve.d.ts +59 -35
  13. package/dist/api/core/geom/query/curve.d.ts.map +1 -1
  14. package/dist/api/core/geom/query/edge.d.ts +2 -2
  15. package/dist/api/core/geom/query/face.d.ts +105 -0
  16. package/dist/api/core/geom/query/face.d.ts.map +1 -1
  17. package/dist/api/core/geom/update/contour.d.ts +10 -2
  18. package/dist/api/core/geom/update/contour.d.ts.map +1 -1
  19. package/dist/api/core/geom/update/curve.d.ts +5 -5
  20. package/dist/api/core/geom/update/profile.d.ts +3 -1
  21. package/dist/api/core/geom/update/profile.d.ts.map +1 -1
  22. package/dist/api/core/handles/index.d.ts +4 -2
  23. package/dist/api/core/handles/index.d.ts.map +1 -1
  24. package/dist/api/core/index.d.ts +4 -4
  25. package/dist/api/core/index.d.ts.map +1 -1
  26. package/dist/api/core/io/import/index.d.ts +3 -1
  27. package/dist/api/core/io/import/index.d.ts.map +1 -1
  28. package/dist/api/core/layers.d.ts +377 -255
  29. package/dist/api/core/layers.d.ts.map +1 -1
  30. package/dist/api/core/mode/index.d.ts +19 -7
  31. package/dist/api/core/mode/index.d.ts.map +1 -1
  32. package/dist/api/core/proposals/index.d.ts +4 -3
  33. package/dist/api/core/proposals/index.d.ts.map +1 -1
  34. package/dist/api/core/storeys/index.d.ts +17 -2
  35. package/dist/api/core/storeys/index.d.ts.map +1 -1
  36. package/dist/api/core/storeys/layers.d.ts +408 -0
  37. package/dist/api/core/storeys/layers.d.ts.map +1 -0
  38. package/dist/api/core/tags.d.ts +84 -3
  39. package/dist/api/core/tags.d.ts.map +1 -1
  40. package/dist/api/core/zoom/index.d.ts +13 -0
  41. package/dist/api/core/zoom/index.d.ts.map +1 -1
  42. package/dist/api/design/bim.d.ts +299 -0
  43. package/dist/api/design/bim.d.ts.map +1 -0
  44. package/dist/api/design/boolean/index.d.ts +2 -0
  45. package/dist/api/design/boolean/index.d.ts.map +1 -1
  46. package/dist/api/design/catalog.d.ts +320 -0
  47. package/dist/api/design/catalog.d.ts.map +1 -0
  48. package/dist/api/design/create/bulk-items.d.ts +269 -0
  49. package/dist/api/design/create/bulk-items.d.ts.map +1 -0
  50. package/dist/api/design/create/grids.d.ts +132 -0
  51. package/dist/api/design/create/grids.d.ts.map +1 -0
  52. package/dist/api/design/create/index.d.ts +753 -102
  53. package/dist/api/design/create/index.d.ts.map +1 -1
  54. package/dist/api/design/create/opening-fields.d.ts +38 -0
  55. package/dist/api/design/create/opening-fields.d.ts.map +1 -0
  56. package/dist/api/design/delete/index.d.ts +7 -20
  57. package/dist/api/design/delete/index.d.ts.map +1 -1
  58. package/dist/api/design/dimensions.d.ts +427 -0
  59. package/dist/api/design/dimensions.d.ts.map +1 -0
  60. package/dist/api/design/doors/index.d.ts +24 -23
  61. package/dist/api/design/doors/index.d.ts.map +1 -1
  62. package/dist/api/design/family.d.ts +1446 -0
  63. package/dist/api/design/family.d.ts.map +1 -0
  64. package/dist/api/design/grids.d.ts +237 -0
  65. package/dist/api/design/grids.d.ts.map +1 -0
  66. package/dist/api/design/index.d.ts +30 -0
  67. package/dist/api/design/index.d.ts.map +1 -1
  68. package/dist/api/design/networks.d.ts +1703 -0
  69. package/dist/api/design/networks.d.ts.map +1 -0
  70. package/dist/api/design/query/geometry/index.d.ts +33 -5
  71. package/dist/api/design/query/geometry/index.d.ts.map +1 -1
  72. package/dist/api/design/query/grids.d.ts +287 -0
  73. package/dist/api/design/query/grids.d.ts.map +1 -0
  74. package/dist/api/design/query/index.d.ts +143 -36
  75. package/dist/api/design/query/index.d.ts.map +1 -1
  76. package/dist/api/design/query/spaces.d.ts +5 -66
  77. package/dist/api/design/query/spaces.d.ts.map +1 -1
  78. package/dist/api/design/selection/index.d.ts +12 -12
  79. package/dist/api/design/types/index.d.ts +40 -24
  80. package/dist/api/design/types/index.d.ts.map +1 -1
  81. package/dist/api/design/update/index.d.ts +412 -8
  82. package/dist/api/design/update/index.d.ts.map +1 -1
  83. package/dist/api/design/windows/index.d.ts +8 -8
  84. package/dist/api/design/windows/index.d.ts.map +1 -1
  85. package/dist/api/entity/buildableEnvelope.d.ts +84 -2
  86. package/dist/api/entity/buildableEnvelope.d.ts.map +1 -1
  87. package/dist/api/entity/referenceLine.d.ts +13 -1
  88. package/dist/api/entity/referenceLine.d.ts.map +1 -1
  89. package/dist/api/entity/space.d.ts +12 -15
  90. package/dist/api/entity/space.d.ts.map +1 -1
  91. package/dist/api/entity/story.d.ts +4 -4
  92. package/dist/api/presentation/aiInspiration.d.ts +24 -24
  93. package/dist/api/presentation/annotate.d.ts +38 -22
  94. package/dist/api/presentation/annotate.d.ts.map +1 -1
  95. package/dist/api/presentation/camera.d.ts +265 -0
  96. package/dist/api/presentation/camera.d.ts.map +1 -0
  97. package/dist/api/presentation/drawingContract.d.ts +32 -0
  98. package/dist/api/presentation/drawingContract.d.ts.map +1 -0
  99. package/dist/api/presentation/drawings.d.ts +3523 -0
  100. package/dist/api/presentation/drawings.d.ts.map +1 -0
  101. package/dist/api/presentation/export.d.ts +23 -5
  102. package/dist/api/presentation/export.d.ts.map +1 -1
  103. package/dist/api/presentation/graphics.d.ts +419 -0
  104. package/dist/api/presentation/graphics.d.ts.map +1 -0
  105. package/dist/api/presentation/index.d.ts +66 -12
  106. package/dist/api/presentation/index.d.ts.map +1 -1
  107. package/dist/api/presentation/placedViews.d.ts +117 -51
  108. package/dist/api/presentation/placedViews.d.ts.map +1 -1
  109. package/dist/api/presentation/sections.d.ts +461 -0
  110. package/dist/api/presentation/sections.d.ts.map +1 -0
  111. package/dist/api/presentation/shapes.d.ts +2 -2
  112. package/dist/api/presentation/sheets.d.ts +175 -37
  113. package/dist/api/presentation/sheets.d.ts.map +1 -1
  114. package/dist/api/presentation/standards.d.ts +590 -0
  115. package/dist/api/presentation/standards.d.ts.map +1 -0
  116. package/dist/api/presentation/viewPresets.d.ts +222 -0
  117. package/dist/api/presentation/viewPresets.d.ts.map +1 -0
  118. package/dist/api/presentation/views.d.ts +144 -9
  119. package/dist/api/presentation/views.d.ts.map +1 -1
  120. package/dist/api/presentation/walkthroughs.d.ts +1350 -0
  121. package/dist/api/presentation/walkthroughs.d.ts.map +1 -0
  122. package/dist/api/program/departments.d.ts +58 -2
  123. package/dist/api/program/departments.d.ts.map +1 -1
  124. package/dist/api/program/index.d.ts +16 -6
  125. package/dist/api/program/index.d.ts.map +1 -1
  126. package/dist/api/program/labels.d.ts +355 -0
  127. package/dist/api/program/labels.d.ts.map +1 -0
  128. package/dist/api/program/metadata.d.ts +328 -0
  129. package/dist/api/program/metadata.d.ts.map +1 -0
  130. package/dist/api/program/site.d.ts +4 -4
  131. package/dist/api/program/spreadsheet.d.ts +267 -4
  132. package/dist/api/program/spreadsheet.d.ts.map +1 -1
  133. package/dist/api/workspace/index.d.ts +49 -2
  134. package/dist/api/workspace/index.d.ts.map +1 -1
  135. package/dist/errors/codes.d.ts +5 -8
  136. package/dist/errors/codes.d.ts.map +1 -1
  137. package/dist/handles.d.ts +32 -3
  138. package/dist/handles.d.ts.map +1 -1
  139. package/dist/index.cjs +6534 -2865
  140. package/dist/index.cjs.map +1 -1
  141. package/dist/index.d.ts +2 -0
  142. package/dist/index.d.ts.map +1 -1
  143. package/dist/index.js +6118 -2857
  144. package/dist/index.js.map +1 -1
  145. package/dist/massParameters.d.ts +287 -0
  146. package/dist/massParameters.d.ts.map +1 -0
  147. package/dist/massParameters.ops.d.ts +138 -0
  148. package/dist/massParameters.ops.d.ts.map +1 -0
  149. package/package.json +2 -1
  150. package/scripts/check-dist-fresh.mjs +22 -0
  151. package/scripts/generate-manifest.mjs +5 -0
  152. package/scripts/generate-manifest.test.mjs +14 -3
  153. package/src/api/core/camera/index.ts +429 -1
  154. package/src/api/core/geom/create/index.ts +1037 -102
  155. package/src/api/core/geom/query/brep.ts +60 -2
  156. package/src/api/core/geom/query/curve.ts +26 -2
  157. package/src/api/core/geom/query/edge.ts +2 -2
  158. package/src/api/core/geom/query/face.ts +109 -4
  159. package/src/api/core/geom/update/contour.ts +10 -2
  160. package/src/api/core/geom/update/profile.ts +2 -0
  161. package/src/api/core/handles/index.ts +3 -2
  162. package/src/api/core/index.ts +4 -4
  163. package/src/api/core/io/import/index.ts +11 -3
  164. package/src/api/core/layers.ts +380 -173
  165. package/src/api/core/mode/index.ts +19 -7
  166. package/src/api/core/proposals/index.ts +4 -3
  167. package/src/api/core/storeys/index.ts +19 -2
  168. package/src/api/core/storeys/layers.ts +339 -0
  169. package/src/api/core/tags.ts +76 -0
  170. package/src/api/core/zoom/index.ts +13 -0
  171. package/src/api/design/bim.ts +210 -0
  172. package/src/api/design/boolean/index.ts +2 -0
  173. package/src/api/design/catalog.ts +203 -0
  174. package/src/api/design/create/bulk-items.ts +294 -0
  175. package/src/api/design/create/grids.ts +137 -0
  176. package/src/api/design/create/index.ts +824 -141
  177. package/src/api/design/create/opening-fields.ts +40 -0
  178. package/src/api/design/delete/index.ts +6 -20
  179. package/src/api/design/dimensions.ts +453 -0
  180. package/src/api/design/doors/index.ts +64 -41
  181. package/src/api/design/family.ts +1342 -0
  182. package/src/api/design/grids.ts +237 -0
  183. package/src/api/design/index.ts +49 -4
  184. package/src/api/design/networks.ts +607 -0
  185. package/src/api/design/query/geometry/index.ts +35 -4
  186. package/src/api/design/query/grids.ts +197 -0
  187. package/src/api/design/query/index.ts +105 -5
  188. package/src/api/design/query/spaces.ts +0 -68
  189. package/src/api/design/types/index.ts +31 -24
  190. package/src/api/design/update/index.ts +398 -4
  191. package/src/api/design/windows/index.ts +40 -22
  192. package/src/api/entity/buildableEnvelope.ts +77 -2
  193. package/src/api/entity/referenceLine.ts +25 -12
  194. package/src/api/entity/space.ts +0 -2
  195. package/src/api/presentation/annotate.ts +90 -58
  196. package/src/api/presentation/camera.ts +289 -0
  197. package/src/api/presentation/drawingContract.ts +70 -0
  198. package/src/api/presentation/drawings.ts +1179 -0
  199. package/src/api/presentation/export.ts +32 -6
  200. package/src/api/presentation/graphics.ts +324 -0
  201. package/src/api/presentation/index.ts +73 -14
  202. package/src/api/presentation/placedViews.ts +120 -57
  203. package/src/api/presentation/sections.ts +420 -0
  204. package/src/api/presentation/sheets.ts +182 -37
  205. package/src/api/presentation/standards.ts +445 -0
  206. package/src/api/presentation/viewPresets.ts +241 -0
  207. package/src/api/presentation/views.ts +81 -10
  208. package/src/api/presentation/walkthroughs.ts +1201 -0
  209. package/src/api/program/departments.ts +54 -2
  210. package/src/api/program/index.ts +16 -6
  211. package/src/api/program/labels.ts +332 -0
  212. package/src/api/program/metadata.ts +364 -0
  213. package/src/api/program/spreadsheet.ts +282 -0
  214. package/src/api/workspace/index.ts +51 -2
  215. package/src/errors/codes.ts +9 -30
  216. package/src/handles.ts +42 -3
  217. package/src/index.ts +2 -0
  218. package/src/massParameters.ops.ts +246 -0
  219. package/src/massParameters.ts +427 -0
  220. package/test/drawingContract.test.mjs +288 -0
  221. package/test/errors.test.mjs +1 -4
  222. package/test/massParameters.test.mjs +952 -0
  223. package/test/paperPensContract.test.mjs +75 -0
  224. package/test/presentationViewSettings.test.mjs +85 -0
  225. package/api-manifest.full.json +0 -6928
  226. package/snaptrude-plugin-core-0.11.0.tgz +0 -0
@@ -4,9 +4,13 @@ import {
4
4
  BrepHandle,
5
5
  ComponentHandle,
6
6
  ContourHandle,
7
+ LineHandle,
8
+ CurveHandle,
7
9
  ProfileHandle,
8
10
  Vec3Handle,
9
11
  } from "../../../handles"
12
+ import { PluginGridStyleOptions } from "./grids"
13
+ import { PluginGraphicsLineStyleId } from "../../presentation/graphics"
10
14
  import {
11
15
  PluginSpaceType,
12
16
  PluginMassType,
@@ -17,13 +21,33 @@ import {
17
21
  PluginBuildableEnvelopeSetbackTier,
18
22
  PluginBuildableEnvelopeVerticalCap,
19
23
  PluginBuildableEnvelopeCreateResult,
24
+ PluginDesignCreateBuildableEnvelopeFromSiteResult,
20
25
  } from "../../entity/buildableEnvelope"
26
+ import { PluginOpeningBaseOptions } from "./opening-fields"
27
+ import type {
28
+ PluginCreateDoorItem,
29
+ PluginCreateFloorItem,
30
+ PluginCreateFurnitureItem,
31
+ PluginCreateVoidItem,
32
+ PluginCreateWallRunItem,
33
+ PluginCreateWindowItem,
34
+ } from "./bulk-items"
35
+ import { PluginWallTopProfile } from "./bulk-items"
36
+ import { PluginWallType } from "../types"
37
+ import {
38
+ PluginFamilyPlacement,
39
+ PluginFamilyFloorOptions,
40
+ PluginFamilySlabOptions,
41
+ PluginFamilyCeilingOptions,
42
+ PluginFamilyValues,
43
+ } from "../catalog"
21
44
 
22
45
  /**
23
46
  * `design.create.*` — author new scene-committed BIM entities.
24
47
  *
25
48
  * Every creator takes geometry handles + scalars and returns a
26
- * {@linkcode ComponentHandle} (or `ComponentHandle[]` for plural creators) — the
49
+ * {@linkcode ComponentHandle} (or `ComponentHandle[]` for plural creators;
50
+ * `wallRuns` returns one `ComponentHandle[]` per run) — the
27
51
  * `Component.id` of the created entity, resolvable across the rest of the
28
52
  * `design.*` surface. Creation is an undoable host call; it **throws** on failure
29
53
  * (no `Result` wrapper — consistent with `core.geom.create.*`).
@@ -31,9 +55,17 @@ import {
31
55
  * Footprint objects take a {@linkcode ContourHandle} (outer profile + holes);
32
56
  * build one with `snaptrude.core.geom.create.contourFromProfile(s)`.
33
57
  *
34
- * `door`/`window` place a catalog item into a host wall (the world point is
58
+ * Family overloads take `{ type, values? }` from `design.catalog`. The family supplies
59
+ * construction; the contour or axis supplies instance geometry. Geometry uses SU,
60
+ * while family length overrides use mm and angles use degrees. Omitted parameters
61
+ * retain family defaults; shared/type, formula and geometry-bound overrides are refused.
62
+ * Placement never publishes a definition. Unsupported references or geometry fail before mutation.
63
+ *
64
+ * `door`/`window` place a parametric family into a host wall (the world point is
35
65
  * projected onto the wall face to locate the opening; the wall is re-cut in one
36
- * undo entry) and throw if the point projects outside the wall. `smartLayout` is
66
+ * undo entry) and throw if the point projects outside the wall. Voids
67
+ * (`void`/`voids`) are the third hosted child: holes through slabs, floors,
68
+ * roofs and ceilings. `smartLayout` is
37
69
  * the one creator returning a grouped {@linkcode PluginSmartLayoutResult} rather
38
70
  * than a handle — it clones an in-scene template into one or more target spaces,
39
71
  * merging every target into a single undo batch (see its JSDoc).
@@ -186,6 +218,9 @@ export abstract class PluginDesignCreateApi {
186
218
  * Create a **slab** by extruding a footprint contour by `thickness`
187
219
  * (default direction: down).
188
220
  *
221
+ * To cut a hole in an EXISTING one, use `design.create.void`; a contour hole
222
+ * here is baked into the footprint.
223
+ *
189
224
  * @param contour - Footprint (outer + holes)
190
225
  * @param thickness - Slab thickness (> 0)
191
226
  * @param direction - Extrusion direction (default `"down"`)
@@ -207,36 +242,33 @@ export abstract class PluginDesignCreateApi {
207
242
  * ```
208
243
  */
209
244
  public abstract slab(
210
- contour: ContourHandle,
211
- thickness: number,
212
- direction?: "up" | "down",
213
- slabType?: PluginSlabType,
245
+ ...args:
246
+ | [
247
+ contour: ContourHandle,
248
+ thickness: number,
249
+ direction?: "up" | "down",
250
+ slabType?: PluginSlabType,
251
+ ]
252
+ | [contour: ContourHandle, options: PluginFamilySlabOptions]
214
253
  ): PluginApiReturn<ComponentHandle>
215
254
 
216
255
  /**
217
256
  * Create a **floor** by extruding a footprint contour by `thickness`
218
257
  * (extruded upward).
219
258
  *
220
- * The parametric engine owns floor topology and may resolve the request
221
- * differently than asked: drawing against existing floors can **split** the
222
- * footprint into several floors, or **merge** it into a pre-existing
223
- * neighbour (that floor's region grows; nothing new is created). The call
224
- * honors the engine's resolution and returns **every floor that covers the
225
- * requested footprint after the operation** — the newly created floor(s),
226
- * and/or the pre-existing floor that absorbed it. The array is never empty
227
- * on success; a lone independently-created floor comes back as `[floor]`.
259
+ * The native family overload takes `{ type, values?, position? }`. Contour
260
+ * geometry uses SU; family values use mm. The family supplies layer thickness.
261
+ * PG2 can split or merge the footprint, so both overloads return an array.
262
+ * A merge into an existing floor can return no newly created handles.
228
263
  *
229
- * **Breaking change in 0.11.0** — previously returned a single
230
- * {@linkcode ComponentHandle}; callers must switch to the array (the first
231
- * element preserves the old single-floor behavior for simple creates).
264
+ * To cut a hole in an EXISTING one, use `design.create.void`; a contour hole
265
+ * here is baked into the footprint.
232
266
  *
233
267
  * @param contour - Footprint (outer + holes)
234
268
  * @param thickness - Floor thickness (> 0)
235
269
  * @param position - Offset from origin
236
- * @returns the {@linkcode ComponentHandle}s of every floor covering the
237
- * requested footprint after parametric resolution (created and/or absorbing)
238
- * @throws if the contour is invalid, the thickness is not positive, or the
239
- * engine rejected the operation (nothing covers the footprint)
270
+ * @returns Newly created {@linkcode ComponentHandle}s; an empty array after a merge
271
+ * @throws if the contour is invalid or the thickness is not positive
240
272
  *
241
273
  * @examplePrompt Create a floor from this room outline
242
274
  * @examplePrompt Add a 100mm thick floor over the footprint
@@ -247,19 +279,66 @@ export abstract class PluginDesignCreateApi {
247
279
  * ```ts
248
280
  * const rect = await snaptrude.core.geom.create.profileRect(5, 4)
249
281
  * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)
250
- * const [floor, ...siblings] = await snaptrude.design.create.floor(contour, 0.1)
282
+ * const floor = await snaptrude.design.create.floor(contour, 0.1)
251
283
  * ```
284
+ *
285
+ * @performance For dimensional floors, `design.create.floors(items[])` uses one
286
+ * host round-trip and one undo entry. That bulk method does not accept native
287
+ * family options; use this method for a family-backed floor.
252
288
  */
253
289
  public abstract floor(
254
- contour: ContourHandle,
255
- thickness: number,
256
- position?: Vec3Handle,
290
+ ...args:
291
+ | [contour: ContourHandle, thickness: number, position?: Vec3Handle]
292
+ | [contour: ContourHandle, options: PluginFamilyFloorOptions]
293
+ ): PluginApiReturn<ComponentHandle[]>
294
+
295
+ /**
296
+ * Create **many floors** in one undoable operation (bulk plural of
297
+ * {@linkcode floor}). Each item extrudes its own footprint contour (outer
298
+ * profile + holes) upward by its own `thickness`, at its own optional
299
+ * position offset. Validate-all-or-throw; one command.
300
+ *
301
+ * @param items - One {@linkcode PluginCreateFloorItem} per floor to create
302
+ * (≥1, ≤1000)
303
+ * @returns the created floors as {@linkcode ComponentHandle}`[]`, in input order
304
+ * @throws `VALIDATION` if the items fail the schema (empty array, more than
305
+ * 1000 items, a non-positive thickness) or a contour is degenerate;
306
+ * `HANDLE_INVALID` if a `contour` or `position` handle is unknown or
307
+ * released; `OPERATION_FAILED` if extrusion fails (nothing is created —
308
+ * `details.itemIndex` is the failing item); `METHOD_NOT_PERMITTED` if the
309
+ * plugin may not write.
310
+ *
311
+ * @examplePrompt Create floors for all these room outlines at once
312
+ * @examplePrompt Add a floor to every space in one operation
313
+ * @examplePrompt Bulk create the floor plates for this building
314
+ * @examplePrompt Lay 100mm floors across these five footprints in one undo step
315
+ * @examplePrompt Create the ground and first floor slabs together
316
+ *
317
+ * @performance Bulk creator — the whole batch is ONE host round-trip and ONE undo
318
+ * entry. Always prefer this over calling `design.create.floor` in a loop: build the
319
+ * full `items[]` array first (all contours and offsets up front), then make one call.
320
+ *
321
+ * # Example
322
+ * ```ts
323
+ * const rect = await snaptrude.core.geom.create.profileRect(5, 4)
324
+ * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)
325
+ * const [ground, upper] = await snaptrude.design.create.floors([
326
+ * { contour, thickness: 0.1 },
327
+ * { contour, thickness: 0.1, position: await snaptrude.core.math.vec3.new(0, 3, 0) },
328
+ * ])
329
+ * ```
330
+ */
331
+ public abstract floors(
332
+ items: PluginCreateFloorItem[],
257
333
  ): PluginApiReturn<ComponentHandle[]>
258
334
 
259
335
  /**
260
336
  * Create a **roof** by extruding a footprint contour by `thickness`. Created
261
337
  * flat (extruded downward); pitch/slope is a separate post-creation edit.
262
338
  *
339
+ * To cut a hole in an EXISTING one, use `design.create.void`; a contour hole
340
+ * here is baked into the footprint.
341
+ *
263
342
  * @param contour - Footprint (outer + holes)
264
343
  * @param thickness - Roof-slab thickness (> 0)
265
344
  * @returns the {@linkcode ComponentHandle} of the created roof
@@ -279,13 +358,16 @@ export abstract class PluginDesignCreateApi {
279
358
  */
280
359
  public abstract roof(
281
360
  contour: ContourHandle,
282
- thickness: number,
361
+ thickness: number | PluginFamilyPlacement,
283
362
  ): PluginApiReturn<ComponentHandle>
284
363
 
285
364
  /**
286
365
  * Create a **ceiling** by extruding a footprint contour by `thickness`, lifted
287
366
  * `heightFromFloor` above the floor.
288
367
  *
368
+ * To cut a hole in an EXISTING one, use `design.create.void`; a contour hole
369
+ * here is baked into the footprint.
370
+ *
289
371
  * @param contour - Footprint (outer + holes)
290
372
  * @param thickness - Ceiling thickness (> 0)
291
373
  * @param heightFromFloor - Lift above the floor (default 0)
@@ -305,9 +387,9 @@ export abstract class PluginDesignCreateApi {
305
387
  * ```
306
388
  */
307
389
  public abstract ceiling(
308
- contour: ContourHandle,
309
- thickness: number,
310
- heightFromFloor?: number,
390
+ ...args:
391
+ | [contour: ContourHandle, thickness: number, heightFromFloor?: number]
392
+ | [contour: ContourHandle, options: PluginFamilyCeilingOptions]
311
393
  ): PluginApiReturn<ComponentHandle>
312
394
 
313
395
  /**
@@ -338,10 +420,14 @@ export abstract class PluginDesignCreateApi {
338
420
  * ```
339
421
  */
340
422
  public abstract column(
341
- position: Vec3Handle,
342
- crossSection: ContourHandle,
343
- height: number,
344
- directionUp?: Vec3Handle,
423
+ ...args:
424
+ | [
425
+ position: Vec3Handle,
426
+ crossSection: ContourHandle,
427
+ height: number,
428
+ directionUp?: Vec3Handle,
429
+ ]
430
+ | [axis: LineHandle, options: PluginFamilyPlacement]
345
431
  ): PluginApiReturn<ComponentHandle>
346
432
 
347
433
  /**
@@ -385,9 +471,9 @@ export abstract class PluginDesignCreateApi {
385
471
  * ```
386
472
  */
387
473
  public abstract beam(
388
- section: ContourHandle,
389
- length: number,
390
- direction?: Vec3Handle,
474
+ ...args:
475
+ | [section: ContourHandle, length: number, direction?: Vec3Handle]
476
+ | [axis: LineHandle, options: PluginFamilyPlacement]
391
477
  ): PluginApiReturn<ComponentHandle>
392
478
 
393
479
  /**
@@ -398,27 +484,27 @@ export abstract class PluginDesignCreateApi {
398
484
  * @param height - Wall height (default engine default)
399
485
  * @param thickness - Wall thickness (default: the `wallType`'s total layer
400
486
  * thickness when `wallType` is given, else the engine default)
401
- * @param wallType - Wall type to build with — the same searchable list the
402
- * Draw tab's Wall Type dropdown offers. Pass a name from
403
- * `design.types.list("wall")` (the summary's `label`, or its `"wall:…"` id).
404
- * Drives the wall's construction layers and default material, and — when
405
- * `thickness` is omitted — its total layer thickness. Default: the engine's
406
- * generic wall.
487
+ * @param wallType - Legacy label/`wall:` id, or the exact `nativeFamily`
488
+ * `{ id, version }` pin from `design.types.list("wall")`. Native pins are
489
+ * authorized on every use; selecting a family in Draw is not registration.
490
+ * Native pins support straight horizontal centerlines and flat tops only.
491
+ * Omit thickness for native pins; a conflicting explicit thickness rejects.
492
+ * Default: the engine's generic wall.
407
493
  * @param storey - Target storey (integer: `1` ground, `2` first floor, `-1`
408
494
  * basement — same convention as `design.create.space`). The walls are
409
495
  * assigned to it and lifted to its base elevation, all inside the same
410
496
  * single undo entry. The storey must already exist
411
497
  * (`core.storeys.create` first). Default: geometry-derived — a profile
412
498
  * drawn at y = 0 lands on storey 1; the ACTIVE storey is NOT consulted.
413
- * @returns the actually-created wall {@linkcode ComponentHandle}s. The
414
- * parametric engine owns wall topology — curves touching existing walls
415
- * may merge into or split against them, so the returned count can differ
416
- * from the input curve count. Profile-ordered 1:1 matches come first,
417
- * then the remaining merged/split products in creation order. Re-query
418
- * (e.g. `design.query.spaces.listGeneratedBim`) rather than assuming a
419
- * wall per input curve.
499
+ * @param topProfiles - Optional top edge per curve of `profile` (same order):
500
+ * a {@linkcode PluginWallTopProfile} makes that wall raked / gabled; `null`
501
+ * or a missing entry keeps it flat at `height`. Straight curves only. Such
502
+ * walls are non-parametric (see {@linkcode PluginWallTopProfile}).
503
+ * @returns a {@linkcode ComponentHandle}`[]` (one per wall), in profile order
420
504
  * @throws if the profile is empty, `wallType` names no wall type in the
421
- * project, `storey` does not exist, or wall creation fails
505
+ * project, `storey` does not exist, `topProfiles` has more entries than the
506
+ * profile has curves or names an arc curve, a top profile drops to the wall
507
+ * base at a mitred corner, or wall creation fails
422
508
  *
423
509
  * @examplePrompt Draw walls along this centerline
424
510
  * @examplePrompt Create 3m high, 200mm thick walls from these points
@@ -427,6 +513,9 @@ export abstract class PluginDesignCreateApi {
427
513
  * @examplePrompt Add walls with mitred corners along the path
428
514
  * @examplePrompt Draw brick walls with plaster along this outline
429
515
  * @examplePrompt Build the perimeter in exposed concrete walls
516
+ * @examplePrompt Create a raked wall that slopes from 3m to 5m
517
+ * @examplePrompt Build a gable end wall with the ridge in the middle
518
+ * @examplePrompt Draw a wall with a sloped top edge
430
519
  *
431
520
  * # Example
432
521
  * ```ts
@@ -442,16 +531,89 @@ export abstract class PluginDesignCreateApi {
442
531
  * const brickWalls = await snaptrude.design.create.walls(
443
532
  * centerlines, 3, undefined, brick.label,
444
533
  * )
534
+ * // …a raked wall (3m at the start, 5m at the end) on a single centerline:
535
+ * const run = await snaptrude.core.geom.create.profileFromCurves([
536
+ * await snaptrude.core.geom.create.line(await v.new(0, 0, 10), await v.new(8, 0, 10)),
537
+ * ])
538
+ * const [raked] = await snaptrude.design.create.walls(run, undefined, 0.2, undefined, undefined, [
539
+ * [{ along: 0, height: 3 }, { along: 1, height: 5 }],
540
+ * ])
445
541
  * ```
542
+ *
543
+ * @performance For MORE THAN ONE run, call `design.create.wallRuns(items[])` — one
544
+ * host round-trip and ONE undo entry for every run in the batch. Looping this
545
+ * single-run creator is N round-trips and N undo entries.
446
546
  */
447
547
  public abstract walls(
448
548
  profile: ProfileHandle,
449
549
  height?: number,
450
550
  thickness?: number,
451
- wallType?: string,
551
+ wallType?: PluginWallType,
452
552
  storey?: number,
553
+ topProfiles?: (PluginWallTopProfile | null)[],
554
+ values?: PluginFamilyValues,
453
555
  ): PluginApiReturn<ComponentHandle[]>
454
556
 
557
+ /**
558
+ * Create **many wall runs** in one undoable operation (bulk plural of
559
+ * {@linkcode walls}). Each item is one full run — a wall per curve in that
560
+ * item's profile chain, mitred at shared endpoints. Junctions resolve within a
561
+ * run, not between runs. Each item carries its own
562
+ * dimensions, `wallType` and `storey`, so a single call can build a whole
563
+ * floor plate or several storeys at once. Per-curve `topProfiles` make raked
564
+ * or gabled walls (see {@linkcode PluginWallTopProfile}). Validate-all-or-throw;
565
+ * one command.
566
+ *
567
+ * @param items - One {@linkcode PluginCreateWallRunItem} per run to create
568
+ * (≥1, ≤1000)
569
+ * @returns one {@linkcode ComponentHandle}`[]` per item, in input order —
570
+ * each inner array holding that run's walls in profile-curve order
571
+ * @throws `VALIDATION` if the items fail the schema (empty array, more than
572
+ * 1000 items, a non-positive height or thickness, a zero-length curve);
573
+ * `HANDLE_INVALID` if a `profile` handle is unknown or released;
574
+ * `PRECONDITION_FAILED` if a profile has no curves, `wallType` names no
575
+ * wall type in the project, or `storey` does not exist;
576
+ * `OPERATION_FAILED` if wall creation fails (nothing is created —
577
+ * `details.itemIndex` is the failing item); `METHOD_NOT_PERMITTED` if the
578
+ * plugin may not write.
579
+ *
580
+ * @examplePrompt Draw all the walls of this floor plan at once
581
+ * @examplePrompt Build every room's perimeter in one operation
582
+ * @examplePrompt Bulk create wall runs from these centerlines
583
+ * @examplePrompt Create the walls for both storeys in a single undo step
584
+ * @examplePrompt Turn these polylines into 200mm brick walls together
585
+ * @examplePrompt Import these raked walls with their top profiles in one go
586
+ *
587
+ * @performance Bulk creator — the whole batch is ONE host round-trip and ONE undo
588
+ * entry. Always prefer this over calling `design.create.walls` in a loop: build the
589
+ * full `items[]` array first (all profiles up front), then make one call.
590
+ *
591
+ * # Example
592
+ * ```ts
593
+ * const v = snaptrude.core.math.vec3
594
+ * const ground = await snaptrude.core.geom.create.profileFromLinePoints([
595
+ * await v.new(0, 0, 0),
596
+ * await v.new(8, 0, 0),
597
+ * await v.new(8, 0, 6),
598
+ * ])
599
+ * const upper = await snaptrude.core.geom.create.profileFromLinePoints([
600
+ * await v.new(0, 0, 0),
601
+ * await v.new(8, 0, 0),
602
+ * ])
603
+ * const [groundWalls, upperWalls] = await snaptrude.design.create.wallRuns([
604
+ * { profile: ground, height: 3, thickness: 0.2 },
605
+ * { profile: upper, height: 3, storey: 2 },
606
+ * // one gable wall: 3m at both ends, 5m ridge in the middle
607
+ * { profile: upper, thickness: 0.2, storey: 3,
608
+ * topProfiles: [[{ along: 0, height: 3 }, { along: 0.5, height: 5 }, { along: 1, height: 3 }]] },
609
+ * ])
610
+ * console.log(groundWalls.length, "walls on the ground floor")
611
+ * ```
612
+ */
613
+ public abstract wallRuns(
614
+ items: PluginCreateWallRunItem[],
615
+ ): PluginApiReturn<ComponentHandle[][]>
616
+
455
617
  /**
456
618
  * Create a **staircase** from a parametric preset, placed at a point.
457
619
  *
@@ -502,8 +664,14 @@ export abstract class PluginDesignCreateApi {
502
664
  * every created line.
503
665
  *
504
666
  * @param profile - Ordered curve chain (centerlines)
505
- * @param color - Hex color `"#RRGGBB"` (default white)
506
- * @param thickness - Line thickness (default 1)
667
+ * Prefer `{ styleId: "wide" }`: the project style's pen/pattern and native
668
+ * legacy mirror are saved in the creation command, as one undo step. styleId
669
+ * requires Paper Pens; unstyled legacy creation works with the feature off.
670
+ * Do not mix options with trailing positional arguments, or styleId with
671
+ * legacy thickness/style. The old positional signature remains supported.
672
+ *
673
+ * @param colorOrOptions - Options, or legacy hex color `"#RRGGBB"`.
674
+ * @param thickness - Legacy line thickness (default 1)
507
675
  * @param style - Line style (default `SOLID`)
508
676
  * @param gridTag - Grid tag label (default `"A"`)
509
677
  * @returns a {@linkcode ComponentHandle}`[]` (one per curve), in profile order
@@ -532,12 +700,140 @@ export abstract class PluginDesignCreateApi {
532
700
  */
533
701
  public abstract referenceLines(
534
702
  profile: ProfileHandle,
535
- color?: string,
703
+ colorOrOptions?: string | PluginReferenceLineOptions,
536
704
  thickness?: number,
537
705
  style?: PluginReferenceLineStyle,
538
706
  gridTag?: string,
539
707
  ): PluginApiReturn<ComponentHandle[]>
540
708
 
709
+ /**
710
+ * Create a free rectangular **structural grid** — one grid group with two
711
+ * perpendicular line families (`linesX` lines spaced `spacingX` apart, and
712
+ * `linesY` lines spaced `spacingY` apart) on the active storey. One undoable
713
+ * creation.
714
+ *
715
+ * Units: spacings in Snaptrude units; `angle` in degrees (CCW about +Y,
716
+ * default 0). Without `center` the grid is centred on the current viewport
717
+ * (the native tool's behavior) — pass `center` for deterministic placement.
718
+ *
719
+ * @param linesX - Line count of the first family (integer ≥ 1)
720
+ * @param linesY - Line count of the second family (integer ≥ 1)
721
+ * @param spacingX - First-family spacing (Snaptrude units, > 0)
722
+ * @param spacingY - Second-family spacing (Snaptrude units, > 0)
723
+ * @param options - `angle`, `center`, and {@linkcode PluginGridStyleOptions}
724
+ * @returns the created grid group's {@linkcode ComponentHandle}
725
+ *
726
+ * @examplePrompt Create a 5 by 8 structural grid with 6 metre spacing
727
+ * @examplePrompt Add a column grid rotated 30 degrees
728
+ * @examplePrompt Set up a structural grid at the site origin
729
+ *
730
+ * # Example
731
+ * ```ts
732
+ * const centre = await snaptrude.core.math.vec3.new(0, 0, 0)
733
+ * const grid = await snaptrude.design.create.grid(5, 8, 6, 6, { center: centre })
734
+ * ```
735
+ */
736
+ public abstract grid(
737
+ linesX: number,
738
+ linesY: number,
739
+ spacingX: number,
740
+ spacingY: number,
741
+ options?: {
742
+ angle?: number
743
+ center?: Vec3Handle
744
+ } & PluginGridStyleOptions,
745
+ ): PluginApiReturn<ComponentHandle>
746
+
747
+ /**
748
+ * Create a **structural grid from a single curve** — one grid group holding
749
+ * one wing with one line (the draw tool's output). The curve may be a line
750
+ * or an arc; circles are rejected with `VALIDATION`. Undoable.
751
+ *
752
+ * @param curve - The grid line's geometry ({@linkcode CurveHandle}, world space)
753
+ * @param options - `tag` (defaults to the draw tool's next tag) and
754
+ * {@linkcode PluginGridStyleOptions}
755
+ * @returns the created grid group's {@linkcode ComponentHandle}
756
+ *
757
+ * @examplePrompt Draw a grid line from A to B
758
+ * @examplePrompt Add a curved grid line through these points
759
+ * @examplePrompt Create grid line "C" along this wall
760
+ *
761
+ * # Example
762
+ * ```ts
763
+ * const { vec3 } = snaptrude.core.math
764
+ * const from = await vec3.new(0, 0, 0)
765
+ * const to = await vec3.new(30, 0, 0)
766
+ * const curve = await snaptrude.core.geom.create.line(from, to)
767
+ * const grid = await snaptrude.design.create.gridFromCurve(curve, { tag: "A" })
768
+ * ```
769
+ */
770
+ public abstract gridFromCurve(
771
+ curve: CurveHandle,
772
+ options?: {
773
+ tag?: string
774
+ } & PluginGridStyleOptions,
775
+ ): PluginApiReturn<ComponentHandle>
776
+
777
+ /**
778
+ * Generate **structural grids fitted to mass footprints** — one grid group
779
+ * per footprint cluster, with counts and orientation derived from each
780
+ * footprint's geometry and the given spacings. All created groups commit as
781
+ * ONE undoable unit.
782
+ *
783
+ * Lenient like the native tool: masses without an extractable footprint
784
+ * (no B-rep, degenerate top face) are skipped; throws `VALIDATION` only when
785
+ * NONE of the given masses yields a footprint.
786
+ *
787
+ * @param masses - Masses whose footprints seat the grids
788
+ * @param spacingX - First-family spacing (Snaptrude units, > 0)
789
+ * @param spacingY - Second-family spacing (Snaptrude units, > 0)
790
+ * @param options - {@linkcode PluginGridStyleOptions}
791
+ * @returns the created grid groups' {@linkcode ComponentHandle}`[]`
792
+ *
793
+ * @examplePrompt Generate grids for the selected masses
794
+ * @examplePrompt Auto-generate a 6m structural grid on this building
795
+ * @examplePrompt Fit column grids to every mass on this storey
796
+ *
797
+ * # Example
798
+ * ```ts
799
+ * const masses = await snaptrude.design.query.listMasses({ storeys: [1] })
800
+ * const grids = await snaptrude.design.create.gridsFromMasses(masses, 6, 6)
801
+ * ```
802
+ */
803
+ public abstract gridsFromMasses(
804
+ masses: ComponentHandle[],
805
+ spacingX: number,
806
+ spacingY: number,
807
+ options?: PluginGridStyleOptions,
808
+ ): PluginApiReturn<ComponentHandle[]>
809
+
810
+ /**
811
+ * Create **columns at every grid-line intersection** of the given grid
812
+ * groups, sized `width` × `depth` and oriented to the grid axes, on the
813
+ * grids' storey. All columns commit as ONE undoable unit.
814
+ *
815
+ * @param grids - Grid groups whose intersections seat the columns
816
+ * @param width - Column width (Snaptrude units, > 0)
817
+ * @param depth - Column depth (Snaptrude units, > 0)
818
+ * @returns the created columns' {@linkcode ComponentHandle}`[]`
819
+ * @throws `VALIDATION` when an id does not resolve to a grid group;
820
+ * `OPERATION_FAILED` when the grids have no line intersections
821
+ *
822
+ * @examplePrompt Generate columns on this grid
823
+ * @examplePrompt Place 450mm square columns at every grid intersection
824
+ *
825
+ * # Example
826
+ * ```ts
827
+ * const grids = await snaptrude.design.query.listGrids()
828
+ * const columns = await snaptrude.design.create.columnsFromGrids(grids, 0.45, 0.45)
829
+ * ```
830
+ */
831
+ public abstract columnsFromGrids(
832
+ grids: ComponentHandle[],
833
+ width: number,
834
+ depth: number,
835
+ ): PluginApiReturn<ComponentHandle[]>
836
+
541
837
  /**
542
838
  * Place a **furniture** item from the project library at a world position.
543
839
  *
@@ -556,17 +852,19 @@ export abstract class PluginDesignCreateApi {
556
852
  * `position.y` (the same surface-flush contract as interactive drag-drop) —
557
853
  * pass the floor/storey elevation to stand furniture on it; never add half
558
854
  * the item's height yourself.
559
- * @param options - Optional placement options: `label` — instance name
560
- * (default auto `${name}Ins${n}`); `createNewSourceMesh` — emit a
561
- * source-mesh creation command (default `true`). `position.y` is always
562
- * grounded: the placed instance's bounding-box bottom rests at it (the
563
- * surface-flush grounding described above)
855
+ * @param options - Optional placement options: `label` — instance name and
856
+ * readable label (`design.query.getLabel`; default auto `${name}Ins${n}`);
857
+ * `createNewSourceMesh` — legacy flag, kept for compatibility. The host owns
858
+ * source-mesh persistence: a catalog source is recorded exactly once, by the
859
+ * first placement that brings it in, and this flag cannot skip that record.
564
860
  * @param angleInDegrees - Optional signed rotation about the vertical axis, in
565
861
  * degrees (same convention as {@linkcode PluginDesignTransformApi.rotate}).
566
862
  * Applied at creation time so it is part of the placement's single undo entry.
567
863
  * Default: the item's own (unrotated) orientation.
568
864
  * @returns the {@linkcode ComponentHandle} of the placed furniture instance
569
- * @throws if the catalog id is unknown, the source mesh fails to load, or placement fails
865
+ * @throws if the catalog id is unknown, the source mesh fails to load, or placement fails;
866
+ * `PRECONDITION_FAILED` (`details.engineCode: "TOOL_ACTIVE"`) while the interactive
867
+ * furniture tool is active — finish or cancel it first
570
868
  *
571
869
  * @examplePrompt Place a chair from the library at this spot
572
870
  * @examplePrompt Add a sofa from the furniture catalog to the living room
@@ -590,39 +888,82 @@ export abstract class PluginDesignCreateApi {
590
888
  * 90,
591
889
  * )
592
890
  * ```
891
+ *
892
+ * @performance For MORE THAN ONE item, call `design.create.furnitureItems(items[])` —
893
+ * one host round-trip and ONE undo entry, and a catalog source shared by several
894
+ * items is fetched once. Looping this single-item creator is N round-trips.
593
895
  */
594
896
  public abstract furniture(
595
897
  catalogId: string,
596
898
  position: Vec3Handle,
597
- options?: {
598
- label?: string
599
- createNewSourceMesh?: boolean
600
- },
899
+ options?: { label?: string; createNewSourceMesh?: boolean },
601
900
  angleInDegrees?: number,
602
901
  ): PluginApiReturn<ComponentHandle>
603
902
 
604
903
  /**
605
- * Place a **door** from the catalog into a host wall.
904
+ * Place **many furniture items** in one undoable operation (bulk plural of
905
+ * {@linkcode furniture}). Each item names a catalog id and an absolute world
906
+ * position (`position.y` is the REST elevation — the same grounding contract
907
+ * as the singular creator), with optional label and rotation. A catalog
908
+ * source shared by several items is fetched and recorded once for the whole
909
+ * batch, so the host — not the caller — owns source persistence (there is no
910
+ * per-item `createNewSourceMesh`). Validate-all-or-throw; one command.
911
+ *
912
+ * @param items - One {@linkcode PluginCreateFurnitureItem} per instance to
913
+ * place (≥1, ≤1000)
914
+ * @returns the placed instances as {@linkcode ComponentHandle}`[]`, in input order
915
+ * @throws `VALIDATION` if the items fail the schema (empty array, more than
916
+ * 1000 items); `HANDLE_INVALID` if a `position` handle is unknown or
917
+ * released; `PRECONDITION_FAILED` if a catalog id is unknown, a furniture
918
+ * tool is active, or a parametric group is active (exit it first, or place
919
+ * items one at a time with `furniture`); `OPERATION_FAILED` if a source
920
+ * mesh fails to load or placement fails (nothing is created —
921
+ * `details.itemIndex` is the failing item); `METHOD_NOT_PERMITTED` if the plugin may not write.
922
+ *
923
+ * @examplePrompt Place all the desks for this office at once
924
+ * @examplePrompt Furnish every bedroom in one operation
925
+ * @examplePrompt Bulk place these chairs around the table
926
+ * @examplePrompt Add the whole furniture layout in a single undo step
927
+ * @examplePrompt Drop twenty copies of this chair at these positions
928
+ *
929
+ * @performance Bulk creator — the whole batch is ONE host round-trip and ONE undo
930
+ * entry, and each distinct catalog source loads once. Always prefer this over
931
+ * calling `design.create.furniture` in a loop: build the full `items[]` array
932
+ * first (all positions and angles up front), then make one call.
606
933
  *
607
- * Identified by a **catalog (library) id** — the team object's stable `_id`
608
- * or the general library's `fullName`. Discover ids with
934
+ * # Example
935
+ * ```ts
936
+ * const v = snaptrude.core.math.vec3
937
+ * const [chair] = await snaptrude.design.furniture.listCatalog()
938
+ * const placed = await snaptrude.design.create.furnitureItems([
939
+ * { catalogId: chair.id, position: await v.new(3, 0, 5), label: "Chair-01" },
940
+ * { catalogId: chair.id, position: await v.new(4, 0, 5), angleInDegrees: 90 },
941
+ * { catalogId: chair.id, position: await v.new(5, 0, 5), angleInDegrees: 180 },
942
+ * ])
943
+ * ```
944
+ */
945
+ public abstract furnitureItems(
946
+ items: PluginCreateFurnitureItem[],
947
+ ): PluginApiReturn<ComponentHandle[]>
948
+
949
+ /**
950
+ * Place a parametric **door** family into a host wall.
951
+ *
952
+ * Identified by a saved **family definition id**. Discover ids with
609
953
  * {@linkcode PluginDesignDoorsApi.listCatalog}. The world `position` is
610
954
  * projected onto the host wall's face to locate the opening; the wall is
611
955
  * re-cut and the door hosted in a single undo entry. Placement is
612
- * asynchronous (the source mesh is fetched if not cached).
956
+ * asynchronous. Values and host bindings are evaluated before the first cut.
613
957
  *
614
- * @param catalogId - Library id: team `_id` or general `fullName`
958
+ * @param familyId - Hosted door definition ID; legacy mesh catalog IDs are not accepted
615
959
  * @param hostWall - The wall to host the door
616
960
  * @param position - World point projected onto the wall to locate the opening
617
- * @param options - Optional placement options: `label` — instance name
618
- * @param facing - World point selecting which side of the wall the door faces
619
- * (the room it opens into) — the same convention as approaching the wall
620
- * from that side with the cursor in the interactive tool. Any point clearly
621
- * on that side works (e.g. the room's center). Default: the engine picks a
622
- * side (nondeterministic when `position` sits on the wall centerline).
961
+ * @param options - `version` pins the definition. `values` sets authored parameters
962
+ * (numeric lengths in SU, or unit strings). `label` names the instance.
963
+ * `hinge` selects the declared `hand` enum: local -X is left, +X is right.
964
+ * @param facing - World point selecting local +Z; omitted uses the picked face
623
965
  * @returns the {@linkcode ComponentHandle} of the placed door
624
- * @throws if the catalog id is unknown, the host is not a wall, the source
625
- * mesh fails to load, or the projected point falls **outside** the host wall
966
+ * @throws if the family/category/parameters are invalid, or placement or cutting fails
626
967
  *
627
968
  * @examplePrompt Add a door to this wall here
628
969
  * @examplePrompt Place a sliding door on the selected wall
@@ -630,6 +971,7 @@ export abstract class PluginDesignCreateApi {
630
971
  * @examplePrompt Insert the entrance door into this wall
631
972
  * @examplePrompt Add a door to the wall and call it Entry-01
632
973
  * @examplePrompt Add a door that opens into the living room
974
+ * @examplePrompt Put a door here hinged on the north jamb
633
975
  *
634
976
  * # Example
635
977
  * ```ts
@@ -649,37 +991,100 @@ export abstract class PluginDesignCreateApi {
649
991
  * undefined,
650
992
  * await snaptrude.core.math.vec3.new(3, 0, 9),
651
993
  * )
994
+ * // …and hinged on the jamb nearest a point (here the +x end of the opening):
995
+ * const hingedRight = await snaptrude.design.create.door(
996
+ * entry.id,
997
+ * wall,
998
+ * await snaptrude.core.math.vec3.new(3, 0, 5),
999
+ * { hinge: await snaptrude.core.math.vec3.new(3.5, 0, 5) },
1000
+ * await snaptrude.core.math.vec3.new(3, 0, 9),
1001
+ * )
652
1002
  * ```
1003
+ *
1004
+ * @performance For MORE THAN ONE door, call `design.create.doors(items[])` — one host
1005
+ * round-trip and ONE undo entry, with each host wall re-cut once per opening inside
1006
+ * that single call. Looping this single-door creator is N round-trips.
653
1007
  */
654
1008
  public abstract door(
655
- catalogId: string,
1009
+ familyId: string,
656
1010
  hostWall: ComponentHandle,
657
1011
  position: Vec3Handle,
658
- options?: { label?: string },
1012
+ options?: {
1013
+ label?: string
1014
+ hinge?: Vec3Handle
1015
+ version?: number
1016
+ values?: Record<string, number | string>
1017
+ },
659
1018
  facing?: Vec3Handle,
660
1019
  ): PluginApiReturn<ComponentHandle>
661
1020
 
662
1021
  /**
663
- * Place a **window** from the catalog into a host wall.
1022
+ * Place **many doors** into host walls in one undoable operation (bulk plural
1023
+ * of {@linkcode door}). Each item carries its own family id, host wall,
1024
+ * world position, and optional `facing`, `label`, `width` and `height` — the
1025
+ * same fields as a `"door"` {@linkcode PluginDesignCreateOpeningOptions}.
1026
+ * Validate-all-or-throw; one command.
1027
+ *
1028
+ * The wall keeps its identity. Several items may use the same host.
1029
+ * Width and height set named recipe parameters, not mesh scale. The recipe's
1030
+ * evaluated hole can include frame allowances. A failed item rolls back the batch.
1031
+ *
1032
+ * @param items - One {@linkcode PluginCreateDoorItem} per door to place
1033
+ * (≥1, ≤1000)
1034
+ * @returns the placed doors as {@linkcode ComponentHandle}`[]`, in input order
1035
+ * @throws `VALIDATION` if the items fail the schema (empty array, more than
1036
+ * 1000 items, unknown fields, a non-positive `width`/`height`);
1037
+ * `HANDLE_INVALID` if a `position`, `facing` or `hinge` handle is unknown
1038
+ * or released; `PRECONDITION_FAILED` if the family cannot be resolved or saved,
1039
+ * or a host is locked / is not in the active proposal;
1040
+ * `OPERATION_FAILED` if placement, containment, overlap checks or cutting fail
1041
+ * (nothing is created — `details.itemIndex` is the failing item);
1042
+ * `METHOD_NOT_PERMITTED` if the plugin may not write.
1043
+ *
1044
+ * @examplePrompt Add doors to all of these walls at once
1045
+ * @examplePrompt Place a door in every room in one operation
1046
+ * @examplePrompt Bulk add the entrance doors from this schedule
1047
+ * @examplePrompt Put three doors on this wall in a single undo step
1048
+ * @examplePrompt Add all the doors for this floor plan together
1049
+ *
1050
+ * @performance Bulk creator — the whole batch is ONE host round-trip and ONE undo
1051
+ * entry. Always prefer this over
1052
+ * calling `design.create.door` in a loop: build the full `items[]` array first
1053
+ * (all host walls and points up front), then make one call.
664
1054
  *
665
- * Identified by a **catalog (library) id** — the team object's stable `_id`
666
- * or the general library's `fullName`. Discover ids with
1055
+ * # Example
1056
+ * ```ts
1057
+ * const v = snaptrude.core.math.vec3
1058
+ * const [wall] = await snaptrude.design.query.listWalls({ isSelected: true })
1059
+ * const [entry] = await snaptrude.design.doors.listCatalog()
1060
+ * const [front, side] = await snaptrude.design.create.doors([
1061
+ * { familyId: entry.id, hostWall: wall, position: await v.new(2, 0, 5), label: "D-01" },
1062
+ * { familyId: entry.id, hostWall: wall, position: await v.new(6, 0, 5), values: { width: "1200mm" } },
1063
+ * ])
1064
+ * // The host still resolves after both cuts:
1065
+ * const currentWall = await snaptrude.design.query.getHost(front)
1066
+ * ```
1067
+ */
1068
+ public abstract doors(
1069
+ items: PluginCreateDoorItem[],
1070
+ ): PluginApiReturn<ComponentHandle[]>
1071
+
1072
+ /**
1073
+ * Place a parametric **window** family into a host wall.
1074
+ *
1075
+ * Identified by a saved **family definition id**. Discover ids with
667
1076
  * {@linkcode PluginDesignWindowsApi.listCatalog}. The world `position` is
668
1077
  * projected onto the host wall's face to locate the opening; the wall is
669
1078
  * re-cut and the window hosted in a single undo entry. Placement is
670
- * asynchronous (the source mesh is fetched if not cached).
1079
+ * asynchronous. The sill parameter determines elevation; position Y is ignored.
671
1080
  *
672
- * @param catalogId - Library id: team `_id` or general `fullName`
1081
+ * @param familyId - Hosted window definition ID; legacy mesh catalog IDs are not accepted
673
1082
  * @param hostWall - The wall to host the window
674
1083
  * @param position - World point projected onto the wall to locate the opening
675
- * @param options - Optional placement options: `label` — instance name
676
- * @param facing - World point selecting which side of the wall the window
677
- * faces (matters for asymmetric windows, e.g. casement swing) — same
678
- * convention as {@linkcode PluginDesignCreateApi.door}. Default: the engine
679
- * picks a side (nondeterministic when `position` sits on the centerline).
1084
+ * @param options - `version`, `values`, `label`, and parameter-driven `hinge`, as for door
1085
+ * @param facing - World point selecting local +Z; omitted uses the picked face
680
1086
  * @returns the {@linkcode ComponentHandle} of the placed window
681
- * @throws if the catalog id is unknown, the host is not a wall, the source
682
- * mesh fails to load, or the projected point falls **outside** the host wall
1087
+ * @throws if the family/category/parameters are invalid, or placement or cutting fails
683
1088
  *
684
1089
  * @examplePrompt Add a window to this wall here
685
1090
  * @examplePrompt Place a casement window on the selected wall
@@ -699,40 +1104,244 @@ export abstract class PluginDesignCreateApi {
699
1104
  * { label: "Win-01" },
700
1105
  * )
701
1106
  * ```
1107
+ *
1108
+ * @performance For MORE THAN ONE window, call `design.create.windows(items[])` — one
1109
+ * host round-trip and ONE undo entry, with each host wall re-cut once per opening
1110
+ * inside that single call. Looping this single-window creator is N round-trips.
702
1111
  */
703
1112
  public abstract window(
704
- catalogId: string,
1113
+ familyId: string,
705
1114
  hostWall: ComponentHandle,
706
1115
  position: Vec3Handle,
707
- options?: { label?: string },
1116
+ options?: {
1117
+ label?: string
1118
+ hinge?: Vec3Handle
1119
+ version?: number
1120
+ values?: Record<string, number | string>
1121
+ },
708
1122
  facing?: Vec3Handle,
709
1123
  ): PluginApiReturn<ComponentHandle>
710
1124
 
711
1125
  /**
712
- * Place a catalog **door or window** into a host wall with optional size overrides.
1126
+ * Place **many windows** into host walls in one undoable operation (bulk
1127
+ * plural of {@linkcode window}). Each item carries its own family id, host
1128
+ * wall, world position, and optional `facing`, `label`, `width`, `height` and
1129
+ * `sillHeight` — the same fields as a `"window"`
1130
+ * {@linkcode PluginDesignCreateOpeningOptions}. `sillHeight` is measured from
1131
+ * the host wall base to the BOTTOM of the window. Validate-all-or-throw; one
1132
+ * command.
1133
+ *
1134
+ * Hosts keep their identity. Width, height and sillHeight set the declared
1135
+ * width, height and sill parameters before the first cut. No mesh scaling occurs.
1136
+ *
1137
+ * @param items - One {@linkcode PluginCreateWindowItem} per window to place
1138
+ * (≥1, ≤1000)
1139
+ * @returns the placed windows as {@linkcode ComponentHandle}`[]`, in input order
1140
+ * @throws `VALIDATION` if the items fail the schema (empty array, more than
1141
+ * 1000 items, unknown fields, a non-positive `width`/`height`, a negative
1142
+ * `sillHeight`); `HANDLE_INVALID` if a `position`, `facing` or `hinge`
1143
+ * handle is unknown or released; `PRECONDITION_FAILED` if the family cannot be
1144
+ * resolved or saved, or a host is locked / is not in the active proposal;
1145
+ * `OPERATION_FAILED` if placement, containment, overlap checks or cutting fail
1146
+ * (nothing is created — `details.itemIndex` is the failing item);
1147
+ * `METHOD_NOT_PERMITTED` if the plugin may not write.
1148
+ *
1149
+ * @examplePrompt Add windows along this whole facade at once
1150
+ * @examplePrompt Place a window in every bedroom in one operation
1151
+ * @examplePrompt Bulk add the windows from this schedule
1152
+ * @examplePrompt Put four windows on this wall in a single undo step
1153
+ * @examplePrompt Add all the windows with a 0.9m sill height together
1154
+ *
1155
+ * @performance Bulk creator — the whole batch is ONE host round-trip and ONE undo
1156
+ * entry. Always prefer this over
1157
+ * calling `design.create.window` in a loop: build the full `items[]` array first
1158
+ * (all host walls and points up front), then make one call.
1159
+ *
1160
+ * # Example
1161
+ * ```ts
1162
+ * const v = snaptrude.core.math.vec3
1163
+ * const [wall] = await snaptrude.design.query.listWalls({ isSelected: true })
1164
+ * const [casement] = await snaptrude.design.windows.listCatalog()
1165
+ * const placed = await snaptrude.design.create.windows([
1166
+ * { familyId: casement.id, hostWall: wall, position: await v.new(2, 0, 5), values: { sill: "900mm" } },
1167
+ * { familyId: casement.id, hostWall: wall, position: await v.new(5, 0, 5), values: { sill: "900mm" } },
1168
+ * ])
1169
+ * const currentWall = await snaptrude.design.query.getHost(placed[0])
1170
+ * ```
1171
+ */
1172
+ public abstract windows(
1173
+ items: PluginCreateWindowItem[],
1174
+ ): PluginApiReturn<ComponentHandle[]>
1175
+
1176
+ /**
1177
+ * Cut a **void** — a hole, opening or shaft — through an existing FLAT slab,
1178
+ * floor, roof or ceiling. The void is a selectable child object of its host
1179
+ * (the "Void" of the Draw Void tool). It always cuts the full host thickness.
1180
+ * It can be moved with `design.transform.move` (a move that would leave the
1181
+ * host is shortened without a report) and removed with
1182
+ * `design.delete.entities`; to resize, delete it and cut a new one
1183
+ * (`design.update.space` refuses a void).
1184
+ *
1185
+ * Give the outline **in plan** (all points at one height): its height is
1186
+ * ignored, and it is dropped straight onto the host's top face, so one outline
1187
+ * works on every storey. Keep it at least 25.4 mm (0.1 SU) inside the host
1188
+ * edge and away from other voids; merge overlapping holes into one outline.
1189
+ * Straight segments and arcs only (no splines).
1190
+ *
1191
+ * **A room has a floor finish AND a structural slab that overlap in plan; a
1192
+ * void in one does not cut the other, so a through-opening needs one void in
1193
+ * each.**
1194
+ *
1195
+ * `getEntityType` reports a void as `"void"`. Use `design.query.listVoids`
1196
+ * or `listByType("void")`; `listMasses` excludes voids. `getHost` and
1197
+ * `listChildren(host)` expose the host relationship, including after a copy.
1198
+ * Authored parametric families with a cut relationship keep their family
1199
+ * identity; discover those through family queries instead.
1200
+ *
1201
+ * Call it as `snaptrude.design.create.void(...)`; `void` is a reserved word and
1202
+ * cannot be destructured. Voids in walls, on sloped, generated or multi-plane
1203
+ * roofs, and on instanced hosts are not available yet; for a door or window
1204
+ * use `design.create.door` / `window`. To make a NEW slab that already has a
1205
+ * fixed hole, pass a contour with holes to `design.create.slab`; that hole is
1206
+ * part of the footprint, not an object. For a contour's outer loop, pass
1207
+ * `core.geom.query.contour.getOuterProfile(contour)`.
1208
+ *
1209
+ * # Example
1210
+ * ```ts
1211
+ * const m = 1000 / 254 // metres → Snaptrude units (SU)
1212
+ * const [slab] = await snaptrude.design.query.listByType("slab", { isSelected: true })
1213
+ * const center = await snaptrude.core.math.vec3.new(10 * m, 0, 5 * m) // plan point; y is ignored
1214
+ * const outline = await snaptrude.core.geom.create.profileRect(2 * m, 2 * m, center) // 2 m x 2 m
1215
+ * const hole = await snaptrude.design.create.void(slab, outline)
1216
+ * ```
1217
+ *
1218
+ * A round hole is two arcs (a circle handle is not a profile):
1219
+ * ```ts
1220
+ * const m = 1000 / 254
1221
+ * const { vec3 } = snaptrude.core.math
1222
+ * const g = snaptrude.core.geom.create
1223
+ * const r = 0.5 * m // 1 m diameter
1224
+ * const centre = await vec3.new(10 * m, 0, 5 * m)
1225
+ * const axis = await vec3.new(0, -1, 0)
1226
+ * const east = await vec3.new(10 * m + r, 0, 5 * m)
1227
+ * const west = await vec3.new(10 * m - r, 0, 5 * m)
1228
+ * const round = await g.profileFromCurves([await g.arc(east, west, centre, axis), await g.arc(west, east, centre, axis)])
1229
+ * await snaptrude.design.create.void(slab, round) // never destructure `void`
1230
+ * ```
1231
+ *
1232
+ * @param host - The slab, floor, roof or ceiling to cut through
1233
+ * @param outline - Closed, flat plan outline (world SU); its height is ignored
1234
+ * @returns the {@linkcode ComponentHandle} of the new void
1235
+ * @throws `VALIDATION` if the outline is open, crosses itself, is not flat or
1236
+ * has a spline, or the arguments are the wrong handle kinds or swapped;
1237
+ * `HANDLE_INVALID` if a handle is unknown or released or the host was
1238
+ * deleted; `HANDLE_STALE` if the host was rebuilt (re-query it);
1239
+ * `PRECONDITION_FAILED` if the
1240
+ * host is a wall or not a slab/floor/roof/ceiling, is sloped, instanced, in
1241
+ * another building, locked, outside the active proposal, a generated roof or
1242
+ * not a single plate, or there is no active structure (`engineCode`
1243
+ * `NO_ACTIVE_STRUCTURE`); `OPERATION_FAILED` with `engineCode`
1244
+ * `VOID_DOES_NOT_FIT` when the outline crosses the host edge, overlaps a void
1245
+ * or hole, is closer than 25.4 mm to either, or the fit could not be checked,
1246
+ * or another `engineCode` when the cut failed (nothing is created, except
1247
+ * `PUBLISH_FAILED` with a hint that the voids are in the scene: undo, then
1248
+ * retry); `METHOD_NOT_PERMITTED` if the plugin may not write.
1249
+ *
1250
+ * @examplePrompt Cut a hole in this slab
1251
+ * @examplePrompt Add a 1.2m by 2.4m stair opening in the first-floor slab
1252
+ * @examplePrompt Make a 600 x 600 service penetration in the floor for the riser
1253
+ * @examplePrompt Put a skylight opening through the roof
1254
+ * @examplePrompt Cut a round 1m duct hole in the slab
1255
+ * @examplePrompt Make a cut-out in the ceiling for the light well
1256
+ * @examplePrompt Add a double-height atrium void in the level 2 floor
1257
+ *
1258
+ * @performance Single-void creator. For MORE THAN ONE void, call
1259
+ * `design.create.voids(items[])`: one host round-trip, ONE undo entry, one cut
1260
+ * per host.
1261
+ */
1262
+ public abstract void(
1263
+ host: ComponentHandle,
1264
+ outline: ProfileHandle,
1265
+ ): PluginApiReturn<ComponentHandle>
1266
+
1267
+ /**
1268
+ * Cut **many voids** (holes, openings, shafts) through slabs, floors, roofs
1269
+ * and ceilings in one undoable operation (bulk plural of {@linkcode void}).
1270
+ * Items may share a host or use different hosts — one shaft outline on every
1271
+ * storey's slab AND floor is one call. Validate-all-or-throw: one item whose
1272
+ * host does not contain the outline rejects the whole batch, so pass only the
1273
+ * hosts that contain it; `details.itemIndex` names the failing item (for two
1274
+ * overlapping items of the batch, the earlier one). At most 50 voids per host
1275
+ * and 200 in all per call. Same outline rules as {@linkcode void}: plan outline, 25.4 mm
1276
+ * (0.1 SU) clearance, floor AND slab are separate hosts. Call it as
1277
+ * `snaptrude.design.create.voids(...)`.
1278
+ *
1279
+ * # Example
1280
+ * ```ts
1281
+ * const m = 1000 / 254
1282
+ * // The user selects the slabs AND floors the shaft passes through; every one must contain the outline.
1283
+ * const hosts = [
1284
+ * ...(await snaptrude.design.query.listByType("slab", { isSelected: true })),
1285
+ * ...(await snaptrude.design.query.listByType("floor", { isSelected: true })),
1286
+ * ]
1287
+ * const centre = await snaptrude.core.math.vec3.new(12 * m, 0, -6 * m)
1288
+ * const shaft = await snaptrude.core.geom.create.profileRect(2 * m, 2 * m, centre)
1289
+ * const holes = await snaptrude.design.create.voids(hosts.map((host) => ({ host, outline: shaft })))
1290
+ * ```
1291
+ *
1292
+ * @param items - One {@linkcode PluginCreateVoidItem} per void (≥1, ≤200; ≤50 per host)
1293
+ * @returns the new voids as {@linkcode ComponentHandle}`[]`, in input order
1294
+ * @throws `VALIDATION` if the items fail the schema (empty array, more than
1295
+ * 1000 items, unknown fields), an outline is invalid (as for
1296
+ * {@linkcode void}), more than 50 items share one host, or there are more
1297
+ * than 200 items; plus every code {@linkcode void} throws, each with
1298
+ * `details.itemIndex`. Nothing is created on any failure, except
1299
+ * `PUBLISH_FAILED` (as for {@linkcode void}).
1300
+ *
1301
+ * @examplePrompt Cut a lift shaft through every floor slab of the core
1302
+ * @examplePrompt Cut a 2m x 2m shaft through all the slabs at this location
1303
+ * @examplePrompt Make stair openings in all the floors above the staircases
1304
+ * @examplePrompt Make holes in the roof for these three skylights
1305
+ * @examplePrompt Cut openings in each storey's slab matching this outline
1306
+ * @examplePrompt Punch the same riser penetration through every floor at once
1307
+ *
1308
+ * @performance ONE host round-trip, ONE undo entry, ONE cut per host. Never call
1309
+ * `void` in a loop. Runs synchronously on the main thread: 50 voids on one
1310
+ * host take about 1 s, and the time grows with the number of hosts (caps:
1311
+ * 50 per host, 200 per call).
1312
+ */
1313
+ public abstract voids(
1314
+ items: PluginCreateVoidItem[],
1315
+ ): PluginApiReturn<ComponentHandle[]>
1316
+
1317
+ /**
1318
+ * Place a parametric **door or window** into a host wall with parameter overrides.
713
1319
  *
714
1320
  * The world `position` is projected onto the host wall to locate the opening.
715
1321
  * All supplied dimensions use Snaptrude engine units. Window `sillHeight` is
716
1322
  * measured from the host wall base to the bottom of the window, not to
717
1323
  * its center. Placement is asynchronous and committed as one undoable creation.
718
1324
  *
719
- * @param options - Opening kind, catalog and host references, placement, and
720
- * optional facing, label, and dimensions
1325
+ * Wall only. For a hole in a slab, floor or roof use `design.create.void`.
1326
+ *
1327
+ * @param options - Opening kind, family and host references, placement, and
1328
+ * optional facing (swing side), hinge (hinged jamb), label, and dimensions
721
1329
  * @returns the {@linkcode ComponentHandle} of the placed door or window
722
- * @throws if the options are invalid, the catalog id is unknown, the host is
1330
+ * @throws if the options are invalid, the family id is unknown, the host is
723
1331
  * not a wall, loading fails, or the opening cannot be placed on the host
724
1332
  *
725
1333
  * @examplePrompt Add a 1m wide door to this wall here
726
1334
  * @examplePrompt Place a window with a 0.9m sill height on the selected wall
727
1335
  *
728
- * @performance Single-opening creator — use it for one hosted opening. A plural
729
- * API is intentionally unavailable until host placement can be atomic.
1336
+ * @performance Single-opening creator — use it for one hosted opening. For MORE
1337
+ * THAN ONE, call `design.create.doors(items[])` / `design.create.windows(items[])`:
1338
+ * the same per-item fields, one host round-trip, ONE undo entry.
730
1339
  *
731
1340
  * # Example
732
1341
  * ```ts
733
1342
  * const opening = await snaptrude.design.create.opening({
734
1343
  * kind: "window",
735
- * catalogId: windowType.id,
1344
+ * familyId: windowType.id,
736
1345
  * hostWall: wall,
737
1346
  * position: await snaptrude.core.math.vec3.new(3, 0, 5),
738
1347
  * width: 1.2,
@@ -927,6 +1536,35 @@ export abstract class PluginDesignCreateApi {
927
1536
  farRatio?: number,
928
1537
  lotCoverageMaxPct?: number,
929
1538
  ): PluginApiReturn<PluginBuildableEnvelopeCreateResult>
1539
+
1540
+ /**
1541
+ * Create a default buildable envelope for an existing site — the same as the
1542
+ * right panel's "Create Buildable Envelope" button. Front, side and rear
1543
+ * distances are seeded from Site Analysis or the Program sheet, with a 10 ft
1544
+ * fallback, and the envelope is linked to the site. Call it only after the
1545
+ * user agrees (for example when `design.update.siteEdge` throws
1546
+ * `NO_BUILDABLE_ENVELOPE`).
1547
+ *
1548
+ * @param siteId - Site component id, from `program.site.listEdges()`.
1549
+ * @returns A {@linkcode PluginDesignCreateBuildableEnvelopeFromSiteResult}
1550
+ * with the `buildableEnvelopeId` of the created envelope.
1551
+ * @throws `HANDLE_INVALID` for an unknown `siteId`. `PRECONDITION_FAILED`
1552
+ * (`details.reason: "SITE_HAS_ENVELOPE"`) when the site already has an
1553
+ * envelope.
1554
+ *
1555
+ * @examplePrompt Create a buildable envelope for this site with default setbacks
1556
+ * @examplePrompt Add the zoning envelope to my site
1557
+ *
1558
+ * # Example
1559
+ * ```ts
1560
+ * const [{ siteId }] = (await snaptrude.program.site.listEdges()).sites
1561
+ * const { buildableEnvelopeId } =
1562
+ * await snaptrude.design.create.buildableEnvelopeFromSite(siteId)
1563
+ * ```
1564
+ */
1565
+ public abstract buildableEnvelopeFromSite(
1566
+ siteId: string,
1567
+ ): PluginApiReturn<PluginDesignCreateBuildableEnvelopeFromSiteResult>
930
1568
  }
931
1569
 
932
1570
  // ---------------------------------------------------------------------------
@@ -1232,21 +1870,24 @@ export type PluginDesignCreateBeamArgs = z.infer<
1232
1870
  * | `profile` | {@linkcode ProfileHandle} | Ordered curve chain (wall centerlines) |
1233
1871
  * | `height` | `number`? | Wall height (default engine default) |
1234
1872
  * | `thickness` | `number`? | Wall thickness (default: `wallType`'s total layer thickness when given, else engine default) |
1235
- * | `wallType` | `string`? | Wall type name from `design.types.list("wall")` — layers/material/thickness defaults (default generic) |
1873
+ * | `wallType` | {@linkcode PluginWallType}? | Legacy name/id or exact native `{ id, version }` pin from `design.types.list("wall")` (default generic) |
1236
1874
  * | `storey` | `number`? | Target storey (integer, must exist) — walls assigned + lifted to its base in the same undo entry (default: geometry-derived) |
1875
+ * | `topProfiles` | ({@linkcode PluginWallTopProfile} \| `null`)[]? | Top edge per curve (same order); `null`/missing = flat at `height`. Straight curves only |
1237
1876
  */
1238
1877
  export const PluginDesignCreateWallsArgs = z.object({
1239
1878
  profile: ProfileHandle,
1240
1879
  height: z.number().optional(),
1241
1880
  thickness: z.number().optional(),
1242
- wallType: z.string().min(1).optional(),
1881
+ wallType: PluginWallType.optional(),
1243
1882
  storey: z.number().int().optional(),
1883
+ topProfiles: z.array(PluginWallTopProfile.nullable()).optional(),
1884
+ values: PluginFamilyValues.optional(),
1244
1885
  })
1245
1886
  export type PluginDesignCreateWallsArgs = z.infer<
1246
1887
  typeof PluginDesignCreateWallsArgs
1247
1888
  >
1248
- // TRANSPORT: positional signature shipped:
1249
- // walls(profile: ProfileHandle, height?: number, thickness?: number, wallType?: string, storey?: number)
1889
+ // TRANSPORT: positional signature shipped — storey is the 5th arg, topProfiles the trailing 6th:
1890
+ // walls(profile: ProfileHandle, height?: number, thickness?: number, wallType?: string, storey?: number, topProfiles?: (PluginWallTopProfile | null)[])
1250
1891
 
1251
1892
  // ---------------------------------------------------------------------------
1252
1893
  // staircase
@@ -1288,7 +1929,7 @@ export type PluginDesignCreateStaircaseArgs = z.infer<
1288
1929
  * | `catalogId` | `string` | Library id: team `_id` or general `fullName` |
1289
1930
  * | `position` | {@linkcode Vec3Handle} | Absolute world placement point |
1290
1931
  * | `label` | `string`? | Instance name (default auto `${name}Ins${n}`) |
1291
- * | `createNewSourceMesh` | `boolean`? | Emit a source-mesh creation command (default `true`) |
1932
+ * | `createNewSourceMesh` | `boolean`? | Legacy, kept for compatibility; the host records a catalog source exactly once and this flag cannot skip it |
1292
1933
  * | `angleInDegrees` | `number`? | Rotation about the vertical axis, in degrees (default unrotated) |
1293
1934
  */
1294
1935
  export const PluginDesignCreateFurnitureArgs = z.object({
@@ -1313,24 +1954,23 @@ export type PluginDesignCreateFurnitureArgs = z.infer<
1313
1954
  *
1314
1955
  * | Property | Type | Description |
1315
1956
  * |---|---|---|
1316
- * | `catalogId` | `string` | Library id: team `_id` or general `fullName` |
1957
+ * | `familyId` | `string` | Hosted door family definition ID |
1958
+ * | `version` | `number`? | Exact version; newest known by default |
1959
+ * | `values` | `Record<string, number \| string>`? | Parameters; numeric lengths use SU |
1317
1960
  * | `hostWall` | {@linkcode ComponentHandle} | The wall to host the door |
1318
1961
  * | `position` | {@linkcode Vec3Handle} | World point projected onto the wall |
1319
1962
  * | `label` | `string`? | Instance name (optional) |
1320
- * | `facing` | {@linkcode Vec3Handle}? | World point on the side of the wall the door faces (default engine-chosen) |
1963
+ * | `facing` | {@linkcode Vec3Handle}? | World point on the side of the wall the door swings open to (default engine-chosen) |
1964
+ * | `hinge` | {@linkcode Vec3Handle}? | World point near the hinged jamb; requires hand control |
1321
1965
  */
1322
1966
  export const PluginDesignCreateDoorArgs = z.object({
1323
- catalogId: z.string().min(1),
1324
- hostWall: ComponentHandle,
1325
- position: Vec3Handle,
1326
- label: z.string().optional(),
1327
- facing: Vec3Handle.optional(),
1967
+ ...PluginOpeningBaseOptions,
1328
1968
  })
1329
1969
  export type PluginDesignCreateDoorArgs = z.infer<
1330
1970
  typeof PluginDesignCreateDoorArgs
1331
1971
  >
1332
- // TRANSPORT: positional signature shipped — facing is the trailing 5th arg:
1333
- // door(catalogId: string, hostWall: ComponentHandle, position: Vec3Handle, options?: { label? }, facing?: Vec3Handle)
1972
+ // TRANSPORT: positional signature shipped — facing is the trailing 5th arg, hinge rides in options:
1973
+ // door(familyId, hostWall, position, options?: { label?, hinge?, version?, values? }, facing?)
1334
1974
 
1335
1975
  // ---------------------------------------------------------------------------
1336
1976
  // window
@@ -1341,39 +1981,28 @@ export type PluginDesignCreateDoorArgs = z.infer<
1341
1981
  *
1342
1982
  * | Property | Type | Description |
1343
1983
  * |---|---|---|
1344
- * | `catalogId` | `string` | Library id: team `_id` or general `fullName` |
1984
+ * | `familyId` | `string` | Hosted window family definition ID |
1985
+ * | `version` | `number`? | Exact version; newest known by default |
1986
+ * | `values` | `Record<string, number \| string>`? | Parameters; numeric lengths use SU |
1345
1987
  * | `hostWall` | {@linkcode ComponentHandle} | The wall to host the window |
1346
1988
  * | `position` | {@linkcode Vec3Handle} | World point projected onto the wall |
1347
1989
  * | `label` | `string`? | Instance name (optional) |
1348
- * | `facing` | {@linkcode Vec3Handle}? | World point on the side of the wall the window faces (default engine-chosen) |
1990
+ * | `facing` | {@linkcode Vec3Handle}? | World point on the side of the wall the window opens to (default engine-chosen) |
1991
+ * | `hinge` | {@linkcode Vec3Handle}? | World point near the hinged jamb; requires hand control |
1349
1992
  */
1350
1993
  export const PluginDesignCreateWindowArgs = z.object({
1351
- catalogId: z.string().min(1),
1352
- hostWall: ComponentHandle,
1353
- position: Vec3Handle,
1354
- label: z.string().optional(),
1355
- facing: Vec3Handle.optional(),
1994
+ ...PluginOpeningBaseOptions,
1356
1995
  })
1357
1996
  export type PluginDesignCreateWindowArgs = z.infer<
1358
1997
  typeof PluginDesignCreateWindowArgs
1359
1998
  >
1360
- // TRANSPORT: positional signature shipped — facing is the trailing 5th arg:
1361
- // window(catalogId: string, hostWall: ComponentHandle, position: Vec3Handle, options?: { label? }, facing?: Vec3Handle)
1999
+ // TRANSPORT: positional signature shipped — facing is the trailing 5th arg, hinge rides in options:
2000
+ // window(familyId, hostWall, position, options?: { label?, hinge?, version?, values? }, facing?)
1362
2001
 
1363
2002
  // ---------------------------------------------------------------------------
1364
2003
  // opening
1365
2004
  // ---------------------------------------------------------------------------
1366
2005
 
1367
- const PluginOpeningBaseOptions = {
1368
- catalogId: z.string().min(1),
1369
- hostWall: ComponentHandle,
1370
- position: Vec3Handle,
1371
- facing: Vec3Handle.optional(),
1372
- label: z.string().optional(),
1373
- width: z.number().finite().positive().optional(),
1374
- height: z.number().finite().positive().optional(),
1375
- }
1376
-
1377
2006
  /**
1378
2007
  * Options for {@linkcode PluginDesignCreateApi.opening}, discriminated by
1379
2008
  * `kind`. Width and height are positive Snaptrude engine-unit values.
@@ -1472,13 +2101,63 @@ export type PluginSmartLayoutResult = z.infer<typeof PluginSmartLayoutResult>
1472
2101
  * | `style` | {@linkcode PluginReferenceLineStyle}? | Line style (default `SOLID`) |
1473
2102
  * | `gridTag` | `string`? | Grid tag label (default `"A"`) |
1474
2103
  */
1475
- export const PluginDesignCreateReferenceLinesArgs = z.object({
1476
- profile: ProfileHandle,
1477
- color: z.string().optional(),
1478
- thickness: z.number().optional(),
1479
- style: PluginReferenceLineStyle.optional(),
1480
- gridTag: z.string().optional(),
1481
- })
2104
+ /**
2105
+ * Reference-line creation options. styleId selects a project pen/pattern; its
2106
+ * native legacy mirror is saved too. Omitted options preserve tool defaults.
2107
+ */
2108
+ export const PluginReferenceLineOptions = z
2109
+ .object({
2110
+ styleId: PluginGraphicsLineStyleId.optional(),
2111
+ color: z
2112
+ .string()
2113
+ .regex(/^#[0-9a-f]{6}$/i)
2114
+ .optional(),
2115
+ thickness: z.number().finite().positive().optional(),
2116
+ style: PluginReferenceLineStyle.optional(),
2117
+ })
2118
+ .strict()
2119
+ .superRefine((options, ctx) => {
2120
+ if (
2121
+ options.styleId !== undefined &&
2122
+ (options.thickness !== undefined || options.style !== undefined)
2123
+ ) {
2124
+ ctx.addIssue({
2125
+ code: "custom",
2126
+ message: "styleId cannot be combined with legacy thickness or style",
2127
+ })
2128
+ }
2129
+ })
2130
+ export type PluginReferenceLineOptions = z.infer<
2131
+ typeof PluginReferenceLineOptions
2132
+ >
2133
+
2134
+ /**
2135
+ * Normalized arguments. The host maps the second positional argument to either
2136
+ * the legacy color field or options; mixing those forms is rejected atomically.
2137
+ */
2138
+ export const PluginDesignCreateReferenceLinesArgs = z
2139
+ .object({
2140
+ profile: ProfileHandle,
2141
+ options: PluginReferenceLineOptions.optional(),
2142
+ color: z.string().optional(),
2143
+ thickness: z.number().finite().optional(),
2144
+ style: PluginReferenceLineStyle.optional(),
2145
+ gridTag: z.string().optional(),
2146
+ })
2147
+ .strict()
2148
+ .superRefine((args, ctx) => {
2149
+ if (
2150
+ args.options !== undefined &&
2151
+ [args.color, args.thickness, args.style, args.gridTag].some(
2152
+ (v) => v !== undefined,
2153
+ )
2154
+ ) {
2155
+ ctx.addIssue({
2156
+ code: "custom",
2157
+ message: "Options cannot be combined with legacy positional arguments",
2158
+ })
2159
+ }
2160
+ })
1482
2161
  export type PluginDesignCreateReferenceLinesArgs = z.infer<
1483
2162
  typeof PluginDesignCreateReferenceLinesArgs
1484
2163
  >
@@ -1550,3 +2229,7 @@ export const PluginCreateSpaceItem = z.object({
1550
2229
  storey: z.number().int().optional(),
1551
2230
  })
1552
2231
  export type PluginCreateSpaceItem = z.infer<typeof PluginCreateSpaceItem>
2232
+
2233
+ export * from "./opening-fields"
2234
+ export * from "./bulk-items"
2235
+ export * from "./grids"