@snaptrude/plugin-core 0.10.0 → 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 (229) hide show
  1. package/AGENTS.md +4 -0
  2. package/CHANGELOG.md +187 -3
  3. package/api-manifest.json +929 -25
  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 +463 -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/project/index.d.ts +68 -1
  33. package/dist/api/core/project/index.d.ts.map +1 -1
  34. package/dist/api/core/proposals/index.d.ts +4 -3
  35. package/dist/api/core/proposals/index.d.ts.map +1 -1
  36. package/dist/api/core/storeys/index.d.ts +31 -2
  37. package/dist/api/core/storeys/index.d.ts.map +1 -1
  38. package/dist/api/core/storeys/layers.d.ts +408 -0
  39. package/dist/api/core/storeys/layers.d.ts.map +1 -0
  40. package/dist/api/core/tags.d.ts +84 -3
  41. package/dist/api/core/tags.d.ts.map +1 -1
  42. package/dist/api/core/zoom/index.d.ts +13 -0
  43. package/dist/api/core/zoom/index.d.ts.map +1 -1
  44. package/dist/api/design/bim.d.ts +299 -0
  45. package/dist/api/design/bim.d.ts.map +1 -0
  46. package/dist/api/design/boolean/index.d.ts +2 -0
  47. package/dist/api/design/boolean/index.d.ts.map +1 -1
  48. package/dist/api/design/catalog.d.ts +320 -0
  49. package/dist/api/design/catalog.d.ts.map +1 -0
  50. package/dist/api/design/create/bulk-items.d.ts +269 -0
  51. package/dist/api/design/create/bulk-items.d.ts.map +1 -0
  52. package/dist/api/design/create/grids.d.ts +132 -0
  53. package/dist/api/design/create/grids.d.ts.map +1 -0
  54. package/dist/api/design/create/index.d.ts +752 -78
  55. package/dist/api/design/create/index.d.ts.map +1 -1
  56. package/dist/api/design/create/opening-fields.d.ts +38 -0
  57. package/dist/api/design/create/opening-fields.d.ts.map +1 -0
  58. package/dist/api/design/delete/index.d.ts +5 -0
  59. package/dist/api/design/delete/index.d.ts.map +1 -1
  60. package/dist/api/design/dimensions.d.ts +427 -0
  61. package/dist/api/design/dimensions.d.ts.map +1 -0
  62. package/dist/api/design/doors/index.d.ts +24 -23
  63. package/dist/api/design/doors/index.d.ts.map +1 -1
  64. package/dist/api/design/family.d.ts +1446 -0
  65. package/dist/api/design/family.d.ts.map +1 -0
  66. package/dist/api/design/furniture/index.d.ts +33 -0
  67. package/dist/api/design/furniture/index.d.ts.map +1 -1
  68. package/dist/api/design/grids.d.ts +237 -0
  69. package/dist/api/design/grids.d.ts.map +1 -0
  70. package/dist/api/design/index.d.ts +30 -0
  71. package/dist/api/design/index.d.ts.map +1 -1
  72. package/dist/api/design/networks.d.ts +1703 -0
  73. package/dist/api/design/networks.d.ts.map +1 -0
  74. package/dist/api/design/query/geometry/index.d.ts +33 -5
  75. package/dist/api/design/query/geometry/index.d.ts.map +1 -1
  76. package/dist/api/design/query/grids.d.ts +287 -0
  77. package/dist/api/design/query/grids.d.ts.map +1 -0
  78. package/dist/api/design/query/index.d.ts +143 -34
  79. package/dist/api/design/query/index.d.ts.map +1 -1
  80. package/dist/api/design/query/spaces.d.ts +5 -5
  81. package/dist/api/design/selection/index.d.ts +12 -12
  82. package/dist/api/design/types/index.d.ts +40 -24
  83. package/dist/api/design/types/index.d.ts.map +1 -1
  84. package/dist/api/design/update/index.d.ts +412 -8
  85. package/dist/api/design/update/index.d.ts.map +1 -1
  86. package/dist/api/design/visibility.d.ts +28 -0
  87. package/dist/api/design/visibility.d.ts.map +1 -1
  88. package/dist/api/design/windows/index.d.ts +8 -8
  89. package/dist/api/design/windows/index.d.ts.map +1 -1
  90. package/dist/api/entity/buildableEnvelope.d.ts +84 -2
  91. package/dist/api/entity/buildableEnvelope.d.ts.map +1 -1
  92. package/dist/api/entity/referenceLine.d.ts +13 -1
  93. package/dist/api/entity/referenceLine.d.ts.map +1 -1
  94. package/dist/api/entity/space.d.ts +12 -12
  95. package/dist/api/entity/story.d.ts +4 -4
  96. package/dist/api/presentation/aiInspiration.d.ts +24 -24
  97. package/dist/api/presentation/annotate.d.ts +38 -22
  98. package/dist/api/presentation/annotate.d.ts.map +1 -1
  99. package/dist/api/presentation/camera.d.ts +265 -0
  100. package/dist/api/presentation/camera.d.ts.map +1 -0
  101. package/dist/api/presentation/drawingContract.d.ts +32 -0
  102. package/dist/api/presentation/drawingContract.d.ts.map +1 -0
  103. package/dist/api/presentation/drawings.d.ts +3523 -0
  104. package/dist/api/presentation/drawings.d.ts.map +1 -0
  105. package/dist/api/presentation/export.d.ts +23 -5
  106. package/dist/api/presentation/export.d.ts.map +1 -1
  107. package/dist/api/presentation/graphics.d.ts +419 -0
  108. package/dist/api/presentation/graphics.d.ts.map +1 -0
  109. package/dist/api/presentation/index.d.ts +66 -12
  110. package/dist/api/presentation/index.d.ts.map +1 -1
  111. package/dist/api/presentation/placedViews.d.ts +116 -16
  112. package/dist/api/presentation/placedViews.d.ts.map +1 -1
  113. package/dist/api/presentation/sections.d.ts +461 -0
  114. package/dist/api/presentation/sections.d.ts.map +1 -0
  115. package/dist/api/presentation/shapes.d.ts +2 -2
  116. package/dist/api/presentation/sheets.d.ts +175 -19
  117. package/dist/api/presentation/sheets.d.ts.map +1 -1
  118. package/dist/api/presentation/standards.d.ts +590 -0
  119. package/dist/api/presentation/standards.d.ts.map +1 -0
  120. package/dist/api/presentation/viewPresets.d.ts +222 -0
  121. package/dist/api/presentation/viewPresets.d.ts.map +1 -0
  122. package/dist/api/presentation/views.d.ts +144 -9
  123. package/dist/api/presentation/views.d.ts.map +1 -1
  124. package/dist/api/presentation/walkthroughs.d.ts +1350 -0
  125. package/dist/api/presentation/walkthroughs.d.ts.map +1 -0
  126. package/dist/api/program/departments.d.ts +58 -2
  127. package/dist/api/program/departments.d.ts.map +1 -1
  128. package/dist/api/program/index.d.ts +16 -6
  129. package/dist/api/program/index.d.ts.map +1 -1
  130. package/dist/api/program/labels.d.ts +355 -0
  131. package/dist/api/program/labels.d.ts.map +1 -0
  132. package/dist/api/program/metadata.d.ts +328 -0
  133. package/dist/api/program/metadata.d.ts.map +1 -0
  134. package/dist/api/program/site.d.ts +4 -4
  135. package/dist/api/program/spreadsheet.d.ts +267 -4
  136. package/dist/api/program/spreadsheet.d.ts.map +1 -1
  137. package/dist/api/workspace/index.d.ts +49 -2
  138. package/dist/api/workspace/index.d.ts.map +1 -1
  139. package/dist/errors/codes.d.ts +3 -1
  140. package/dist/errors/codes.d.ts.map +1 -1
  141. package/dist/handles.d.ts +32 -3
  142. package/dist/handles.d.ts.map +1 -1
  143. package/dist/index.cjs +6539 -2804
  144. package/dist/index.cjs.map +1 -1
  145. package/dist/index.d.ts +2 -0
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js +6121 -2801
  148. package/dist/index.js.map +1 -1
  149. package/dist/massParameters.d.ts +287 -0
  150. package/dist/massParameters.d.ts.map +1 -0
  151. package/dist/massParameters.ops.d.ts +138 -0
  152. package/dist/massParameters.ops.d.ts.map +1 -0
  153. package/package.json +14 -13
  154. package/scripts/check-dist-fresh.mjs +22 -0
  155. package/scripts/generate-manifest.mjs +5 -0
  156. package/scripts/generate-manifest.test.mjs +14 -3
  157. package/src/api/core/camera/index.ts +446 -1
  158. package/src/api/core/geom/create/index.ts +1037 -102
  159. package/src/api/core/geom/query/brep.ts +60 -2
  160. package/src/api/core/geom/query/curve.ts +26 -2
  161. package/src/api/core/geom/query/edge.ts +2 -2
  162. package/src/api/core/geom/query/face.ts +109 -4
  163. package/src/api/core/geom/update/contour.ts +10 -2
  164. package/src/api/core/geom/update/profile.ts +2 -0
  165. package/src/api/core/handles/index.ts +3 -2
  166. package/src/api/core/index.ts +4 -4
  167. package/src/api/core/io/import/index.ts +11 -3
  168. package/src/api/core/layers.ts +380 -173
  169. package/src/api/core/mode/index.ts +19 -7
  170. package/src/api/core/project/index.ts +62 -1
  171. package/src/api/core/proposals/index.ts +4 -3
  172. package/src/api/core/storeys/index.ts +34 -2
  173. package/src/api/core/storeys/layers.ts +339 -0
  174. package/src/api/core/tags.ts +76 -0
  175. package/src/api/core/zoom/index.ts +13 -0
  176. package/src/api/design/bim.ts +210 -0
  177. package/src/api/design/boolean/index.ts +2 -0
  178. package/src/api/design/catalog.ts +203 -0
  179. package/src/api/design/create/bulk-items.ts +294 -0
  180. package/src/api/design/create/grids.ts +137 -0
  181. package/src/api/design/create/index.ts +823 -114
  182. package/src/api/design/create/opening-fields.ts +40 -0
  183. package/src/api/design/delete/index.ts +5 -0
  184. package/src/api/design/dimensions.ts +453 -0
  185. package/src/api/design/doors/index.ts +64 -41
  186. package/src/api/design/family.ts +1342 -0
  187. package/src/api/design/furniture/index.ts +34 -0
  188. package/src/api/design/grids.ts +237 -0
  189. package/src/api/design/index.ts +49 -4
  190. package/src/api/design/networks.ts +607 -0
  191. package/src/api/design/query/geometry/index.ts +35 -4
  192. package/src/api/design/query/grids.ts +197 -0
  193. package/src/api/design/query/index.ts +105 -3
  194. package/src/api/design/types/index.ts +31 -24
  195. package/src/api/design/update/index.ts +398 -4
  196. package/src/api/design/visibility.ts +34 -0
  197. package/src/api/design/windows/index.ts +40 -22
  198. package/src/api/entity/buildableEnvelope.ts +77 -2
  199. package/src/api/entity/referenceLine.ts +25 -12
  200. package/src/api/presentation/annotate.ts +90 -58
  201. package/src/api/presentation/camera.ts +289 -0
  202. package/src/api/presentation/drawingContract.ts +70 -0
  203. package/src/api/presentation/drawings.ts +1179 -0
  204. package/src/api/presentation/export.ts +32 -6
  205. package/src/api/presentation/graphics.ts +324 -0
  206. package/src/api/presentation/index.ts +73 -14
  207. package/src/api/presentation/placedViews.ts +119 -28
  208. package/src/api/presentation/sections.ts +420 -0
  209. package/src/api/presentation/sheets.ts +181 -20
  210. package/src/api/presentation/standards.ts +445 -0
  211. package/src/api/presentation/viewPresets.ts +241 -0
  212. package/src/api/presentation/views.ts +81 -10
  213. package/src/api/presentation/walkthroughs.ts +1201 -0
  214. package/src/api/program/departments.ts +54 -2
  215. package/src/api/program/index.ts +16 -6
  216. package/src/api/program/labels.ts +332 -0
  217. package/src/api/program/metadata.ts +364 -0
  218. package/src/api/program/spreadsheet.ts +282 -0
  219. package/src/api/workspace/index.ts +51 -2
  220. package/src/errors/codes.ts +7 -0
  221. package/src/handles.ts +42 -3
  222. package/src/index.ts +2 -0
  223. package/src/massParameters.ops.ts +246 -0
  224. package/src/massParameters.ts +427 -0
  225. package/test/drawingContract.test.mjs +288 -0
  226. package/test/errors.test.mjs +1 -0
  227. package/test/massParameters.test.mjs +952 -0
  228. package/test/paperPensContract.test.mjs +75 -0
  229. package/test/presentationViewSettings.test.mjs +85 -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,20 +242,32 @@ 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
  *
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.
263
+ *
264
+ * To cut a hole in an EXISTING one, use `design.create.void`; a contour hole
265
+ * here is baked into the footprint.
266
+ *
220
267
  * @param contour - Footprint (outer + holes)
221
268
  * @param thickness - Floor thickness (> 0)
222
269
  * @param position - Offset from origin
223
- * @returns the {@linkcode ComponentHandle} of the created floor
270
+ * @returns Newly created {@linkcode ComponentHandle}s; an empty array after a merge
224
271
  * @throws if the contour is invalid or the thickness is not positive
225
272
  *
226
273
  * @examplePrompt Create a floor from this room outline
@@ -234,17 +281,64 @@ export abstract class PluginDesignCreateApi {
234
281
  * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)
235
282
  * const floor = await snaptrude.design.create.floor(contour, 0.1)
236
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.
237
288
  */
238
289
  public abstract floor(
239
- contour: ContourHandle,
240
- thickness: number,
241
- position?: Vec3Handle,
242
- ): PluginApiReturn<ComponentHandle>
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[],
333
+ ): PluginApiReturn<ComponentHandle[]>
243
334
 
244
335
  /**
245
336
  * Create a **roof** by extruding a footprint contour by `thickness`. Created
246
337
  * flat (extruded downward); pitch/slope is a separate post-creation edit.
247
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
+ *
248
342
  * @param contour - Footprint (outer + holes)
249
343
  * @param thickness - Roof-slab thickness (> 0)
250
344
  * @returns the {@linkcode ComponentHandle} of the created roof
@@ -264,13 +358,16 @@ export abstract class PluginDesignCreateApi {
264
358
  */
265
359
  public abstract roof(
266
360
  contour: ContourHandle,
267
- thickness: number,
361
+ thickness: number | PluginFamilyPlacement,
268
362
  ): PluginApiReturn<ComponentHandle>
269
363
 
270
364
  /**
271
365
  * Create a **ceiling** by extruding a footprint contour by `thickness`, lifted
272
366
  * `heightFromFloor` above the floor.
273
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
+ *
274
371
  * @param contour - Footprint (outer + holes)
275
372
  * @param thickness - Ceiling thickness (> 0)
276
373
  * @param heightFromFloor - Lift above the floor (default 0)
@@ -290,9 +387,9 @@ export abstract class PluginDesignCreateApi {
290
387
  * ```
291
388
  */
292
389
  public abstract ceiling(
293
- contour: ContourHandle,
294
- thickness: number,
295
- heightFromFloor?: number,
390
+ ...args:
391
+ | [contour: ContourHandle, thickness: number, heightFromFloor?: number]
392
+ | [contour: ContourHandle, options: PluginFamilyCeilingOptions]
296
393
  ): PluginApiReturn<ComponentHandle>
297
394
 
298
395
  /**
@@ -323,10 +420,14 @@ export abstract class PluginDesignCreateApi {
323
420
  * ```
324
421
  */
325
422
  public abstract column(
326
- position: Vec3Handle,
327
- crossSection: ContourHandle,
328
- height: number,
329
- directionUp?: Vec3Handle,
423
+ ...args:
424
+ | [
425
+ position: Vec3Handle,
426
+ crossSection: ContourHandle,
427
+ height: number,
428
+ directionUp?: Vec3Handle,
429
+ ]
430
+ | [axis: LineHandle, options: PluginFamilyPlacement]
330
431
  ): PluginApiReturn<ComponentHandle>
331
432
 
332
433
  /**
@@ -370,9 +471,9 @@ export abstract class PluginDesignCreateApi {
370
471
  * ```
371
472
  */
372
473
  public abstract beam(
373
- section: ContourHandle,
374
- length: number,
375
- direction?: Vec3Handle,
474
+ ...args:
475
+ | [section: ContourHandle, length: number, direction?: Vec3Handle]
476
+ | [axis: LineHandle, options: PluginFamilyPlacement]
376
477
  ): PluginApiReturn<ComponentHandle>
377
478
 
378
479
  /**
@@ -383,21 +484,27 @@ export abstract class PluginDesignCreateApi {
383
484
  * @param height - Wall height (default engine default)
384
485
  * @param thickness - Wall thickness (default: the `wallType`'s total layer
385
486
  * thickness when `wallType` is given, else the engine default)
386
- * @param wallType - Wall type to build with — the same searchable list the
387
- * Draw tab's Wall Type dropdown offers. Pass a name from
388
- * `design.types.list("wall")` (the summary's `label`, or its `"wall:…"` id).
389
- * Drives the wall's construction layers and default material, and — when
390
- * `thickness` is omitted — its total layer thickness. Default: the engine's
391
- * 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.
392
493
  * @param storey - Target storey (integer: `1` ground, `2` first floor, `-1`
393
494
  * basement — same convention as `design.create.space`). The walls are
394
495
  * assigned to it and lifted to its base elevation, all inside the same
395
496
  * single undo entry. The storey must already exist
396
497
  * (`core.storeys.create` first). Default: geometry-derived — a profile
397
498
  * drawn at y = 0 lands on storey 1; the ACTIVE storey is NOT consulted.
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}).
398
503
  * @returns a {@linkcode ComponentHandle}`[]` (one per wall), in profile order
399
504
  * @throws if the profile is empty, `wallType` names no wall type in the
400
- * 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
401
508
  *
402
509
  * @examplePrompt Draw walls along this centerline
403
510
  * @examplePrompt Create 3m high, 200mm thick walls from these points
@@ -406,6 +513,9 @@ export abstract class PluginDesignCreateApi {
406
513
  * @examplePrompt Add walls with mitred corners along the path
407
514
  * @examplePrompt Draw brick walls with plaster along this outline
408
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
409
519
  *
410
520
  * # Example
411
521
  * ```ts
@@ -421,16 +531,89 @@ export abstract class PluginDesignCreateApi {
421
531
  * const brickWalls = await snaptrude.design.create.walls(
422
532
  * centerlines, 3, undefined, brick.label,
423
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
+ * ])
424
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.
425
546
  */
426
547
  public abstract walls(
427
548
  profile: ProfileHandle,
428
549
  height?: number,
429
550
  thickness?: number,
430
- wallType?: string,
551
+ wallType?: PluginWallType,
431
552
  storey?: number,
553
+ topProfiles?: (PluginWallTopProfile | null)[],
554
+ values?: PluginFamilyValues,
432
555
  ): PluginApiReturn<ComponentHandle[]>
433
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
+
434
617
  /**
435
618
  * Create a **staircase** from a parametric preset, placed at a point.
436
619
  *
@@ -481,8 +664,14 @@ export abstract class PluginDesignCreateApi {
481
664
  * every created line.
482
665
  *
483
666
  * @param profile - Ordered curve chain (centerlines)
484
- * @param color - Hex color `"#RRGGBB"` (default white)
485
- * @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)
486
675
  * @param style - Line style (default `SOLID`)
487
676
  * @param gridTag - Grid tag label (default `"A"`)
488
677
  * @returns a {@linkcode ComponentHandle}`[]` (one per curve), in profile order
@@ -511,12 +700,140 @@ export abstract class PluginDesignCreateApi {
511
700
  */
512
701
  public abstract referenceLines(
513
702
  profile: ProfileHandle,
514
- color?: string,
703
+ colorOrOptions?: string | PluginReferenceLineOptions,
515
704
  thickness?: number,
516
705
  style?: PluginReferenceLineStyle,
517
706
  gridTag?: string,
518
707
  ): PluginApiReturn<ComponentHandle[]>
519
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
+
520
837
  /**
521
838
  * Place a **furniture** item from the project library at a world position.
522
839
  *
@@ -535,15 +852,19 @@ export abstract class PluginDesignCreateApi {
535
852
  * `position.y` (the same surface-flush contract as interactive drag-drop) —
536
853
  * pass the floor/storey elevation to stand furniture on it; never add half
537
854
  * the item's height yourself.
538
- * @param options - Optional placement options: `label` — instance name
539
- * (default auto `${name}Ins${n}`); `createNewSourceMesh` — emit a
540
- * source-mesh creation command (default `true`)
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.
541
860
  * @param angleInDegrees - Optional signed rotation about the vertical axis, in
542
861
  * degrees (same convention as {@linkcode PluginDesignTransformApi.rotate}).
543
862
  * Applied at creation time so it is part of the placement's single undo entry.
544
863
  * Default: the item's own (unrotated) orientation.
545
864
  * @returns the {@linkcode ComponentHandle} of the placed furniture instance
546
- * @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
547
868
  *
548
869
  * @examplePrompt Place a chair from the library at this spot
549
870
  * @examplePrompt Add a sofa from the furniture catalog to the living room
@@ -567,6 +888,10 @@ export abstract class PluginDesignCreateApi {
567
888
  * 90,
568
889
  * )
569
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.
570
895
  */
571
896
  public abstract furniture(
572
897
  catalogId: string,
@@ -576,27 +901,69 @@ export abstract class PluginDesignCreateApi {
576
901
  ): PluginApiReturn<ComponentHandle>
577
902
 
578
903
  /**
579
- * 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.
933
+ *
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.
580
951
  *
581
- * Identified by a **catalog (library) id** — the team object's stable `_id`
582
- * or the general library's `fullName`. Discover ids with
952
+ * Identified by a saved **family definition id**. Discover ids with
583
953
  * {@linkcode PluginDesignDoorsApi.listCatalog}. The world `position` is
584
954
  * projected onto the host wall's face to locate the opening; the wall is
585
955
  * re-cut and the door hosted in a single undo entry. Placement is
586
- * asynchronous (the source mesh is fetched if not cached).
956
+ * asynchronous. Values and host bindings are evaluated before the first cut.
587
957
  *
588
- * @param catalogId - Library id: team `_id` or general `fullName`
958
+ * @param familyId - Hosted door definition ID; legacy mesh catalog IDs are not accepted
589
959
  * @param hostWall - The wall to host the door
590
960
  * @param position - World point projected onto the wall to locate the opening
591
- * @param options - Optional placement options: `label` — instance name
592
- * @param facing - World point selecting which side of the wall the door faces
593
- * (the room it opens into) — the same convention as approaching the wall
594
- * from that side with the cursor in the interactive tool. Any point clearly
595
- * on that side works (e.g. the room's center). Default: the engine picks a
596
- * 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
597
965
  * @returns the {@linkcode ComponentHandle} of the placed door
598
- * @throws if the catalog id is unknown, the host is not a wall, the source
599
- * 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
600
967
  *
601
968
  * @examplePrompt Add a door to this wall here
602
969
  * @examplePrompt Place a sliding door on the selected wall
@@ -604,6 +971,7 @@ export abstract class PluginDesignCreateApi {
604
971
  * @examplePrompt Insert the entrance door into this wall
605
972
  * @examplePrompt Add a door to the wall and call it Entry-01
606
973
  * @examplePrompt Add a door that opens into the living room
974
+ * @examplePrompt Put a door here hinged on the north jamb
607
975
  *
608
976
  * # Example
609
977
  * ```ts
@@ -623,37 +991,100 @@ export abstract class PluginDesignCreateApi {
623
991
  * undefined,
624
992
  * await snaptrude.core.math.vec3.new(3, 0, 9),
625
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
+ * )
626
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.
627
1007
  */
628
1008
  public abstract door(
629
- catalogId: string,
1009
+ familyId: string,
630
1010
  hostWall: ComponentHandle,
631
1011
  position: Vec3Handle,
632
- options?: { label?: string },
1012
+ options?: {
1013
+ label?: string
1014
+ hinge?: Vec3Handle
1015
+ version?: number
1016
+ values?: Record<string, number | string>
1017
+ },
633
1018
  facing?: Vec3Handle,
634
1019
  ): PluginApiReturn<ComponentHandle>
635
1020
 
636
1021
  /**
637
- * 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.
638
1054
  *
639
- * Identified by a **catalog (library) id** — the team object's stable `_id`
640
- * 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
641
1076
  * {@linkcode PluginDesignWindowsApi.listCatalog}. The world `position` is
642
1077
  * projected onto the host wall's face to locate the opening; the wall is
643
1078
  * re-cut and the window hosted in a single undo entry. Placement is
644
- * asynchronous (the source mesh is fetched if not cached).
1079
+ * asynchronous. The sill parameter determines elevation; position Y is ignored.
645
1080
  *
646
- * @param catalogId - Library id: team `_id` or general `fullName`
1081
+ * @param familyId - Hosted window definition ID; legacy mesh catalog IDs are not accepted
647
1082
  * @param hostWall - The wall to host the window
648
1083
  * @param position - World point projected onto the wall to locate the opening
649
- * @param options - Optional placement options: `label` — instance name
650
- * @param facing - World point selecting which side of the wall the window
651
- * faces (matters for asymmetric windows, e.g. casement swing) — same
652
- * convention as {@linkcode PluginDesignCreateApi.door}. Default: the engine
653
- * 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
654
1086
  * @returns the {@linkcode ComponentHandle} of the placed window
655
- * @throws if the catalog id is unknown, the host is not a wall, the source
656
- * 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
657
1088
  *
658
1089
  * @examplePrompt Add a window to this wall here
659
1090
  * @examplePrompt Place a casement window on the selected wall
@@ -673,40 +1104,244 @@ export abstract class PluginDesignCreateApi {
673
1104
  * { label: "Win-01" },
674
1105
  * )
675
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.
676
1111
  */
677
1112
  public abstract window(
678
- catalogId: string,
1113
+ familyId: string,
679
1114
  hostWall: ComponentHandle,
680
1115
  position: Vec3Handle,
681
- options?: { label?: string },
1116
+ options?: {
1117
+ label?: string
1118
+ hinge?: Vec3Handle
1119
+ version?: number
1120
+ values?: Record<string, number | string>
1121
+ },
682
1122
  facing?: Vec3Handle,
683
1123
  ): PluginApiReturn<ComponentHandle>
684
1124
 
685
1125
  /**
686
- * 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.
687
1319
  *
688
1320
  * The world `position` is projected onto the host wall to locate the opening.
689
1321
  * All supplied dimensions use Snaptrude engine units. Window `sillHeight` is
690
1322
  * measured from the host wall base to the bottom of the window, not to
691
1323
  * its center. Placement is asynchronous and committed as one undoable creation.
692
1324
  *
693
- * @param options - Opening kind, catalog and host references, placement, and
694
- * 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
695
1329
  * @returns the {@linkcode ComponentHandle} of the placed door or window
696
- * @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
697
1331
  * not a wall, loading fails, or the opening cannot be placed on the host
698
1332
  *
699
1333
  * @examplePrompt Add a 1m wide door to this wall here
700
1334
  * @examplePrompt Place a window with a 0.9m sill height on the selected wall
701
1335
  *
702
- * @performance Single-opening creator — use it for one hosted opening. A plural
703
- * 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.
704
1339
  *
705
1340
  * # Example
706
1341
  * ```ts
707
1342
  * const opening = await snaptrude.design.create.opening({
708
1343
  * kind: "window",
709
- * catalogId: windowType.id,
1344
+ * familyId: windowType.id,
710
1345
  * hostWall: wall,
711
1346
  * position: await snaptrude.core.math.vec3.new(3, 0, 5),
712
1347
  * width: 1.2,
@@ -901,6 +1536,35 @@ export abstract class PluginDesignCreateApi {
901
1536
  farRatio?: number,
902
1537
  lotCoverageMaxPct?: number,
903
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>
904
1568
  }
905
1569
 
906
1570
  // ---------------------------------------------------------------------------
@@ -1206,21 +1870,24 @@ export type PluginDesignCreateBeamArgs = z.infer<
1206
1870
  * | `profile` | {@linkcode ProfileHandle} | Ordered curve chain (wall centerlines) |
1207
1871
  * | `height` | `number`? | Wall height (default engine default) |
1208
1872
  * | `thickness` | `number`? | Wall thickness (default: `wallType`'s total layer thickness when given, else engine default) |
1209
- * | `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) |
1210
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 |
1211
1876
  */
1212
1877
  export const PluginDesignCreateWallsArgs = z.object({
1213
1878
  profile: ProfileHandle,
1214
1879
  height: z.number().optional(),
1215
1880
  thickness: z.number().optional(),
1216
- wallType: z.string().min(1).optional(),
1881
+ wallType: PluginWallType.optional(),
1217
1882
  storey: z.number().int().optional(),
1883
+ topProfiles: z.array(PluginWallTopProfile.nullable()).optional(),
1884
+ values: PluginFamilyValues.optional(),
1218
1885
  })
1219
1886
  export type PluginDesignCreateWallsArgs = z.infer<
1220
1887
  typeof PluginDesignCreateWallsArgs
1221
1888
  >
1222
- // TRANSPORT: positional signature shipped — storey is the trailing 5th arg:
1223
- // 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)[])
1224
1891
 
1225
1892
  // ---------------------------------------------------------------------------
1226
1893
  // staircase
@@ -1262,7 +1929,7 @@ export type PluginDesignCreateStaircaseArgs = z.infer<
1262
1929
  * | `catalogId` | `string` | Library id: team `_id` or general `fullName` |
1263
1930
  * | `position` | {@linkcode Vec3Handle} | Absolute world placement point |
1264
1931
  * | `label` | `string`? | Instance name (default auto `${name}Ins${n}`) |
1265
- * | `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 |
1266
1933
  * | `angleInDegrees` | `number`? | Rotation about the vertical axis, in degrees (default unrotated) |
1267
1934
  */
1268
1935
  export const PluginDesignCreateFurnitureArgs = z.object({
@@ -1287,24 +1954,23 @@ export type PluginDesignCreateFurnitureArgs = z.infer<
1287
1954
  *
1288
1955
  * | Property | Type | Description |
1289
1956
  * |---|---|---|
1290
- * | `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 |
1291
1960
  * | `hostWall` | {@linkcode ComponentHandle} | The wall to host the door |
1292
1961
  * | `position` | {@linkcode Vec3Handle} | World point projected onto the wall |
1293
1962
  * | `label` | `string`? | Instance name (optional) |
1294
- * | `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 |
1295
1965
  */
1296
1966
  export const PluginDesignCreateDoorArgs = z.object({
1297
- catalogId: z.string().min(1),
1298
- hostWall: ComponentHandle,
1299
- position: Vec3Handle,
1300
- label: z.string().optional(),
1301
- facing: Vec3Handle.optional(),
1967
+ ...PluginOpeningBaseOptions,
1302
1968
  })
1303
1969
  export type PluginDesignCreateDoorArgs = z.infer<
1304
1970
  typeof PluginDesignCreateDoorArgs
1305
1971
  >
1306
- // TRANSPORT: positional signature shipped — facing is the trailing 5th arg:
1307
- // 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?)
1308
1974
 
1309
1975
  // ---------------------------------------------------------------------------
1310
1976
  // window
@@ -1315,39 +1981,28 @@ export type PluginDesignCreateDoorArgs = z.infer<
1315
1981
  *
1316
1982
  * | Property | Type | Description |
1317
1983
  * |---|---|---|
1318
- * | `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 |
1319
1987
  * | `hostWall` | {@linkcode ComponentHandle} | The wall to host the window |
1320
1988
  * | `position` | {@linkcode Vec3Handle} | World point projected onto the wall |
1321
1989
  * | `label` | `string`? | Instance name (optional) |
1322
- * | `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 |
1323
1992
  */
1324
1993
  export const PluginDesignCreateWindowArgs = z.object({
1325
- catalogId: z.string().min(1),
1326
- hostWall: ComponentHandle,
1327
- position: Vec3Handle,
1328
- label: z.string().optional(),
1329
- facing: Vec3Handle.optional(),
1994
+ ...PluginOpeningBaseOptions,
1330
1995
  })
1331
1996
  export type PluginDesignCreateWindowArgs = z.infer<
1332
1997
  typeof PluginDesignCreateWindowArgs
1333
1998
  >
1334
- // TRANSPORT: positional signature shipped — facing is the trailing 5th arg:
1335
- // 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?)
1336
2001
 
1337
2002
  // ---------------------------------------------------------------------------
1338
2003
  // opening
1339
2004
  // ---------------------------------------------------------------------------
1340
2005
 
1341
- const PluginOpeningBaseOptions = {
1342
- catalogId: z.string().min(1),
1343
- hostWall: ComponentHandle,
1344
- position: Vec3Handle,
1345
- facing: Vec3Handle.optional(),
1346
- label: z.string().optional(),
1347
- width: z.number().finite().positive().optional(),
1348
- height: z.number().finite().positive().optional(),
1349
- }
1350
-
1351
2006
  /**
1352
2007
  * Options for {@linkcode PluginDesignCreateApi.opening}, discriminated by
1353
2008
  * `kind`. Width and height are positive Snaptrude engine-unit values.
@@ -1446,13 +2101,63 @@ export type PluginSmartLayoutResult = z.infer<typeof PluginSmartLayoutResult>
1446
2101
  * | `style` | {@linkcode PluginReferenceLineStyle}? | Line style (default `SOLID`) |
1447
2102
  * | `gridTag` | `string`? | Grid tag label (default `"A"`) |
1448
2103
  */
1449
- export const PluginDesignCreateReferenceLinesArgs = z.object({
1450
- profile: ProfileHandle,
1451
- color: z.string().optional(),
1452
- thickness: z.number().optional(),
1453
- style: PluginReferenceLineStyle.optional(),
1454
- gridTag: z.string().optional(),
1455
- })
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
+ })
1456
2161
  export type PluginDesignCreateReferenceLinesArgs = z.infer<
1457
2162
  typeof PluginDesignCreateReferenceLinesArgs
1458
2163
  >
@@ -1524,3 +2229,7 @@ export const PluginCreateSpaceItem = z.object({
1524
2229
  storey: z.number().int().optional(),
1525
2230
  })
1526
2231
  export type PluginCreateSpaceItem = z.infer<typeof PluginCreateSpaceItem>
2232
+
2233
+ export * from "./opening-fields"
2234
+ export * from "./bulk-items"
2235
+ export * from "./grids"