@snaptrude/plugin-core 0.9.13 → 0.9.15

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 (101) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/api-manifest.full.json +8930 -0
  3. package/api-manifest.json +1002 -55
  4. package/dist/api/core/geom/create/index.d.ts +935 -100
  5. package/dist/api/core/geom/create/index.d.ts.map +1 -1
  6. package/dist/api/core/geom/query/brep.d.ts +59 -2
  7. package/dist/api/core/geom/query/brep.d.ts.map +1 -1
  8. package/dist/api/core/geom/query/curve.d.ts +59 -35
  9. package/dist/api/core/geom/query/curve.d.ts.map +1 -1
  10. package/dist/api/core/geom/query/edge.d.ts +2 -2
  11. package/dist/api/core/geom/query/face.d.ts +105 -0
  12. package/dist/api/core/geom/query/face.d.ts.map +1 -1
  13. package/dist/api/core/geom/update/contour.d.ts +2 -0
  14. package/dist/api/core/geom/update/contour.d.ts.map +1 -1
  15. package/dist/api/core/geom/update/curve.d.ts +5 -5
  16. package/dist/api/core/geom/update/profile.d.ts +3 -1
  17. package/dist/api/core/geom/update/profile.d.ts.map +1 -1
  18. package/dist/api/core/handles/index.d.ts +4 -2
  19. package/dist/api/core/handles/index.d.ts.map +1 -1
  20. package/dist/api/core/index.d.ts +4 -4
  21. package/dist/api/core/index.d.ts.map +1 -1
  22. package/dist/api/core/layers.d.ts +377 -255
  23. package/dist/api/core/layers.d.ts.map +1 -1
  24. package/dist/api/core/proposals/index.d.ts +4 -3
  25. package/dist/api/core/proposals/index.d.ts.map +1 -1
  26. package/dist/api/core/storeys/index.d.ts +12 -0
  27. package/dist/api/core/storeys/index.d.ts.map +1 -1
  28. package/dist/api/core/storeys/layers.d.ts +408 -0
  29. package/dist/api/core/storeys/layers.d.ts.map +1 -0
  30. package/dist/api/core/tags.d.ts +3 -3
  31. package/dist/api/design/create/bulk-items.d.ts +44 -0
  32. package/dist/api/design/create/bulk-items.d.ts.map +1 -1
  33. package/dist/api/design/create/grids.d.ts +1 -1
  34. package/dist/api/design/create/index.d.ts +31 -3
  35. package/dist/api/design/create/index.d.ts.map +1 -1
  36. package/dist/api/design/family.d.ts +67 -10
  37. package/dist/api/design/family.d.ts.map +1 -1
  38. package/dist/api/design/query/geometry/index.d.ts +33 -5
  39. package/dist/api/design/query/geometry/index.d.ts.map +1 -1
  40. package/dist/api/design/query/grids.d.ts +3 -3
  41. package/dist/api/design/query/index.d.ts +47 -0
  42. package/dist/api/design/query/index.d.ts.map +1 -1
  43. package/dist/api/design/query/spaces.d.ts +5 -5
  44. package/dist/api/design/selection/index.d.ts +2 -2
  45. package/dist/api/design/update/index.d.ts +172 -0
  46. package/dist/api/design/update/index.d.ts.map +1 -1
  47. package/dist/api/entity/referenceLine.d.ts +1 -1
  48. package/dist/api/entity/space.d.ts +4 -4
  49. package/dist/api/entity/story.d.ts +4 -4
  50. package/dist/api/presentation/aiInspiration.d.ts +13 -13
  51. package/dist/api/presentation/camera.d.ts +265 -0
  52. package/dist/api/presentation/camera.d.ts.map +1 -0
  53. package/dist/api/presentation/index.d.ts +5 -0
  54. package/dist/api/presentation/index.d.ts.map +1 -1
  55. package/dist/api/presentation/placedViews.d.ts +1 -1
  56. package/dist/api/presentation/views.d.ts +3 -1
  57. package/dist/api/presentation/views.d.ts.map +1 -1
  58. package/dist/api/program/site.d.ts +4 -4
  59. package/dist/api/program/spreadsheet.d.ts +4 -4
  60. package/dist/handles.d.ts +13 -3
  61. package/dist/handles.d.ts.map +1 -1
  62. package/dist/index.cjs +3638 -2958
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.ts +2 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +3558 -2955
  67. package/dist/index.js.map +1 -1
  68. package/dist/massParameters.d.ts +287 -0
  69. package/dist/massParameters.d.ts.map +1 -0
  70. package/dist/massParameters.ops.d.ts +138 -0
  71. package/dist/massParameters.ops.d.ts.map +1 -0
  72. package/package.json +2 -1
  73. package/scripts/check-dist-fresh.mjs +22 -0
  74. package/scripts/generate-manifest.test.mjs +14 -3
  75. package/src/api/core/geom/create/index.ts +1030 -101
  76. package/src/api/core/geom/query/brep.ts +60 -2
  77. package/src/api/core/geom/query/curve.ts +26 -2
  78. package/src/api/core/geom/query/edge.ts +2 -2
  79. package/src/api/core/geom/query/face.ts +109 -4
  80. package/src/api/core/geom/update/contour.ts +2 -0
  81. package/src/api/core/geom/update/profile.ts +2 -0
  82. package/src/api/core/handles/index.ts +3 -2
  83. package/src/api/core/index.ts +4 -4
  84. package/src/api/core/layers.ts +380 -173
  85. package/src/api/core/proposals/index.ts +4 -3
  86. package/src/api/core/storeys/index.ts +14 -0
  87. package/src/api/core/storeys/layers.ts +339 -0
  88. package/src/api/design/create/bulk-items.ts +68 -0
  89. package/src/api/design/create/index.ts +30 -4
  90. package/src/api/design/family.ts +68 -10
  91. package/src/api/design/query/geometry/index.ts +35 -4
  92. package/src/api/design/query/index.ts +50 -0
  93. package/src/api/design/update/index.ts +186 -1
  94. package/src/api/presentation/camera.ts +289 -0
  95. package/src/api/presentation/index.ts +5 -0
  96. package/src/api/presentation/views.ts +3 -1
  97. package/src/handles.ts +18 -3
  98. package/src/index.ts +2 -0
  99. package/src/massParameters.ops.ts +246 -0
  100. package/src/massParameters.ts +427 -0
  101. package/test/massParameters.test.mjs +952 -0
@@ -1,6 +1,6 @@
1
1
  import * as z from "zod"
2
2
  import { PluginApiReturn } from "../../types"
3
- import { ComponentHandle, Vec3Components } from "../../handles"
3
+ import { BrepHandle, ComponentHandle, Vec3Components } from "../../handles"
4
4
 
5
5
  /**
6
6
  * `snaptrude.design.family.*` — author NATIVE parametric families from a JSON spec.
@@ -18,15 +18,21 @@ import { ComponentHandle, Vec3Components } from "../../handles"
18
18
  * getReference() read the spec DSL once — grammar, ops, expressions, example
19
19
  * prepare({ bundle }) validate + compile + show a live ghost; nothing committed
20
20
  * flex({ values }) re-run at new values; returns the geometry delta
21
- * place() arm placement — the USER clicks to drop it
22
- * create({ name }) commit it to the project's family library
21
+ * place({ position }) put it in the model — saves the family as the first instance drops
22
+ * place() save the family, then arm placement — the USER clicks to drop it
23
+ * create({ teamId }) share with a team; a save with nothing placed also drops one instance
23
24
  * ```
24
25
  *
26
+ * `place` saves the prepared family to the user's personal library, so it
27
+ * survives reload with no extra step; finish on `place`, not on `create`.
28
+ *
25
29
  * ## Read getReference() first
26
30
  *
27
31
  * The spec DSL is a closed expression language with its own op set, coordinate
28
- * convention (**+Y up, Snaptrude units** — the scene's own unit, 1 SU = 254 mm;
29
- * literals may carry a unit suffix such as `600mm` or `2ft`)
32
+ * convention (**+Y up, Snaptrude units** — the scene's own unit, 1 SU = 254 mm,
33
+ * the same unit `core.units.getBabylonType()` names and every geometry API
34
+ * takes; literals inside spec expressions may carry a unit suffix such as
35
+ * `600mm` or `2ft`)
30
36
  * and part-naming law. It is deliberately NOT duplicated in these types:
31
37
  * {@linkcode PluginDesignFamilyApi.getReference} returns the whole reference,
32
38
  * generated from a family that is actually registered in the host, so the
@@ -41,6 +47,18 @@ import { ComponentHandle, Vec3Components } from "../../handles"
41
47
  * leaves the previous good draft standing, so it costs nothing but the node you
42
48
  * broke. Re-authoring from scratch throws away everything already correct.
43
49
  *
50
+ * ## Curved parts are exact
51
+ *
52
+ * A part whose sketch has a curved edge — any `revolve`, or an `extrude` /
53
+ * `sweep` / `loft` with an arc or circle in its profile (or an arc in a sweep
54
+ * path) — is built by the exact geometry kernel, the same one behind curved
55
+ * masses. Straight-edged parts keep the fast mesh builder. `getParameters` on a
56
+ * placed component reports which built each part in `parts[].engine`, and a
57
+ * kernel part carries a `brep`: read it with `core.geom.query.brep.*` and
58
+ * `core.geom.query.face.getSurfaceKind` — the same face-kind check as for a
59
+ * curved mass. If the kernel is unavailable a part falls back to the mesh
60
+ * builder (`engine: "mesh"`) rather than failing the family.
61
+ *
44
62
  * Accessed via `snaptrude.design.family`.
45
63
  */
46
64
  export abstract class PluginDesignFamilyApi {
@@ -68,20 +86,25 @@ export abstract class PluginDesignFamilyApi {
68
86
 
69
87
  /**
70
88
  * Validate, compile and preview a family spec. Nothing is committed to the
71
- * model or the library — call {@linkcode PluginDesignFamilyApi.place} or
72
- * {@linkcode PluginDesignFamilyApi.create} for that.
89
+ * model or the library — call {@linkcode PluginDesignFamilyApi.place} to put
90
+ * it in the model, which also saves it.
73
91
  *
74
92
  * Safe to call repeatedly while iterating. On success, check `partCount` and
75
93
  * `bbox` against what you intended before treating the family as correct — a
76
94
  * facade that built 3 parts, or a 3 m screen reporting 40 m of height, is a
77
95
  * visible bug the validator cannot catch.
78
96
  *
97
+ * Lengths — the spec's bare numbers, `bbox`, the sheet's `length` values — are
98
+ * Snaptrude units: the same 254 mm unit as the kernel's internal (`"babylon"`)
99
+ * unit. A unit suffix (`600mm`) is legal only inside a spec expression.
100
+ *
79
101
  * @param args The spec bundle, as `{ bundle }` or `{ spec }`
80
102
  * @returns Build report, or `{ ok: false, errors }` with JSON-pointer paths
81
103
  *
82
104
  * @examplePrompt Make a louvre screen with adjustable blade count and tilt
83
105
  * @examplePrompt Design a curtain wall panel family with 8 bays
84
106
  * @examplePrompt Create a railing family I can adjust
107
+ * @examplePrompt Make a pendant lamp family with a domed shade I can resize
85
108
  *
86
109
  * # Example
87
110
  * ```ts
@@ -147,6 +170,9 @@ export abstract class PluginDesignFamilyApi {
147
170
  * Put a family into the model — either the prepared draft or any family from
148
171
  * {@linkcode PluginDesignFamilyApi.list}.
149
172
  *
173
+ * Placing the prepared draft saves it to the user's personal library first,
174
+ * so its instances survive reload.
175
+ *
150
176
  * Two modes, chosen by whether you pass `position`:
151
177
  *
152
178
  * **With `position`** the component is created there and then, undoably. Use
@@ -195,6 +221,9 @@ export abstract class PluginDesignFamilyApi {
195
221
  * label the user says, and a Revit-imported family carries parameters that
196
222
  * do not drive geometry at all.
197
223
  *
224
+ * `length` values are Snaptrude units: the same 254 mm unit as the kernel's
225
+ * internal (`"babylon"`) unit — convert with `core.units.convert` to report.
226
+ *
198
227
  * @param args The component to read
199
228
  * @returns Its definition, current values, and the parameter sheet
200
229
  *
@@ -220,6 +249,11 @@ export abstract class PluginDesignFamilyApi {
220
249
  * that does not is accepted and stored (it round-trips to Revit) but nothing
221
250
  * visible happens — say so rather than claiming a change the user cannot see.
222
251
  *
252
+ * `length` values are Snaptrude units: the same 254 mm unit as the kernel's
253
+ * internal (`"babylon"`) unit — convert the user's number with
254
+ * `core.units.convert` first; a bare `400` is 400 SU, not 400 mm. Kernel part
255
+ * breps read before the edit are stale: re-read `getParameters`.
256
+ *
223
257
  * @param args The component and the values to change
224
258
  * @returns The values now in effect
225
259
  *
@@ -236,8 +270,11 @@ export abstract class PluginDesignFamilyApi {
236
270
  ): PluginApiReturn<PluginFamilyInstanceParameters>
237
271
 
238
272
  /**
239
- * Commit the prepared bundle to the project's parametric-definition library,
240
- * so it survives the session and appears in the Families panel.
273
+ * Save the prepared bundle to the library — the user's personal library
274
+ * unless `teamId` is given. {@linkcode PluginDesignFamilyApi.place} already
275
+ * saves, so `create` is for sharing with a team. When nothing of the family is
276
+ * in the model yet, one instance is also dropped at the centre of the current
277
+ * view on the active storey (`placed: true`), so a save is never invisible.
241
278
  *
242
279
  * @param args Optional library name and team scope
243
280
  * @returns The catalogued definition's id and version
@@ -318,7 +355,7 @@ export interface PluginFamilyParameter {
318
355
  key: string
319
356
  label: string
320
357
  type: "length" | "integer" | "enum" | "text" | "boolean"
321
- /** Current value. `length`/`integer` are numbers; the rest are strings. */
358
+ /** Current value. `length` (Snaptrude units) and `integer` are numbers; the rest are strings. */
322
359
  value: string | number
323
360
  default: string | number
324
361
  /**
@@ -354,6 +391,25 @@ export interface PluginFamilyDiagnostic {
354
391
  hint?: string
355
392
  }
356
393
 
394
+ /**
395
+ * One placed part and which engine built it, from `getParameters`. `"kernel"` —
396
+ * a curved sketch built exactly; `"mesh"` — straight-edged, or the kernel
397
+ * fallback.
398
+ */
399
+ export interface PluginFamilyPart {
400
+ name: string
401
+ engine: "kernel" | "mesh"
402
+ /**
403
+ * Kernel parts only: the part's exact solid, for `core.geom.query.brep.*` /
404
+ * `core.geom.query.face.getSurfaceKind`. READ-ONLY and PART-LOCAL — in the
405
+ * part's own frame, not where the component sits, so never pass it to a
406
+ * boolean or `design.create.massFromBrep` (a family changes only through
407
+ * `setParameters`). Valid until the next parameter change: re-read
408
+ * `getParameters` after `setParameters`.
409
+ */
410
+ brep?: BrepHandle
411
+ }
412
+
357
413
  /** What `prepare` reports. */
358
414
  export interface PluginFamilyPrepareResult {
359
415
  /** `false` means the spec was rejected — read `errors`, fix, re-prepare. */
@@ -455,6 +511,8 @@ export interface PluginFamilyInstanceParameters {
455
511
  values: Record<string, number | string>
456
512
  /** The sheet: what each key means, its bounds, and whether it moves geometry. */
457
513
  parameters: PluginFamilyParameter[]
514
+ /** The placed parts; kernel parts carry their exact `brep`. */
515
+ parts?: PluginFamilyPart[]
458
516
  /** Present when `getParameters` was asked for a `snapshot`. */
459
517
  snapshot?: PluginFamilySnapshot
460
518
  }
@@ -19,8 +19,8 @@ import {
19
19
  * {@linkcode PluginDesignQueryGeometryApi.getCenterline} mints a
20
20
  * {@linkcode CurveHandle} for a wall's core curve.
21
21
  * {@linkcode PluginDesignQueryGeometryApi.getTriangulatedMeshes} is the
22
- * handle-free exception: it returns the triangulated render meshes as plain
23
- * world-space arrays.
22
+ * handle-free exception: it returns `{ meshes }` — the triangulated render
23
+ * meshes as plain world-space arrays, wrapped in an object, never bare.
24
24
  *
25
25
  * Returned handles are session-ephemeral and go stale on undo/redo/re-topologize
26
26
  * — re-fetch a fresh one after edits.
@@ -34,10 +34,26 @@ export abstract class PluginDesignQueryGeometryApi {
34
34
  * scene entity, as a reusable {@linkcode BrepHandle} for the
35
35
  * `core.geom.query.brep.*` reads.
36
36
  *
37
+ * **Frame.** By default the brep comes back in the component's own LOCAL frame
38
+ * (its vertices are relative to the mesh origin, exactly as stored). This is
39
+ * the long-standing default, and existing callers rely on it. Pass
40
+ * `{ frame: "world" }` to get the same solid placed where it sits in the
41
+ * scene (world coordinates, like
42
+ * `getBottomContour` / `getTriangulatedMeshes` and every `design.query.measure`
43
+ * read). Use `"world"` whenever the handle will be COMBINED with other scene
44
+ * geometry or committed back beside its source: booleans between two scene
45
+ * masses, `brepFromFillet` / `brepFromShell` / `brepFromOffset` /
46
+ * `brepFromChamfer` on a scene mass followed by `massFromBrep`, or comparing
47
+ * its vertices with a bounding box. A world-frame handle from a parametrically
48
+ * live mass also carries that mass's recipe, so an operation result keeps an
49
+ * editable record.
50
+ *
37
51
  * @param component - The scene component to read
38
- * @returns The brep as a {@linkcode BrepHandle}, or `null` if the component has no brep geometry
52
+ * @param options - `frame`: `"local"` (default, mesh-local coordinates) or `"world"` (scene coordinates)
53
+ * @returns The brep as a {@linkcode BrepHandle}, or `null` if the component has no brep geometry — also `null` for a parametric family: read `design.family.getParameters({ component }).parts[].brep`
39
54
  *
40
55
  * @examplePrompt Get the 3D solid geometry of the selected wall
56
+ * @examplePrompt Round the top rim of this column and put it back where it is
41
57
  * @examplePrompt How many faces does this mass have?
42
58
  * @examplePrompt Inspect the mesh of this slab so I can look at its faces and edges
43
59
  * @examplePrompt Read the vertices of the selected column's geometry
@@ -48,10 +64,16 @@ export abstract class PluginDesignQueryGeometryApi {
48
64
  * if (brep) {
49
65
  * const faces = await snaptrude.core.geom.query.brep.listFaces(brep)
50
66
  * }
67
+ *
68
+ * // Operate on a scene mass and commit the result where the mass sits:
69
+ * const placed = await snaptrude.design.query.geometry.getBrep(mass, { frame: "world" })
70
+ * const rounded = await snaptrude.core.geom.create.brepFromFillet(placed, edges, 0.2)
71
+ * await snaptrude.design.create.massFromBrep(rounded, "Rounded")
51
72
  * ```
52
73
  */
53
74
  public abstract getBrep(
54
75
  component: ComponentHandle,
76
+ options?: PluginDesignQueryGeometryGetBrepOptions,
55
77
  ): PluginApiReturn<BrepHandle | null>
56
78
 
57
79
  /**
@@ -126,7 +148,8 @@ export abstract class PluginDesignQueryGeometryApi {
126
148
  * Get the **triangulated render meshes** of scene components as plain,
127
149
  * serializable arrays — flat world-space vertex `positions` (`[x0, y0, z0,
128
150
  * x1, …]`) and triangle `indices` (three per triangle), one record per
129
- * requested component.
151
+ * requested component. The result is `{ meshes }`, not a bare array —
152
+ * destructure it before iterating.
130
153
  *
131
154
  * Unlike the other `geometry.*` reads this does NOT mint kernel handles — the
132
155
  * raw arrays cross the boundary directly, ready for export, custom analysis,
@@ -189,8 +212,16 @@ export abstract class PluginDesignQueryGeometryApi {
189
212
  * |---|---|---|
190
213
  * | `component` | {@linkcode ComponentHandle} | The scene component to read |
191
214
  */
215
+ /** Options for {@linkcode PluginDesignQueryGeometryApi.getBrep}: `frame` defaults to `"local"`. */
216
+ export type PluginDesignQueryGeometryGetBrepOptions = { frame?: "local" | "world" }
217
+ export const PluginDesignQueryGeometryGetBrepOptions: z.ZodType<
218
+ PluginDesignQueryGeometryGetBrepOptions,
219
+ PluginDesignQueryGeometryGetBrepOptions
220
+ > = z.object({ frame: z.enum(["local", "world"]).optional() })
221
+
192
222
  export const PluginDesignQueryGeometryGetBrepArgs = z.object({
193
223
  component: ComponentHandle,
224
+ options: PluginDesignQueryGeometryGetBrepOptions.optional(),
194
225
  })
195
226
 
196
227
  export type PluginDesignQueryGeometryGetBrepArgs = z.infer<
@@ -1,6 +1,7 @@
1
1
  import * as z from "zod"
2
2
  import { PluginApiReturn } from "../../../types"
3
3
  import { ComponentHandle, BBoxComponents } from "../../../handles"
4
+ import type { MassParametersRecord } from "../../../massParameters"
4
5
  import {
5
6
  PluginSpaceType,
6
7
  PluginMassType,
@@ -1040,6 +1041,55 @@ export abstract class PluginDesignQueryApi {
1040
1041
  staircase: ComponentHandle,
1041
1042
  ): PluginApiReturn<PluginStaircaseParams | null>
1042
1043
 
1044
+ /**
1045
+ * Read a mass's creation-parameter record — the recipe it was built from
1046
+ * (`kind` plus the values that constructor was called with). Present on
1047
+ * masses committed from a `core.geom.create` brep constructor that carries a
1048
+ * recipe: {@linkcode MassParametersRecord} covers the four primitives
1049
+ * (sphere, cylinder, cone, torus), extrusions, revolutions, lofts and sweeps.
1050
+ *
1051
+ * A fillet, chamfer, shell or offset of a recorded solid keeps the record
1052
+ * too: `kind` stays the base constructor and the operation is appended to the
1053
+ * record's `operations` list. Edit any of it with `design.update.parameters`.
1054
+ *
1055
+ * `null` for everything else — drawn masses, imported geometry, booleans,
1056
+ * split parts, `brepFromFaces` / `brepFromMesh`, and results built on a
1057
+ * source that carried no record — and for a mass whose record no longer
1058
+ * matches what is on
1059
+ * screen (a later free-form edit dropped the recipe, or the mass carries a
1060
+ * baked scale). `null` too when the recipe was not recordable: the record
1061
+ * schema has its own caps (a sweep path of at most 512 points, a loft of at
1062
+ * most 32 sections, at most 8 chained `operations`, at most 256 curves in one
1063
+ * profile loop and 64 holes in one contour), and a mass built past one of
1064
+ * them — through a route that does not enforce the matching creator cap —
1065
+ * commits as ordinary geometry carrying no record. The `operations` cap is
1066
+ * the one an agent can walk into by accident: a 9th chained
1067
+ * fillet/chamfer/shell/offset drops the record silently, so the result
1068
+ * commits as ordinary geometry and reads back `null` here. A record is never
1069
+ * reported unless it is still the truth.
1070
+ *
1071
+ * Points and axes come back in **world** coordinates. Pure read — nothing is
1072
+ * created or modified.
1073
+ *
1074
+ * @param component - the mass's {@linkcode ComponentHandle}
1075
+ * @returns the {@linkcode MassParametersRecord}, or `null`
1076
+ *
1077
+ * @examplePrompt What radius is this sphere?
1078
+ * @examplePrompt Is this mass parametric?
1079
+ * @examplePrompt Read back the dome's creation parameters
1080
+ * @examplePrompt How tall was this cylinder made?
1081
+ *
1082
+ * # Example
1083
+ * ```ts
1084
+ * const [mass] = await snaptrude.design.query.listMasses({ isSelected: true })
1085
+ * const params = await snaptrude.design.query.getParameters(mass)
1086
+ * if (params?.kind === "sphere") console.log("radius:", params.values.radius)
1087
+ * ```
1088
+ */
1089
+ public abstract getParameters(
1090
+ component: ComponentHandle,
1091
+ ): PluginApiReturn<MassParametersRecord | null>
1092
+
1043
1093
  /**
1044
1094
  * Get the **union** axis-aligned bounding box enclosing a set of entities.
1045
1095
  *
@@ -9,6 +9,7 @@ import {
9
9
  PluginSpaceUpdateResult,
10
10
  } from "../../entity/space"
11
11
  import { PluginDesignChangeResult } from "../lock"
12
+ import { MassParametersPatch, type MassParametersRecord } from "../../../massParameters"
12
13
  import {
13
14
  PluginBuildableEnvelopePolygonVertex,
14
15
  PluginBuildableEnvelopeSetbackTier,
@@ -16,6 +17,7 @@ import {
16
17
  PluginBuildableEnvelopeUpdateResult,
17
18
  } from "../../entity/buildableEnvelope"
18
19
  import { PluginGridStyleOptions } from "../create/grids"
20
+ import { PluginEntityType } from "../query"
19
21
 
20
22
  /**
21
23
  * Sparse property updates for a space. Only provided fields change.
@@ -137,6 +139,22 @@ export type PluginDesignUpdateStaircaseArgs = z.infer<
137
139
  typeof PluginDesignUpdateStaircaseArgs
138
140
  >
139
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
+
140
158
  /**
141
159
  * Sparse edits for a wall (all optional; at least one required). Only the
142
160
  * fields you provide change. Dimensions are in **engine units** (the same
@@ -239,6 +257,34 @@ export type PluginDesignSetStoreyArgs = z.infer<
239
257
  typeof PluginDesignSetStoreyArgs
240
258
  >
241
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
+
242
288
  /**
243
289
  * The sparse GROUP-LEVEL patch accepted by {@linkcode PluginDesignUpdateApi.grid}.
244
290
  * Only provided fields change. Wing- and line-level updates live on the grids
@@ -258,7 +304,9 @@ export const PluginDesignUpdateGridArgs = z.object({
258
304
  grid: ComponentHandle,
259
305
  patch: PluginGridPatch,
260
306
  })
261
- export type PluginDesignUpdateGridArgs = z.infer<typeof PluginDesignUpdateGridArgs>
307
+ export type PluginDesignUpdateGridArgs = z.infer<
308
+ typeof PluginDesignUpdateGridArgs
309
+ >
262
310
 
263
311
  /**
264
312
  * `snaptrude.design.update` — edit existing scene entities (properties + geometry).
@@ -462,6 +510,95 @@ export abstract class PluginDesignUpdateApi {
462
510
  params: PluginStaircaseParamUpdates,
463
511
  ): PluginApiReturn<PluginDesignChangeResult>
464
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
+
465
602
  /**
466
603
  * Edit a **wall** — its thickness and/or height (the same command the
467
604
  * properties panel's Thickness/Height fields drive) and/or its **wall type**
@@ -743,6 +880,54 @@ export abstract class PluginDesignUpdateApi {
743
880
  storey: number,
744
881
  ): PluginApiReturn<PluginDesignChangeResult>
745
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
+
746
931
  /**
747
932
  * Update an existing parametric buildable envelope — regenerate the zoning
748
933
  * volume from a revised site polygon, setbacks, vertical cap, or FAR and