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