@snaptrude/plugin-core 0.9.17 → 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 (213) hide show
  1. package/AGENTS.md +231 -231
  2. package/CHANGELOG.md +408 -408
  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 +32 -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/shapes.d.ts +2 -2
  59. package/dist/api/presentation/sheets.d.ts +175 -19
  60. package/dist/api/presentation/sheets.d.ts.map +1 -1
  61. package/dist/api/presentation/viewPresets.d.ts +222 -0
  62. package/dist/api/presentation/viewPresets.d.ts.map +1 -0
  63. package/dist/api/presentation/views.d.ts +103 -5
  64. package/dist/api/presentation/views.d.ts.map +1 -1
  65. package/dist/api/presentation/walkthroughs.d.ts +6 -6
  66. package/dist/errors/codes.d.ts +3 -1
  67. package/dist/errors/codes.d.ts.map +1 -1
  68. package/dist/index.cjs +3732 -2383
  69. package/dist/index.cjs.map +1 -1
  70. package/dist/index.js +3607 -2383
  71. package/dist/index.js.map +1 -1
  72. package/package.json +42 -42
  73. package/scripts/check-dist-fresh.mjs +22 -22
  74. package/scripts/generate-manifest.mjs +243 -238
  75. package/scripts/generate-manifest.test.mjs +303 -303
  76. package/src/api/analysis/daylight.ts +470 -470
  77. package/src/api/analysis/heatmaps.ts +683 -683
  78. package/src/api/analysis/illuminance.ts +155 -155
  79. package/src/api/analysis/index.ts +61 -61
  80. package/src/api/analysis/shadows.ts +183 -183
  81. package/src/api/analysis/solar.ts +237 -237
  82. package/src/api/analysis/sunlightHours.ts +211 -211
  83. package/src/api/analysis/sunpath.ts +83 -83
  84. package/src/api/analysis/weather.ts +179 -179
  85. package/src/api/core/buildings.ts +485 -485
  86. package/src/api/core/camera/index.ts +696 -667
  87. package/src/api/core/comment/index.ts +376 -376
  88. package/src/api/core/geom/create/index.ts +2139 -2133
  89. package/src/api/core/geom/delete/index.ts +84 -84
  90. package/src/api/core/geom/index.ts +35 -35
  91. package/src/api/core/geom/query/arc.ts +169 -169
  92. package/src/api/core/geom/query/brep.ts +621 -621
  93. package/src/api/core/geom/query/circle.ts +404 -404
  94. package/src/api/core/geom/query/contour.ts +540 -540
  95. package/src/api/core/geom/query/curve.ts +1102 -1102
  96. package/src/api/core/geom/query/edge.ts +130 -130
  97. package/src/api/core/geom/query/face.ts +508 -508
  98. package/src/api/core/geom/query/halfedge.ts +210 -210
  99. package/src/api/core/geom/query/index.ts +63 -63
  100. package/src/api/core/geom/query/profile.ts +473 -473
  101. package/src/api/core/geom/query/vertex.ts +205 -205
  102. package/src/api/core/geom/update/contour.ts +323 -317
  103. package/src/api/core/geom/update/curve.ts +209 -209
  104. package/src/api/core/geom/update/index.ts +29 -29
  105. package/src/api/core/geom/update/profile.ts +379 -379
  106. package/src/api/core/groups.ts +521 -521
  107. package/src/api/core/handles/index.ts +234 -234
  108. package/src/api/core/history/index.ts +84 -84
  109. package/src/api/core/index.ts +99 -99
  110. package/src/api/core/io/export/index.ts +126 -126
  111. package/src/api/core/io/import/index.ts +504 -504
  112. package/src/api/core/io/index.ts +42 -42
  113. package/src/api/core/io/job/index.ts +140 -140
  114. package/src/api/core/io/query/index.ts +71 -71
  115. package/src/api/core/io/terrain/index.ts +360 -360
  116. package/src/api/core/io/underlay/index.ts +705 -705
  117. package/src/api/core/layers.ts +540 -540
  118. package/src/api/core/math/index.ts +20 -20
  119. package/src/api/core/math/quat.ts +172 -172
  120. package/src/api/core/math/vec3.ts +179 -179
  121. package/src/api/core/mode/index.ts +108 -108
  122. package/src/api/core/project/index.ts +479 -479
  123. package/src/api/core/proposals/index.ts +570 -570
  124. package/src/api/core/storeys/index.ts +308 -308
  125. package/src/api/core/storeys/layers.ts +339 -339
  126. package/src/api/core/tags.ts +911 -911
  127. package/src/api/core/units/index.ts +164 -164
  128. package/src/api/core/user.ts +46 -46
  129. package/src/api/core/zoom/index.ts +64 -51
  130. package/src/api/design/bim.ts +121 -0
  131. package/src/api/design/boolean/index.ts +181 -179
  132. package/src/api/design/catalog.ts +203 -0
  133. package/src/api/design/create/bulk-items.ts +294 -254
  134. package/src/api/design/create/grids.ts +137 -137
  135. package/src/api/design/create/index.ts +2148 -1980
  136. package/src/api/design/create/opening-fields.ts +40 -37
  137. package/src/api/design/delete/index.ts +75 -74
  138. package/src/api/design/dimensions.ts +453 -453
  139. package/src/api/design/doors/index.ts +516 -500
  140. package/src/api/design/edit/index.ts +54 -54
  141. package/src/api/design/erase/index.ts +151 -151
  142. package/src/api/design/family.ts +1334 -704
  143. package/src/api/design/furniture/index.ts +319 -319
  144. package/src/api/design/grids.ts +237 -237
  145. package/src/api/design/index.ts +304 -274
  146. package/src/api/design/lock.ts +69 -69
  147. package/src/api/design/materials/index.ts +608 -608
  148. package/src/api/design/networks.ts +667 -0
  149. package/src/api/design/query/geometry/index.ts +319 -319
  150. package/src/api/design/query/grids.ts +197 -197
  151. package/src/api/design/query/index.ts +1236 -1226
  152. package/src/api/design/query/referenceLines.ts +52 -52
  153. package/src/api/design/query/spaces.ts +275 -275
  154. package/src/api/design/selection/index.ts +273 -273
  155. package/src/api/design/shared.ts +40 -40
  156. package/src/api/design/transform/index.ts +254 -254
  157. package/src/api/design/types/index.ts +163 -156
  158. package/src/api/design/update/index.ts +1024 -983
  159. package/src/api/design/visibility.ts +143 -143
  160. package/src/api/design/windows/index.ts +351 -333
  161. package/src/api/entity/buildableEnvelope.ts +322 -322
  162. package/src/api/entity/index.ts +30 -30
  163. package/src/api/entity/referenceLine.ts +235 -235
  164. package/src/api/entity/space.ts +612 -612
  165. package/src/api/entity/story.ts +517 -517
  166. package/src/api/index.ts +47 -47
  167. package/src/api/presentation/aiInspiration.ts +1116 -1116
  168. package/src/api/presentation/annotate.ts +417 -385
  169. package/src/api/presentation/camera.ts +289 -289
  170. package/src/api/presentation/diagrams.ts +194 -194
  171. package/src/api/presentation/drawingContract.ts +70 -0
  172. package/src/api/presentation/drawings.ts +1179 -0
  173. package/src/api/presentation/export.ts +134 -108
  174. package/src/api/presentation/import.ts +159 -159
  175. package/src/api/presentation/index.ts +160 -135
  176. package/src/api/presentation/placedViews.ts +1211 -1120
  177. package/src/api/presentation/sections.ts +420 -420
  178. package/src/api/presentation/shapes.ts +274 -274
  179. package/src/api/presentation/sheets.ts +746 -585
  180. package/src/api/presentation/slideshow.ts +134 -134
  181. package/src/api/presentation/tables.ts +84 -84
  182. package/src/api/presentation/viewPresets.ts +241 -0
  183. package/src/api/presentation/views.ts +713 -673
  184. package/src/api/presentation/walkthroughs.ts +1201 -1201
  185. package/src/api/program/adjacency.ts +164 -164
  186. package/src/api/program/areas.ts +408 -408
  187. package/src/api/program/classification.ts +138 -138
  188. package/src/api/program/cores.ts +83 -83
  189. package/src/api/program/departments.ts +517 -517
  190. package/src/api/program/index.ts +73 -73
  191. package/src/api/program/labels.ts +332 -332
  192. package/src/api/program/layout.ts +422 -422
  193. package/src/api/program/metadata.ts +364 -364
  194. package/src/api/program/metrics.ts +120 -120
  195. package/src/api/program/site.ts +748 -748
  196. package/src/api/program/spreadsheet.ts +2698 -2698
  197. package/src/api/workspace/index.ts +612 -612
  198. package/src/errors/codes.ts +143 -136
  199. package/src/errors/envelope.ts +75 -75
  200. package/src/errors/index.ts +21 -21
  201. package/src/errors/plugin-error.ts +134 -134
  202. package/src/handles.ts +297 -297
  203. package/src/host-utils.ts +67 -67
  204. package/src/index.ts +7 -7
  205. package/src/massParameters.ops.ts +246 -246
  206. package/src/massParameters.ts +427 -427
  207. package/src/types.ts +16 -16
  208. package/test/drawingContract.test.mjs +288 -0
  209. package/test/errors.test.mjs +185 -184
  210. package/test/massParameters.test.mjs +952 -952
  211. package/tsconfig.json +22 -22
  212. package/tsup.config.ts +12 -12
  213. package/api-manifest.json +0 -9711
@@ -1,704 +1,1334 @@
1
- import * as z from "zod"
2
- import { PluginApiReturn } from "../../types"
3
- import { BrepHandle, ComponentHandle, Vec3Components } from "../../handles"
4
-
5
- /**
6
- * `snaptrude.design.family.*` — author NATIVE parametric families from a JSON spec.
7
- *
8
- * A parametric family is a reusable, parameter-driven building component: a
9
- * curtain-wall panel, a louvre screen, a balustrade, a shading fin. Unlike the
10
- * rest of `design.*`, you do not build one out of geometry calls — you write a
11
- * JSON **spec** describing its parameters and its geometry rules, and the host
12
- * compiles that into a live parametric definition the user can place, flex and
13
- * save to their library.
14
- *
15
- * ## Workflow
16
- *
17
- * ```
18
- * getReference() read the spec DSL once — grammar, ops, expressions, example
19
- * prepare({ bundle }) validate + compile + show a live ghost; nothing committed
20
- * flex({ values }) re-run at new values; returns the geometry delta
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
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
- *
29
- * ## Read getReference() first
30
- *
31
- * The spec DSL is a closed expression language with its own op set, coordinate
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`)
36
- * and part-naming law. It is deliberately NOT duplicated in these types:
37
- * {@linkcode PluginDesignFamilyApi.getReference} returns the whole reference,
38
- * generated from a family that is actually registered in the host, so the
39
- * documentation cannot drift from what the compiler accepts.
40
- *
41
- * ## The repair contract
42
- *
43
- * {@linkcode PluginDesignFamilyApi.prepare} does **not** throw on an invalid
44
- * spec — it returns `{ ok: false, errors }` where each error carries a JSON
45
- * pointer into the bundle you sent (e.g. `"/root/geometry/2/body/0/w"`). Read
46
- * `errors[0].path`, fix that node, and call `prepare` again. A failed `prepare`
47
- * leaves the previous good draft standing, so it costs nothing but the node you
48
- * broke. Re-authoring from scratch throws away everything already correct.
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
- *
62
- * Accessed via `snaptrude.design.family`.
63
- */
64
- export abstract class PluginDesignFamilyApi {
65
- constructor() {}
66
-
67
- /**
68
- * Read the family-spec DSL reference: grammar, the op table, the expression
69
- * whitelist, the coordinate convention, the repair contract, and a worked
70
- * example taken from a registered family.
71
- *
72
- * Read this before authoring a family for the first time in a session. The
73
- * DSL is a closed language — a guessed op or function is a rejected spec.
74
- *
75
- * @returns The reference text
76
- *
77
- * @examplePrompt How do I write a parametric family spec?
78
- * @examplePrompt Show me the family DSL
79
- *
80
- * # Example
81
- * ```ts
82
- * const { reference } = await snaptrude.design.family.getReference()
83
- * ```
84
- */
85
- public abstract getReference(): PluginApiReturn<PluginFamilyReference>
86
-
87
- /**
88
- * Validate, compile and preview a family spec. Nothing is committed to the
89
- * model or the library — call {@linkcode PluginDesignFamilyApi.place} to put
90
- * it in the model, which also saves it.
91
- *
92
- * Safe to call repeatedly while iterating. On success, check `partCount` and
93
- * `bbox` against what you intended before treating the family as correct — a
94
- * facade that built 3 parts, or a 3 m screen reporting 40 m of height, is a
95
- * visible bug the validator cannot catch.
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
- *
101
- * @param args The spec bundle, as `{ bundle }` or `{ spec }`
102
- * @returns Build report, or `{ ok: false, errors }` with JSON-pointer paths
103
- *
104
- * @examplePrompt Make a louvre screen with adjustable blade count and tilt
105
- * @examplePrompt Design a curtain wall panel family with 8 bays
106
- * @examplePrompt Create a railing family I can adjust
107
- * @examplePrompt Make a pendant lamp family with a domed shade I can resize
108
- *
109
- * # Example
110
- * ```ts
111
- * const r = await snaptrude.design.family.prepare({ bundle })
112
- * if (!r.ok) console.error(r.errors[0].path, r.errors[0].message)
113
- * ```
114
- */
115
- public abstract prepare(
116
- args: PluginDesignFamilyPrepareArgs,
117
- ): PluginApiReturn<PluginFamilyPrepareResult>
118
-
119
- /**
120
- * Re-run the prepared family's recipe at new parameter values.
121
- *
122
- * Values are merged over the current ones and clamped to the parameter
123
- * sheet's min/max, so a partial `{ bays: 6 }` is fine. The returned `changed`
124
- * buckets are the host's own diff of what the geometry did — flexing a
125
- * driving parameter and reporting them demonstrates the family is parametric.
126
- *
127
- * @param args The parameter values to change
128
- * @returns The new build plus the geometry delta
129
- *
130
- * @examplePrompt Change it to 6 bays
131
- * @examplePrompt Show me what happens with 20 blades
132
- *
133
- * # Example
134
- * ```ts
135
- * const { changed } = await snaptrude.design.family.flex({ values: { bays: 6 } })
136
- * console.log(`removed ${changed.removed}, rebuilt ${changed.rebuilt}`)
137
- * ```
138
- */
139
- public abstract flex(
140
- args: PluginDesignFamilyFlexArgs,
141
- ): PluginApiReturn<PluginFamilyFlexResult>
142
-
143
- /**
144
- * The prepared family's spec bundle, or `null` when nothing is prepared.
145
- *
146
- * @returns The draft bundle, or `null`
147
- *
148
- * # Example
149
- * ```ts
150
- * const bundle = await snaptrude.design.family.get()
151
- * ```
152
- */
153
- public abstract get(): PluginApiReturn<PluginFamilyBundle | null>
154
-
155
- /**
156
- * Every parametric family registered in this session — native and imported.
157
- *
158
- * @returns One summary per family
159
- *
160
- * @examplePrompt What families are available?
161
- *
162
- * # Example
163
- * ```ts
164
- * const families = await snaptrude.design.family.list()
165
- * ```
166
- */
167
- public abstract list(): PluginApiReturn<PluginFamilySummary[]>
168
-
169
- /**
170
- * Put a family into the model — either the prepared draft or any family from
171
- * {@linkcode PluginDesignFamilyApi.list}.
172
- *
173
- * Placing the prepared draft saves it to the user's personal library first,
174
- * so its instances survive reload.
175
- *
176
- * Two modes, chosen by whether you pass `position`:
177
- *
178
- * **With `position`** the component is created there and then, undoably. Use
179
- * this when you know where it goes — you were given coordinates, or you are
180
- * copying an existing instance. The returned `component` handle works with
181
- * `design.transform.move` / `.rotate` and with
182
- * {@linkcode PluginDesignFamilyApi.setParameters}.
183
- *
184
- * **Without `position`** it arms placement mode with a ghost on the cursor
185
- * and returns immediately — no geometry yet. The USER clicks to drop it, so
186
- * tell them to. Prefer this when the location is a judgement call.
187
- *
188
- * To COPY a placed component, place its family again at the new position with
189
- * the same `values` — there is no separate copy call.
190
- *
191
- * @param args Which family, where, and at what parameter values
192
- * @returns What was placed; `component` is present only in positioned mode
193
- *
194
- * @examplePrompt Place it in the model
195
- * @examplePrompt Put a louvre screen at the origin
196
- * @examplePrompt Copy that table two metres to the right
197
- *
198
- * # Example
199
- * ```ts
200
- * // positioned — geometry exists when this resolves
201
- * const { component } = await snaptrude.design.family.place({
202
- * id: "revit:gypsum-stud-partition~90ad72",
203
- * position: { x: 0, y: 0, z: 0 },
204
- * rotation: 90,
205
- * })
206
- * await snaptrude.design.transform.move([component], { x: 2, y: 0, z: 0 })
207
- *
208
- * // interactive — the user clicks
209
- * await snaptrude.design.family.place()
210
- * ```
211
- */
212
- public abstract place(
213
- args?: PluginDesignFamilyPlaceArgs,
214
- ): PluginApiReturn<PluginFamilyPlaceResult>
215
-
216
- /**
217
- * Read a placed component's current parameter values and its sheet.
218
- *
219
- * Use before {@linkcode PluginDesignFamilyApi.setParameters} so you change
220
- * what is actually there: parameter KEYS are the family's own, not the
221
- * label the user says, and a Revit-imported family carries parameters that
222
- * do not drive geometry at all.
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
- *
227
- * @param args The component to read
228
- * @returns Its definition, current values, and the parameter sheet
229
- *
230
- * @examplePrompt What size is that table?
231
- * @examplePrompt Show me the parameters on this partition
232
- *
233
- * # Example
234
- * ```ts
235
- * const { values, parameters } = await snaptrude.design.family.getParameters({ component })
236
- * ```
237
- */
238
- public abstract getParameters(
239
- args: PluginDesignFamilyGetParametersArgs,
240
- ): PluginApiReturn<PluginFamilyInstanceParameters | null>
241
-
242
- /**
243
- * Change a placed component's parameters and rebuild its geometry in place.
244
- *
245
- * Partial: pass only what changes. The component keeps its position,
246
- * rotation and identity — this is an edit, not a replace, and it is undoable.
247
- *
248
- * Only parameters with `drivesGeometry` actually move the mesh. Setting one
249
- * that does not is accepted and stored (it round-trips to Revit) but nothing
250
- * visible happens — say so rather than claiming a change the user cannot see.
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
- *
257
- * @param args The component and the values to change
258
- * @returns The values now in effect
259
- *
260
- * @examplePrompt Make that table seat 8
261
- * @examplePrompt Change the stud spacing to 400
262
- *
263
- * # Example
264
- * ```ts
265
- * await snaptrude.design.family.setParameters({ component, values: { seats: 8 } })
266
- * ```
267
- */
268
- public abstract setParameters(
269
- args: PluginDesignFamilySetParametersArgs,
270
- ): PluginApiReturn<PluginFamilyInstanceParameters>
271
-
272
- /**
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.
278
- *
279
- * @param args Optional library name and team scope
280
- * @returns The catalogued definition's id and version
281
- *
282
- * @examplePrompt Save this as a family called Vertical Louvre
283
- * @examplePrompt Add it to my library
284
- *
285
- * # Example
286
- * ```ts
287
- * const { definitionId } = await snaptrude.design.family.create({ name: "Louvre Screen" })
288
- * ```
289
- */
290
- public abstract create(
291
- args?: PluginDesignFamilyCreateArgs,
292
- ): PluginApiReturn<PluginFamilyCreateResult>
293
-
294
- /**
295
- * Remove a family from the library: its catalog row is archived, so it
296
- * leaves the Families panel. Placed instances are untouched — in this
297
- * project and every other — because archived pins keep resolving. Pass
298
- * `instances: true` to ALSO delete this project's instances in one undoable
299
- * command; do that only when the user asks for the instances to go.
300
- *
301
- * Omit `id` to target the prepared draft. `id` may be a draft id, a saved
302
- * catalog id, or any family from `list()`.
303
- *
304
- * @param args Optional family id, and whether to delete instances too
305
- * @returns Whether a library row was archived and how many instances were removed
306
- *
307
- * @examplePrompt Delete the Louvre Screen family from my library
308
- * @examplePrompt Get rid of that bollard and every copy of it in here
309
- *
310
- * # Example
311
- * ```ts
312
- * await snaptrude.design.family.delete({ id: "revit:steel-bollard~bef047" })
313
- * await snaptrude.design.family.delete({ id: "revit:steel-bollard~bef047", instances: true })
314
- * ```
315
- */
316
- public abstract delete(
317
- args?: PluginDesignFamilyDeleteArgs,
318
- ): PluginApiReturn<PluginFamilyDeleteResult>
319
- }
320
-
321
- // ---------------------------------------------------------------------------
322
- // Result types
323
- // ---------------------------------------------------------------------------
324
-
325
- /** The family-spec DSL reference returned by `getReference`. */
326
- export interface PluginFamilyReference {
327
- /** Grammar, op table, expression whitelist, repair contract, worked example. */
328
- reference: string
329
- }
330
-
331
- /**
332
- * A family spec bundle. `root` is the family; `deps` carries every spec
333
- * reachable through an `instance` op, keyed by spec id, so a saved family is
334
- * self-contained and reloads anywhere.
335
- *
336
- * The internal shape of a spec is documented by
337
- * {@linkcode PluginDesignFamilyApi.getReference}, not by these types — it is a
338
- * data DSL whose grammar changes independently of this API surface.
339
- */
340
- export interface PluginFamilyBundle {
341
- root: Record<string, unknown>
342
- deps?: Record<string, Record<string, unknown>>
343
- }
344
-
345
- /** Axis-aligned extents of a built family. Snaptrude units (1 SU = 254 mm), +Y up. */
346
- export interface PluginFamilyBBox {
347
- min: [number, number, number]
348
- max: [number, number, number]
349
- /** `[width, height, depth]` in Snaptrude units — convert with `core.units.convert` for display. */
350
- size: [number, number, number]
351
- }
352
-
353
- /** One row of a family's parameter sheet. */
354
- export interface PluginFamilyParameter {
355
- key: string
356
- label: string
357
- type: "length" | "integer" | "enum" | "text" | "boolean"
358
- /** Current value. `length` (Snaptrude units) and `integer` are numbers; the rest are strings. */
359
- value: string | number
360
- default: string | number
361
- /**
362
- * Whether this parameter actually drives geometry — INFERRED by the host from
363
- * which names the spec's expressions reference. `false` means the value is
364
- * stored and round-tripped but moves nothing.
365
- */
366
- drivesGeometry: boolean
367
- min?: number
368
- max?: number
369
- options?: readonly string[]
370
- }
371
-
372
- /**
373
- * A validation diagnostic. `path` is a JSON pointer into the submitted bundle —
374
- * fix that node and re-`prepare`.
375
- */
376
- export interface PluginFamilyDiagnostic {
377
- /** JSON pointer, e.g. `"/root/geometry/2/body/0/w"`. */
378
- path: string
379
- code:
380
- | "schema"
381
- | "bad-expression"
382
- | "unknown-name"
383
- | "unknown-def"
384
- | "cycle"
385
- | "duplicate-name"
386
- | "unbounded-repeat"
387
- | "depth"
388
- | "bad-dimension"
389
- | "proportion"
390
- message: string
391
- hint?: string
392
- }
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
-
413
- /** What `prepare` reports. */
414
- export interface PluginFamilyPrepareResult {
415
- /** `false` means the spec was rejected — read `errors`, fix, re-prepare. */
416
- ok: boolean
417
- familyId?: string
418
- version?: number
419
- label?: string
420
- /** How many parts the family built. Sanity-check against intent. */
421
- partCount?: number
422
- /** Extents in Snaptrude units. Sanity-check against intent. */
423
- bbox?: PluginFamilyBBox
424
- evalMs?: number
425
- parameters?: PluginFamilyParameter[]
426
- /** Computed quantities, when the spec declares a `takeoff` block. */
427
- takeoff?: Record<string, number | string>
428
- /** The first few part names — enough to check the naming came out right. */
429
- sampleParts?: string[]
430
- /** Present when `snapshot` was asked for. */
431
- snapshot?: PluginFamilySnapshot
432
- errors: PluginFamilyDiagnostic[]
433
- warnings: PluginFamilyDiagnostic[]
434
- /** Set when the bundle validated but threw while building. */
435
- error?: string
436
- }
437
-
438
- /**
439
- * The host's diff of what a flex did to the geometry. This is the evidence that
440
- * a family is parametric.
441
- */
442
- export interface PluginFamilyChangeBuckets {
443
- added: number
444
- removed: number
445
- moved: number
446
- rebuilt: number
447
- addedNames: string[]
448
- removedNames: string[]
449
- }
450
-
451
- /** What `flex` reports. */
452
- export interface PluginFamilyFlexResult {
453
- ok: boolean
454
- /** The RESOLVED values after merge + clamping — not necessarily what was sent. */
455
- values?: Record<string, number | string>
456
- partCount?: number
457
- bbox?: PluginFamilyBBox
458
- evalMs?: number
459
- changed?: PluginFamilyChangeBuckets
460
- takeoff?: Record<string, number | string>
461
- /** Proportion warnings at the flexed values (code `proportion`): a part thinner than 0.5 mm, a part or family over 50 m, a family under 10 mm — the signature of a unit slip. */
462
- warnings?: PluginFamilyDiagnostic[]
463
- /** Present when `snapshot` was asked for. */
464
- snapshot?: PluginFamilySnapshot
465
- error?: string
466
- }
467
-
468
- /** One family available to place — from the session registry or the catalog. */
469
- export interface PluginFamilySummary {
470
- id: string
471
- version: number
472
- label: string
473
- category?: string
474
- /** Number of rows on the family's parameter sheet. */
475
- parameters: number
476
- /**
477
- * Where the family came from, which decides how far you can edit it.
478
- *
479
- * `"native"` — authored from a JSON spec through this API. Its recipe is
480
- * data, so every parameter genuinely re-runs the geometry.
481
- *
482
- * `"revit"` — imported from a Revit family. Parameters exist and are
483
- * editable, but only those the importer could bind to geometry actually
484
- * change the mesh; the rest are carried for round-tripping. Check
485
- * `drivesGeometry` on the parameter before promising the user a change.
486
- */
487
- kind: "native" | "revit"
488
- }
489
-
490
- /** What `place` reports. */
491
- export interface PluginFamilyPlaceResult {
492
- ok: boolean
493
- familyId?: string
494
- version?: number
495
- /**
496
- * The created component — present ONLY when `position` was given. In
497
- * interactive mode nothing exists yet, so there is nothing to hand back;
498
- * the user has not clicked.
499
- */
500
- component?: ComponentHandle
501
- error?: string
502
- }
503
-
504
- /** A placed component's parameter state, from `getParameters` / `setParameters`. */
505
- export interface PluginFamilyInstanceParameters {
506
- /** The definition this instance is pinned to. */
507
- familyId: string
508
- version: number
509
- label: string
510
- /** Current values, keyed by parameter key. */
511
- values: Record<string, number | string>
512
- /** The sheet: what each key means, its bounds, and whether it moves geometry. */
513
- parameters: PluginFamilyParameter[]
514
- /** The placed parts; kernel parts carry their exact `brep`. */
515
- parts?: PluginFamilyPart[]
516
- /** Present when `getParameters` was asked for a `snapshot`. */
517
- snapshot?: PluginFamilySnapshot
518
- }
519
-
520
- /** What `create` reports. */
521
- export interface PluginFamilyCreateResult {
522
- ok: boolean
523
- definitionId?: string
524
- version?: number
525
- guid?: string
526
- /** True when the save also dropped one instance into the model (nothing was placed yet). */
527
- placed?: boolean
528
- /** Host guidance for the caller, e.g. that an instance was placed at the view centre. */
529
- hint?: string
530
- error?: string
531
- }
532
-
533
- /** What `delete` reports. */
534
- export interface PluginFamilyDeleteResult {
535
- ok: boolean
536
- familyId?: string
537
- /** Instances deleted from the scene (only with `instances: true`), in one undoable command. */
538
- removedInstances?: number
539
- /** True when a library row was archived; false for an unsaved draft. */
540
- archived?: boolean
541
- error?: string
542
- }
543
-
544
- // ---------------------------------------------------------------------------
545
- // Argument schemas
546
- // ---------------------------------------------------------------------------
547
-
548
- /**
549
- * A spec bundle as it crosses the boundary: `root` plus optional `deps`.
550
- *
551
- * Loose by design — the spec grammar is a data DSL validated by the host's own
552
- * validator, which returns JSON-pointer diagnostics far more useful than a Zod
553
- * shape error. This schema only guarantees the envelope is an object.
554
- */
555
- export const PluginFamilyBundleSchema = z.object({
556
- root: z.record(z.string(), z.unknown()),
557
- deps: z.record(z.string(), z.record(z.string(), z.unknown())).optional(),
558
- })
559
-
560
- /**
561
- * Arguments for {@linkcode PluginDesignFamilyApi.prepare}.
562
- *
563
- * | Property | Type | Description |
564
- * |---|---|---|
565
- * | `bundle` | {@linkcode PluginFamilyBundle} | The family spec bundle |
566
- * | `spec` | {@linkcode PluginFamilyBundle} | Alias for `bundle` |
567
- * | `snapshot` | {@linkcode PluginFamilySnapshotOption} | Also return a picture of the built family |
568
- *
569
- * Exactly one of `bundle` / `spec` is required. A bare `FamilySpec` (no `root`
570
- * wrapper) is also accepted by the host and wrapped for the caller.
571
- */
572
- /**
573
- * Ask for a picture with the result: `true` for the default sheet (iso, front,
574
- * right and top tiles with a dimension line), or an object to choose views and
575
- * tile size. The picture is an isolated offscreen render of the family alone —
576
- * the viewport is not moved — and comes back as a PNG data URL in the result's
577
- * `snapshot.image`. Through the agent's `run_worker`, return the result and the
578
- * image reaches the model as an image.
579
- */
580
- export const PluginFamilySnapshotOption = z.union([
581
- z.boolean(),
582
- z.object({
583
- views: z.array(z.enum(["iso", "front", "right", "top"])).optional(),
584
- /** Pixels per tile (square), 64–2048; default 512. */
585
- tileSize: z.number().optional(),
586
- }),
587
- ])
588
- export type PluginFamilySnapshotOption = z.infer<typeof PluginFamilySnapshotOption>
589
-
590
- /** The picture block on prepare / flex / getParameters results when `snapshot` was asked for. */
591
- export interface PluginFamilySnapshot {
592
- ok: boolean
593
- /** PNG data URL of the composed sheet. */
594
- image?: string
595
- views?: ("iso" | "front" | "right" | "top")[]
596
- /** [width, height, depth] in Snaptrude units. */
597
- size?: [number, number, number]
598
- ms?: number
599
- /** Set when the picture could not be taken (e.g. the legacy renderer); the call itself still succeeded. */
600
- error?: string
601
- }
602
-
603
- export const PluginDesignFamilyPrepareArgs = z.object({
604
- bundle: PluginFamilyBundleSchema.optional(),
605
- spec: PluginFamilyBundleSchema.optional(),
606
- snapshot: PluginFamilySnapshotOption.optional(),
607
- })
608
- export type PluginDesignFamilyPrepareArgs = z.infer<typeof PluginDesignFamilyPrepareArgs>
609
-
610
- /**
611
- * Arguments for {@linkcode PluginDesignFamilyApi.flex}.
612
- *
613
- * | Property | Type | Description |
614
- * |---|---|---|
615
- * | `values` | `Record<string, number \| string>` | Parameter values to change (partial) |
616
- * | `snapshot` | {@linkcode PluginFamilySnapshotOption} | Also return a picture of the flexed family |
617
- */
618
- export const PluginDesignFamilyFlexArgs = z.object({
619
- values: z.record(z.string(), z.union([z.number(), z.string()])),
620
- snapshot: PluginFamilySnapshotOption.optional(),
621
- })
622
- export type PluginDesignFamilyFlexArgs = z.infer<typeof PluginDesignFamilyFlexArgs>
623
-
624
- /**
625
- * Arguments for {@linkcode PluginDesignFamilyApi.create}.
626
- *
627
- * | Property | Type | Description |
628
- * |---|---|---|
629
- * | `name` | `string` | Library name; defaults to the spec's own label |
630
- * | `teamId` | `string` | Team library to save into; omit for personal |
631
- */
632
- export const PluginDesignFamilyCreateArgs = z.object({
633
- name: z.string().optional(),
634
- teamId: z.string().optional(),
635
- })
636
- export type PluginDesignFamilyCreateArgs = z.infer<typeof PluginDesignFamilyCreateArgs>
637
-
638
- /**
639
- * Arguments for {@linkcode PluginDesignFamilyApi.delete}.
640
- *
641
- * | Property | Type | Description |
642
- * |---|---|---|
643
- * | `id` | `string` | Family to remove; omit for the prepared draft |
644
- * | `instances` | `boolean` | Also delete this project's placed instances; default false |
645
- */
646
- export const PluginDesignFamilyDeleteArgs = z.object({
647
- id: z.string().optional(),
648
- instances: z.boolean().optional(),
649
- })
650
- export type PluginDesignFamilyDeleteArgs = z.infer<typeof PluginDesignFamilyDeleteArgs>
651
-
652
- /**
653
- * Arguments for {@linkcode PluginDesignFamilyApi.place}.
654
- *
655
- * | Property | Type | Description |
656
- * |---|---|---|
657
- * | `id` | `string` | Family to place, from {@linkcode PluginDesignFamilyApi.list}. Omit to place the prepared draft |
658
- * | `version` | `number` | Exact version; defaults to the newest known |
659
- * | `position` | `{ x, y, z }` | Place HERE, without user interaction. Omit to arm the cursor instead |
660
- * | `rotation` | `number` | Yaw in degrees about +Y. Only with `position` |
661
- * | `values` | `Record<string, number \| string>` | Parameter values for THIS instance |
662
- * | `storey` | `number` | Storey to place on; defaults to the active one |
663
- */
664
- export const PluginDesignFamilyPlaceArgs = z.object({
665
- id: z.string().optional(),
666
- version: z.number().int().positive().optional(),
667
- position: Vec3Components.optional(),
668
- rotation: z.number().optional(),
669
- values: z.record(z.string(), z.union([z.number(), z.string()])).optional(),
670
- storey: z.number().int().optional(),
671
- })
672
- export type PluginDesignFamilyPlaceArgs = z.infer<typeof PluginDesignFamilyPlaceArgs>
673
-
674
- /**
675
- * Arguments for {@linkcode PluginDesignFamilyApi.getParameters}.
676
- *
677
- * | Property | Type | Description |
678
- * |---|---|---|
679
- * | `component` | {@linkcode ComponentHandle} | A placed parametric component |
680
- */
681
- export const PluginDesignFamilyGetParametersArgs = z.object({
682
- component: ComponentHandle,
683
- /** Also return a picture of this instance (its recipe re-run at its values, with its paint). */
684
- snapshot: PluginFamilySnapshotOption.optional(),
685
- })
686
- export type PluginDesignFamilyGetParametersArgs = z.infer<
687
- typeof PluginDesignFamilyGetParametersArgs
688
- >
689
-
690
- /**
691
- * Arguments for {@linkcode PluginDesignFamilyApi.setParameters}.
692
- *
693
- * | Property | Type | Description |
694
- * |---|---|---|
695
- * | `component` | {@linkcode ComponentHandle} | A placed parametric component |
696
- * | `values` | `Record<string, number \| string>` | Values to change (partial) |
697
- */
698
- export const PluginDesignFamilySetParametersArgs = z.object({
699
- component: ComponentHandle,
700
- values: z.record(z.string(), z.union([z.number(), z.string()])),
701
- })
702
- export type PluginDesignFamilySetParametersArgs = z.infer<
703
- typeof PluginDesignFamilySetParametersArgs
704
- >
1
+ import * as z from "zod"
2
+ import { PluginApiReturn } from "../../types"
3
+ import {
4
+ BrepHandle,
5
+ ComponentHandle,
6
+ Vec3Components,
7
+ Vec3Handle,
8
+ } from "../../handles"
9
+ import { NetworkFamilyContract } from "./networks"
10
+
11
+ /**
12
+ * `snaptrude.design.family.*` — author NATIVE parametric families from a JSON spec.
13
+ *
14
+ * A parametric family is a reusable, parameter-driven building component: a
15
+ * curtain-wall panel, a louvre screen, a balustrade, a shading fin. Unlike the
16
+ * rest of `design.*`, you do not build one out of geometry calls — you write a
17
+ * JSON **spec** describing its parameters and its geometry rules, and the host
18
+ * compiles that into a live parametric definition the user can place, flex and
19
+ * save to their library.
20
+ *
21
+ * ## Generic component workflow
22
+ *
23
+ * ```
24
+ * getReference() read the spec DSL once — grammar, ops, expressions, examples
25
+ * getExamples() worked example families by discipline; getExamples({ id }) for one
26
+ * prepare({ bundle }) validate + compile; nothing committed, nothing in the viewport
27
+ * flex({ values }) re-run at new values; returns the geometry delta
28
+ * place({ position }) put it in the model — saves the family as the first instance drops
29
+ * place() save the family, then arm placement — the USER clicks to drop it
30
+ * create() save an EDIT of a saved family — the app asks the user
31
+ * create({ teamId }) share with a team; a save with nothing placed also drops one instance
32
+ * ```
33
+ *
34
+ * `place` saves the prepared family to the user's personal library, so it
35
+ * survives reload with no extra step; finish a NEW generic component on
36
+ * `place`, not on `create` (`create` ends only an edit of a saved family, below).
37
+ *
38
+ * Architectural types use `create({ name })` to save the prepared recipe.
39
+ * Saving does not assign a family to a native host.
40
+ * Read `design.catalog` and pass the saved `{ id, version }` reference to the
41
+ * native `design.create` method with its contour, wall profile, or straight axis.
42
+ * Standalone placement does not create a native host. Selecting a Draw type
43
+ * does not register an API type. Native creator values use mm, while this
44
+ * generic family API uses SU. Read `family_authoring` for the full workflow.
45
+ *
46
+ * ## Door and window templates
47
+ *
48
+ * Start with `getTemplate({ kind: "door" | "window", subType? })`, edit the
49
+ * recipe's style while keeping its contract, then `prepare` → `flex` → `create`.
50
+ * Place the saved family with
51
+ * `design.create.door(familyId, hostWall, position, { version, values, hinge }, facing)`
52
+ * or `design.create.window` with the same positional signature. Positions,
53
+ * hinge and facing are vec3 handles; values use the authored parameter keys.
54
+ * Templates produce ParametricComponents, not separate OpeningComponents. Read
55
+ * and edit their instances through `getParameters` / `setParameters`.
56
+ * The contract maps opening dimensions to parameter keys; do not assume the
57
+ * recipe names its width parameter `width`. Generic family lengths use SU.
58
+ * Hosted door/window `create({ name })` saves without placing a standalone
59
+ * sample. Supply the host and position in a subsequent placement.
60
+ *
61
+ * ## Changing a saved family
62
+ *
63
+ * Open it with `get({ id })` or `get({ component })`, edit with `prepare` /
64
+ * `flex`, then `create()`. The app — not the agent — asks the user whether to
65
+ * update that family (this project's placed copies move to the new version) or
66
+ * save a new family. Do not ask this yourself; report the returned `decision`.
67
+ *
68
+ * ## Read getReference() first
69
+ *
70
+ * The spec DSL is a closed expression language with its own op set, coordinate
71
+ * convention (**+Y up, Snaptrude units** — the scene's own unit, 1 SU = 254 mm,
72
+ * the same unit `core.units.getBabylonType()` names and every geometry API
73
+ * takes; literals inside spec expressions may carry a unit suffix such as
74
+ * `600mm` or `2ft`)
75
+ * and part-naming law. It is deliberately NOT duplicated in these types:
76
+ * {@linkcode PluginDesignFamilyApi.getReference} returns the whole reference,
77
+ * generated from a family that is actually registered in the host, so the
78
+ * documentation cannot drift from what the compiler accepts.
79
+ *
80
+ * ## The repair contract
81
+ *
82
+ * {@linkcode PluginDesignFamilyApi.prepare} does **not** throw on an invalid
83
+ * spec — it returns `{ ok: false, errors }` where each error carries a JSON
84
+ * pointer into the bundle you sent (e.g. `"/root/geometry/2/body/0/w"`). Read
85
+ * `errors[0].path`, fix that node, and call `prepare` again. A failed `prepare`
86
+ * leaves the previous good draft standing, so it costs nothing but the node you
87
+ * broke. Re-authoring from scratch throws away everything already correct.
88
+ *
89
+ * ## Curved parts are exact
90
+ *
91
+ * A part whose sketch has a curved edge — any `revolve`, or an `extrude` /
92
+ * `sweep` / `loft` with an arc or circle in its profile (or an arc in a sweep
93
+ * path) — is built by the exact geometry kernel, the same one behind curved
94
+ * masses. Straight-edged parts keep the fast mesh builder. `getParameters` on a
95
+ * placed component reports which built each part in `parts[].engine`, and a
96
+ * kernel part carries a `brep`: read it with `core.geom.query.brep.*` and
97
+ * `core.geom.query.face.getSurfaceKind` — the same face-kind check as for a
98
+ * curved mass. If the kernel is unavailable a part falls back to the mesh
99
+ * builder (`engine: "mesh"`) rather than failing the family.
100
+ *
101
+ * Accessed via `snaptrude.design.family`.
102
+ */
103
+ export abstract class PluginDesignFamilyApi {
104
+ constructor() {}
105
+
106
+ /**
107
+ * Read the family-spec DSL reference: grammar, the op table, the expression
108
+ * whitelist, the coordinate convention, the repair contract, and a worked
109
+ * example taken from a registered family.
110
+ *
111
+ * Read this before authoring a family for the first time in a session. The
112
+ * DSL is a closed language — a guessed op or function is a rejected spec.
113
+ *
114
+ * @param args Optional section, or its bare name. Omit for the core reference.
115
+ * Fetch architectural, networks or relations only when the family needs them.
116
+ * @returns The reference text
117
+ *
118
+ * @examplePrompt How do I write a parametric family spec?
119
+ * @examplePrompt Show me the family DSL
120
+ *
121
+ * # Example
122
+ * ```ts
123
+ * const { reference } = await snaptrude.design.family.getReference()
124
+ * ```
125
+ */
126
+ public abstract getReference(
127
+ args?: PluginDesignFamilyGetReferenceArgs | PluginFamilyReferenceSection,
128
+ ): PluginApiReturn<PluginFamilyReference>
129
+
130
+ /**
131
+ * Worked example families to learn from: real, tested bundles by discipline
132
+ * (architecture, structure, MEP, furniture). With no `id`, returns the
133
+ * catalogue — each example's id, label, discipline and the patterns it
134
+ * teaches — filtered by `discipline` / `tag` when given. With an `id`,
135
+ * returns that example's full bundle. Fetch the one closest to the task
136
+ * before authoring and borrow its patterns; examples are patterns to
137
+ * combine, not templates to copy whole.
138
+ *
139
+ * @param args - `{ id }` for one bundle, or `{ discipline?, tag? }` to list
140
+ * @returns The catalogue, or one example with its bundle
141
+ *
142
+ * @examplePrompt Show me an example of a structural family
143
+ * @examplePrompt How is a hosted door family written?
144
+ *
145
+ * # Example
146
+ * ```ts
147
+ * const list = await snaptrude.design.family.getExamples({ discipline: "mep" })
148
+ * const ex = await snaptrude.design.family.getExamples({ id: list[0].id })
149
+ * ```
150
+ */
151
+ public abstract getExamples(
152
+ args?: PluginDesignFamilyGetExamplesArgs | string,
153
+ ): PluginApiReturn<PluginFamilyExampleSummary[] | PluginFamilyExample>
154
+
155
+ /**
156
+ * Get a validated starter recipe for a door or window, with an opening
157
+ * contract, host wall-thickness binding and role-named parts. Edit profiles,
158
+ * panels, glazing, hardware and materials while preserving the contract.
159
+ * Use `prepare` / `flex` to check the recipe, then `create` to save it and
160
+ * `design.create.door(familyId, hostWall, position, { version, values }, facing)`
161
+ * (or `window`) to place it. Edit the resulting ParametricComponent through
162
+ * `getParameters` / `setParameters`, not a separate opening API.
163
+ *
164
+ * `subType` defaults to `singleDoor` / `fixedWindow`. An unsupported subType
165
+ * returns the kind's default template, a note in `guidance`, and `available`
166
+ * templates. Start from the closest recipe and set `root.subType` to your token.
167
+ *
168
+ * @param args The opening kind and optional subtype token
169
+ * @returns The bundle, parameter-key contract, subtype, guidance and available templates
170
+ * @example
171
+ * const { bundle, contract } = await snaptrude.design.family.getTemplate({
172
+ * kind: "door", subType: "doubleDoor",
173
+ * })
174
+ * // Flex the authored width key, not an assumed parameter name (lengths in SU).
175
+ * await snaptrude.design.family.prepare({ bundle })
176
+ * await snaptrude.design.family.flex({ values: { [contract.width]: 4 } })
177
+ */
178
+ public abstract getTemplate(
179
+ args: PluginDesignFamilyGetTemplateArgs,
180
+ ): PluginApiReturn<PluginFamilyTemplateResult>
181
+
182
+ /**
183
+ * Validate, compile and preview a family spec. Nothing is committed to the
184
+ * model or the library — call {@linkcode PluginDesignFamilyApi.place} to put
185
+ * it in the model, which also saves it.
186
+ *
187
+ * Safe to call repeatedly while iterating. On success, check `partCount` and
188
+ * `bbox` against what you intended before treating the family as correct — a
189
+ * facade that built 3 parts, or a 3 m screen reporting 40 m of height, is a
190
+ * visible bug the validator cannot catch.
191
+ *
192
+ * Lengths — the spec's bare numbers, `bbox`, the sheet's `length` values — are
193
+ * Snaptrude units: the same 254 mm unit as the kernel's internal (`"babylon"`)
194
+ * unit. A unit suffix (`600mm`) is legal only inside a spec expression.
195
+ *
196
+ * @param args The spec bundle, as `{ bundle }` or `{ spec }`
197
+ * @returns Build report, or `{ ok: false, errors }` with JSON-pointer paths
198
+ *
199
+ * @examplePrompt Make a louvre screen with adjustable blade count and tilt
200
+ * @examplePrompt Design a curtain wall panel family with 8 bays
201
+ * @examplePrompt Create a railing family I can adjust
202
+ * @examplePrompt Make a pendant lamp family with a domed shade I can resize
203
+ *
204
+ * # Example
205
+ * ```ts
206
+ * const r = await snaptrude.design.family.prepare({ bundle })
207
+ * if (!r.ok) console.error(r.errors[0].path, r.errors[0].message)
208
+ * ```
209
+ */
210
+ public abstract prepare(
211
+ args: PluginDesignFamilyPrepareArgs,
212
+ ): PluginApiReturn<PluginFamilyPrepareResult>
213
+
214
+ /**
215
+ * Re-run the prepared family's recipe at new parameter values.
216
+ *
217
+ * Values are merged over the current ones and clamped to the parameter
218
+ * sheet's min/max, so a partial `{ bays: 6 }` is fine. The returned `changed`
219
+ * buckets are the host's own diff of what the geometry did — flexing a
220
+ * driving parameter and reporting them demonstrates the family is parametric.
221
+ *
222
+ * @param args The parameter values to change. Omit values (or pass an empty
223
+ * record) to report or photograph the current draft without rebuilding it.
224
+ * @returns The new build plus the geometry delta
225
+ *
226
+ * @examplePrompt Change it to 6 bays
227
+ * @examplePrompt Show me what happens with 20 blades
228
+ *
229
+ * # Example
230
+ * ```ts
231
+ * const { changed } = await snaptrude.design.family.flex({ values: { bays: 6 } })
232
+ * console.log(`removed ${changed.removed}, rebuilt ${changed.rebuilt}`)
233
+ * ```
234
+ */
235
+ public abstract flex(
236
+ args?: PluginDesignFamilyFlexArgs,
237
+ ): PluginApiReturn<PluginFamilyFlexResult>
238
+
239
+ /**
240
+ * A family's spec bundle: the prepared draft's, or — to CHANGE a saved
241
+ * family — that family's recipe, opened as the new draft.
242
+ *
243
+ * - No arguments: the prepared draft, or `null` when nothing is prepared.
244
+ * - `{ id, version? }`: open a saved family from the library (newest version
245
+ * when `version` is omitted).
246
+ * - `{ component }`: open the family (at its version) of that placed copy.
247
+ *
248
+ * Opening makes the recipe the current draft and returns `editing` — which
249
+ * saved family it is. Edit it with `prepare` / `flex`, then call `create`.
250
+ * On that save the APP shows the user its own window asking whether to
251
+ * update the family (every placed copy in this project moves to the new
252
+ * version) or save it as a new family — "new family" is the default. Never
253
+ * ask the user this yourself, and never promise either outcome: report what
254
+ * `create` returns.
255
+ *
256
+ * @param args Optional: which saved family to open
257
+ * @returns The bundle (plus `editing` when it edits a saved family), or `null`
258
+ *
259
+ * @examplePrompt Make the chairs I placed 50 mm wider
260
+ * @examplePrompt Change the Louvre Screen family so its blades are deeper
261
+ *
262
+ * # Example
263
+ * ```ts
264
+ * const draft = await snaptrude.design.family.get()
265
+ * const family = await snaptrude.design.family.get({ component })
266
+ * // family.editing → { familyId, version, name, library }
267
+ * ```
268
+ */
269
+ public abstract get(
270
+ args?: PluginDesignFamilyGetArgs | string,
271
+ ): PluginApiReturn<PluginFamilyGetResult | null>
272
+
273
+ /**
274
+ * Every available parametric family, merging the session registry and catalog.
275
+ *
276
+ * @param args Opt into compact lines for generic discovery. The default stays
277
+ * object-shaped so existing callers retain version, openingKind and subType.
278
+ * @returns Summary objects, or `id | label | category | kind` lines when compact.
279
+ *
280
+ * @examplePrompt What families are available?
281
+ *
282
+ * # Example
283
+ * ```ts
284
+ * const families = await snaptrude.design.family.list()
285
+ * ```
286
+ */
287
+ public abstract list(
288
+ args: PluginDesignFamilyListArgs & { compact: true },
289
+ ): PluginApiReturn<string[]>
290
+ public abstract list(
291
+ args?: PluginDesignFamilyListArgs & { compact?: false },
292
+ ): PluginApiReturn<PluginFamilySummary[]>
293
+ public abstract list(
294
+ args?: PluginDesignFamilyListArgs,
295
+ ): PluginApiReturn<PluginFamilySummary[] | string[]>
296
+
297
+ /**
298
+ * Read one saved catalog definition without opening a draft or placing geometry.
299
+ * Both id and version are required. The catalog checks access on every read.
300
+ * Length defaults and bounds are in mm, ready for network graph overrides;
301
+ * angle parameters retain degrees, identified by each parameter's unit.
302
+ * existing getParameters/place/setParameters continue to use Snaptrude units.
303
+ * Defaults include current catalog overrides, which can change without a new version.
304
+ * No scene, registry, history, or persistence state is changed.
305
+ * Missing/inaccessible pins and invalid recipes fail; no latest-version fallback.
306
+ * This metadata does not certify connectors or engineering compatibility.
307
+ *
308
+ * @param args Exact saved catalog id and version, from list()
309
+ * @returns Compact parameter metadata, not geometry or the recipe bundle
310
+ * @example
311
+ * const definition = await snaptrude.design.family.getDefinition({ id, version })
312
+ */
313
+ public abstract getDefinition(
314
+ args: PluginDesignFamilyGetDefinitionArgs,
315
+ ): PluginApiReturn<PluginFamilyDefinition>
316
+
317
+ /**
318
+ * Put a family into the model — either the prepared draft or any family from
319
+ * {@linkcode PluginDesignFamilyApi.list}.
320
+ *
321
+ * Placing the prepared draft saves it to the user's personal library first,
322
+ * so its instances survive reload.
323
+ *
324
+ * Two modes, chosen by whether you pass `position`:
325
+ *
326
+ * **With `position`** the component is created there and then, undoably. Use
327
+ * this when you know where it goes — you were given coordinates, or you are
328
+ * copying an existing instance. The returned `component` handle works with
329
+ * `design.transform.move` / `.rotate` and with
330
+ * {@linkcode PluginDesignFamilyApi.setParameters}.
331
+ *
332
+ * **Without `position`** it arms placement mode with a ghost on the cursor
333
+ * and returns immediately — no geometry yet. The USER clicks to drop it, so
334
+ * tell them to. Prefer this when the location is a judgement call.
335
+ *
336
+ * To COPY a placed component, place its family again at the new position with
337
+ * the same `values` — there is no separate copy call.
338
+ *
339
+ * HOSTED families (doors, windows — anything with an `opening`, a `host`
340
+ * block, or a void with `cuts: "host"`) take `host`: a wall or a placed
341
+ * family. The family is set into the host's face nearest `position` (the
342
+ * host's middle when omitted), and the host is cut — a system wall by the
343
+ * wall cutter, a family by its own recipe. Host-bound parameters then follow
344
+ * the host (a door's wall thickness follows the wall).
345
+ *
346
+ * @param args Which family, where, and at what parameter values
347
+ * @returns What was placed; `component` is present only in positioned mode
348
+ *
349
+ * @examplePrompt Place it in the model
350
+ * @examplePrompt Put a louvre screen at the origin
351
+ * @examplePrompt Copy that table two metres to the right
352
+ *
353
+ * # Example
354
+ * ```ts
355
+ * // positioned — geometry exists when this resolves
356
+ * const { component } = await snaptrude.design.family.place({
357
+ * id: "revit:gypsum-stud-partition~90ad72",
358
+ * position: { x: 0, y: 0, z: 0 },
359
+ * rotation: 90,
360
+ * })
361
+ * await snaptrude.design.transform.move([component], { x: 2, y: 0, z: 0 })
362
+ *
363
+ * // interactive — the user clicks
364
+ * await snaptrude.design.family.place()
365
+ *
366
+ * // hosted — a door in a wall, near a point along it
367
+ * await snaptrude.design.family.place({ id: doorId, host: wall, position: { x: 3, y: 0, z: 0 } })
368
+ * ```
369
+ */
370
+ public abstract place(
371
+ args?: PluginDesignFamilyPlaceArgs,
372
+ ): PluginApiReturn<PluginFamilyPlaceResult>
373
+
374
+ /**
375
+ * Read a placed component's current parameter values and its sheet.
376
+ *
377
+ * Use before {@linkcode PluginDesignFamilyApi.setParameters} so you change
378
+ * what is actually there: parameter KEYS are the family's own, not the
379
+ * label the user says, and a Revit-imported family carries parameters that
380
+ * do not drive geometry at all.
381
+ *
382
+ * `length` values are Snaptrude units: the same 254 mm unit as the kernel's
383
+ * internal (`"babylon"`) unit — convert with `core.units.convert` to report.
384
+ *
385
+ * @param args The component to read
386
+ * @returns Its definition, current values, and the parameter sheet
387
+ *
388
+ * @examplePrompt What size is that table?
389
+ * @examplePrompt Show me the parameters on this partition
390
+ *
391
+ * # Example
392
+ * ```ts
393
+ * const { values, parameters } = await snaptrude.design.family.getParameters({ component })
394
+ * ```
395
+ */
396
+ public abstract getParameters(
397
+ args: PluginDesignFamilyGetParametersArgs,
398
+ ): PluginApiReturn<PluginFamilyInstanceParameters | null>
399
+
400
+ /**
401
+ * Change a placed component's parameters and rebuild its geometry in place.
402
+ *
403
+ * Partial: pass only what changes. The component keeps its position,
404
+ * rotation and identity — this is an edit, not a replace, and it is undoable.
405
+ *
406
+ * Only parameters with `drivesGeometry` actually move the mesh. Setting one
407
+ * that does not is accepted and stored (it round-trips to Revit) but nothing
408
+ * visible happens — say so rather than claiming a change the user cannot see.
409
+ *
410
+ * NESTED children: `children: { "<path>": { key: value } }` sets a nested
411
+ * child's own values (every copy of it). A value its parent sets is sent up
412
+ * to what it follows when that is a plain relationship; otherwise it is
413
+ * refused with what to change instead (see `refused`).
414
+ *
415
+ * LINKS: `links: { key: { from, expr } }` makes this family's `key` follow
416
+ * another placed object — `from` is a family (it reports its parameters and
417
+ * outputs) or a wall (it reports thickness, length, height); `expr` is an
418
+ * expression over those reports. `null` removes a link. Links that would
419
+ * make objects follow each other in a loop are refused. Values accept unit
420
+ * literals ("600mm").
421
+ *
422
+ * RELATIONS: `relations: { add: [...], remove: ["r1"] }` sets how the family
423
+ * sits against other placed objects or cuts its host:
424
+ * - `attach` — `{ kind: "attach", side: "base" | "top", to: <object> | { level, face? }, offset? }`:
425
+ * sits on a surface, or its top reaches one (reports `reach`, `gap`, `surface`).
426
+ * - `join` — `{ kind: "join", end, to: <family>, toEnd, bindOther? }`: its end moves onto
427
+ * the other's end (reports `angle`, `absAngle`, `mitre`).
428
+ * - `span` — `{ kind: "span", from, to, at? }`: pinned between two objects, running along
429
+ * its +X (reports `distance`, `run`, `rise`, `slope`, `from.*`, `to.*`).
430
+ * - `path` — `{ kind: "path", from? | points, closed? }`: follows a wall's centreline, a
431
+ * family's run or given points (reports `length`; the recipe reads the path).
432
+ * - `cut` — `{ kind: "cut", to: "host" }`: applies recipe voids to the current host.
433
+ * Removing it closes the opening without detaching or deleting the family.
434
+ * Door/window placement adds it. Hosting alone does not cut. No parameter binds.
435
+ * Placement relations take `bind: { <param>: "<expression over its reports>" }`; bound
436
+ * parameters are read-only. A relation with an existing `id` replaces it.
437
+ *
438
+ * `length` values are Snaptrude units: the same 254 mm unit as the kernel's
439
+ * internal (`"babylon"`) unit — convert the user's number with
440
+ * `core.units.convert` first; a bare `400` is 400 SU, not 400 mm. Kernel part
441
+ * breps read before the edit are stale: re-read `getParameters`.
442
+ *
443
+ * @param args The component and the values to change
444
+ * @returns The values now in effect
445
+ *
446
+ * @examplePrompt Make that table seat 8
447
+ * @examplePrompt Change the stud spacing to 400
448
+ *
449
+ * # Example
450
+ * ```ts
451
+ * await snaptrude.design.family.setParameters({ component, values: { seats: 8 } })
452
+ * await snaptrude.design.family.setParameters({ component, children: { chair: { width: "380mm" } } })
453
+ * await snaptrude.design.family.setParameters({
454
+ * component: railing,
455
+ * links: { steps: { from: stair, expr: "steps" }, side: { from: stair, expr: "width - 40mm" } },
456
+ * })
457
+ * await snaptrude.design.family.setParameters({
458
+ * component: post,
459
+ * relations: { add: [{ kind: "attach", side: "top", to: slab, bind: { height: "reach" } }] },
460
+ * })
461
+ * ```
462
+ */
463
+ public abstract setParameters(
464
+ args: PluginDesignFamilySetParametersArgs,
465
+ ): PluginApiReturn<PluginFamilyInstanceParameters>
466
+
467
+ /**
468
+ * Save the prepared bundle to the library — the user's personal library
469
+ * unless `teamId` is given. {@linkcode PluginDesignFamilyApi.place} already
470
+ * saves generic components. Hosted doors/windows save without placement;
471
+ * they need a wall and position through `design.create.door` / `design.create.window`.
472
+ * For a non-hosted family with no placed instance, saving also drops one
473
+ * standalone sample at the current view centre on the active storey
474
+ * (`placed: true`). This includes architectural families: the sample does
475
+ * not assign a family to a native host.
476
+ *
477
+ * When the draft edits a saved family (opened with `get({ id })` /
478
+ * `get({ component })`), or its name is already taken in that library, the
479
+ * APP asks the user in its own window whether to save a new version of that
480
+ * family — every placed copy in this project moves to it, and families
481
+ * nesting it may be offered theirs — or a new family ("new family" is the
482
+ * default). The agent never asks this itself and cannot answer the window.
483
+ * `decision` reports what the user chose; `"cancelled"` means nothing was
484
+ * saved (`ok: false`) and nothing will auto-save until `create` is called again.
485
+ *
486
+ * @param args Optional library name and team scope
487
+ * @returns The catalogued definition's id and version, and what the user chose
488
+ *
489
+ * @examplePrompt Save this as a family called Vertical Louvre
490
+ * @examplePrompt Add it to my library
491
+ *
492
+ * # Example
493
+ * ```ts
494
+ * const { definitionId } = await snaptrude.design.family.create({ name: "Louvre Screen" })
495
+ * ```
496
+ */
497
+ public abstract create(
498
+ args?: PluginDesignFamilyCreateArgs,
499
+ ): PluginApiReturn<PluginFamilyCreateResult>
500
+
501
+ /**
502
+ * Remove a family from the library: its catalog row is archived, so it
503
+ * leaves the Families panel. Placed instances are untouched — in this
504
+ * project and every other — because archived pins keep resolving. Pass
505
+ * `instances: true` to ALSO delete this project's instances in one undoable
506
+ * command; do that only when the user asks for the instances to go.
507
+ *
508
+ * Omit `id` to target the prepared draft. `id` may be a draft id, a saved
509
+ * catalog id, or any family from `list()`.
510
+ *
511
+ * @param args Optional family id, and whether to delete instances too
512
+ * @returns Whether a library row was archived and how many instances were removed
513
+ *
514
+ * @examplePrompt Delete the Louvre Screen family from my library
515
+ * @examplePrompt Get rid of that bollard and every copy of it in here
516
+ *
517
+ * # Example
518
+ * ```ts
519
+ * await snaptrude.design.family.delete({ id: "revit:steel-bollard~bef047" })
520
+ * await snaptrude.design.family.delete({ id: "revit:steel-bollard~bef047", instances: true })
521
+ * ```
522
+ */
523
+ public abstract delete(
524
+ args?: PluginDesignFamilyDeleteArgs | string,
525
+ ): PluginApiReturn<PluginFamilyDeleteResult>
526
+ }
527
+
528
+ /** Optional reference sections keep unrelated grammar out of the core response. */
529
+ export const PluginFamilyReferenceSection = z.enum([
530
+ "architectural",
531
+ "networks",
532
+ "relations",
533
+ ])
534
+ export type PluginFamilyReferenceSection = z.infer<
535
+ typeof PluginFamilyReferenceSection
536
+ >
537
+
538
+ /** Omit section for the core grammar, workflow and example catalogue. */
539
+ export const PluginDesignFamilyGetReferenceArgs = z.object({
540
+ section: PluginFamilyReferenceSection.optional(),
541
+ })
542
+ export type PluginDesignFamilyGetReferenceArgs = z.infer<
543
+ typeof PluginDesignFamilyGetReferenceArgs
544
+ >
545
+
546
+ /** Compact output is opt-in: existing object consumers must retain their metadata. */
547
+ export const PluginDesignFamilyListArgs = z.object({
548
+ compact: z.boolean().optional(),
549
+ })
550
+ export type PluginDesignFamilyListArgs = z.infer<
551
+ typeof PluginDesignFamilyListArgs
552
+ >
553
+
554
+ // ---------------------------------------------------------------------------
555
+ // Result types
556
+ // ---------------------------------------------------------------------------
557
+
558
+ /** One worked example in the catalogue returned by `getExamples`. */
559
+ export interface PluginFamilyExampleSummary {
560
+ id: string
561
+ label: string
562
+ discipline: "architecture" | "structure" | "mep" | "furniture"
563
+ /** The patterns it demonstrates, e.g. "nesting", "slot", "path", "union", "trim". */
564
+ teaches: string[]
565
+ /** Size of the bundle as compact JSON, kilobytes — what fetching it costs. */
566
+ sizeKB: number
567
+ }
568
+
569
+ /** One worked example with its full bundle, from `getExamples({ id })`. */
570
+ export interface PluginFamilyExample extends PluginFamilyExampleSummary {
571
+ bundle: PluginFamilyBundle
572
+ }
573
+
574
+ /** The family-spec DSL reference returned by `getReference`. */
575
+ export interface PluginFamilyReference {
576
+ /** Grammar, op table, expression whitelist, repair contract, worked example. */
577
+ reference: string
578
+ }
579
+
580
+ /**
581
+ * A family spec bundle. `root` is the family; `deps` carries every spec
582
+ * reachable through an `instance` op, keyed by spec id, so a saved family is
583
+ * self-contained and reloads anywhere.
584
+ *
585
+ * The internal shape of a spec is documented by
586
+ * {@linkcode PluginDesignFamilyApi.getReference}, not by these types — it is a
587
+ * data DSL whose grammar changes independently of this API surface.
588
+ */
589
+ export interface PluginFamilyBundle {
590
+ root: Record<string, unknown>
591
+ deps?: Record<string, Record<string, unknown>>
592
+ /** Connector metadata saved with the recipe; network placement evaluates these expressions. */
593
+ network?: NetworkFamilyContract
594
+ }
595
+
596
+ /** Which saved family a draft edits. */
597
+ export interface PluginFamilyEditing {
598
+ /** Library id of the family being edited. */
599
+ familyId: string
600
+ /** The version that was opened. */
601
+ version: number
602
+ /** Its library name — an update saves under this name. */
603
+ name: string
604
+ /** "your library", or the team library's name. */
605
+ library: string
606
+ }
607
+
608
+ /** What `get` returns: the bundle, plus `editing` when the draft edits a saved family. */
609
+ export interface PluginFamilyGetResult extends PluginFamilyBundle {
610
+ editing?: PluginFamilyEditing
611
+ }
612
+
613
+ /** A family at a version, as `create` reports parents it did or did not update. */
614
+ export interface PluginFamilyVersionRef {
615
+ id: string
616
+ name: string
617
+ version: number
618
+ /** Set when the user chose to update it but the update failed. */
619
+ error?: string
620
+ }
621
+
622
+ /** Axis-aligned extents of a built family. Snaptrude units (1 SU = 254 mm), +Y up. */
623
+ export interface PluginFamilyBBox {
624
+ min: [number, number, number]
625
+ max: [number, number, number]
626
+ /** `[width, height, depth]` in Snaptrude units — convert with `core.units.convert` for display. */
627
+ size: [number, number, number]
628
+ }
629
+
630
+ /** One row of a family's parameter sheet. */
631
+ export interface PluginFamilyParameter {
632
+ key: string
633
+ label: string
634
+ type: "length" | "integer" | "enum" | "text" | "boolean"
635
+ /** Current value. `length` (Snaptrude units) and `integer` are numbers; the rest are strings. */
636
+ value: string | number
637
+ default: string | number
638
+ /**
639
+ * Whether this parameter actually drives geometry — INFERRED by the host from
640
+ * which names the spec's expressions reference. `false` means the value is
641
+ * stored and round-tripped but moves nothing.
642
+ */
643
+ drivesGeometry: boolean
644
+ min?: number
645
+ max?: number
646
+ options?: readonly string[]
647
+ }
648
+
649
+ /**
650
+ * A validation diagnostic. `path` is a JSON pointer into the submitted bundle —
651
+ * fix that node and re-`prepare`.
652
+ */
653
+ export interface PluginFamilyDiagnostic {
654
+ /** JSON pointer, e.g. `"/root/geometry/2/body/0/w"`. */
655
+ path: string
656
+ code:
657
+ | "schema"
658
+ | "bad-expression"
659
+ | "unknown-name"
660
+ | "unknown-def"
661
+ | "cycle"
662
+ | "duplicate-name"
663
+ | "unbounded-repeat"
664
+ | "depth"
665
+ | "bad-dimension"
666
+ | "proportion"
667
+ /** A part the geometry kernel could not build exactly; drawn approximately. */
668
+ | "kernel-fallback"
669
+ /** A part whose hole loop crosses its outline. */
670
+ | "profile-hole"
671
+ message: string
672
+ hint?: string
673
+ }
674
+
675
+ /**
676
+ * One placed part and which engine built it, from `getParameters`. `"kernel"` —
677
+ * a curved sketch built exactly; `"mesh"` — straight-edged, or the kernel
678
+ * fallback.
679
+ */
680
+ export interface PluginFamilyPart {
681
+ name: string
682
+ engine: "kernel" | "mesh"
683
+ /**
684
+ * Kernel parts only: the part's exact solid, for `core.geom.query.brep.*` /
685
+ * `core.geom.query.face.getSurfaceKind`. READ-ONLY and PART-LOCAL — in the
686
+ * part's own frame, not where the component sits, so never pass it to a
687
+ * boolean or `design.create.massFromBrep` (a family changes only through
688
+ * `setParameters`). Valid until the next parameter change: re-read
689
+ * `getParameters` after `setParameters`.
690
+ */
691
+ brep?: BrepHandle
692
+ }
693
+
694
+ /** What `prepare` reports. */
695
+ export interface PluginFamilyPrepareResult {
696
+ /** `false` means the spec was rejected — read `errors`, fix, re-prepare. */
697
+ ok: boolean
698
+ familyId?: string
699
+ version?: number
700
+ label?: string
701
+ /** How many parts the family built. Sanity-check against intent. */
702
+ partCount?: number
703
+ /** Extents in Snaptrude units. Sanity-check against intent. */
704
+ bbox?: PluginFamilyBBox
705
+ evalMs?: number
706
+ parameters?: PluginFamilyParameter[]
707
+ /** Computed quantities, when the spec declares a `takeoff` block. */
708
+ takeoff?: Record<string, number | string>
709
+ /** The first few part names — enough to check the naming came out right. */
710
+ sampleParts?: string[]
711
+ /** Present when `snapshot` was asked for. */
712
+ snapshot?: PluginFamilySnapshot
713
+ errors: PluginFamilyDiagnostic[]
714
+ warnings: PluginFamilyDiagnostic[]
715
+ /** Set when the bundle validated but threw while building. */
716
+ error?: string
717
+ }
718
+
719
+ /**
720
+ * The host's diff of what a flex did to the geometry. This is the evidence that
721
+ * a family is parametric.
722
+ */
723
+ export interface PluginFamilyChangeBuckets {
724
+ added: number
725
+ removed: number
726
+ moved: number
727
+ rebuilt: number
728
+ addedNames: string[]
729
+ removedNames: string[]
730
+ }
731
+
732
+ /** What `flex` reports. */
733
+ export interface PluginFamilyFlexResult {
734
+ ok: boolean
735
+ /** The RESOLVED values after merge + clamping — not necessarily what was sent. */
736
+ values?: Record<string, number | string>
737
+ partCount?: number
738
+ bbox?: PluginFamilyBBox
739
+ evalMs?: number
740
+ changed?: PluginFamilyChangeBuckets
741
+ takeoff?: Record<string, number | string>
742
+ /** Proportion warnings at the flexed values (code `proportion`): a part thinner than 0.5 mm, a part or family over 50 m, a family under 10 mm — the signature of a unit slip. */
743
+ warnings?: PluginFamilyDiagnostic[]
744
+ /** Present when `snapshot` was asked for. */
745
+ snapshot?: PluginFamilySnapshot
746
+ error?: string
747
+ }
748
+
749
+ /** One family available to place — from the session registry or the catalog. */
750
+ export interface PluginFamilySummary {
751
+ id: string
752
+ version: number
753
+ label: string
754
+ category?: string
755
+ /** The category's type token (`root.subType`), e.g. singleDoor or casementDouble. */
756
+ subType?: string
757
+ /**
758
+ * Opening contract kind, distinct from provenance (`kind`). Place with the
759
+ * positional `design.create.door` / `window` API and edit via family parameters.
760
+ */
761
+ openingKind?: "door" | "window"
762
+ /** Number of rows on the family's parameter sheet. */
763
+ parameters: number
764
+ /**
765
+ * Where the family came from, which decides how far you can edit it.
766
+ *
767
+ * `"native"` — authored from a JSON spec through this API. Its recipe is
768
+ * data, so every parameter genuinely re-runs the geometry.
769
+ *
770
+ * `"revit"` — imported from a Revit family. Parameters exist and are
771
+ * editable, but only those the importer could bind to geometry actually
772
+ * change the mesh; the rest are carried for round-tripping. Check
773
+ * `drivesGeometry` on the parameter before promising the user a change.
774
+ */
775
+ kind: "native" | "revit"
776
+ }
777
+
778
+ /** Exact catalog pin. Unsaved session definitions are not catalog inventory. */
779
+ export const PluginDesignFamilyGetDefinitionArgs = z.object({
780
+ id: z.string().min(1),
781
+ version: z.number().int().positive(),
782
+ })
783
+ export type PluginDesignFamilyGetDefinitionArgs = z.infer<
784
+ typeof PluginDesignFamilyGetDefinitionArgs
785
+ >
786
+
787
+ /** Saved-family metadata. Physical lengths use mm; angle entries retain degrees. */
788
+ export const PluginFamilyDefinitionParameter = z.object({
789
+ key: z.string(),
790
+ label: z.string(),
791
+ type: z.enum(["length", "integer", "enum", "text", "boolean"]),
792
+ /** Angles use the legacy length type. Check unit before interpreting numeric values. */
793
+ unit: z.enum(["mm", "degrees"]).optional(),
794
+ default: z.union([z.number().finite(), z.string()]),
795
+ min: z.number().finite().optional(),
796
+ max: z.number().finite().optional(),
797
+ options: z.array(z.string()).optional(),
798
+ drivesGeometry: z.boolean(),
799
+ readOnly: z.boolean(),
800
+ formulaDriven: z.boolean(),
801
+ /** False means shared/type: placement cannot change it for one instance. */
802
+ isInstance: z.boolean(),
803
+ })
804
+ export type PluginFamilyDefinitionParameter = z.infer<
805
+ typeof PluginFamilyDefinitionParameter
806
+ >
807
+
808
+ /** Read-only catalog inventory. Boolean defaults retain the family DSL's Yes/No strings. */
809
+ export const PluginFamilyDefinition = z.object({
810
+ id: z.string(),
811
+ version: z.number().int().positive(),
812
+ label: z.string(),
813
+ category: z.string().optional(),
814
+ lengthUnit: z.literal("mm"),
815
+ parameters: z.array(PluginFamilyDefinitionParameter),
816
+ /** Pinned connector metadata. Absence means this family cannot build a topology network. */
817
+ network: NetworkFamilyContract.optional(),
818
+ })
819
+ export type PluginFamilyDefinition = z.infer<typeof PluginFamilyDefinition>
820
+
821
+ /** What `place` reports. */
822
+ export interface PluginFamilyPlaceResult {
823
+ ok: boolean
824
+ familyId?: string
825
+ version?: number
826
+ /**
827
+ * The created component — present ONLY when `position` was given. In
828
+ * interactive mode nothing exists yet, so there is nothing to hand back;
829
+ * the user has not clicked.
830
+ */
831
+ component?: ComponentHandle
832
+ error?: string
833
+ }
834
+
835
+ /** A placed component's parameter state, from `getParameters` / `setParameters`. */
836
+ export interface PluginFamilyInstanceParameters {
837
+ /** The definition this instance is pinned to. */
838
+ familyId: string
839
+ version: number
840
+ label: string
841
+ /** Current values, keyed by parameter key. */
842
+ values: Record<string, number | string>
843
+ /** The sheet: what each key means, its bounds, and whether it moves geometry. */
844
+ parameters: PluginFamilyParameter[]
845
+ /** The placed parts; kernel parts carry their exact `brep`. */
846
+ parts?: PluginFamilyPart[]
847
+ /** Present when `getParameters` was asked for a `snapshot`. */
848
+ snapshot?: PluginFamilySnapshot
849
+ /** The family's `outputs`, as last built. */
850
+ reports?: Record<string, number | string>
851
+ /** Every declared nested child, as last built. */
852
+ children?: PluginFamilyNestedChild[]
853
+ /** Parameters that follow another object (links) or the host. */
854
+ follows?: PluginFamilyFollow[]
855
+ /** Where a hosted family sits: a system wall, or a family by id. */
856
+ host?: ({ kind: "wall" } | { kind: "family"; id: string }) & {
857
+ /** True when the host no longer has room for it: it is out of the model until it fits again. */
858
+ parked?: boolean
859
+ }
860
+ /** Guests hosted in THIS family that no longer fit it (parked); they return when they fit. */
861
+ parkedGuests?: { id: string; family: string }[]
862
+ /** How it sits against other placed objects (attach, join, span, path), with current reports. */
863
+ relations?: PluginFamilyRelation[]
864
+ /** setParameters only: the edits refused, each with what to change instead. */
865
+ refused?: string[]
866
+ }
867
+
868
+ /** A nested child of a placed family. */
869
+ export interface PluginFamilyNestedChild {
870
+ /** "chair", or "chair/cushion" for a child of a child — the key for `children` edits. */
871
+ path: string
872
+ label: string
873
+ /** The family it is now (a slot's current pick). */
874
+ family: string
875
+ familyLabel: string
876
+ /** How many copies the parent places. */
877
+ copies: number
878
+ slot?: { expr: string; options: string[] }
879
+ values: Record<string, number | string>
880
+ /** Values the parent sets, with the expression it sets them by. */
881
+ setBy: Record<string, string | undefined>
882
+ reports: Record<string, number | string>
883
+ }
884
+
885
+ /** A placed relation as stored, plus what it measures now. */
886
+ export type PluginFamilyRelation = PluginFamilyRelationInput & {
887
+ id: string
888
+ /** "Attached to", "Joins", "Spans between", "Along a path", "Cuts host". */
889
+ label: string
890
+ summary: string
891
+ /** False when a target is gone: the family stayed where it was. */
892
+ live: boolean
893
+ reports: Record<string, number | string>
894
+ }
895
+
896
+ /** One relation to add (`relations.add`). Targets are handles, except a cut's symbolic "host". */
897
+ export type PluginFamilyRelationInput = {
898
+ /** Replaces the relation with this id; a new one gets "r1", "r2"… */
899
+ id?: string
900
+ /** This family's parameter → an expression over the relation's reports and own parameters. */
901
+ bind?: Record<string, string>
902
+ } & (
903
+ | {
904
+ kind: "attach"
905
+ side: "base" | "top"
906
+ to: ComponentHandle | { level: number; face?: "base" | "top" }
907
+ offset?: number
908
+ }
909
+ | {
910
+ kind: "join"
911
+ end: "start" | "end"
912
+ to: ComponentHandle
913
+ toEnd: "start" | "end"
914
+ /** The other family's parameters, over the same reports. */
915
+ bindOther?: Record<string, string>
916
+ }
917
+ | {
918
+ kind: "span"
919
+ from: ComponentHandle
920
+ to: ComponentHandle
921
+ at?: "top" | "centre" | "base"
922
+ }
923
+ | {
924
+ kind: "path"
925
+ from?: ComponentHandle
926
+ points?: [number, number, number][]
927
+ closed?: boolean
928
+ }
929
+ | {
930
+ /** Apply recipe voids to the existing host without adding another host ID. */
931
+ kind: "cut"
932
+ to: "host"
933
+ bind?: never
934
+ }
935
+ )
936
+
937
+ /** One parameter that follows something else. */
938
+ export interface PluginFamilyFollow {
939
+ key: string
940
+ /** "relation": set by a placed relation — `from` is the relation's id. */
941
+ kind: "link" | "host" | "relation"
942
+ /** The source's id. */
943
+ from: string
944
+ fromLabel: string
945
+ /** The expression over the source's reports (for a host: the report name). */
946
+ expr: string
947
+ }
948
+
949
+ /** What `create` reports. */
950
+ export interface PluginFamilyCreateResult {
951
+ ok: boolean
952
+ definitionId?: string
953
+ version?: number
954
+ guid?: string
955
+ /** True when the save also dropped one instance into the model (nothing was placed yet). */
956
+ placed?: boolean
957
+ /** Host guidance for the caller, e.g. that an instance was placed at the view centre. */
958
+ hint?: string
959
+ /**
960
+ * What the user chose in the app's window: `"version"` (a new version of
961
+ * the saved family), `"new"` (a new family), `"cancelled"` (nothing saved).
962
+ * `"saved"` = no question was needed.
963
+ */
964
+ decision?: "version" | "new" | "cancelled" | "saved"
965
+ /** The library name it was saved under. */
966
+ name?: string
967
+ /** Placed copies in this project moved to the new version (the family's and updated parents'). */
968
+ movedCopies?: number
969
+ /** What the moved copies lost because the new version no longer has it. */
970
+ dropped?: {
971
+ values: Array<{ copy: string; key: string }>
972
+ childValues: Array<{ copy: string; path: string }>
973
+ follows: Array<{ copy: string; key: string; kind: "link" | "relation" }>
974
+ }
975
+ /** Families nesting this one that the user chose to update, at their new versions. */
976
+ parentsUpdated?: PluginFamilyVersionRef[]
977
+ /** Families nesting this one that keep their version. */
978
+ parentsKept?: PluginFamilyVersionRef[]
979
+ error?: string
980
+ }
981
+
982
+ /** What `delete` reports. */
983
+ export interface PluginFamilyDeleteResult {
984
+ ok: boolean
985
+ familyId?: string
986
+ /** Instances deleted from the scene (only with `instances: true`), in one undoable command. */
987
+ removedInstances?: number
988
+ /** True when a library row was archived; false for an unsaved draft. */
989
+ archived?: boolean
990
+ error?: string
991
+ }
992
+
993
+ // ---------------------------------------------------------------------------
994
+ // Argument schemas
995
+ // ---------------------------------------------------------------------------
996
+
997
+ /**
998
+ * A spec bundle as it crosses the boundary: `root` plus optional `deps`.
999
+ *
1000
+ * Loose by design — the spec grammar is a data DSL validated by the host's own
1001
+ * validator, which returns JSON-pointer diagnostics far more useful than a Zod
1002
+ * shape error. This schema only guarantees the envelope is an object.
1003
+ */
1004
+ export const PluginFamilyBundleSchema = z.object({
1005
+ root: z.record(z.string(), z.unknown()),
1006
+ deps: z.record(z.string(), z.record(z.string(), z.unknown())).optional(),
1007
+ network: NetworkFamilyContract.optional(),
1008
+ })
1009
+
1010
+ /**
1011
+ * Arguments for {@linkcode PluginDesignFamilyApi.prepare}.
1012
+ *
1013
+ * | Property | Type | Description |
1014
+ * |---|---|---|
1015
+ * | `bundle` | {@linkcode PluginFamilyBundle} | The family spec bundle |
1016
+ * | `spec` | {@linkcode PluginFamilyBundle} | Alias for `bundle` |
1017
+ * | `snapshot` | {@linkcode PluginFamilySnapshotOption} | Also return a picture of the built family |
1018
+ *
1019
+ * Exactly one of `bundle` / `spec` is required. A bare `FamilySpec` (no `root`
1020
+ * wrapper) is also accepted by the host and wrapped for the caller.
1021
+ */
1022
+ /**
1023
+ * Ask for a picture with the result: `true` for the default sheet (iso, front,
1024
+ * right and top tiles with a dimension line), or an object to choose views and
1025
+ * tile size. The picture is an isolated offscreen render of the family alone —
1026
+ * the viewport is not moved — and comes back as a PNG data URL in the result's
1027
+ * `snapshot.image`. Through the agent's `run_worker`, return the result and the
1028
+ * image reaches the model as an image.
1029
+ *
1030
+ * `focus` makes it a close-up: `{ parts: ["seat", "back", "slat@*"] }` frames
1031
+ * the joint where those groups meet (`*` matches any run of characters;
1032
+ * nested names are `a/b`), or their full bounds if they do not meet. A margin
1033
+ * of a quarter of their size (at least 50 mm) surrounds the joint.
1034
+ * `{ box: { min, max } }` frames a region in family space
1035
+ * (Snaptrude units, +Y up). Every other part is still drawn; the dimension line
1036
+ * then gives the framed region and the result says `closeUp: true`. A part
1037
+ * name that matches nothing fails the snapshot with a list of real names.
1038
+ */
1039
+ export const PluginFamilySnapshotFocus = z.union([
1040
+ z.object({ parts: z.array(z.string().min(1)).min(1) }),
1041
+ z.object({
1042
+ box: z
1043
+ .object({
1044
+ min: z.tuple([
1045
+ z.number().finite(),
1046
+ z.number().finite(),
1047
+ z.number().finite(),
1048
+ ]),
1049
+ max: z.tuple([
1050
+ z.number().finite(),
1051
+ z.number().finite(),
1052
+ z.number().finite(),
1053
+ ]),
1054
+ })
1055
+ .refine(
1056
+ ({ min, max }) => min.every((value, axis) => value <= max[axis]),
1057
+ {
1058
+ message: "Each minimum must be at or below its maximum",
1059
+ },
1060
+ ),
1061
+ }),
1062
+ ])
1063
+ export type PluginFamilySnapshotFocus = z.infer<
1064
+ typeof PluginFamilySnapshotFocus
1065
+ >
1066
+
1067
+ /** One close-up tile; omission of view uses the sheet's first view. */
1068
+ export const PluginFamilySnapshotCloseUp = z.intersection(
1069
+ PluginFamilySnapshotFocus,
1070
+ z.object({ view: z.enum(["iso", "front", "right", "top"]).optional() }),
1071
+ )
1072
+ export type PluginFamilySnapshotCloseUp = z.infer<
1073
+ typeof PluginFamilySnapshotCloseUp
1074
+ >
1075
+
1076
+ export const PluginFamilySnapshotOption = z.union([
1077
+ z.boolean(),
1078
+ z.object({
1079
+ views: z.array(z.enum(["iso", "front", "right", "top"])).optional(),
1080
+ /** Pixels per tile (square), 64–2048; default 512. */
1081
+ tileSize: z.number().optional(),
1082
+ /** One focus across views, or 1–6 independent close-up tiles in one image. */
1083
+ focus: z
1084
+ .union([
1085
+ PluginFamilySnapshotFocus,
1086
+ z.array(PluginFamilySnapshotCloseUp).min(1).max(6),
1087
+ ])
1088
+ .optional(),
1089
+ }),
1090
+ ])
1091
+ export type PluginFamilySnapshotOption = z.infer<
1092
+ typeof PluginFamilySnapshotOption
1093
+ >
1094
+
1095
+ /** The picture block on prepare / flex / getParameters results when `snapshot` was asked for. */
1096
+ export interface PluginFamilySnapshot {
1097
+ ok: boolean
1098
+ /** PNG data URL of the composed sheet. */
1099
+ image?: string
1100
+ views?: ("iso" | "front" | "right" | "top")[]
1101
+ /** [width, height, depth] in SU. For a close-up list, this remains the whole family's size. */
1102
+ size?: [number, number, number]
1103
+ ms?: number
1104
+ /** True when the sheet is a close-up (`focus`), not the whole family. */
1105
+ closeUp?: boolean
1106
+ /** Per-tile results for a focus list. One invalid target does not discard other tiles. */
1107
+ closeUps?: Array<{
1108
+ view: "iso" | "front" | "right" | "top"
1109
+ label: string
1110
+ size?: [number, number, number]
1111
+ error?: string
1112
+ }>
1113
+ /** Set when the picture could not be taken (e.g. the legacy renderer); the call itself still succeeded. */
1114
+ error?: string
1115
+ }
1116
+
1117
+ export const PluginDesignFamilyPrepareArgs = z.object({
1118
+ bundle: PluginFamilyBundleSchema.optional(),
1119
+ spec: PluginFamilyBundleSchema.optional(),
1120
+ snapshot: PluginFamilySnapshotOption.optional(),
1121
+ })
1122
+ export type PluginDesignFamilyPrepareArgs = z.infer<
1123
+ typeof PluginDesignFamilyPrepareArgs
1124
+ >
1125
+
1126
+ /**
1127
+ * Arguments for {@linkcode PluginDesignFamilyApi.flex}.
1128
+ *
1129
+ * | Property | Type | Description |
1130
+ * |---|---|---|
1131
+ * | `values` | `Record<string, number \| string>` | Optional sparse changes; omission does not rebuild |
1132
+ * | `snapshot` | {@linkcode PluginFamilySnapshotOption} | Also return a picture of the flexed family |
1133
+ */
1134
+ export const PluginDesignFamilyFlexArgs = z.object({
1135
+ values: z.record(z.string(), z.union([z.number(), z.string()])).optional(),
1136
+ snapshot: PluginFamilySnapshotOption.optional(),
1137
+ })
1138
+ export type PluginDesignFamilyFlexArgs = z.infer<
1139
+ typeof PluginDesignFamilyFlexArgs
1140
+ >
1141
+
1142
+ /**
1143
+ * Arguments for {@linkcode PluginDesignFamilyApi.get}. All optional — no
1144
+ * arguments returns the prepared draft.
1145
+ *
1146
+ * | Property | Type | Description |
1147
+ * |---|---|---|
1148
+ * | `id` | `string` | A saved family to open for editing, from {@linkcode PluginDesignFamilyApi.list} |
1149
+ * | `version` | `number` | Exact version to open; defaults to the newest |
1150
+ * | `component` | {@linkcode ComponentHandle} | Open the family of this placed copy, at its version |
1151
+ */
1152
+ export const PluginDesignFamilyGetArgs = z.object({
1153
+ id: z.string().optional(),
1154
+ version: z.number().int().positive().optional(),
1155
+ component: ComponentHandle.optional(),
1156
+ })
1157
+ export type PluginDesignFamilyGetArgs = z.infer<
1158
+ typeof PluginDesignFamilyGetArgs
1159
+ >
1160
+
1161
+ /**
1162
+ * Arguments for {@linkcode PluginDesignFamilyApi.getExamples}.
1163
+ *
1164
+ * | Property | Type | Description |
1165
+ * |---|---|---|
1166
+ * | `id` | `string` | One example's id — returns its bundle |
1167
+ * | `discipline` | `string` | List only this discipline: architecture, structure, mep, furniture |
1168
+ * | `tag` | `string` | List only examples teaching this pattern (e.g. "path", "union") |
1169
+ */
1170
+ export const PluginDesignFamilyGetExamplesArgs = z.object({
1171
+ id: z.string().optional(),
1172
+ discipline: z
1173
+ .enum(["architecture", "structure", "mep", "furniture"])
1174
+ .optional(),
1175
+ tag: z.string().optional(),
1176
+ })
1177
+ export type PluginDesignFamilyGetExamplesArgs = z.infer<
1178
+ typeof PluginDesignFamilyGetExamplesArgs
1179
+ >
1180
+
1181
+ /**
1182
+ * Arguments for {@linkcode PluginDesignFamilyApi.getTemplate}.
1183
+ * `kind` selects the opening contract. `subType` is a nonempty type token;
1184
+ * omission selects singleDoor / fixedWindow. Unsupported tokens return the
1185
+ * default template with guidance rather than preventing custom subtype authoring.
1186
+ */
1187
+ export const PluginDesignFamilyGetTemplateArgs = z.object({
1188
+ kind: z.enum(["door", "window"]),
1189
+ subType: z.string().min(1).optional(),
1190
+ })
1191
+ export type PluginDesignFamilyGetTemplateArgs = z.infer<
1192
+ typeof PluginDesignFamilyGetTemplateArgs
1193
+ >
1194
+
1195
+ /**
1196
+ * Maps opening dimensions to authored length parameter KEYS, not values.
1197
+ * Width and height describe the wall hole; sill measures from the wall base
1198
+ * to the hole bottom (windows). Wall thickness follows the host wall.
1199
+ * The recipe and family parameter APIs use Snaptrude units (1 SU = 254 mm).
1200
+ */
1201
+ export interface PluginFamilyOpeningContract {
1202
+ kind: "door" | "window"
1203
+ width: string
1204
+ height: string
1205
+ sill?: string
1206
+ wallThickness?: string
1207
+ }
1208
+
1209
+ /** Starter recipe metadata returned by `getTemplate`; instances are ParametricComponents. */
1210
+ export interface PluginFamilyTemplateResult {
1211
+ /** Validated recipe bundle: edit its style while preserving the contract. */
1212
+ bundle: PluginFamilyBundle
1213
+ contract: PluginFamilyOpeningContract
1214
+ /** Actual template subtype, including when an unsupported token used the default. */
1215
+ subType: string
1216
+ /** Authoring rules and any note about an unsupported requested subtype. */
1217
+ guidance: string[]
1218
+ /** Available starter templates for the requested kind. */
1219
+ available: Array<{ subType: string; label: string; teaches: string[] }>
1220
+ }
1221
+
1222
+ /**
1223
+ * Arguments for {@linkcode PluginDesignFamilyApi.create}.
1224
+ *
1225
+ * | Property | Type | Description |
1226
+ * |---|---|---|
1227
+ * | `name` | `string` | Library name; defaults to the spec's own label |
1228
+ * | `teamId` | `string` | Team library to save into; omit for personal |
1229
+ */
1230
+ export const PluginDesignFamilyCreateArgs = z.object({
1231
+ name: z.string().optional(),
1232
+ teamId: z.string().optional(),
1233
+ })
1234
+ export type PluginDesignFamilyCreateArgs = z.infer<
1235
+ typeof PluginDesignFamilyCreateArgs
1236
+ >
1237
+
1238
+ /**
1239
+ * Arguments for {@linkcode PluginDesignFamilyApi.delete}.
1240
+ *
1241
+ * | Property | Type | Description |
1242
+ * |---|---|---|
1243
+ * | `id` | `string` | Family to remove; omit for the prepared draft |
1244
+ * | `instances` | `boolean` | Also delete this project's placed instances; default false |
1245
+ */
1246
+ export const PluginDesignFamilyDeleteArgs = z.object({
1247
+ id: z.string().optional(),
1248
+ instances: z.boolean().optional(),
1249
+ })
1250
+ export type PluginDesignFamilyDeleteArgs = z.infer<
1251
+ typeof PluginDesignFamilyDeleteArgs
1252
+ >
1253
+
1254
+ /**
1255
+ * Arguments for {@linkcode PluginDesignFamilyApi.place}.
1256
+ *
1257
+ * | Property | Type | Description |
1258
+ * |---|---|---|
1259
+ * | `id` | `string` | Family to place, from {@linkcode PluginDesignFamilyApi.list}. Omit to place the prepared draft |
1260
+ * | `version` | `number` | Exact version; defaults to the newest known |
1261
+ * | `position` | `Vec3Handle` or `{ x, y, z }` | Place here in SU. Prefer a vec3 handle; coordinates remain for existing callers. Omit to arm the cursor |
1262
+ * | `rotation` | `number` | Yaw in degrees about +Y. Only with `position` |
1263
+ * | `values` | `Record<string, number \| string>` | Parameter values for THIS instance |
1264
+ * | `storey` | `number` | Storey to place on; defaults to the active one |
1265
+ */
1266
+ export const PluginDesignFamilyPlaceArgs = z.object({
1267
+ id: z.string().optional(),
1268
+ version: z.number().int().positive().optional(),
1269
+ position: z.union([Vec3Handle, Vec3Components]).optional(),
1270
+ rotation: z.number().optional(),
1271
+ values: z.record(z.string(), z.union([z.number(), z.string()])).optional(),
1272
+ storey: z.number().int().optional(),
1273
+ /** Hosted families: wall or family host. Values apply before fit checks and cutting. */
1274
+ host: ComponentHandle.optional(),
1275
+ })
1276
+ export type PluginDesignFamilyPlaceArgs = z.infer<
1277
+ typeof PluginDesignFamilyPlaceArgs
1278
+ >
1279
+
1280
+ /**
1281
+ * Arguments for {@linkcode PluginDesignFamilyApi.getParameters}.
1282
+ *
1283
+ * | Property | Type | Description |
1284
+ * |---|---|---|
1285
+ * | `component` | {@linkcode ComponentHandle} | A placed parametric component |
1286
+ */
1287
+ export const PluginDesignFamilyGetParametersArgs = z.object({
1288
+ component: ComponentHandle,
1289
+ /** Also return a picture of this instance (its recipe re-run at its values, with its paint). */
1290
+ snapshot: PluginFamilySnapshotOption.optional(),
1291
+ })
1292
+ export type PluginDesignFamilyGetParametersArgs = z.infer<
1293
+ typeof PluginDesignFamilyGetParametersArgs
1294
+ >
1295
+
1296
+ /**
1297
+ * Arguments for {@linkcode PluginDesignFamilyApi.setParameters}.
1298
+ *
1299
+ * | Property | Type | Description |
1300
+ * |---|---|---|
1301
+ * | `component` | {@linkcode ComponentHandle} | A placed parametric component |
1302
+ * | `values` | `Record<string, number \| string>` | Values to change (partial) |
1303
+ */
1304
+ export const PluginDesignFamilySetParametersArgs = z.object({
1305
+ component: ComponentHandle,
1306
+ values: z
1307
+ .record(z.string(), z.union([z.number(), z.string()]))
1308
+ .optional()
1309
+ .default({}),
1310
+ /** Nested children's own values: { "<path>": { key: value } }. */
1311
+ children: z
1312
+ .record(z.string(), z.record(z.string(), z.union([z.number(), z.string()])))
1313
+ .optional(),
1314
+ /** Links: { key: { from, expr } | null }. */
1315
+ links: z
1316
+ .record(
1317
+ z.string(),
1318
+ z.union([
1319
+ z.object({ from: ComponentHandle, expr: z.string() }),
1320
+ z.null(),
1321
+ ]),
1322
+ )
1323
+ .optional(),
1324
+ /** Placed relations: { add?: [{ kind, … }], remove?: ["r1"] } — checked by the host. */
1325
+ relations: z
1326
+ .object({
1327
+ add: z.array(z.record(z.string(), z.unknown())).optional(),
1328
+ remove: z.array(z.string()).optional(),
1329
+ })
1330
+ .optional(),
1331
+ })
1332
+ export type PluginDesignFamilySetParametersArgs = z.infer<
1333
+ typeof PluginDesignFamilySetParametersArgs
1334
+ >