@snaptrude/plugin-core 0.9.16 → 0.9.18-edgesetbacks-20261021.1

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 (221) hide show
  1. package/AGENTS.md +231 -231
  2. package/CHANGELOG.md +408 -402
  3. package/CLAUDE.md +11 -11
  4. package/api-manifest.json +1510 -701
  5. package/dist/api/analysis/daylight.d.ts +5 -5
  6. package/dist/api/analysis/solar.d.ts +2 -2
  7. package/dist/api/core/camera/index.d.ts +29 -0
  8. package/dist/api/core/camera/index.d.ts.map +1 -1
  9. package/dist/api/core/geom/create/index.d.ts +7 -1
  10. package/dist/api/core/geom/create/index.d.ts.map +1 -1
  11. package/dist/api/core/geom/update/contour.d.ts +8 -2
  12. package/dist/api/core/geom/update/contour.d.ts.map +1 -1
  13. package/dist/api/core/storeys/index.d.ts +5 -2
  14. package/dist/api/core/storeys/index.d.ts.map +1 -1
  15. package/dist/api/core/zoom/index.d.ts +13 -0
  16. package/dist/api/core/zoom/index.d.ts.map +1 -1
  17. package/dist/api/design/bim.d.ts +299 -0
  18. package/dist/api/design/bim.d.ts.map +1 -0
  19. package/dist/api/design/boolean/index.d.ts +2 -0
  20. package/dist/api/design/boolean/index.d.ts.map +1 -1
  21. package/dist/api/design/catalog.d.ts +320 -0
  22. package/dist/api/design/catalog.d.ts.map +1 -0
  23. package/dist/api/design/create/bulk-items.d.ts +65 -25
  24. package/dist/api/design/create/bulk-items.d.ts.map +1 -1
  25. package/dist/api/design/create/index.d.ts +299 -100
  26. package/dist/api/design/create/index.d.ts.map +1 -1
  27. package/dist/api/design/create/opening-fields.d.ts +13 -12
  28. package/dist/api/design/create/opening-fields.d.ts.map +1 -1
  29. package/dist/api/design/delete/index.d.ts +1 -0
  30. package/dist/api/design/delete/index.d.ts.map +1 -1
  31. package/dist/api/design/doors/index.d.ts +18 -24
  32. package/dist/api/design/doors/index.d.ts.map +1 -1
  33. package/dist/api/design/family.d.ts +813 -35
  34. package/dist/api/design/family.d.ts.map +1 -1
  35. package/dist/api/design/index.d.ts +16 -0
  36. package/dist/api/design/index.d.ts.map +1 -1
  37. package/dist/api/design/networks.d.ts +1703 -0
  38. package/dist/api/design/networks.d.ts.map +1 -0
  39. package/dist/api/design/query/grids.d.ts +2 -2
  40. package/dist/api/design/query/index.d.ts +42 -32
  41. package/dist/api/design/query/index.d.ts.map +1 -1
  42. package/dist/api/design/selection/index.d.ts +10 -10
  43. package/dist/api/design/types/index.d.ts +40 -24
  44. package/dist/api/design/types/index.d.ts.map +1 -1
  45. package/dist/api/design/update/index.d.ts +111 -6
  46. package/dist/api/design/update/index.d.ts.map +1 -1
  47. package/dist/api/design/windows/index.d.ts +8 -8
  48. package/dist/api/design/windows/index.d.ts.map +1 -1
  49. package/dist/api/entity/buildableEnvelope.d.ts +84 -2
  50. package/dist/api/entity/buildableEnvelope.d.ts.map +1 -1
  51. package/dist/api/presentation/aiInspiration.d.ts +10 -10
  52. package/dist/api/presentation/annotate.d.ts +38 -22
  53. package/dist/api/presentation/annotate.d.ts.map +1 -1
  54. package/dist/api/presentation/drawingContract.d.ts +32 -0
  55. package/dist/api/presentation/drawingContract.d.ts.map +1 -0
  56. package/dist/api/presentation/drawings.d.ts +3523 -0
  57. package/dist/api/presentation/drawings.d.ts.map +1 -0
  58. package/dist/api/presentation/export.d.ts +23 -5
  59. package/dist/api/presentation/export.d.ts.map +1 -1
  60. package/dist/api/presentation/index.d.ts +41 -12
  61. package/dist/api/presentation/index.d.ts.map +1 -1
  62. package/dist/api/presentation/placedViews.d.ts +106 -6
  63. package/dist/api/presentation/placedViews.d.ts.map +1 -1
  64. package/dist/api/presentation/sections.d.ts +461 -0
  65. package/dist/api/presentation/sections.d.ts.map +1 -0
  66. package/dist/api/presentation/shapes.d.ts +2 -2
  67. package/dist/api/presentation/sheets.d.ts +175 -19
  68. package/dist/api/presentation/sheets.d.ts.map +1 -1
  69. package/dist/api/presentation/viewPresets.d.ts +222 -0
  70. package/dist/api/presentation/viewPresets.d.ts.map +1 -0
  71. package/dist/api/presentation/views.d.ts +141 -8
  72. package/dist/api/presentation/views.d.ts.map +1 -1
  73. package/dist/api/presentation/walkthroughs.d.ts +6 -6
  74. package/dist/errors/codes.d.ts +3 -1
  75. package/dist/errors/codes.d.ts.map +1 -1
  76. package/dist/index.cjs +3883 -2385
  77. package/dist/index.cjs.map +1 -1
  78. package/dist/index.js +3744 -2385
  79. package/dist/index.js.map +1 -1
  80. package/package.json +1 -1
  81. package/scripts/check-dist-fresh.mjs +22 -22
  82. package/scripts/generate-manifest.mjs +243 -238
  83. package/scripts/generate-manifest.test.mjs +303 -303
  84. package/src/api/analysis/daylight.ts +470 -470
  85. package/src/api/analysis/heatmaps.ts +683 -683
  86. package/src/api/analysis/illuminance.ts +155 -155
  87. package/src/api/analysis/index.ts +61 -61
  88. package/src/api/analysis/shadows.ts +183 -183
  89. package/src/api/analysis/solar.ts +237 -237
  90. package/src/api/analysis/sunlightHours.ts +211 -211
  91. package/src/api/analysis/sunpath.ts +83 -83
  92. package/src/api/analysis/weather.ts +179 -179
  93. package/src/api/core/buildings.ts +485 -485
  94. package/src/api/core/camera/index.ts +696 -667
  95. package/src/api/core/comment/index.ts +376 -376
  96. package/src/api/core/geom/create/index.ts +2139 -2133
  97. package/src/api/core/geom/delete/index.ts +84 -84
  98. package/src/api/core/geom/index.ts +35 -35
  99. package/src/api/core/geom/query/arc.ts +169 -169
  100. package/src/api/core/geom/query/brep.ts +621 -621
  101. package/src/api/core/geom/query/circle.ts +404 -404
  102. package/src/api/core/geom/query/contour.ts +540 -540
  103. package/src/api/core/geom/query/curve.ts +1102 -1102
  104. package/src/api/core/geom/query/edge.ts +130 -130
  105. package/src/api/core/geom/query/face.ts +508 -508
  106. package/src/api/core/geom/query/halfedge.ts +210 -210
  107. package/src/api/core/geom/query/index.ts +63 -63
  108. package/src/api/core/geom/query/profile.ts +473 -473
  109. package/src/api/core/geom/query/vertex.ts +205 -205
  110. package/src/api/core/geom/update/contour.ts +323 -317
  111. package/src/api/core/geom/update/curve.ts +209 -209
  112. package/src/api/core/geom/update/index.ts +29 -29
  113. package/src/api/core/geom/update/profile.ts +379 -379
  114. package/src/api/core/groups.ts +521 -521
  115. package/src/api/core/handles/index.ts +234 -234
  116. package/src/api/core/history/index.ts +84 -84
  117. package/src/api/core/index.ts +99 -99
  118. package/src/api/core/io/export/index.ts +126 -126
  119. package/src/api/core/io/import/index.ts +504 -504
  120. package/src/api/core/io/index.ts +42 -42
  121. package/src/api/core/io/job/index.ts +140 -140
  122. package/src/api/core/io/query/index.ts +71 -71
  123. package/src/api/core/io/terrain/index.ts +360 -360
  124. package/src/api/core/io/underlay/index.ts +705 -705
  125. package/src/api/core/layers.ts +540 -540
  126. package/src/api/core/math/index.ts +20 -20
  127. package/src/api/core/math/quat.ts +172 -172
  128. package/src/api/core/math/vec3.ts +179 -179
  129. package/src/api/core/mode/index.ts +108 -108
  130. package/src/api/core/project/index.ts +479 -479
  131. package/src/api/core/proposals/index.ts +570 -570
  132. package/src/api/core/storeys/index.ts +311 -308
  133. package/src/api/core/storeys/layers.ts +339 -339
  134. package/src/api/core/tags.ts +911 -911
  135. package/src/api/core/units/index.ts +164 -164
  136. package/src/api/core/user.ts +46 -46
  137. package/src/api/core/zoom/index.ts +64 -51
  138. package/src/api/design/bim.ts +210 -0
  139. package/src/api/design/boolean/index.ts +181 -179
  140. package/src/api/design/catalog.ts +203 -0
  141. package/src/api/design/create/bulk-items.ts +294 -254
  142. package/src/api/design/create/grids.ts +137 -137
  143. package/src/api/design/create/index.ts +2178 -1980
  144. package/src/api/design/create/opening-fields.ts +40 -37
  145. package/src/api/design/delete/index.ts +75 -74
  146. package/src/api/design/dimensions.ts +453 -453
  147. package/src/api/design/doors/index.ts +516 -500
  148. package/src/api/design/edit/index.ts +54 -54
  149. package/src/api/design/erase/index.ts +151 -151
  150. package/src/api/design/family.ts +1342 -704
  151. package/src/api/design/furniture/index.ts +319 -319
  152. package/src/api/design/grids.ts +237 -237
  153. package/src/api/design/index.ts +304 -274
  154. package/src/api/design/lock.ts +69 -69
  155. package/src/api/design/materials/index.ts +608 -608
  156. package/src/api/design/networks.ts +607 -0
  157. package/src/api/design/query/geometry/index.ts +319 -319
  158. package/src/api/design/query/grids.ts +197 -197
  159. package/src/api/design/query/index.ts +1236 -1226
  160. package/src/api/design/query/referenceLines.ts +52 -52
  161. package/src/api/design/query/spaces.ts +275 -275
  162. package/src/api/design/selection/index.ts +273 -273
  163. package/src/api/design/shared.ts +40 -40
  164. package/src/api/design/transform/index.ts +254 -254
  165. package/src/api/design/types/index.ts +163 -156
  166. package/src/api/design/update/index.ts +1096 -983
  167. package/src/api/design/visibility.ts +143 -143
  168. package/src/api/design/windows/index.ts +351 -333
  169. package/src/api/entity/buildableEnvelope.ts +397 -322
  170. package/src/api/entity/index.ts +30 -30
  171. package/src/api/entity/referenceLine.ts +235 -235
  172. package/src/api/entity/space.ts +612 -612
  173. package/src/api/entity/story.ts +517 -517
  174. package/src/api/index.ts +47 -47
  175. package/src/api/presentation/aiInspiration.ts +1116 -1116
  176. package/src/api/presentation/annotate.ts +417 -385
  177. package/src/api/presentation/camera.ts +289 -289
  178. package/src/api/presentation/diagrams.ts +194 -194
  179. package/src/api/presentation/drawingContract.ts +70 -0
  180. package/src/api/presentation/drawings.ts +1179 -0
  181. package/src/api/presentation/export.ts +134 -108
  182. package/src/api/presentation/import.ts +159 -159
  183. package/src/api/presentation/index.ts +160 -126
  184. package/src/api/presentation/placedViews.ts +1211 -1120
  185. package/src/api/presentation/sections.ts +420 -0
  186. package/src/api/presentation/shapes.ts +274 -274
  187. package/src/api/presentation/sheets.ts +746 -585
  188. package/src/api/presentation/slideshow.ts +134 -134
  189. package/src/api/presentation/tables.ts +84 -84
  190. package/src/api/presentation/viewPresets.ts +241 -0
  191. package/src/api/presentation/views.ts +742 -673
  192. package/src/api/presentation/walkthroughs.ts +1201 -1201
  193. package/src/api/program/adjacency.ts +164 -164
  194. package/src/api/program/areas.ts +408 -408
  195. package/src/api/program/classification.ts +138 -138
  196. package/src/api/program/cores.ts +83 -83
  197. package/src/api/program/departments.ts +517 -517
  198. package/src/api/program/index.ts +73 -73
  199. package/src/api/program/labels.ts +332 -332
  200. package/src/api/program/layout.ts +422 -422
  201. package/src/api/program/metadata.ts +364 -364
  202. package/src/api/program/metrics.ts +120 -120
  203. package/src/api/program/site.ts +748 -748
  204. package/src/api/program/spreadsheet.ts +2698 -2698
  205. package/src/api/workspace/index.ts +612 -612
  206. package/src/errors/codes.ts +143 -136
  207. package/src/errors/envelope.ts +75 -75
  208. package/src/errors/index.ts +21 -21
  209. package/src/errors/plugin-error.ts +134 -134
  210. package/src/handles.ts +297 -297
  211. package/src/host-utils.ts +67 -67
  212. package/src/index.ts +7 -7
  213. package/src/massParameters.ops.ts +246 -246
  214. package/src/massParameters.ts +427 -427
  215. package/src/types.ts +16 -16
  216. package/test/drawingContract.test.mjs +288 -0
  217. package/test/errors.test.mjs +185 -184
  218. package/test/massParameters.test.mjs +952 -952
  219. package/test/presentationViewSettings.test.mjs +85 -0
  220. package/tsconfig.json +22 -22
  221. package/tsup.config.ts +12 -12
@@ -1,983 +1,1096 @@
1
- import * as z from "zod"
2
- import { PluginApiReturn } from "../../../types"
3
- import { ComponentHandle, ProfileHandle } from "../../../handles"
4
- import {
5
- PluginSpaceType,
6
- PluginMassType,
7
- PluginDepartmentId,
8
- PluginAreaClass,
9
- PluginSpaceUpdateResult,
10
- } from "../../entity/space"
11
- import { PluginDesignChangeResult } from "../lock"
12
- import { MassParametersPatch, type MassParametersRecord } from "../../../massParameters"
13
- import {
14
- PluginBuildableEnvelopePolygonVertex,
15
- PluginBuildableEnvelopeSetbackTier,
16
- PluginBuildableEnvelopeVerticalCap,
17
- PluginBuildableEnvelopeUpdateResult,
18
- } from "../../entity/buildableEnvelope"
19
- import { PluginGridStyleOptions } from "../create/grids"
20
- import { PluginEntityType } from "../query"
21
-
22
- /**
23
- * Sparse property updates for a space. Only provided fields change.
24
- *
25
- * | Property | Type | Description |
26
- * |---|---|---|
27
- * | `room_type` | `string`? | Room label |
28
- * | `massType` | `PluginMassType`? | Mass type |
29
- * | `spaceType` | `PluginSpaceType`? | Space type |
30
- * | `areaClass` | `PluginAreaClass`? | Area class (NET/GROSS/EXCLUDED) |
31
- * | `departmentId` | `PluginDepartmentId`? | Department |
32
- */
33
- export const PluginSpacePropertyUpdates = z.object({
34
- room_type: z.string().optional(),
35
- massType: PluginMassType.optional(),
36
- spaceType: PluginSpaceType.optional(),
37
- areaClass: PluginAreaClass.optional(),
38
- departmentId: PluginDepartmentId.optional(),
39
- })
40
- export type PluginSpacePropertyUpdates = z.infer<
41
- typeof PluginSpacePropertyUpdates
42
- >
43
-
44
- /** Geometry replacement for a space — re-extrude a profile. */
45
- export const PluginSpaceGeometryUpdate = z.object({
46
- profile: ProfileHandle,
47
- extrudeHeight: z.number(),
48
- })
49
- export type PluginSpaceGeometryUpdate = z.infer<
50
- typeof PluginSpaceGeometryUpdate
51
- >
52
-
53
- /** One space to update via {@linkcode PluginDesignUpdateApi.spaces}. */
54
- export const PluginUpdateSpaceItem = z.object({
55
- space: ComponentHandle,
56
- properties: PluginSpacePropertyUpdates.optional(),
57
- geometry: PluginSpaceGeometryUpdate.optional(),
58
- })
59
- export type PluginUpdateSpaceItem = z.infer<typeof PluginUpdateSpaceItem>
60
-
61
- /**
62
- * Wire schema for {@linkcode PluginDesignUpdateApi.setLabel}.
63
- *
64
- * | Property | Type | Description |
65
- * |---|---|---|
66
- * | `component` | `ComponentHandle` | The component to relabel |
67
- * | `label` | `string` | New label (non-empty after trimming) |
68
- */
69
- export const PluginDesignSetLabelArgs = z.object({
70
- component: ComponentHandle,
71
- label: z.string().trim().min(1),
72
- })
73
- export type PluginDesignSetLabelArgs = z.infer<typeof PluginDesignSetLabelArgs>
74
-
75
- /**
76
- * Sparse parametric updates for a staircase (all fields optional; at least one
77
- * required). Only the fields you provide change. Consumed by
78
- * {@linkcode PluginDesignUpdateApi.staircase}; the paired read is
79
- * `design.query.getStaircaseParams`.
80
- *
81
- * All dimensions are in **engine units** (the same convention as
82
- * `design.doors.setWidth`); `steps` is a unitless integer count. Height, riser,
83
- * and step count are **coupled** (`staircaseHeight = steps × riser`); the two
84
- * lock booleans govern which value recomputes — see the method JSDoc.
85
- *
86
- * | Property | Type | Description |
87
- * |---|---|---|
88
- * | `isStoreyHeightUnlocked` | `boolean`? | Unlock the staircase height from the storey height |
89
- * | `isStaircaseHeightUnlocked` | `boolean`? | Unlock the height from `steps × riser` |
90
- * | `storeyHeight` | `number`? | Storey height the stair sits within (engine units) |
91
- * | `staircaseHeight` | `number`? | Total rise (engine units) |
92
- * | `steps` | `number`? | Step count (unitless integer) |
93
- * | `riser` | `number`? | Riser height (engine units) |
94
- * | `tread` | `number`? | Tread depth (engine units) |
95
- * | `width` | `number`? | Flight width (engine units) |
96
- * | `depth` | `number`? | Slab/waist thickness (engine units) |
97
- * | `wellSize` | `number`? | Stairwell gap (dogLegged / openWell / square) |
98
- * | `flightStartDistAfterTurn` | `number`? | Flight offset after a turn (dogLegged / lShaped / square) |
99
- * | `landingWidth` | `number`? | Landing depth (straight / dogLegged) |
100
- * | `baseOffset` | `number`? | Vertical offset from the storey base (engine units) |
101
- */
102
- export const PluginStaircaseParamUpdates = z
103
- .object({
104
- isStoreyHeightUnlocked: z.boolean().optional(),
105
- isStaircaseHeightUnlocked: z.boolean().optional(),
106
- storeyHeight: z.number().optional(),
107
- staircaseHeight: z.number().optional(),
108
- steps: z.number().int().optional(),
109
- riser: z.number().optional(),
110
- tread: z.number().optional(),
111
- width: z.number().optional(),
112
- depth: z.number().optional(),
113
- wellSize: z.number().optional(),
114
- flightStartDistAfterTurn: z.number().optional(),
115
- landingWidth: z.number().optional(),
116
- baseOffset: z.number().optional(),
117
- })
118
- .refine((p) => Object.keys(p).length > 0, {
119
- message:
120
- "PluginStaircaseParamUpdates requires at least one field to change",
121
- })
122
- export type PluginStaircaseParamUpdates = z.infer<
123
- typeof PluginStaircaseParamUpdates
124
- >
125
-
126
- /**
127
- * Arguments for {@linkcode PluginDesignUpdateApi.staircase}.
128
- *
129
- * | Property | Type | Description |
130
- * |---|---|---|
131
- * | `staircases` | {@linkcode ComponentHandle}`[]` | The staircases to update |
132
- * | `params` | {@linkcode PluginStaircaseParamUpdates} | Sparse parametric changes (≥ 1 field) |
133
- */
134
- export const PluginDesignUpdateStaircaseArgs = z.object({
135
- staircases: z.array(ComponentHandle),
136
- params: PluginStaircaseParamUpdates,
137
- })
138
- export type PluginDesignUpdateStaircaseArgs = z.infer<
139
- typeof PluginDesignUpdateStaircaseArgs
140
- >
141
-
142
- /**
143
- * Arguments for {@linkcode PluginDesignUpdateApi.parameters}.
144
- *
145
- * | Property | Type | Description |
146
- * |---|---|---|
147
- * | `component` | {@linkcode ComponentHandle} | The parametric mass to edit |
148
- * | `patch` | {@linkcode MassParametersPatch} | Partial `values` (+ optional `operations`), or the full edited record |
149
- */
150
- export const PluginDesignUpdateParametersArgs = z.object({
151
- component: ComponentHandle,
152
- patch: MassParametersPatch,
153
- })
154
- export type PluginDesignUpdateParametersArgs = z.infer<
155
- typeof PluginDesignUpdateParametersArgs
156
- >
157
-
158
- /**
159
- * Sparse edits for a wall (all optional; at least one required). Only the
160
- * fields you provide change. Dimensions are in **engine units** (the same
161
- * convention as `design.doors.setWidth`). Consumed by
162
- * {@linkcode PluginDesignUpdateApi.wall}.
163
- *
164
- * | Property | Type | Description |
165
- * |---|---|---|
166
- * | `thickness` | `number`? | Wall thickness, engine units (> 0) |
167
- * | `height` | `number`? | Wall height, engine units (> 0) |
168
- * | `type` | `string`? | Convert the wall to another wall type — a name (or `wall:`-prefixed id) from `design.types.list("wall")` |
169
- */
170
- export const PluginWallUpdates = z
171
- .object({
172
- thickness: z.number().positive().optional(),
173
- height: z.number().positive().optional(),
174
- type: z.string().trim().min(1).optional(),
175
- })
176
- .refine((p) => Object.keys(p).length > 0, {
177
- message: "PluginWallUpdates requires at least one field to change",
178
- })
179
- export type PluginWallUpdates = z.infer<typeof PluginWallUpdates>
180
-
181
- /**
182
- * Sparse dimension edits for a slab-family element (slab / floor / roof /
183
- * ceiling). All values are engine units, the same convention as
184
- * `design.doors.setWidth`. Consumed by {@linkcode PluginDesignUpdateApi.slab},
185
- * `floor`, `roof`, and `ceiling`.
186
- *
187
- * `overhang` is only available on **slabs and roofs** (the panel's Overhang
188
- * slider) — floors and ceilings reject it with `PRECONDITION_FAILED`.
189
- *
190
- * | Property | Type | Description |
191
- * |---|---|---|
192
- * | `thickness` | `number`? | Element thickness, engine units (> 0) |
193
- * | `overhang` | `number`? | Absolute overhang offset, engine units (slab / roof only) |
194
- */
195
- export const PluginSlabUpdates = z
196
- .object({
197
- thickness: z.number().positive().optional(),
198
- overhang: z.number().optional(),
199
- })
200
- .refine((p) => Object.keys(p).length > 0, {
201
- message: "PluginSlabUpdates requires at least one field to change",
202
- })
203
- export type PluginSlabUpdates = z.infer<typeof PluginSlabUpdates>
204
-
205
- /** Arguments for {@linkcode PluginDesignUpdateApi.wall}. */
206
- export const PluginDesignUpdateWallArgs = z.object({
207
- component: ComponentHandle,
208
- updates: PluginWallUpdates,
209
- })
210
- export type PluginDesignUpdateWallArgs = z.infer<
211
- typeof PluginDesignUpdateWallArgs
212
- >
213
-
214
- /**
215
- * Arguments for the slab-family updaters —
216
- * {@linkcode PluginDesignUpdateApi.slab}, `floor`, `roof`, `ceiling`.
217
- */
218
- export const PluginDesignUpdateSlabArgs = z.object({
219
- component: ComponentHandle,
220
- updates: PluginSlabUpdates,
221
- })
222
- export type PluginDesignUpdateSlabArgs = z.infer<
223
- typeof PluginDesignUpdateSlabArgs
224
- >
225
-
226
- /** Arguments for {@linkcode PluginDesignUpdateApi.setElevation}. */
227
- export const PluginDesignSetElevationArgs = z.object({
228
- component: ComponentHandle,
229
- elevation: z.number(),
230
- })
231
- export type PluginDesignSetElevationArgs = z.infer<
232
- typeof PluginDesignSetElevationArgs
233
- >
234
-
235
- /** Arguments for {@linkcode PluginDesignUpdateApi.setBaseOffset}. */
236
- export const PluginDesignSetBaseOffsetArgs = z.object({
237
- component: ComponentHandle,
238
- offset: z.number(),
239
- })
240
- export type PluginDesignSetBaseOffsetArgs = z.infer<
241
- typeof PluginDesignSetBaseOffsetArgs
242
- >
243
-
244
- /**
245
- * Arguments for {@linkcode PluginDesignUpdateApi.setStorey}.
246
- *
247
- * | Property | Type | Description |
248
- * |---|---|---|
249
- * | `components` | {@linkcode ComponentHandle}`[]` | The components to reassign |
250
- * | `storey` | `number` | Target integer storey number (`entity.story` convention; must exist) |
251
- */
252
- export const PluginDesignSetStoreyArgs = z.object({
253
- components: z.array(ComponentHandle),
254
- storey: z.number().int(),
255
- })
256
- export type PluginDesignSetStoreyArgs = z.infer<
257
- typeof PluginDesignSetStoreyArgs
258
- >
259
-
260
- /**
261
- * Object types {@linkcode PluginDesignUpdateApi.setObjectType} can turn a mass
262
- * into — the `design.query` `type` vocabulary.
263
- */
264
- export const PluginObjectType = PluginEntityType.extract([
265
- "wall",
266
- "roof",
267
- "slab",
268
- "floor",
269
- ])
270
- export type PluginObjectType = z.infer<typeof PluginObjectType>
271
-
272
- /**
273
- * Arguments for {@linkcode PluginDesignUpdateApi.setObjectType}.
274
- *
275
- * | Property | Type | Description |
276
- * |---|---|---|
277
- * | `components` | {@linkcode ComponentHandle}`[]` | The masses to convert (>= 1) |
278
- * | `objectType` | {@linkcode PluginObjectType} | Target object type (`"wall"`, `"roof"`, `"slab"`, `"floor"`) |
279
- */
280
- export const PluginDesignSetObjectTypeArgs = z.object({
281
- components: z.array(ComponentHandle).min(1),
282
- objectType: PluginObjectType,
283
- })
284
- export type PluginDesignSetObjectTypeArgs = z.infer<
285
- typeof PluginDesignSetObjectTypeArgs
286
- >
287
-
288
- /**
289
- * The sparse GROUP-LEVEL patch accepted by {@linkcode PluginDesignUpdateApi.grid}.
290
- * Only provided fields change. Wing- and line-level updates live on the grids
291
- * entity family: `design.grids.updateWings` / `design.grids.updateLines`.
292
- */
293
- export const PluginGridPatch = z.object({
294
- angle: z.number().finite().optional(),
295
- props: PluginGridStyleOptions.optional(),
296
- storeyVisibility: z
297
- .array(z.object({ storey: z.number().int(), visible: z.boolean() }))
298
- .optional(),
299
- })
300
- export type PluginGridPatch = z.infer<typeof PluginGridPatch>
301
-
302
- /** Arguments for {@linkcode PluginDesignUpdateApi.grid}. */
303
- export const PluginDesignUpdateGridArgs = z.object({
304
- grid: ComponentHandle,
305
- patch: PluginGridPatch,
306
- })
307
- export type PluginDesignUpdateGridArgs = z.infer<
308
- typeof PluginDesignUpdateGridArgs
309
- >
310
-
311
- /**
312
- * `snaptrude.design.update` — edit existing scene entities (properties + geometry).
313
- *
314
- * Migrated from the removed `entity.space.{update, updateGeometryFromProfile, bulkUpdate}`.
315
- * Only provided fields change; each call is undoable. Failures throw (RPC rejects).
316
- */
317
- export abstract class PluginDesignUpdateApi {
318
- constructor() {}
319
-
320
- /**
321
- * Set a component's **Label** — the same editable Label field the properties
322
- * panel shows (doors, windows, furniture, spaces, and every other labelled
323
- * component). The paired read is `design.query.getLabel`. Undoable; the
324
- * canvas label text updates immediately.
325
- *
326
- * Strictly **per component**: labelling one door of a placed family never
327
- * touches its sibling doors (the panel's Label edit, by contrast, applies to
328
- * the whole family). On **legacy masses** without a space type, panel parity
329
- * applies — a label matching a room-type preset with a height can also
330
- * adjust the mass's height and material, exactly like typing it in the panel.
331
- *
332
- * Throws when the component is locked, the handle is gone/forged, the label
333
- * is empty, or the component has no mesh to label. Re-applying the current
334
- * label is a no-op that returns `true` without touching the undo stack.
335
- *
336
- * @param component - The component to relabel
337
- * @param label - New label text (non-empty; leading/trailing whitespace trimmed)
338
- * @returns `true` once the label is applied
339
- *
340
- * @examplePrompt Rename this door to D-101
341
- * @examplePrompt Number all doors on storey 2 with a D-2-XX tag
342
- * @examplePrompt Set the selected window's label to W-04
343
- * @examplePrompt Tag every door with its fire rating
344
- *
345
- * # Example
346
- * ```ts
347
- * // Door tag numbering: label every door as D-<storey>-<n>, numbered per storey
348
- * await snaptrude.design.selection.setByFilter({ types: ["door"] })
349
- * const doors = await snaptrude.design.selection.get()
350
- * const counts = {}
351
- * for (const door of doors) {
352
- * const { storey = 0 } = await snaptrude.design.query.getProperties(door)
353
- * counts[storey] = (counts[storey] ?? 0) + 1
354
- * await snaptrude.design.update.setLabel(door, `D-${storey}-${String(counts[storey]).padStart(2, "0")}`)
355
- * }
356
- * ```
357
- */
358
- public abstract setLabel(
359
- component: ComponentHandle,
360
- label: string,
361
- ): PluginApiReturn<boolean>
362
-
363
- /**
364
- * Update a structural grid's **group-level** fields with a sparse patch —
365
- * default line styling, absolute angle, and per-storey 2D visibility. Only
366
- * provided fields change. Wing- and line-level updates live on the grids
367
- * entity family: {@linkcode PluginDesignGridsApi.updateWings} /
368
- * {@linkcode PluginDesignGridsApi.updateLines}.
369
- *
370
- * Every change routes through the exact setters the native panel uses —
371
- * each changed field commits its own undoable command, identical to editing
372
- * that field in the panel. `angle` is an absolute target in degrees (CCW
373
- * about +Y).
374
- *
375
- * @param grid - The grid group to update
376
- * @param patch - The sparse {@linkcode PluginGridPatch}
377
- * @returns `true` once applied
378
- *
379
- * @examplePrompt Rotate the grid to 45 degrees
380
- * @examplePrompt Make this grid's lines dashed and grey
381
- * @examplePrompt Hide this grid on storey 3's plan
382
- *
383
- * # Example
384
- * ```ts
385
- * const [grid] = await snaptrude.design.query.listGrids()
386
- * await snaptrude.design.update.grid(grid, { angle: 45, props: { style: "DASHED" } })
387
- * ```
388
- */
389
- public abstract grid(
390
- grid: ComponentHandle,
391
- patch: PluginGridPatch,
392
- ): PluginApiReturn<boolean>
393
-
394
- /**
395
- * Update a single space (room) — change its properties (room label, space
396
- * type, mass type, area class, department) and/or replace its geometry by
397
- * re-extruding a profile to a new height. Sparse and undoable: only the
398
- * fields you provide change.
399
- *
400
- * @param space - The space to update
401
- * @param options - `properties` (sparse) and/or `geometry` ({ profile, extrudeHeight })
402
- * @returns the updated space's echoed {@linkcode PluginSpaceUpdateResult}
403
- *
404
- * @examplePrompt Rename this room to Office
405
- * @examplePrompt Change this space's room type to Meeting Room
406
- * @examplePrompt Set the height of this room to 3 metres
407
- * @examplePrompt Move this space into the Circulation department
408
- * @examplePrompt Exclude this room from the area calculations
409
- *
410
- * @performance For MORE THAN ONE space, call `design.update.spaces(items[])` — the whole
411
- * batch is one host round-trip. Looping this single-space updater is N round-trips.
412
- *
413
- * # Example
414
- * ```ts
415
- * await snaptrude.design.update.space("space-id", { properties: { room_type: "Office" } })
416
- * ```
417
- */
418
- public abstract space(
419
- space: ComponentHandle,
420
- options: {
421
- properties?: PluginSpacePropertyUpdates
422
- geometry?: PluginSpaceGeometryUpdate
423
- },
424
- ): PluginApiReturn<PluginSpaceUpdateResult>
425
-
426
- /**
427
- * Update many spaces (rooms) in one undoable operation — the bulk form of
428
- * {@linkcode space}. Each item pairs a space with the sparse property
429
- * changes and/or geometry re-extrude to apply; every item is validated up
430
- * front, then all changes are applied as a single command.
431
- *
432
- * Migrated from `entity.space.bulkUpdate`.
433
- *
434
- * @param items - One {@linkcode PluginUpdateSpaceItem} per space
435
- * @returns one {@linkcode PluginSpaceUpdateResult} per item, in input order
436
- *
437
- * @examplePrompt Label all the selected rooms as Bedrooms
438
- * @examplePrompt Change the room type of these five spaces at once
439
- * @examplePrompt Set every room on this floor to a 3m height in one go
440
- * @examplePrompt Bulk-assign these spaces to the Service department
441
- * @examplePrompt Mark all of these rooms as excluded from area in one update
442
- *
443
- * @performance Bulk update — the whole batch is ONE host round-trip. Prefer over looping
444
- * `design.update.space`: build the `items[]` array first, then make one call.
445
- *
446
- * # Example
447
- * ```ts
448
- * // Re-label every selected room and mark it NET area — one undoable bulk update
449
- * const rooms = await snaptrude.design.query.listSpaces({ isSelected: true })
450
- * const results = await snaptrude.design.update.spaces(
451
- * rooms.map((space) => ({
452
- * space,
453
- * properties: { room_type: "Bedroom", areaClass: "NET" },
454
- * })),
455
- * )
456
- * // One result per item, in input order
457
- * console.log(results.map((r) => r.spaceId))
458
- * ```
459
- */
460
- public abstract spaces(
461
- items: PluginUpdateSpaceItem[],
462
- ): PluginApiReturn<PluginSpaceUpdateResult[]>
463
-
464
- /**
465
- * Update the parameters of one or more staircases — riser, tread, width,
466
- * depth, step count, total height, well size, landing depth, base offset,
467
- * and the two height locks — in one undoable operation. Sparse: only the
468
- * fields you provide change. The paired read is
469
- * `design.query.getStaircaseParams`.
470
- *
471
- * All dimensions are in **engine units** (the same convention as
472
- * `design.doors.setWidth`); `steps` is a unitless integer count. The height,
473
- * riser, and step count are **coupled** — `staircaseHeight = steps × riser` —
474
- * and the two lock booleans govern which value recomputes when another
475
- * changes. With `isStoreyHeightUnlocked` false the staircase height follows
476
- * the storey height; unlocking it also unlocks the staircase height. Setting
477
- * `steps` recomputes `riser` (or the height, once the height is unlocked),
478
- * and setting `riser` recomputes `steps`/height in turn.
479
- *
480
- * Presets-only: this edits the parametric fields of a preset staircase; it
481
- * does not change the staircase's type/preset (there is no in-place re-type).
482
- * Every edit rebuilds the staircase geometry, so the call is async.
483
- *
484
- * Throws when a handle is not a staircase, when a staircase is locked, or
485
- * when a value falls outside the engine's accepted range (RPC rejects).
486
- *
487
- * @param staircases - the staircases to update
488
- * @param params - sparse {@linkcode PluginStaircaseParamUpdates} (at least one field)
489
- * @returns the affected staircases
490
- *
491
- * @examplePrompt Set the riser of this staircase to 165mm
492
- * @examplePrompt Change all the stairs to 14 steps
493
- * @examplePrompt Make the selected staircase 1200 wide with a 280 tread
494
- * @examplePrompt Widen every staircase on level 1 to 1 metre
495
- * @examplePrompt Unlock the height and set this stair to 3200 tall
496
- *
497
- * # Example
498
- * ```ts
499
- * // Give every ground-floor staircase a common riser + tread (engine units)
500
- * const stairs = await snaptrude.design.query.listStaircases({ storeys: [1] })
501
- * const { affected } = await snaptrude.design.update.staircase(stairs, {
502
- * riser: 165,
503
- * tread: 280,
504
- * })
505
- * console.log("updated", affected.length, "staircases")
506
- * ```
507
- */
508
- public abstract staircase(
509
- staircases: ComponentHandle[],
510
- params: PluginStaircaseParamUpdates,
511
- ): PluginApiReturn<PluginDesignChangeResult>
512
-
513
- /**
514
- * Edit a parametric mass by patching its creation recipe and rebuilding the
515
- * solid in place — the mass keeps its id, storey, materials, position and
516
- * linked copies; only the geometry changes. Read the current record with
517
- * `design.query.getParameters` first.
518
- *
519
- * `patch` is a partial of `record.values` — or the whole record you read
520
- * back, edited. A key you leave out is unchanged (that includes
521
- * `operations`). Each key you send replaces that value whole — a scalar, a
522
- * point, a list or a record (`contour`, `profile`, `sections`, `path`,
523
- * `options`, `operations`) alike — so send the complete value you read,
524
- * edited (a partial `options` drops the switches it leaves out);
525
- * `{ operations: [] }` drops every operation. An optional key
526
- * the record does not carry yet may be patched in when the kind allows it:
527
- * a straight extrusion acquires `twistInDegrees` or `topScale`, an
528
- * options-less loft or sweep acquires `options` (`levels` is not one — send
529
- * the angle, not the section count). A patch that changes nothing
530
- * returns the current record and records no edit (no undo step). `kind` and
531
- * `version` are immutable: to change the kind, build a new mass. Keys are the
532
- * record's `values` keys for its `kind` (sphere: `centre`, `radius`;
533
- * cylinder: `base`, `axis`, `radius`, `height`; torus: `centre`, `axis`,
534
- * `majorRadius`, `minorRadius`; …) plus `operations`. To change a fillet
535
- * radius, edit `operations[i].radius` in the record you read and send
536
- * `{ operations }` back. Points and axes are WORLD coordinates in internal
537
- * units, exactly as `getParameters` reports them.
538
- *
539
- * Rebuilds in memory first, then swaps onto the SAME component; every linked
540
- * copy regenerates together. One undo step; saves and syncs to collaborators
541
- * like any geometry edit. Kernel kinds (revolution, sweep, loft, primitives,
542
- * any `operations`) run on OpenCascade — expect a short pause.
543
- *
544
- * @param component - a mass whose `getParameters` is non-null
545
- * @param patch - partial `values` (+ optional `operations`), or the full edited record
546
- * @returns the complete post-edit record, world coordinates (what
547
- * `getParameters` now returns)
548
- * @throws PRECONDITION_FAILED when the component is not a mass (a family
549
- * instance: use `design.family.setParameters`).
550
- * Also PRECONDITION_FAILED when the mass carries no live recipe — built
551
- * from a boolean, split, `brepFromFaces`/`brepFromMesh` or drawn; edited
552
- * free-form since (push/pull, move face, boolean); or scaled with the
553
- * transform tool. `getParameters` returns `null` for exactly these.
554
- * Do not retry — rebuild it with a recipe constructor and say so.
555
- * A scaled mass is the exception: undo the scale and call again.
556
- * Also PRECONDITION_FAILED when the mass (or a linked copy) is **locked**
557
- * (the message says so). Locking is invisible to `getParameters` — a
558
- * locked parametric mass still reads back a full record. Unlock it (and
559
- * every linked copy) and call again; never rebuild it, the recipe is intact.
560
- * Also PRECONDITION_FAILED when an `operations` entry's edge/face selectors
561
- * no longer resolve on the rebuilt base (the message names the operation
562
- * and edge): re-select the edges on the new shape and re-apply that
563
- * operation.
564
- * Also PRECONDITION_FAILED when the edit is the first twist or taper — or
565
- * the straightening — of an extrusion that carries a shell, fillet or
566
- * chamfer (the kernel numbers its faces differently): send the twist/taper
567
- * keys with `operations: []` first (the message names them), re-select on
568
- * the new shape, then send `{ operations }` back.
569
- * @throws VALIDATION for a key the kind does not have (the message lists the
570
- * kind's keys), `kind` or `version` in a partial patch, or a value outside
571
- * the constructor's own rules (torus tube ≥ ring, revolution angle ∉
572
- * (0, 360], a profile crossing its axis, a spline with fewer than
573
- * degree + 1 poles).
574
- * @throws OPERATION_FAILED when the kernel cannot build the edited recipe;
575
- * the model is left unchanged.
576
- *
577
- * @examplePrompt Make the dome's radius 8 metres
578
- * @examplePrompt Raise the podium to 5 m
579
- * @examplePrompt Widen the vase — move its profile points 20 cm outward
580
- * @examplePrompt Change the fillet on the rounded column to 300 mm
581
- *
582
- * # Example
583
- * ```ts
584
- * // { kind: "sphere", values: { centre, radius: 19.685 }, … }
585
- * const before = await snaptrude.design.query.getParameters(dome)
586
- * const after = await snaptrude.design.update.parameters(dome, { radius: 31.496 }) // 8 m in internal units
587
- *
588
- * // Fillet radius on a recorded result: read → edit → send the array back
589
- * const rec = await snaptrude.design.query.getParameters(column)
590
- * const operations = rec?.operations ?? []
591
- * if (operations[0]?.op === "fillet") {
592
- * operations[0].radius = 1.181
593
- * await snaptrude.design.update.parameters(column, { operations })
594
- * }
595
- * ```
596
- */
597
- public abstract parameters(
598
- component: ComponentHandle,
599
- patch: MassParametersPatch,
600
- ): PluginApiReturn<MassParametersRecord>
601
-
602
- /**
603
- * Edit a **wall** — its thickness and/or height (the same command the
604
- * properties panel's Thickness/Height fields drive) and/or its **wall type**
605
- * (the panel's Wall Types dropdown: converts the wall to another type,
606
- * rebuilding its layer stack and materials). Sparse: only the fields you
607
- * provide change. Undoable; the geometry rebuilds and any dimension lines
608
- * refresh. Stacked-wall companions and hosted fenestration are handled
609
- * exactly as the panel does.
610
- *
611
- * Thickness/height are in **engine units** (the same convention as
612
- * `design.doors.setWidth`) and must be `> 0`. Shrinking a wall past its
613
- * openings is rejected by the engine (the wall is skipped with no change).
614
- * `type` accepts a type name from `design.types.list("wall")` (or its
615
- * `wall:`-prefixed id); when combined with dimensions, the type conversion
616
- * is applied first so an explicit thickness/height wins over the type's
617
- * defaults. Re-applying the wall's current type is a no-op.
618
- *
619
- * Throws when the handle is not a wall, when the wall is locked, when the
620
- * handle is gone/forged, when `type` names an unknown wall type, when the
621
- * engine rejects the conversion (e.g. a curved wall thinner than the new
622
- * type's minimum thickness), or when no field is provided.
623
- *
624
- * A type conversion (and a thickness edit on a parametric wall) REBUILDS the
625
- * wall, replacing it with a new component: the input handle is then dead
626
- * (further calls throw HANDLE_INVALID) and `affected[0]` carries the
627
- * replacement's handle — use it for any follow-up calls.
628
- *
629
- * @param component - The wall to edit
630
- * @param updates - Sparse {@linkcode PluginWallUpdates} (at least one field)
631
- * @returns the affected wall — after a rebuild, the REPLACEMENT wall's handle
632
- *
633
- * @examplePrompt Make this wall 230mm thick
634
- * @examplePrompt Set the selected wall's height to 3 metres
635
- * @examplePrompt Thicken every exterior wall to 300
636
- * @examplePrompt Change this wall to a Glass Wall
637
- * @examplePrompt Convert all the partitions on this floor to Brick Wall
638
- *
639
- * # Example
640
- * ```ts
641
- * // Convert every wall on storey 1 to the first listed wall type
642
- * const [wallType] = await snaptrude.design.types.list("wall")
643
- * const walls = await snaptrude.design.query.listWalls({ storeys: [1] })
644
- * for (const wall of walls) {
645
- * await snaptrude.design.update.wall(wall, { type: wallType.id })
646
- * }
647
- * ```
648
- */
649
- public abstract wall(
650
- component: ComponentHandle,
651
- updates: PluginWallUpdates,
652
- ): PluginApiReturn<PluginDesignChangeResult>
653
-
654
- /**
655
- * Edit a **slab's** thickness and/or overhang — the same commands the
656
- * properties panel's Thickness field and Overhang slider drive. Sparse: only
657
- * the fields you provide change. Undoable; the slab rebuilds and dimension
658
- * lines refresh. Both values are in **engine units** (the same convention as
659
- * `design.doors.setWidth`); thickness must be `> 0`, overhang is an absolute
660
- * offset (negative insets the slab) and re-applying the current overhang is
661
- * a no-op.
662
- *
663
- * Throws when the handle is not a slab, when it is locked, when the handle is
664
- * gone/forged, when `overhang` is set on a slab that has no overhang (plinth /
665
- * basement slabs, or overhang disabled), or when no field is provided.
666
- *
667
- * @param component - The slab to edit
668
- * @param updates - Sparse {@linkcode PluginSlabUpdates} (at least one field)
669
- * @returns the affected slab
670
- *
671
- * @examplePrompt Set this slab's thickness to 250mm
672
- * @examplePrompt Make the floor slab 300 thick
673
- * @examplePrompt Give this slab a 200 overhang
674
- * @examplePrompt Remove the overhang on every slab on level 2
675
- *
676
- * # Example
677
- * ```ts
678
- * const [slab] = await snaptrude.design.query.listSlabs()
679
- * await snaptrude.design.update.slab(slab, { thickness: 250, overhang: 200 })
680
- * ```
681
- */
682
- public abstract slab(
683
- component: ComponentHandle,
684
- updates: PluginSlabUpdates,
685
- ): PluginApiReturn<PluginDesignChangeResult>
686
-
687
- /**
688
- * Edit a **floor's** thickness — the slab-family updater for floors (shares
689
- * the slab thickness command). Undoable. Thickness is in **engine units**
690
- * (the `design.doors.setWidth` convention) and must be `> 0`. Floors have no
691
- * Overhang control, so `overhang` is rejected with `PRECONDITION_FAILED`.
692
- *
693
- * Throws when the handle is not a floor, when it is locked, when the handle
694
- * is gone/forged, when `overhang` is provided, or when no field is provided.
695
- *
696
- * @param component - The floor to edit
697
- * @param updates - {@linkcode PluginSlabUpdates} (a `thickness`)
698
- * @returns the affected floor
699
- *
700
- * @examplePrompt Make this floor 200mm thick
701
- * @examplePrompt Set every floor to a 250 thickness
702
- * @examplePrompt Thicken the ground floor slab to 300
703
- *
704
- * # Example
705
- * ```ts
706
- * const [floor] = await snaptrude.design.query.listFloors()
707
- * await snaptrude.design.update.floor(floor, { thickness: 200 })
708
- * ```
709
- */
710
- public abstract floor(
711
- component: ComponentHandle,
712
- updates: PluginSlabUpdates,
713
- ): PluginApiReturn<PluginDesignChangeResult>
714
-
715
- /**
716
- * Edit a **roof's** thickness and/or overhang — the same commands the roof
717
- * panel's Thickness field and Overhang slider drive (shares the slab-family
718
- * machinery). Sparse: only the fields you provide change. Undoable. Both
719
- * values are in **engine units** (the `design.doors.setWidth` convention);
720
- * thickness must be `> 0`, overhang is an absolute offset (negative insets
721
- * the roof) and re-applying the current overhang is a no-op.
722
- *
723
- * Throws when the handle is not a roof, when it is locked, when the handle is
724
- * gone/forged, when `overhang` is set on a roof whose overhang is disabled,
725
- * or when no field is provided.
726
- *
727
- * @param component - The roof to edit
728
- * @param updates - Sparse {@linkcode PluginSlabUpdates} (at least one field)
729
- * @returns the affected roof
730
- *
731
- * @examplePrompt Make this roof 300mm thick
732
- * @examplePrompt Set the roof slab thickness to 250
733
- * @examplePrompt Give the roof a 600 overhang
734
- * @examplePrompt Pull the roof overhang back to 0
735
- *
736
- * # Example
737
- * ```ts
738
- * const [roof] = await snaptrude.design.query.listRoofs()
739
- * await snaptrude.design.update.roof(roof, { overhang: 600 })
740
- * ```
741
- */
742
- public abstract roof(
743
- component: ComponentHandle,
744
- updates: PluginSlabUpdates,
745
- ): PluginApiReturn<PluginDesignChangeResult>
746
-
747
- /**
748
- * Edit a **ceiling's** thickness — the slab-family updater for ceilings
749
- * (shares the slab thickness command). Undoable. Thickness is in **engine
750
- * units** (the `design.doors.setWidth` convention) and must be `> 0`.
751
- * Ceilings have no Overhang control, so `overhang` is rejected with
752
- * `PRECONDITION_FAILED`.
753
- *
754
- * Throws when the handle is not a ceiling, when it is locked, when the handle
755
- * is gone/forged, when `overhang` is provided, or when no field is provided.
756
- *
757
- * @param component - The ceiling to edit
758
- * @param updates - {@linkcode PluginSlabUpdates} (a `thickness`)
759
- * @returns the affected ceiling
760
- *
761
- * @examplePrompt Make this ceiling 150mm thick
762
- * @examplePrompt Set every ceiling to a 100 thickness
763
- * @examplePrompt Thicken the ceiling to 200
764
- *
765
- * # Example
766
- * ```ts
767
- * const [ceiling] = await snaptrude.design.query.listCeilings()
768
- * await snaptrude.design.update.ceiling(ceiling, { thickness: 150 })
769
- * ```
770
- */
771
- public abstract ceiling(
772
- component: ComponentHandle,
773
- updates: PluginSlabUpdates,
774
- ): PluginApiReturn<PluginDesignChangeResult>
775
-
776
- /**
777
- * Set a component's **absolute world elevation** — moves it vertically so its
778
- * base (the bounding-box bottom, world Y) lands at `elevation`. Composed over
779
- * the canonical command-backed move machinery, so it is undoable and persisted
780
- * exactly like dragging the element up or down; per-type invariants (e.g. a
781
- * staircase's base offset) stay in sync. `elevation` is in **engine units**
782
- * (the `design.doors.setWidth` convention). Re-applying the current elevation
783
- * is a no-op.
784
- *
785
- * Throws when the component is locked or the handle is gone/forged.
786
- *
787
- * @param component - The component to move vertically
788
- * @param elevation - Target world elevation of the component's base (engine units)
789
- * @returns the affected component
790
- *
791
- * @examplePrompt Set this element's elevation to 3000
792
- * @examplePrompt Raise this mass so its base sits at 3 metres
793
- * @examplePrompt Drop the selected slab to elevation 0
794
- * @examplePrompt Put this staircase's base at 3200
795
- *
796
- * # Example
797
- * ```ts
798
- * // Sit the roof slab exactly at the top of a 3m storey
799
- * const [roof] = await snaptrude.design.query.listRoofs()
800
- * await snaptrude.design.update.setElevation(roof, 3000)
801
- * ```
802
- */
803
- public abstract setElevation(
804
- component: ComponentHandle,
805
- elevation: number,
806
- ): PluginApiReturn<PluginDesignChangeResult>
807
-
808
- /**
809
- * Set a component's **base offset** — its vertical distance above its own
810
- * storey's base level. Moves the component so its base lands at
811
- * `storeyBase + offset`, composed over the same command-backed move machinery
812
- * as {@linkcode setElevation} (undoable, persisted, per-type invariants kept
813
- * in sync). Use this instead of {@linkcode setElevation} when you want an
814
- * offset relative to the storey rather than an absolute world height.
815
- * `offset` is in **engine units** (the `design.doors.setWidth` convention);
816
- * `0` sits the base flush with the storey base. Re-applying the current
817
- * offset is a no-op.
818
- *
819
- * Throws when the component is locked, when it has no storey to offset from,
820
- * or when the handle is gone/forged.
821
- *
822
- * @param component - The component to offset
823
- * @param offset - Distance of the component's base above its storey base (engine units)
824
- * @returns the affected component
825
- *
826
- * @examplePrompt Lift this element 300 above its floor
827
- * @examplePrompt Set this staircase's base offset to 0
828
- * @examplePrompt Raise the selected mass 500 off the storey base
829
- * @examplePrompt Offset this slab 150 above the floor level
830
- *
831
- * # Example
832
- * ```ts
833
- * const [stair] = await snaptrude.design.query.listStaircases()
834
- * await snaptrude.design.update.setBaseOffset(stair, 0)
835
- * ```
836
- */
837
- public abstract setBaseOffset(
838
- component: ComponentHandle,
839
- offset: number,
840
- ): PluginApiReturn<PluginDesignChangeResult>
841
-
842
- /**
843
- * Reassign one or more components to another **storey** — the same operation
844
- * as the properties panel's Story dropdown, and it works across element kinds
845
- * exactly like the panel (walls, slabs, masses, furniture, staircases, ...).
846
- * Each component is moved vertically to the target storey's level (per-kind
847
- * placement rules — e.g. a slab sits at the storey base, a wall on it) and
848
- * its storey assignment, children, and dimension lines update together as
849
- * **one undoable operation**.
850
- *
851
- * `storey` is an integer storey number in the `entity.story` convention
852
- * (`1` is the ground storey, `-1` the first basement; `0` does not exist)
853
- * and **must already exist** in each component's building — this call never
854
- * creates storeys (use `core.storeys.create` first). Components already on
855
- * the target storey are skipped (no-op) and are not echoed in `affected`.
856
- *
857
- * Throws when the target storey does not exist, when any component is
858
- * locked, or when a handle is gone/forged — all before anything moves
859
- * (all-or-nothing).
860
- *
861
- * @param components - The components to reassign
862
- * @param storey - Target storey number (integer, `entity.story` convention)
863
- * @returns the components actually reassigned
864
- *
865
- * @examplePrompt Move this sofa to storey 2
866
- * @examplePrompt Put the selected walls on the first basement level
867
- * @examplePrompt Send everything on this storey up one level
868
- * @examplePrompt Reassign these slabs to storey 3
869
- *
870
- * # Example
871
- * ```ts
872
- * // Move the current selection up to storey 2
873
- * const selection = await snaptrude.design.selection.get()
874
- * const { affected } = await snaptrude.design.update.setStorey(selection, 2)
875
- * console.log("moved", affected.length, "components")
876
- * ```
877
- */
878
- public abstract setStorey(
879
- components: ComponentHandle[],
880
- storey: number,
881
- ): PluginApiReturn<PluginDesignChangeResult>
882
-
883
- /**
884
- * Change the **object type** of one or more masses — turn a mass into a real
885
- * wall, roof, slab or floor: the properties panel's Object Type dropdown,
886
- * headless. The geometry is kept exactly as it is (a wedge stays a wedge;
887
- * nothing is flattened to a default thickness), so a sloped mass built with
888
- * `design.create.massFromBrep` becomes a sloped roof or a raked wall. The
889
- * component keeps its handle, label and storey; it gains the target type's
890
- * construction layers and properties, and the type's default material when
891
- * the mass still wore the default. One undoable operation for the whole
892
- * list; undo turns them back into masses.
893
- *
894
- * Instances convert as a family: passing one instance (or the source) of an
895
- * instanced mass converts its source and every sibling; `affected` echoes
896
- * every visible member (the hidden instancing template converts too but is
897
- * not listed). A converted component cannot be converted again (same rule
898
- * as the panel). A converted wall has no parametric centerline: it hosts
899
- * doors and windows and exports, but neighbours do not re-mitre against it.
900
- *
901
- * Throws before anything changes (all-or-nothing) when a component is not a
902
- * mass, is a plinth, is locked or in a locked bucket, already had its type
903
- * changed, or when a handle is gone/forged.
904
- *
905
- * @param components - The masses to convert
906
- * @param objectType - Target type: `"wall"`, `"roof"`, `"slab"` or `"floor"`
907
- * (the `design.query.getEntityType` vocabulary)
908
- * @returns the visible components converted — the same handles, plus any
909
- * instance siblings pulled in by family expansion
910
- *
911
- * @examplePrompt Convert this mass into a wall
912
- * @examplePrompt Turn the sloped mass into a roof
913
- * @examplePrompt Make these masses real slabs
914
- * @examplePrompt Change the object type of the selection to wall
915
- * @examplePrompt Convert the wedge I built into a raked wall
916
- * @examplePrompt Turn this mass into a floor
917
- *
918
- * # Example
919
- * ```ts
920
- * const brep = await snaptrude.core.geom.create.brepFromFaces(wedgeFaces)
921
- * const wedge = await snaptrude.design.create.massFromBrep(brep, "Gable end")
922
- * const { affected } = await snaptrude.design.update.setObjectType([wedge], "wall")
923
- * console.log(await snaptrude.design.query.getEntityType(affected[0])) // "wall"
924
- * ```
925
- */
926
- public abstract setObjectType(
927
- components: ComponentHandle[],
928
- objectType: PluginObjectType,
929
- ): PluginApiReturn<PluginDesignChangeResult>
930
-
931
- /**
932
- * Update an existing parametric buildable envelope — regenerate the zoning
933
- * volume from a revised site polygon, setbacks, vertical cap, or FAR and
934
- * lot-coverage limits, keeping the same envelope id. Canonical home of the
935
- * update formerly at `entity.buildableEnvelope.update` (now deprecated).
936
- *
937
- * @param buildableEnvelopeId - Existing envelope ID to update; non-empty and
938
- * must match an existing envelope on the canvas.
939
- * @param sitePolygon - Site polygon vertices in `lengthUnit`; minimum 3 vertices.
940
- * @param lengthUnit - Unit used by all length fields (`"ft" | "m"`).
941
- * @param setbacks - Setback profile, ground tier first; minimum 1 tier.
942
- * @param verticalCap - Maximum height or floor count.
943
- * @param floorToFloor - Required floor-to-floor height in `lengthUnit`.
944
- * @param farRatio - Optional FAR value, positive when provided.
945
- * @param lotCoverageMaxPct - Optional lot coverage cap, `0..100`.
946
- * @returns A {@linkcode PluginBuildableEnvelopeUpdateResult} with the
947
- * `buildableEnvelopeId` of the updated envelope.
948
- * @throws If validation fails, the envelope does not exist, or generation
949
- * produces no renderable geometry.
950
- *
951
- * @examplePrompt Change the envelope's height limit to 175 feet
952
- * @examplePrompt Update the setbacks on the buildable envelope
953
- * @examplePrompt Regenerate the zoning envelope with a new site boundary
954
- * @examplePrompt Increase the FAR on the buildable volume
955
- *
956
- * # Example
957
- * ```ts
958
- * const { buildableEnvelopeId } = await snaptrude.design.update.buildableEnvelope(
959
- * existingId,
960
- * [
961
- * { x: 0, z: 0 },
962
- * { x: 100, z: 0 },
963
- * { x: 100, z: 80 },
964
- * { x: 0, z: 80 },
965
- * ],
966
- * "ft",
967
- * [{ aboveHeight: 0, front: 10, side: 5, rear: 10 }],
968
- * { kind: "max_height", maxHeight: 175 },
969
- * 12,
970
- * )
971
- * ```
972
- */
973
- public abstract buildableEnvelope(
974
- buildableEnvelopeId: string,
975
- sitePolygon: PluginBuildableEnvelopePolygonVertex[],
976
- lengthUnit: "ft" | "m",
977
- setbacks: PluginBuildableEnvelopeSetbackTier[],
978
- verticalCap: PluginBuildableEnvelopeVerticalCap,
979
- floorToFloor: number,
980
- farRatio?: number,
981
- lotCoverageMaxPct?: number,
982
- ): PluginApiReturn<PluginBuildableEnvelopeUpdateResult>
983
- }
1
+ import * as z from "zod"
2
+ import { PluginApiReturn } from "../../../types"
3
+ import { ComponentHandle, ProfileHandle } from "../../../handles"
4
+ import {
5
+ PluginSpaceType,
6
+ PluginMassType,
7
+ PluginDepartmentId,
8
+ PluginAreaClass,
9
+ PluginSpaceUpdateResult,
10
+ } from "../../entity/space"
11
+ import { PluginDesignChangeResult } from "../lock"
12
+ import {
13
+ MassParametersPatch,
14
+ type MassParametersRecord,
15
+ } from "../../../massParameters"
16
+ import {
17
+ PluginBuildableEnvelopePolygonVertex,
18
+ PluginBuildableEnvelopeSetbackTier,
19
+ PluginBuildableEnvelopeVerticalCap,
20
+ PluginBuildableEnvelopeUpdateResult,
21
+ PluginSiteEdgePatch,
22
+ PluginDesignUpdateSiteEdgeResult,
23
+ } from "../../entity/buildableEnvelope"
24
+ import { PluginGridStyleOptions } from "../create/grids"
25
+ import { PluginEntityType } from "../query"
26
+ import { PluginWallType } from "../types"
27
+ import {
28
+ PluginConstructionType,
29
+ PluginFamilyValues,
30
+ type PluginFamilyUpdates,
31
+ } from "../catalog"
32
+
33
+ /**
34
+ * Sparse property updates for a space. Only provided fields change.
35
+ *
36
+ * | Property | Type | Description |
37
+ * |---|---|---|
38
+ * | `room_type` | `string`? | Room label |
39
+ * | `massType` | `PluginMassType`? | Mass type |
40
+ * | `spaceType` | `PluginSpaceType`? | Space type |
41
+ * | `areaClass` | `PluginAreaClass`? | Area class (NET/GROSS/EXCLUDED) |
42
+ * | `departmentId` | `PluginDepartmentId`? | Department |
43
+ */
44
+ export const PluginSpacePropertyUpdates = z.object({
45
+ room_type: z.string().optional(),
46
+ massType: PluginMassType.optional(),
47
+ spaceType: PluginSpaceType.optional(),
48
+ areaClass: PluginAreaClass.optional(),
49
+ departmentId: PluginDepartmentId.optional(),
50
+ })
51
+ export type PluginSpacePropertyUpdates = z.infer<
52
+ typeof PluginSpacePropertyUpdates
53
+ >
54
+
55
+ /** Geometry replacement for a space — re-extrude a profile. */
56
+ export const PluginSpaceGeometryUpdate = z.object({
57
+ profile: ProfileHandle,
58
+ extrudeHeight: z.number(),
59
+ })
60
+ export type PluginSpaceGeometryUpdate = z.infer<
61
+ typeof PluginSpaceGeometryUpdate
62
+ >
63
+
64
+ /** One space to update via {@linkcode PluginDesignUpdateApi.spaces}. */
65
+ export const PluginUpdateSpaceItem = z.object({
66
+ space: ComponentHandle,
67
+ properties: PluginSpacePropertyUpdates.optional(),
68
+ geometry: PluginSpaceGeometryUpdate.optional(),
69
+ })
70
+ export type PluginUpdateSpaceItem = z.infer<typeof PluginUpdateSpaceItem>
71
+
72
+ /**
73
+ * Wire schema for {@linkcode PluginDesignUpdateApi.setLabel}.
74
+ *
75
+ * | Property | Type | Description |
76
+ * |---|---|---|
77
+ * | `component` | `ComponentHandle` | The component to relabel |
78
+ * | `label` | `string` | New label (non-empty after trimming) |
79
+ */
80
+ export const PluginDesignSetLabelArgs = z.object({
81
+ component: ComponentHandle,
82
+ label: z.string().trim().min(1),
83
+ })
84
+ export type PluginDesignSetLabelArgs = z.infer<typeof PluginDesignSetLabelArgs>
85
+
86
+ /**
87
+ * Sparse parametric updates for a staircase (all fields optional; at least one
88
+ * required). Only the fields you provide change. Consumed by
89
+ * {@linkcode PluginDesignUpdateApi.staircase}; the paired read is
90
+ * `design.query.getStaircaseParams`.
91
+ *
92
+ * All dimensions are in **engine units** (the same convention as
93
+ * `design.doors.setWidth`); `steps` is a unitless integer count. Height, riser,
94
+ * and step count are **coupled** (`staircaseHeight = steps × riser`); the two
95
+ * lock booleans govern which value recomputes — see the method JSDoc.
96
+ *
97
+ * | Property | Type | Description |
98
+ * |---|---|---|
99
+ * | `isStoreyHeightUnlocked` | `boolean`? | Unlock the staircase height from the storey height |
100
+ * | `isStaircaseHeightUnlocked` | `boolean`? | Unlock the height from `steps × riser` |
101
+ * | `storeyHeight` | `number`? | Storey height the stair sits within (engine units) |
102
+ * | `staircaseHeight` | `number`? | Total rise (engine units) |
103
+ * | `steps` | `number`? | Step count (unitless integer) |
104
+ * | `riser` | `number`? | Riser height (engine units) |
105
+ * | `tread` | `number`? | Tread depth (engine units) |
106
+ * | `width` | `number`? | Flight width (engine units) |
107
+ * | `depth` | `number`? | Slab/waist thickness (engine units) |
108
+ * | `wellSize` | `number`? | Stairwell gap (dogLegged / openWell / square) |
109
+ * | `flightStartDistAfterTurn` | `number`? | Flight offset after a turn (dogLegged / lShaped / square) |
110
+ * | `landingWidth` | `number`? | Landing depth (straight / dogLegged) |
111
+ * | `baseOffset` | `number`? | Vertical offset from the storey base (engine units) |
112
+ */
113
+ export const PluginStaircaseParamUpdates = z
114
+ .object({
115
+ isStoreyHeightUnlocked: z.boolean().optional(),
116
+ isStaircaseHeightUnlocked: z.boolean().optional(),
117
+ storeyHeight: z.number().optional(),
118
+ staircaseHeight: z.number().optional(),
119
+ steps: z.number().int().optional(),
120
+ riser: z.number().optional(),
121
+ tread: z.number().optional(),
122
+ width: z.number().optional(),
123
+ depth: z.number().optional(),
124
+ wellSize: z.number().optional(),
125
+ flightStartDistAfterTurn: z.number().optional(),
126
+ landingWidth: z.number().optional(),
127
+ baseOffset: z.number().optional(),
128
+ })
129
+ .refine((p) => Object.keys(p).length > 0, {
130
+ message:
131
+ "PluginStaircaseParamUpdates requires at least one field to change",
132
+ })
133
+ export type PluginStaircaseParamUpdates = z.infer<
134
+ typeof PluginStaircaseParamUpdates
135
+ >
136
+
137
+ /**
138
+ * Arguments for {@linkcode PluginDesignUpdateApi.staircase}.
139
+ *
140
+ * | Property | Type | Description |
141
+ * |---|---|---|
142
+ * | `staircases` | {@linkcode ComponentHandle}`[]` | The staircases to update |
143
+ * | `params` | {@linkcode PluginStaircaseParamUpdates} | Sparse parametric changes (≥ 1 field) |
144
+ */
145
+ export const PluginDesignUpdateStaircaseArgs = z.object({
146
+ staircases: z.array(ComponentHandle),
147
+ params: PluginStaircaseParamUpdates,
148
+ })
149
+ export type PluginDesignUpdateStaircaseArgs = z.infer<
150
+ typeof PluginDesignUpdateStaircaseArgs
151
+ >
152
+
153
+ /**
154
+ * Arguments for {@linkcode PluginDesignUpdateApi.parameters}.
155
+ *
156
+ * | Property | Type | Description |
157
+ * |---|---|---|
158
+ * | `component` | {@linkcode ComponentHandle} | The parametric mass to edit |
159
+ * | `patch` | {@linkcode MassParametersPatch} | Partial `values` (+ optional `operations`), or the full edited record |
160
+ */
161
+ export const PluginDesignUpdateParametersArgs = z.object({
162
+ component: ComponentHandle,
163
+ patch: MassParametersPatch,
164
+ })
165
+ export type PluginDesignUpdateParametersArgs = z.infer<
166
+ typeof PluginDesignUpdateParametersArgs
167
+ >
168
+
169
+ /**
170
+ * Sparse edits for a wall (all optional; at least one required). Only the
171
+ * fields you provide change. Dimensions are in **engine units** (the same
172
+ * convention as `design.doors.setWidth`). Consumed by
173
+ * {@linkcode PluginDesignUpdateApi.wall}.
174
+ *
175
+ * | Property | Type | Description |
176
+ * |---|---|---|
177
+ * | `thickness` | `number`? | Wall thickness, engine units (> 0) |
178
+ * | `height` | `number`? | Wall height, engine units (> 0) |
179
+ * | `type` | {@linkcode PluginWallType}? | Legacy name/id or exact authorized native `{ id, version }` pin from `design.types.list("wall")` |
180
+ */
181
+ export const PluginWallUpdates = z
182
+ .object({
183
+ thickness: z.number().positive().optional(),
184
+ height: z.number().positive().optional(),
185
+ type: PluginWallType.optional(),
186
+ values: PluginFamilyValues.optional(),
187
+ })
188
+ .refine((p) => Object.keys(p).length > 0, {
189
+ message: "PluginWallUpdates requires at least one field to change",
190
+ })
191
+ export type PluginWallUpdates = z.infer<typeof PluginWallUpdates>
192
+
193
+ /**
194
+ * Sparse dimension edits for a slab-family element (slab / floor / roof /
195
+ * ceiling). All values are engine units, the same convention as
196
+ * `design.doors.setWidth`. Consumed by {@linkcode PluginDesignUpdateApi.slab},
197
+ * `floor`, `roof`, and `ceiling`.
198
+ *
199
+ * `overhang` is only available on **slabs and roofs** (the panel's Overhang
200
+ * slider) — floors and ceilings reject it with `PRECONDITION_FAILED`.
201
+ *
202
+ * | Property | Type | Description |
203
+ * |---|---|---|
204
+ * | `thickness` | `number`? | Element thickness, engine units (> 0) |
205
+ * | `overhang` | `number`? | Absolute overhang offset, engine units (slab / roof only) |
206
+ */
207
+ export const PluginSlabUpdates = z
208
+ .object({
209
+ thickness: z.number().positive().optional(),
210
+ overhang: z.number().optional(),
211
+ type: PluginConstructionType.optional(),
212
+ values: PluginFamilyValues.optional(),
213
+ })
214
+ .refine((p) => Object.keys(p).length > 0, {
215
+ message: "PluginSlabUpdates requires at least one field to change",
216
+ })
217
+ export type PluginSlabUpdates = z.infer<typeof PluginSlabUpdates>
218
+
219
+ /** Arguments for {@linkcode PluginDesignUpdateApi.wall}. */
220
+ export const PluginDesignUpdateWallArgs = z.object({
221
+ component: ComponentHandle,
222
+ updates: PluginWallUpdates,
223
+ })
224
+ export type PluginDesignUpdateWallArgs = z.infer<
225
+ typeof PluginDesignUpdateWallArgs
226
+ >
227
+
228
+ /**
229
+ * Arguments for the slab-family updaters —
230
+ * {@linkcode PluginDesignUpdateApi.slab}, `floor`, `roof`, `ceiling`.
231
+ */
232
+ export const PluginDesignUpdateSlabArgs = z.object({
233
+ component: ComponentHandle,
234
+ updates: PluginSlabUpdates,
235
+ })
236
+ export type PluginDesignUpdateSlabArgs = z.infer<
237
+ typeof PluginDesignUpdateSlabArgs
238
+ >
239
+
240
+ /** Arguments for {@linkcode PluginDesignUpdateApi.setElevation}. */
241
+ export const PluginDesignSetElevationArgs = z.object({
242
+ component: ComponentHandle,
243
+ elevation: z.number(),
244
+ })
245
+ export type PluginDesignSetElevationArgs = z.infer<
246
+ typeof PluginDesignSetElevationArgs
247
+ >
248
+
249
+ /** Arguments for {@linkcode PluginDesignUpdateApi.setBaseOffset}. */
250
+ export const PluginDesignSetBaseOffsetArgs = z.object({
251
+ component: ComponentHandle,
252
+ offset: z.number(),
253
+ })
254
+ export type PluginDesignSetBaseOffsetArgs = z.infer<
255
+ typeof PluginDesignSetBaseOffsetArgs
256
+ >
257
+
258
+ /**
259
+ * Arguments for {@linkcode PluginDesignUpdateApi.setStorey}.
260
+ *
261
+ * | Property | Type | Description |
262
+ * |---|---|---|
263
+ * | `components` | {@linkcode ComponentHandle}`[]` | The components to reassign |
264
+ * | `storey` | `number` | Target integer storey number (`entity.story` convention; must exist) |
265
+ */
266
+ export const PluginDesignSetStoreyArgs = z.object({
267
+ components: z.array(ComponentHandle),
268
+ storey: z.number().int(),
269
+ })
270
+ export type PluginDesignSetStoreyArgs = z.infer<
271
+ typeof PluginDesignSetStoreyArgs
272
+ >
273
+
274
+ /**
275
+ * Object types {@linkcode PluginDesignUpdateApi.setObjectType} can turn a mass
276
+ * into — the `design.query` `type` vocabulary.
277
+ */
278
+ export const PluginObjectType = PluginEntityType.extract([
279
+ "wall",
280
+ "roof",
281
+ "slab",
282
+ "floor",
283
+ ])
284
+ export type PluginObjectType = z.infer<typeof PluginObjectType>
285
+
286
+ /**
287
+ * Arguments for {@linkcode PluginDesignUpdateApi.setObjectType}.
288
+ *
289
+ * | Property | Type | Description |
290
+ * |---|---|---|
291
+ * | `components` | {@linkcode ComponentHandle}`[]` | The masses to convert (>= 1) |
292
+ * | `objectType` | {@linkcode PluginObjectType} | Target object type (`"wall"`, `"roof"`, `"slab"`, `"floor"`) |
293
+ */
294
+ export const PluginDesignSetObjectTypeArgs = z.object({
295
+ components: z.array(ComponentHandle).min(1),
296
+ objectType: PluginObjectType,
297
+ })
298
+ export type PluginDesignSetObjectTypeArgs = z.infer<
299
+ typeof PluginDesignSetObjectTypeArgs
300
+ >
301
+
302
+ /**
303
+ * The sparse GROUP-LEVEL patch accepted by {@linkcode PluginDesignUpdateApi.grid}.
304
+ * Only provided fields change. Wing- and line-level updates live on the grids
305
+ * entity family: `design.grids.updateWings` / `design.grids.updateLines`.
306
+ */
307
+ export const PluginGridPatch = z.object({
308
+ angle: z.number().finite().optional(),
309
+ props: PluginGridStyleOptions.optional(),
310
+ storeyVisibility: z
311
+ .array(z.object({ storey: z.number().int(), visible: z.boolean() }))
312
+ .optional(),
313
+ })
314
+ export type PluginGridPatch = z.infer<typeof PluginGridPatch>
315
+
316
+ /** Arguments for {@linkcode PluginDesignUpdateApi.grid}. */
317
+ export const PluginDesignUpdateGridArgs = z.object({
318
+ grid: ComponentHandle,
319
+ patch: PluginGridPatch,
320
+ })
321
+ export type PluginDesignUpdateGridArgs = z.infer<
322
+ typeof PluginDesignUpdateGridArgs
323
+ >
324
+
325
+ /**
326
+ * `snaptrude.design.update` — edit existing scene entities (properties + geometry).
327
+ *
328
+ * Migrated from the removed `entity.space.{update, updateGeometryFromProfile, bulkUpdate}`.
329
+ * Only provided fields change; each call is undoable. Failures throw (RPC rejects).
330
+ *
331
+ * Native family edits accept `type` and/or sparse `values` from `design.catalog`.
332
+ * Length overrides use mm; dimensional arguments use SU. Contours and axes stay unchanged.
333
+ * Shared/type overrides are refused. A thickness argument must agree with the layer values.
334
+ * Family roof overhang is unsupported; use the native slab path for family overhang edits.
335
+ */
336
+ export abstract class PluginDesignUpdateApi {
337
+ constructor() {}
338
+
339
+ /**
340
+ * Set a component's **Label** — the same editable Label field the properties
341
+ * panel shows (doors, windows, furniture, spaces, and every other labelled
342
+ * component). The paired read is `design.query.getLabel`. Undoable; the
343
+ * canvas label text updates immediately.
344
+ *
345
+ * Strictly **per component**: labelling one door of a placed family never
346
+ * touches its sibling doors (the panel's Label edit, by contrast, applies to
347
+ * the whole family). On **legacy masses** without a space type, panel parity
348
+ * applies — a label matching a room-type preset with a height can also
349
+ * adjust the mass's height and material, exactly like typing it in the panel.
350
+ *
351
+ * Throws when the component is locked, the handle is gone/forged, the label
352
+ * is empty, or the component has no mesh to label. Re-applying the current
353
+ * label is a no-op that returns `true` without touching the undo stack.
354
+ *
355
+ * @param component - The component to relabel
356
+ * @param label - New label text (non-empty; leading/trailing whitespace trimmed)
357
+ * @returns `true` once the label is applied
358
+ *
359
+ * @examplePrompt Rename this door to D-101
360
+ * @examplePrompt Number all doors on storey 2 with a D-2-XX tag
361
+ * @examplePrompt Set the selected window's label to W-04
362
+ * @examplePrompt Tag every door with its fire rating
363
+ *
364
+ * # Example
365
+ * ```ts
366
+ * // Door tag numbering: label every door as D-<storey>-<n>, numbered per storey
367
+ * await snaptrude.design.selection.setByFilter({ types: ["door"] })
368
+ * const doors = await snaptrude.design.selection.get()
369
+ * const counts = {}
370
+ * for (const door of doors) {
371
+ * const { storey = 0 } = await snaptrude.design.query.getProperties(door)
372
+ * counts[storey] = (counts[storey] ?? 0) + 1
373
+ * await snaptrude.design.update.setLabel(door, `D-${storey}-${String(counts[storey]).padStart(2, "0")}`)
374
+ * }
375
+ * ```
376
+ */
377
+ public abstract setLabel(
378
+ component: ComponentHandle,
379
+ label: string,
380
+ ): PluginApiReturn<boolean>
381
+
382
+ /**
383
+ * Update a structural grid's **group-level** fields with a sparse patch —
384
+ * default line styling, absolute angle, and per-storey 2D visibility. Only
385
+ * provided fields change. Wing- and line-level updates live on the grids
386
+ * entity family: {@linkcode PluginDesignGridsApi.updateWings} /
387
+ * {@linkcode PluginDesignGridsApi.updateLines}.
388
+ *
389
+ * Every change routes through the exact setters the native panel uses —
390
+ * each changed field commits its own undoable command, identical to editing
391
+ * that field in the panel. `angle` is an absolute target in degrees (CCW
392
+ * about +Y).
393
+ *
394
+ * @param grid - The grid group to update
395
+ * @param patch - The sparse {@linkcode PluginGridPatch}
396
+ * @returns `true` once applied
397
+ *
398
+ * @examplePrompt Rotate the grid to 45 degrees
399
+ * @examplePrompt Make this grid's lines dashed and grey
400
+ * @examplePrompt Hide this grid on storey 3's plan
401
+ *
402
+ * # Example
403
+ * ```ts
404
+ * const [grid] = await snaptrude.design.query.listGrids()
405
+ * await snaptrude.design.update.grid(grid, { angle: 45, props: { style: "DASHED" } })
406
+ * ```
407
+ */
408
+ public abstract grid(
409
+ grid: ComponentHandle,
410
+ patch: PluginGridPatch,
411
+ ): PluginApiReturn<boolean>
412
+
413
+ /**
414
+ * Update a single space (room) — change its properties (room label, space
415
+ * type, mass type, area class, department) and/or replace its geometry by
416
+ * re-extruding a profile to a new height. Sparse and undoable: only the
417
+ * fields you provide change.
418
+ *
419
+ * @param space - The space to update
420
+ * @param options - `properties` (sparse) and/or `geometry` ({ profile, extrudeHeight })
421
+ * @returns the updated space's echoed {@linkcode PluginSpaceUpdateResult}
422
+ * @throws PRECONDITION_FAILED for a void (a hole cut with `design.create.void`): delete it and
423
+ * cut a new one
424
+ *
425
+ * @examplePrompt Rename this room to Office
426
+ * @examplePrompt Change this space's room type to Meeting Room
427
+ * @examplePrompt Set the height of this room to 3 metres
428
+ * @examplePrompt Move this space into the Circulation department
429
+ * @examplePrompt Exclude this room from the area calculations
430
+ *
431
+ * @performance For MORE THAN ONE space, call `design.update.spaces(items[])` — the whole
432
+ * batch is one host round-trip. Looping this single-space updater is N round-trips.
433
+ *
434
+ * # Example
435
+ * ```ts
436
+ * await snaptrude.design.update.space("space-id", { properties: { room_type: "Office" } })
437
+ * ```
438
+ */
439
+ public abstract space(
440
+ space: ComponentHandle,
441
+ options: {
442
+ properties?: PluginSpacePropertyUpdates
443
+ geometry?: PluginSpaceGeometryUpdate
444
+ },
445
+ ): PluginApiReturn<PluginSpaceUpdateResult>
446
+
447
+ /**
448
+ * Update many spaces (rooms) in one undoable operation — the bulk form of
449
+ * {@linkcode space}. Each item pairs a space with the sparse property
450
+ * changes and/or geometry re-extrude to apply; every item is validated up
451
+ * front, then all changes are applied as a single command.
452
+ *
453
+ * Migrated from `entity.space.bulkUpdate`.
454
+ *
455
+ * @param items - One {@linkcode PluginUpdateSpaceItem} per space
456
+ * @returns one {@linkcode PluginSpaceUpdateResult} per item, in input order
457
+ * @throws PRECONDITION_FAILED for a void, like {@linkcode space}
458
+ *
459
+ * @examplePrompt Label all the selected rooms as Bedrooms
460
+ * @examplePrompt Change the room type of these five spaces at once
461
+ * @examplePrompt Set every room on this floor to a 3m height in one go
462
+ * @examplePrompt Bulk-assign these spaces to the Service department
463
+ * @examplePrompt Mark all of these rooms as excluded from area in one update
464
+ *
465
+ * @performance Bulk update — the whole batch is ONE host round-trip. Prefer over looping
466
+ * `design.update.space`: build the `items[]` array first, then make one call.
467
+ *
468
+ * # Example
469
+ * ```ts
470
+ * // Re-label every selected room and mark it NET area — one undoable bulk update
471
+ * const rooms = await snaptrude.design.query.listSpaces({ isSelected: true })
472
+ * const results = await snaptrude.design.update.spaces(
473
+ * rooms.map((space) => ({
474
+ * space,
475
+ * properties: { room_type: "Bedroom", areaClass: "NET" },
476
+ * })),
477
+ * )
478
+ * // One result per item, in input order
479
+ * console.log(results.map((r) => r.spaceId))
480
+ * ```
481
+ */
482
+ public abstract spaces(
483
+ items: PluginUpdateSpaceItem[],
484
+ ): PluginApiReturn<PluginSpaceUpdateResult[]>
485
+
486
+ /**
487
+ * Update the parameters of one or more staircases — riser, tread, width,
488
+ * depth, step count, total height, well size, landing depth, base offset,
489
+ * and the two height locks — in one undoable operation. Sparse: only the
490
+ * fields you provide change. The paired read is
491
+ * `design.query.getStaircaseParams`.
492
+ *
493
+ * All dimensions are in **engine units** (the same convention as
494
+ * `design.doors.setWidth`); `steps` is a unitless integer count. The height,
495
+ * riser, and step count are **coupled** — `staircaseHeight = steps × riser` —
496
+ * and the two lock booleans govern which value recomputes when another
497
+ * changes. With `isStoreyHeightUnlocked` false the staircase height follows
498
+ * the storey height; unlocking it also unlocks the staircase height. Setting
499
+ * `steps` recomputes `riser` (or the height, once the height is unlocked),
500
+ * and setting `riser` recomputes `steps`/height in turn.
501
+ *
502
+ * Presets-only: this edits the parametric fields of a preset staircase; it
503
+ * does not change the staircase's type/preset (there is no in-place re-type).
504
+ * Every edit rebuilds the staircase geometry, so the call is async.
505
+ *
506
+ * Throws when a handle is not a staircase, when a staircase is locked, or
507
+ * when a value falls outside the engine's accepted range (RPC rejects).
508
+ *
509
+ * @param staircases - the staircases to update
510
+ * @param params - sparse {@linkcode PluginStaircaseParamUpdates} (at least one field)
511
+ * @returns the affected staircases
512
+ *
513
+ * @examplePrompt Set the riser of this staircase to 165mm
514
+ * @examplePrompt Change all the stairs to 14 steps
515
+ * @examplePrompt Make the selected staircase 1200 wide with a 280 tread
516
+ * @examplePrompt Widen every staircase on level 1 to 1 metre
517
+ * @examplePrompt Unlock the height and set this stair to 3200 tall
518
+ *
519
+ * # Example
520
+ * ```ts
521
+ * // Give every ground-floor staircase a common riser + tread (engine units)
522
+ * const stairs = await snaptrude.design.query.listStaircases({ storeys: [1] })
523
+ * const { affected } = await snaptrude.design.update.staircase(stairs, {
524
+ * riser: 165,
525
+ * tread: 280,
526
+ * })
527
+ * console.log("updated", affected.length, "staircases")
528
+ * ```
529
+ */
530
+ public abstract staircase(
531
+ staircases: ComponentHandle[],
532
+ params: PluginStaircaseParamUpdates,
533
+ ): PluginApiReturn<PluginDesignChangeResult>
534
+
535
+ /**
536
+ * Edit a parametric mass by patching its creation recipe and rebuilding the
537
+ * solid in place — the mass keeps its id, storey, materials, position and
538
+ * linked copies; only the geometry changes. Read the current record with
539
+ * `design.query.getParameters` first.
540
+ *
541
+ * `patch` is a partial of `record.values` — or the whole record you read
542
+ * back, edited. A key you leave out is unchanged (that includes
543
+ * `operations`). Each key you send replaces that value whole — a scalar, a
544
+ * point, a list or a record (`contour`, `profile`, `sections`, `path`,
545
+ * `options`, `operations`) alike — so send the complete value you read,
546
+ * edited (a partial `options` drops the switches it leaves out);
547
+ * `{ operations: [] }` drops every operation. An optional key
548
+ * the record does not carry yet may be patched in when the kind allows it:
549
+ * a straight extrusion acquires `twistInDegrees` or `topScale`, an
550
+ * options-less loft or sweep acquires `options` (`levels` is not one — send
551
+ * the angle, not the section count). A patch that changes nothing
552
+ * returns the current record and records no edit (no undo step). `kind` and
553
+ * `version` are immutable: to change the kind, build a new mass. Keys are the
554
+ * record's `values` keys for its `kind` (sphere: `centre`, `radius`;
555
+ * cylinder: `base`, `axis`, `radius`, `height`; torus: `centre`, `axis`,
556
+ * `majorRadius`, `minorRadius`; …) plus `operations`. To change a fillet
557
+ * radius, edit `operations[i].radius` in the record you read and send
558
+ * `{ operations }` back. Points and axes are WORLD coordinates in internal
559
+ * units, exactly as `getParameters` reports them.
560
+ *
561
+ * Rebuilds in memory first, then swaps onto the SAME component; every linked
562
+ * copy regenerates together. One undo step; saves and syncs to collaborators
563
+ * like any geometry edit. Kernel kinds (revolution, sweep, loft, primitives,
564
+ * any `operations`) run on OpenCascade — expect a short pause.
565
+ *
566
+ * @param component - a mass whose `getParameters` is non-null
567
+ * @param patch - partial `values` (+ optional `operations`), or the full edited record
568
+ * @returns the complete post-edit record, world coordinates (what
569
+ * `getParameters` now returns)
570
+ * @throws PRECONDITION_FAILED when the component is not a mass (a family
571
+ * instance: use `design.family.setParameters`).
572
+ * Also PRECONDITION_FAILED when the mass carries no live recipe — built
573
+ * from a boolean, split, `brepFromFaces`/`brepFromMesh` or drawn; edited
574
+ * free-form since (push/pull, move face, boolean); or scaled with the
575
+ * transform tool. `getParameters` returns `null` for exactly these.
576
+ * Do not retry — rebuild it with a recipe constructor and say so.
577
+ * A scaled mass is the exception: undo the scale and call again.
578
+ * Also PRECONDITION_FAILED when the mass (or a linked copy) is **locked**
579
+ * (the message says so). Locking is invisible to `getParameters` — a
580
+ * locked parametric mass still reads back a full record. Unlock it (and
581
+ * every linked copy) and call again; never rebuild it, the recipe is intact.
582
+ * Also PRECONDITION_FAILED when an `operations` entry's edge/face selectors
583
+ * no longer resolve on the rebuilt base (the message names the operation
584
+ * and edge): re-select the edges on the new shape and re-apply that
585
+ * operation.
586
+ * Also PRECONDITION_FAILED when the edit is the first twist or taper — or
587
+ * the straightening — of an extrusion that carries a shell, fillet or
588
+ * chamfer (the kernel numbers its faces differently): send the twist/taper
589
+ * keys with `operations: []` first (the message names them), re-select on
590
+ * the new shape, then send `{ operations }` back.
591
+ * Graph-bound spaces accept amount-only extrusion edits. Footprint, twist,
592
+ * taper and operation edits require the owning boundary tools, so generated
593
+ * walls, junctions and hosted openings remain synchronized.
594
+ * @throws VALIDATION for a key the kind does not have (the message lists the
595
+ * kind's keys), `kind` or `version` in a partial patch, or a value outside
596
+ * the constructor's own rules (torus tube ≥ ring, revolution angle ∉
597
+ * (0, 360], a profile crossing its axis, a spline with fewer than
598
+ * degree + 1 poles).
599
+ * @throws OPERATION_FAILED when the kernel cannot build the edited recipe;
600
+ * the model is left unchanged.
601
+ *
602
+ * @examplePrompt Make the dome's radius 8 metres
603
+ * @examplePrompt Raise the podium to 5 m
604
+ * @examplePrompt Widen the vase — move its profile points 20 cm outward
605
+ * @examplePrompt Change the fillet on the rounded column to 300 mm
606
+ *
607
+ * # Example
608
+ * ```ts
609
+ * // { kind: "sphere", values: { centre, radius: 19.685 }, … }
610
+ * const before = await snaptrude.design.query.getParameters(dome)
611
+ * const after = await snaptrude.design.update.parameters(dome, { radius: 31.496 }) // 8 m in internal units
612
+ *
613
+ * // Fillet radius on a recorded result: read → edit → send the array back
614
+ * const rec = await snaptrude.design.query.getParameters(column)
615
+ * const operations = rec?.operations ?? []
616
+ * if (operations[0]?.op === "fillet") {
617
+ * operations[0].radius = 1.181
618
+ * await snaptrude.design.update.parameters(column, { operations })
619
+ * }
620
+ * ```
621
+ */
622
+ public abstract parameters(
623
+ component: ComponentHandle,
624
+ patch: MassParametersPatch,
625
+ ): PluginApiReturn<MassParametersRecord>
626
+
627
+ /**
628
+ * Edit a **wall** — its thickness and/or height (the same command the
629
+ * properties panel's Thickness/Height fields drive) and/or its **wall type**
630
+ * (the panel's Wall Types dropdown: converts the wall to another type,
631
+ * rebuilding its layer stack and materials). Sparse: only the fields you
632
+ * provide change. Undoable; the geometry rebuilds and any dimension lines
633
+ * refresh. Stacked-wall companions and hosted fenestration are handled
634
+ * exactly as the panel does.
635
+ *
636
+ * Thickness/height are in **engine units** (the same convention as
637
+ * `design.doors.setWidth`) and must be `> 0`. Shrinking a wall past its
638
+ * openings is rejected by the engine (the wall is skipped with no change).
639
+ * `type` accepts a type name from `design.types.list("wall")` (or its
640
+ * `wall:`-prefixed id); when combined with dimensions, the type conversion
641
+ * is applied first so an explicit thickness/height wins over the type's
642
+ * defaults. Re-applying the wall's current type is a no-op.
643
+ *
644
+ * Throws when the handle is not a wall, when the wall is locked, when the
645
+ * handle is gone/forged, when `type` names an unknown wall type, when the
646
+ * engine rejects the conversion (e.g. a curved wall thinner than the new
647
+ * type's minimum thickness), or when no field is provided.
648
+ *
649
+ * A type conversion (and a thickness edit on a parametric wall) REBUILDS the
650
+ * wall, replacing it with a new component: the input handle is then dead
651
+ * (further calls throw HANDLE_INVALID) and `affected[0]` carries the
652
+ * replacement's handle — use it for any follow-up calls.
653
+ *
654
+ * @param component - The wall to edit
655
+ * @param updates - Sparse {@linkcode PluginWallUpdates} (at least one field)
656
+ * @returns the affected wall — after a rebuild, the REPLACEMENT wall's handle
657
+ *
658
+ * @examplePrompt Make this wall 230mm thick
659
+ * @examplePrompt Set the selected wall's height to 3 metres
660
+ * @examplePrompt Thicken every exterior wall to 300
661
+ * @examplePrompt Change this wall to a Glass Wall
662
+ * @examplePrompt Convert all the partitions on this floor to Brick Wall
663
+ *
664
+ * # Example
665
+ * ```ts
666
+ * // Convert every wall on storey 1 to the first listed wall type
667
+ * const [wallType] = await snaptrude.design.types.list("wall")
668
+ * const walls = await snaptrude.design.query.listWalls({ storeys: [1] })
669
+ * for (const wall of walls) {
670
+ * await snaptrude.design.update.wall(wall, { type: wallType.id })
671
+ * }
672
+ * ```
673
+ */
674
+ public abstract wall(
675
+ component: ComponentHandle,
676
+ updates: PluginWallUpdates,
677
+ ): PluginApiReturn<PluginDesignChangeResult>
678
+
679
+ /**
680
+ * Edit a **slab's** thickness and/or overhang — the same commands the
681
+ * properties panel's Thickness field and Overhang slider drive. Sparse: only
682
+ * the fields you provide change. Undoable; the slab rebuilds and dimension
683
+ * lines refresh. Both values are in **engine units** (the same convention as
684
+ * `design.doors.setWidth`); thickness must be `> 0`, overhang is an absolute
685
+ * offset (negative insets the slab) and re-applying the current overhang is
686
+ * a no-op.
687
+ *
688
+ * Throws when the handle is not a slab, when it is locked, when the handle is
689
+ * gone/forged, when `overhang` is set on a slab that has no overhang (plinth /
690
+ * basement slabs, or overhang disabled), or when no field is provided.
691
+ *
692
+ * @param component - The slab to edit
693
+ * @param updates - Sparse {@linkcode PluginSlabUpdates} (at least one field)
694
+ * @returns the affected slab
695
+ *
696
+ * @examplePrompt Set this slab's thickness to 250mm
697
+ * @examplePrompt Make the floor slab 300 thick
698
+ * @examplePrompt Give this slab a 200 overhang
699
+ * @examplePrompt Remove the overhang on every slab on level 2
700
+ *
701
+ * # Example
702
+ * ```ts
703
+ * const [slab] = await snaptrude.design.query.listSlabs()
704
+ * await snaptrude.design.update.slab(slab, { thickness: 250, overhang: 200 })
705
+ * ```
706
+ */
707
+ public abstract slab(
708
+ component: ComponentHandle,
709
+ updates: PluginSlabUpdates,
710
+ ): PluginApiReturn<PluginDesignChangeResult>
711
+
712
+ /**
713
+ * Edit a **floor's** thickness — the slab-family updater for floors (shares
714
+ * the slab thickness command). Undoable. Thickness is in **engine units**
715
+ * (the `design.doors.setWidth` convention) and must be `> 0`. Floors have no
716
+ * Overhang control, so `overhang` is rejected with `PRECONDITION_FAILED`.
717
+ *
718
+ * Throws when the handle is not a floor, when it is locked, when the handle
719
+ * is gone/forged, when `overhang` is provided, or when no field is provided.
720
+ *
721
+ * @param component - The floor to edit
722
+ * @param updates - {@linkcode PluginSlabUpdates} (a `thickness`)
723
+ * @returns the affected floor
724
+ *
725
+ * @examplePrompt Make this floor 200mm thick
726
+ * @examplePrompt Set every floor to a 250 thickness
727
+ * @examplePrompt Thicken the ground floor slab to 300
728
+ *
729
+ * # Example
730
+ * ```ts
731
+ * const [floor] = await snaptrude.design.query.listFloors()
732
+ * await snaptrude.design.update.floor(floor, { thickness: 200 })
733
+ * ```
734
+ */
735
+ public abstract floor(
736
+ component: ComponentHandle,
737
+ updates: PluginSlabUpdates,
738
+ ): PluginApiReturn<PluginDesignChangeResult>
739
+
740
+ /**
741
+ * Edit a **roof's** thickness and/or overhang — the same commands the roof
742
+ * panel's Thickness field and Overhang slider drive (shares the slab-family
743
+ * machinery). Sparse: only the fields you provide change. Undoable. Both
744
+ * values are in **engine units** (the `design.doors.setWidth` convention);
745
+ * thickness must be `> 0`, overhang is an absolute offset (negative insets
746
+ * the roof) and re-applying the current overhang is a no-op.
747
+ *
748
+ * Throws when the handle is not a roof, when it is locked, when the handle is
749
+ * gone/forged, when `overhang` is set on a roof whose overhang is disabled,
750
+ * or when no field is provided.
751
+ *
752
+ * @param component - The roof to edit
753
+ * @param updates - Sparse {@linkcode PluginSlabUpdates} (at least one field)
754
+ * @returns the affected roof
755
+ *
756
+ * @examplePrompt Make this roof 300mm thick
757
+ * @examplePrompt Set the roof slab thickness to 250
758
+ * @examplePrompt Give the roof a 600 overhang
759
+ * @examplePrompt Pull the roof overhang back to 0
760
+ *
761
+ * # Example
762
+ * ```ts
763
+ * const [roof] = await snaptrude.design.query.listRoofs()
764
+ * await snaptrude.design.update.roof(roof, { overhang: 600 })
765
+ * ```
766
+ */
767
+ public abstract roof(
768
+ component: ComponentHandle,
769
+ updates: PluginSlabUpdates,
770
+ ): PluginApiReturn<PluginDesignChangeResult>
771
+
772
+ /**
773
+ * Edit a **ceiling's** thickness — the slab-family updater for ceilings
774
+ * (shares the slab thickness command). Undoable. Thickness is in **engine
775
+ * units** (the `design.doors.setWidth` convention) and must be `> 0`.
776
+ * Ceilings have no Overhang control, so `overhang` is rejected with
777
+ * `PRECONDITION_FAILED`.
778
+ *
779
+ * Throws when the handle is not a ceiling, when it is locked, when the handle
780
+ * is gone/forged, when `overhang` is provided, or when no field is provided.
781
+ *
782
+ * @param component - The ceiling to edit
783
+ * @param updates - {@linkcode PluginSlabUpdates} (a `thickness`)
784
+ * @returns the affected ceiling
785
+ *
786
+ * @examplePrompt Make this ceiling 150mm thick
787
+ * @examplePrompt Set every ceiling to a 100 thickness
788
+ * @examplePrompt Thicken the ceiling to 200
789
+ *
790
+ * # Example
791
+ * ```ts
792
+ * const [ceiling] = await snaptrude.design.query.listCeilings()
793
+ * await snaptrude.design.update.ceiling(ceiling, { thickness: 150 })
794
+ * ```
795
+ */
796
+ public abstract ceiling(
797
+ component: ComponentHandle,
798
+ updates: PluginSlabUpdates,
799
+ ): PluginApiReturn<PluginDesignChangeResult>
800
+
801
+ /** Replace a native beam family while preserving its authored axis. Values use mm/degrees.
802
+ * @param component Native beam. @param updates Saved family reference and optional instance values.
803
+ * @returns Affected component IDs. One undoable operation; unsupported geometry fails before mutation.
804
+ */
805
+ public abstract beam(
806
+ component: ComponentHandle,
807
+ updates: PluginFamilyUpdates,
808
+ ): PluginApiReturn<PluginDesignChangeResult>
809
+
810
+ /** Replace a native column family while preserving its authored axis. Values use mm/degrees.
811
+ * @param component Native column. @param updates Saved family reference and optional instance values.
812
+ * @returns Affected component IDs. One undoable operation; unsupported geometry fails before mutation.
813
+ */
814
+ public abstract column(
815
+ component: ComponentHandle,
816
+ updates: PluginFamilyUpdates,
817
+ ): PluginApiReturn<PluginDesignChangeResult>
818
+
819
+ /**
820
+ * Set a component's **absolute world elevation** — moves it vertically so its
821
+ * base (the bounding-box bottom, world Y) lands at `elevation`. Composed over
822
+ * the canonical command-backed move machinery, so it is undoable and persisted
823
+ * exactly like dragging the element up or down; per-type invariants (e.g. a
824
+ * staircase's base offset) stay in sync. `elevation` is in **engine units**
825
+ * (the `design.doors.setWidth` convention). Re-applying the current elevation
826
+ * is a no-op.
827
+ *
828
+ * Throws when the component is locked or the handle is gone/forged.
829
+ *
830
+ * @param component - The component to move vertically
831
+ * @param elevation - Target world elevation of the component's base (engine units)
832
+ * @returns the affected component
833
+ *
834
+ * @examplePrompt Set this element's elevation to 3000
835
+ * @examplePrompt Raise this mass so its base sits at 3 metres
836
+ * @examplePrompt Drop the selected slab to elevation 0
837
+ * @examplePrompt Put this staircase's base at 3200
838
+ *
839
+ * # Example
840
+ * ```ts
841
+ * // Sit the roof slab exactly at the top of a 3m storey
842
+ * const [roof] = await snaptrude.design.query.listRoofs()
843
+ * await snaptrude.design.update.setElevation(roof, 3000)
844
+ * ```
845
+ */
846
+ public abstract setElevation(
847
+ component: ComponentHandle,
848
+ elevation: number,
849
+ ): PluginApiReturn<PluginDesignChangeResult>
850
+
851
+ /**
852
+ * Set a component's **base offset** — its vertical distance above its own
853
+ * storey's base level. Moves the component so its base lands at
854
+ * `storeyBase + offset`, composed over the same command-backed move machinery
855
+ * as {@linkcode setElevation} (undoable, persisted, per-type invariants kept
856
+ * in sync). Use this instead of {@linkcode setElevation} when you want an
857
+ * offset relative to the storey rather than an absolute world height.
858
+ * `offset` is in **engine units** (the `design.doors.setWidth` convention);
859
+ * `0` sits the base flush with the storey base. Re-applying the current
860
+ * offset is a no-op.
861
+ *
862
+ * Throws when the component is locked, when it has no storey to offset from,
863
+ * or when the handle is gone/forged.
864
+ *
865
+ * @param component - The component to offset
866
+ * @param offset - Distance of the component's base above its storey base (engine units)
867
+ * @returns the affected component
868
+ *
869
+ * @examplePrompt Lift this element 300 above its floor
870
+ * @examplePrompt Set this staircase's base offset to 0
871
+ * @examplePrompt Raise the selected mass 500 off the storey base
872
+ * @examplePrompt Offset this slab 150 above the floor level
873
+ *
874
+ * # Example
875
+ * ```ts
876
+ * const [stair] = await snaptrude.design.query.listStaircases()
877
+ * await snaptrude.design.update.setBaseOffset(stair, 0)
878
+ * ```
879
+ */
880
+ public abstract setBaseOffset(
881
+ component: ComponentHandle,
882
+ offset: number,
883
+ ): PluginApiReturn<PluginDesignChangeResult>
884
+
885
+ /**
886
+ * Reassign one or more components to another **storey** — the same operation
887
+ * as the properties panel's Story dropdown, and it works across element kinds
888
+ * exactly like the panel (walls, slabs, masses, furniture, staircases, ...).
889
+ * Each component is moved vertically to the target storey's level (per-kind
890
+ * placement rules — e.g. a slab sits at the storey base, a wall on it) and
891
+ * its storey assignment, children, and dimension lines update together as
892
+ * **one undoable operation**.
893
+ *
894
+ * `storey` is an integer storey number in the `entity.story` convention
895
+ * (`1` is the ground storey, `-1` the first basement; `0` does not exist)
896
+ * and **must already exist** in each component's building — this call never
897
+ * creates storeys (use `core.storeys.create` first). Components already on
898
+ * the target storey are skipped (no-op) and are not echoed in `affected`.
899
+ *
900
+ * Throws when the target storey does not exist, when any component is
901
+ * locked, or when a handle is gone/forged — all before anything moves
902
+ * (all-or-nothing).
903
+ *
904
+ * @param components - The components to reassign
905
+ * @param storey - Target storey number (integer, `entity.story` convention)
906
+ * @returns the components actually reassigned
907
+ *
908
+ * @examplePrompt Move this sofa to storey 2
909
+ * @examplePrompt Put the selected walls on the first basement level
910
+ * @examplePrompt Send everything on this storey up one level
911
+ * @examplePrompt Reassign these slabs to storey 3
912
+ *
913
+ * # Example
914
+ * ```ts
915
+ * // Move the current selection up to storey 2
916
+ * const selection = await snaptrude.design.selection.get()
917
+ * const { affected } = await snaptrude.design.update.setStorey(selection, 2)
918
+ * console.log("moved", affected.length, "components")
919
+ * ```
920
+ */
921
+ public abstract setStorey(
922
+ components: ComponentHandle[],
923
+ storey: number,
924
+ ): PluginApiReturn<PluginDesignChangeResult>
925
+
926
+ /**
927
+ * Change the **object type** of one or more masses — turn a mass into a real
928
+ * wall, roof, slab or floor: the properties panel's Object Type dropdown,
929
+ * headless. The geometry is kept exactly as it is (a wedge stays a wedge;
930
+ * nothing is flattened to a default thickness), so a sloped mass built with
931
+ * `design.create.massFromBrep` becomes a sloped roof or a raked wall. The
932
+ * component keeps its handle, label and storey; it gains the target type's
933
+ * construction layers and properties, and the type's default material when
934
+ * the mass still wore the default. One undoable operation for the whole
935
+ * list; undo turns them back into masses.
936
+ *
937
+ * Instances convert as a family: passing one instance (or the source) of an
938
+ * instanced mass converts its source and every sibling; `affected` echoes
939
+ * every visible member (the hidden instancing template converts too but is
940
+ * not listed). A converted component cannot be converted again (same rule
941
+ * as the panel). A converted wall has no parametric centerline: it hosts
942
+ * doors and windows and exports, but neighbours do not re-mitre against it.
943
+ *
944
+ * Throws before anything changes (all-or-nothing) when a component is not a
945
+ * mass, is a plinth, is locked or in a locked bucket, already had its type
946
+ * changed, or when a handle is gone/forged.
947
+ *
948
+ * @param components - The masses to convert
949
+ * @param objectType - Target type: `"wall"`, `"roof"`, `"slab"` or `"floor"`
950
+ * (the `design.query.getEntityType` vocabulary)
951
+ * @returns the visible components converted — the same handles, plus any
952
+ * instance siblings pulled in by family expansion
953
+ *
954
+ * @examplePrompt Convert this mass into a wall
955
+ * @examplePrompt Turn the sloped mass into a roof
956
+ * @examplePrompt Make these masses real slabs
957
+ * @examplePrompt Change the object type of the selection to wall
958
+ * @examplePrompt Convert the wedge I built into a raked wall
959
+ * @examplePrompt Turn this mass into a floor
960
+ *
961
+ * # Example
962
+ * ```ts
963
+ * const brep = await snaptrude.core.geom.create.brepFromFaces(wedgeFaces)
964
+ * const wedge = await snaptrude.design.create.massFromBrep(brep, "Gable end")
965
+ * const { affected } = await snaptrude.design.update.setObjectType([wedge], "wall")
966
+ * console.log(await snaptrude.design.query.getEntityType(affected[0])) // "wall"
967
+ * ```
968
+ */
969
+ public abstract setObjectType(
970
+ components: ComponentHandle[],
971
+ objectType: PluginObjectType,
972
+ ): PluginApiReturn<PluginDesignChangeResult>
973
+
974
+ /**
975
+ * Update an existing parametric buildable envelope — regenerate the zoning
976
+ * volume from a revised site polygon, setbacks, vertical cap, or FAR and
977
+ * lot-coverage limits, keeping the same envelope id. Canonical home of the
978
+ * update formerly at `entity.buildableEnvelope.update` (now deprecated).
979
+ *
980
+ * Edge state is preserved, like the right panel: every edge's type
981
+ * (front/side/rear) is kept. For each type whose value changed (by more than
982
+ * 2 mm), the per-edge custom distances on edges of that type are cleared;
983
+ * custom distances on types whose value did not change are kept.
984
+ *
985
+ * @param buildableEnvelopeId - Existing envelope ID to update; non-empty and
986
+ * must match an existing envelope on the canvas.
987
+ * @param sitePolygon - Site polygon vertices in `lengthUnit`; minimum 3 vertices.
988
+ * @param lengthUnit - Unit used by all length fields (`"ft" | "m"`).
989
+ * @param setbacks - Setback profile, ground tier first; minimum 1 tier.
990
+ * @param verticalCap - Maximum height or floor count.
991
+ * @param floorToFloor - Required floor-to-floor height in `lengthUnit`.
992
+ * @param farRatio - Optional FAR value, positive when provided.
993
+ * @param lotCoverageMaxPct - Optional lot coverage cap, `0..100`.
994
+ * @returns A {@linkcode PluginBuildableEnvelopeUpdateResult} with the
995
+ * `buildableEnvelopeId` of the updated envelope and `clearedEdgeSetbacks`,
996
+ * the `edgeIndex` of each edge that lost its custom distance (empty when
997
+ * none). Tell the user about any cleared edges.
998
+ * @throws If validation fails, the envelope does not exist, or generation
999
+ * produces no renderable geometry. Also throws `PRECONDITION_FAILED`
1000
+ * (`details.reason: "STALE_EDGE_SETTINGS"`, nothing changes) when the site
1001
+ * outline changed since the edge settings were made. Tell the user;
1002
+ * editing any edge on the canvas setback pills resets the edge settings
1003
+ * to the new outline (dropping the old ones), after which you can retry.
1004
+ *
1005
+ * @examplePrompt Change the envelope's height limit to 175 feet
1006
+ * @examplePrompt Update the setbacks on the buildable envelope
1007
+ * @examplePrompt Regenerate the zoning envelope with a new site boundary
1008
+ * @examplePrompt Increase the FAR on the buildable volume
1009
+ *
1010
+ * # Example
1011
+ * ```ts
1012
+ * const { buildableEnvelopeId } = await snaptrude.design.update.buildableEnvelope(
1013
+ * existingId,
1014
+ * [
1015
+ * { x: 0, z: 0 },
1016
+ * { x: 100, z: 0 },
1017
+ * { x: 100, z: 80 },
1018
+ * { x: 0, z: 80 },
1019
+ * ],
1020
+ * "ft",
1021
+ * [{ aboveHeight: 0, front: 10, side: 5, rear: 10 }],
1022
+ * { kind: "max_height", maxHeight: 175 },
1023
+ * 12,
1024
+ * )
1025
+ * ```
1026
+ */
1027
+ public abstract buildableEnvelope(
1028
+ buildableEnvelopeId: string,
1029
+ sitePolygon: PluginBuildableEnvelopePolygonVertex[],
1030
+ lengthUnit: "ft" | "m",
1031
+ setbacks: PluginBuildableEnvelopeSetbackTier[],
1032
+ verticalCap: PluginBuildableEnvelopeVerticalCap,
1033
+ floorToFloor: number,
1034
+ farRatio?: number,
1035
+ lotCoverageMaxPct?: number,
1036
+ ): PluginApiReturn<PluginBuildableEnvelopeUpdateResult>
1037
+
1038
+ /**
1039
+ * Change one site edge's type (front / side / rear) and/or give that edge
1040
+ * its own setback distance, the same as editing a setback pill on the
1041
+ * canvas. One undo step; the pills and Site Analysis sheet refresh.
1042
+ *
1043
+ * Address the edge with `siteId` and `edgeIndex` from
1044
+ * `program.site.listEdges()`. Setting `role` clears that edge's own
1045
+ * distance. `setback: null` clears the edge's own distance and puts it back
1046
+ * on its type's distance. When `role` and a numeric `setback` are both
1047
+ * given, the role is applied first and then the setback, so the new distance
1048
+ * wins. On the only edge of its type, a numeric setback becomes that type's
1049
+ * value (like the canvas pills), so `null` afterwards has nothing to
1050
+ * clear; set the type's value instead. Only call this when the user asks to
1051
+ * change an edge.
1052
+ *
1053
+ * @param siteId - Site component id, from `program.site.listEdges()`.
1054
+ * @param edgeIndex - Edge index from `program.site.listEdges()`; nonnegative
1055
+ * integer, upper bound checked by the host.
1056
+ * @param patch - A {@linkcode PluginSiteEdgePatch}: `role`, `setback` (a
1057
+ * number in `lengthUnit`, or `null` to clear), and `lengthUnit` (required
1058
+ * with a numeric `setback`). Provide `role` and/or `setback`.
1059
+ * @returns The refreshed {@linkcode PluginDesignUpdateSiteEdgeResult} for the
1060
+ * site, as `program.site.listEdges()` returns it.
1061
+ * @throws
1062
+ * - `HANDLE_INVALID`: unknown `siteId` (invalid site handle).
1063
+ * - `VALIDATION`: `edgeIndex` is out of range or not a nonnegative
1064
+ * integer; when the index is out of range, `details` include `edgeCount`.
1065
+ * - `PRECONDITION_FAILED`, with `details.reason` one of:
1066
+ * - `NO_BUILDABLE_ENVELOPE`: the site has no envelope to store edge
1067
+ * settings on. Ask the user whether to create a default envelope with
1068
+ * `design.create.buildableEnvelopeFromSite(siteId)` or to create one
1069
+ * themselves; do not create one unasked.
1070
+ * - `MASSING_ENVELOPE`: the site's envelope is a massing envelope. Tell
1071
+ * the user; change nothing.
1072
+ * - `ENVELOPE_TRANSFORMED`: the envelope has been rotated or scaled, so
1073
+ * edge editing is unavailable. Tell the user.
1074
+ * - `STALE_EDGE_SETTINGS`: the site outline changed since the edge
1075
+ * settings were made. Tell the user; editing any edge on the canvas
1076
+ * setback pills resets the edge settings to the new outline (dropping
1077
+ * the old ones), after which you can retry.
1078
+ *
1079
+ * @examplePrompt Make edge 2 the front
1080
+ * @examplePrompt Set just the north edge setback to 8 m
1081
+ * @examplePrompt Remove the custom setback on edge 3
1082
+ *
1083
+ * # Example
1084
+ * ```ts
1085
+ * const [{ siteId }] = (await snaptrude.program.site.listEdges()).sites
1086
+ * await snaptrude.design.update.siteEdge(siteId, 2, { role: "front" })
1087
+ * await snaptrude.design.update.siteEdge(siteId, 3, { setback: 8, lengthUnit: "m" })
1088
+ * await snaptrude.design.update.siteEdge(siteId, 3, { setback: null })
1089
+ * ```
1090
+ */
1091
+ public abstract siteEdge(
1092
+ siteId: string,
1093
+ edgeIndex: number,
1094
+ patch: PluginSiteEdgePatch,
1095
+ ): PluginApiReturn<PluginDesignUpdateSiteEdgeResult>
1096
+ }