@snaptrude/plugin-core 0.9.14 → 0.9.16-networks-20261008.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 (73) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/api/analysis/daylight.d.ts +5 -5
  3. package/dist/api/analysis/solar.d.ts +2 -2
  4. package/dist/api/core/camera/index.d.ts +418 -0
  5. package/dist/api/core/camera/index.d.ts.map +1 -1
  6. package/dist/api/core/index.d.ts +4 -4
  7. package/dist/api/core/index.d.ts.map +1 -1
  8. package/dist/api/core/layers.d.ts +377 -255
  9. package/dist/api/core/layers.d.ts.map +1 -1
  10. package/dist/api/core/proposals/index.d.ts +4 -3
  11. package/dist/api/core/proposals/index.d.ts.map +1 -1
  12. package/dist/api/core/storeys/index.d.ts +12 -0
  13. package/dist/api/core/storeys/index.d.ts.map +1 -1
  14. package/dist/api/core/storeys/layers.d.ts +408 -0
  15. package/dist/api/core/storeys/layers.d.ts.map +1 -0
  16. package/dist/api/core/tags.d.ts +3 -3
  17. package/dist/api/design/bim.d.ts +209 -0
  18. package/dist/api/design/bim.d.ts.map +1 -0
  19. package/dist/api/design/catalog.d.ts +242 -0
  20. package/dist/api/design/catalog.d.ts.map +1 -0
  21. package/dist/api/design/create/bulk-items.d.ts +55 -3
  22. package/dist/api/design/create/bulk-items.d.ts.map +1 -1
  23. package/dist/api/design/create/index.d.ts +77 -22
  24. package/dist/api/design/create/index.d.ts.map +1 -1
  25. package/dist/api/design/family.d.ts +576 -12
  26. package/dist/api/design/family.d.ts.map +1 -1
  27. package/dist/api/design/index.d.ts +16 -0
  28. package/dist/api/design/index.d.ts.map +1 -1
  29. package/dist/api/design/networks.d.ts +1703 -0
  30. package/dist/api/design/networks.d.ts.map +1 -0
  31. package/dist/api/design/query/index.d.ts +30 -30
  32. package/dist/api/design/selection/index.d.ts +12 -12
  33. package/dist/api/design/types/index.d.ts +40 -24
  34. package/dist/api/design/types/index.d.ts.map +1 -1
  35. package/dist/api/design/update/index.d.ts +113 -3
  36. package/dist/api/design/update/index.d.ts.map +1 -1
  37. package/dist/api/presentation/aiInspiration.d.ts +23 -23
  38. package/dist/api/presentation/camera.d.ts +265 -0
  39. package/dist/api/presentation/camera.d.ts.map +1 -0
  40. package/dist/api/presentation/index.d.ts +14 -0
  41. package/dist/api/presentation/index.d.ts.map +1 -1
  42. package/dist/api/presentation/placedViews.d.ts +1 -1
  43. package/dist/api/presentation/views.d.ts +3 -1
  44. package/dist/api/presentation/views.d.ts.map +1 -1
  45. package/dist/api/presentation/walkthroughs.d.ts +1350 -0
  46. package/dist/api/presentation/walkthroughs.d.ts.map +1 -0
  47. package/dist/index.cjs +3886 -2598
  48. package/dist/index.cjs.map +1 -1
  49. package/dist/index.js +3744 -2595
  50. package/dist/index.js.map +1 -1
  51. package/package.json +1 -1
  52. package/scripts/generate-manifest.mjs +5 -0
  53. package/scripts/generate-manifest.test.mjs +14 -3
  54. package/src/api/core/camera/index.ts +400 -1
  55. package/src/api/core/index.ts +4 -4
  56. package/src/api/core/layers.ts +380 -173
  57. package/src/api/core/proposals/index.ts +4 -3
  58. package/src/api/core/storeys/index.ts +14 -0
  59. package/src/api/core/storeys/layers.ts +339 -0
  60. package/src/api/design/bim.ts +121 -0
  61. package/src/api/design/catalog.ts +128 -0
  62. package/src/api/design/create/bulk-items.ts +74 -2
  63. package/src/api/design/create/index.ts +92 -36
  64. package/src/api/design/family.ts +448 -17
  65. package/src/api/design/index.ts +34 -4
  66. package/src/api/design/networks.ts +607 -0
  67. package/src/api/design/types/index.ts +31 -24
  68. package/src/api/design/update/index.ts +121 -4
  69. package/src/api/presentation/camera.ts +289 -0
  70. package/src/api/presentation/index.ts +14 -0
  71. package/src/api/presentation/views.ts +3 -1
  72. package/src/api/presentation/walkthroughs.ts +1201 -0
  73. package/api-manifest.json +0 -9117
@@ -1,6 +1,7 @@
1
1
  import * as z from "zod"
2
2
  import { PluginApiReturn } from "../../types"
3
3
  import { BrepHandle, ComponentHandle, Vec3Components } from "../../handles"
4
+ import { NetworkFamilyContract } from "./networks"
4
5
 
5
6
  /**
6
7
  * `snaptrude.design.family.*` — author NATIVE parametric families from a JSON spec.
@@ -12,19 +13,36 @@ import { BrepHandle, ComponentHandle, Vec3Components } from "../../handles"
12
13
  * compiles that into a live parametric definition the user can place, flex and
13
14
  * save to their library.
14
15
  *
15
- * ## Workflow
16
+ * ## Generic component workflow
16
17
  *
17
18
  * ```
18
- * getReference() read the spec DSL once — grammar, ops, expressions, example
19
- * prepare({ bundle }) validate + compile + show a live ghost; nothing committed
19
+ * getReference() read the spec DSL once — grammar, ops, expressions, examples
20
+ * getExamples() worked example families by discipline; getExamples({ id }) for one
21
+ * prepare({ bundle }) validate + compile; nothing committed, nothing in the viewport
20
22
  * flex({ values }) re-run at new values; returns the geometry delta
21
23
  * place({ position }) put it in the model — saves the family as the first instance drops
22
24
  * place() save the family, then arm placement — the USER clicks to drop it
25
+ * create() save an EDIT of a saved family — the app asks the user
23
26
  * create({ teamId }) share with a team; a save with nothing placed also drops one instance
24
27
  * ```
25
28
  *
26
29
  * `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`.
30
+ * survives reload with no extra step; finish a NEW generic component on
31
+ * `place`, not on `create` (`create` ends only an edit of a saved family, below).
32
+ *
33
+ * For architectural families, use `create({ name })` to save the prepared recipe.
34
+ * Read `design.catalog` and pass the saved `{ id, version }` reference to the
35
+ * native `design.create` method with its contour, wall profile, or straight axis.
36
+ * Standalone placement does not create a native host. Selecting a Draw type
37
+ * does not register an API type. Native creator values use mm, while this
38
+ * generic family API uses SU. Read `family_authoring` for the full workflow.
39
+ *
40
+ * ## Changing a saved family
41
+ *
42
+ * Open it with `get({ id })` or `get({ component })`, edit with `prepare` /
43
+ * `flex`, then `create()`. The app — not the agent — asks the user whether to
44
+ * update that family (this project's placed copies move to the new version) or
45
+ * save a new family. Do not ask this yourself; report the returned `decision`.
28
46
  *
29
47
  * ## Read getReference() first
30
48
  *
@@ -84,6 +102,31 @@ export abstract class PluginDesignFamilyApi {
84
102
  */
85
103
  public abstract getReference(): PluginApiReturn<PluginFamilyReference>
86
104
 
105
+ /**
106
+ * Worked example families to learn from: real, tested bundles by discipline
107
+ * (architecture, structure, MEP, furniture). With no `id`, returns the
108
+ * catalogue — each example's id, label, discipline and the patterns it
109
+ * teaches — filtered by `discipline` / `tag` when given. With an `id`,
110
+ * returns that example's full bundle. Fetch the one closest to the task
111
+ * before authoring and borrow its patterns; examples are patterns to
112
+ * combine, not templates to copy whole.
113
+ *
114
+ * @param args - `{ id }` for one bundle, or `{ discipline?, tag? }` to list
115
+ * @returns The catalogue, or one example with its bundle
116
+ *
117
+ * @examplePrompt Show me an example of a structural family
118
+ * @examplePrompt How is a hosted door family written?
119
+ *
120
+ * # Example
121
+ * ```ts
122
+ * const list = await snaptrude.design.family.getExamples({ discipline: "mep" })
123
+ * const ex = await snaptrude.design.family.getExamples({ id: list[0].id })
124
+ * ```
125
+ */
126
+ public abstract getExamples(
127
+ args?: PluginDesignFamilyGetExamplesArgs,
128
+ ): PluginApiReturn<PluginFamilyExampleSummary[] | PluginFamilyExample>
129
+
87
130
  /**
88
131
  * Validate, compile and preview a family spec. Nothing is committed to the
89
132
  * model or the library — call {@linkcode PluginDesignFamilyApi.place} to put
@@ -141,16 +184,38 @@ export abstract class PluginDesignFamilyApi {
141
184
  ): PluginApiReturn<PluginFamilyFlexResult>
142
185
 
143
186
  /**
144
- * The prepared family's spec bundle, or `null` when nothing is prepared.
187
+ * A family's spec bundle: the prepared draft's, or — to CHANGE a saved
188
+ * family — that family's recipe, opened as the new draft.
189
+ *
190
+ * - No arguments: the prepared draft, or `null` when nothing is prepared.
191
+ * - `{ id, version? }`: open a saved family from the library (newest version
192
+ * when `version` is omitted).
193
+ * - `{ component }`: open the family (at its version) of that placed copy.
145
194
  *
146
- * @returns The draft bundle, or `null`
195
+ * Opening makes the recipe the current draft and returns `editing` — which
196
+ * saved family it is. Edit it with `prepare` / `flex`, then call `create`.
197
+ * On that save the APP shows the user its own window asking whether to
198
+ * update the family (every placed copy in this project moves to the new
199
+ * version) or save it as a new family — "new family" is the default. Never
200
+ * ask the user this yourself, and never promise either outcome: report what
201
+ * `create` returns.
202
+ *
203
+ * @param args Optional: which saved family to open
204
+ * @returns The bundle (plus `editing` when it edits a saved family), or `null`
205
+ *
206
+ * @examplePrompt Make the chairs I placed 50 mm wider
207
+ * @examplePrompt Change the Louvre Screen family so its blades are deeper
147
208
  *
148
209
  * # Example
149
210
  * ```ts
150
- * const bundle = await snaptrude.design.family.get()
211
+ * const draft = await snaptrude.design.family.get()
212
+ * const family = await snaptrude.design.family.get({ component })
213
+ * // family.editing → { familyId, version, name, library }
151
214
  * ```
152
215
  */
153
- public abstract get(): PluginApiReturn<PluginFamilyBundle | null>
216
+ public abstract get(
217
+ args?: PluginDesignFamilyGetArgs,
218
+ ): PluginApiReturn<PluginFamilyGetResult | null>
154
219
 
155
220
  /**
156
221
  * Every parametric family registered in this session — native and imported.
@@ -166,6 +231,26 @@ export abstract class PluginDesignFamilyApi {
166
231
  */
167
232
  public abstract list(): PluginApiReturn<PluginFamilySummary[]>
168
233
 
234
+ /**
235
+ * Read one saved catalog definition without opening a draft or placing geometry.
236
+ * Both id and version are required. The catalog checks access on every read.
237
+ * Length defaults and bounds are in mm, ready for network graph overrides;
238
+ * angle parameters retain degrees, identified by each parameter's unit.
239
+ * existing getParameters/place/setParameters continue to use Snaptrude units.
240
+ * Defaults include current catalog overrides, which can change without a new version.
241
+ * No scene, registry, history, or persistence state is changed.
242
+ * Missing/inaccessible pins and invalid recipes fail; no latest-version fallback.
243
+ * This metadata does not certify connectors or engineering compatibility.
244
+ *
245
+ * @param args Exact saved catalog id and version, from list()
246
+ * @returns Compact parameter metadata, not geometry or the recipe bundle
247
+ * @example
248
+ * const definition = await snaptrude.design.family.getDefinition({ id, version })
249
+ */
250
+ public abstract getDefinition(
251
+ args: PluginDesignFamilyGetDefinitionArgs,
252
+ ): PluginApiReturn<PluginFamilyDefinition>
253
+
169
254
  /**
170
255
  * Put a family into the model — either the prepared draft or any family from
171
256
  * {@linkcode PluginDesignFamilyApi.list}.
@@ -188,6 +273,13 @@ export abstract class PluginDesignFamilyApi {
188
273
  * To COPY a placed component, place its family again at the new position with
189
274
  * the same `values` — there is no separate copy call.
190
275
  *
276
+ * HOSTED families (doors, windows — anything with an `opening`, a `host`
277
+ * block, or a void with `cuts: "host"`) take `host`: a wall or a placed
278
+ * family. The family is set into the host's face nearest `position` (the
279
+ * host's middle when omitted), and the host is cut — a system wall by the
280
+ * wall cutter, a family by its own recipe. Host-bound parameters then follow
281
+ * the host (a door's wall thickness follows the wall).
282
+ *
191
283
  * @param args Which family, where, and at what parameter values
192
284
  * @returns What was placed; `component` is present only in positioned mode
193
285
  *
@@ -207,6 +299,9 @@ export abstract class PluginDesignFamilyApi {
207
299
  *
208
300
  * // interactive — the user clicks
209
301
  * await snaptrude.design.family.place()
302
+ *
303
+ * // hosted — a door in a wall, near a point along it
304
+ * await snaptrude.design.family.place({ id: doorId, host: wall, position: { x: 3, y: 0, z: 0 } })
210
305
  * ```
211
306
  */
212
307
  public abstract place(
@@ -249,6 +344,34 @@ export abstract class PluginDesignFamilyApi {
249
344
  * that does not is accepted and stored (it round-trips to Revit) but nothing
250
345
  * visible happens — say so rather than claiming a change the user cannot see.
251
346
  *
347
+ * NESTED children: `children: { "<path>": { key: value } }` sets a nested
348
+ * child's own values (every copy of it). A value its parent sets is sent up
349
+ * to what it follows when that is a plain relationship; otherwise it is
350
+ * refused with what to change instead (see `refused`).
351
+ *
352
+ * LINKS: `links: { key: { from, expr } }` makes this family's `key` follow
353
+ * another placed object — `from` is a family (it reports its parameters and
354
+ * outputs) or a wall (it reports thickness, length, height); `expr` is an
355
+ * expression over those reports. `null` removes a link. Links that would
356
+ * make objects follow each other in a loop are refused. Values accept unit
357
+ * literals ("600mm").
358
+ *
359
+ * RELATIONS: `relations: { add: [...], remove: ["r1"] }` sets how the family
360
+ * sits against other placed objects or cuts its host:
361
+ * - `attach` — `{ kind: "attach", side: "base" | "top", to: <object> | { level, face? }, offset? }`:
362
+ * sits on a surface, or its top reaches one (reports `reach`, `gap`, `surface`).
363
+ * - `join` — `{ kind: "join", end, to: <family>, toEnd, bindOther? }`: its end moves onto
364
+ * the other's end (reports `angle`, `absAngle`, `mitre`).
365
+ * - `span` — `{ kind: "span", from, to, at? }`: pinned between two objects, running along
366
+ * its +X (reports `distance`, `run`, `rise`, `slope`, `from.*`, `to.*`).
367
+ * - `path` — `{ kind: "path", from? | points, closed? }`: follows a wall's centreline, a
368
+ * family's run or given points (reports `length`; the recipe reads the path).
369
+ * - `cut` — `{ kind: "cut", to: "host" }`: applies recipe voids to the current host.
370
+ * Removing it closes the opening without detaching or deleting the family.
371
+ * Door/window placement adds it. Hosting alone does not cut. No parameter binds.
372
+ * Placement relations take `bind: { <param>: "<expression over its reports>" }`; bound
373
+ * parameters are read-only. A relation with an existing `id` replaces it.
374
+ *
252
375
  * `length` values are Snaptrude units: the same 254 mm unit as the kernel's
253
376
  * internal (`"babylon"`) unit — convert the user's number with
254
377
  * `core.units.convert` first; a bare `400` is 400 SU, not 400 mm. Kernel part
@@ -263,6 +386,15 @@ export abstract class PluginDesignFamilyApi {
263
386
  * # Example
264
387
  * ```ts
265
388
  * await snaptrude.design.family.setParameters({ component, values: { seats: 8 } })
389
+ * await snaptrude.design.family.setParameters({ component, children: { chair: { width: "380mm" } } })
390
+ * await snaptrude.design.family.setParameters({
391
+ * component: railing,
392
+ * links: { steps: { from: stair, expr: "steps" }, side: { from: stair, expr: "width - 40mm" } },
393
+ * })
394
+ * await snaptrude.design.family.setParameters({
395
+ * component: post,
396
+ * relations: { add: [{ kind: "attach", side: "top", to: slab, bind: { height: "reach" } }] },
397
+ * })
266
398
  * ```
267
399
  */
268
400
  public abstract setParameters(
@@ -276,8 +408,17 @@ export abstract class PluginDesignFamilyApi {
276
408
  * in the model yet, one instance is also dropped at the centre of the current
277
409
  * view on the active storey (`placed: true`), so a save is never invisible.
278
410
  *
411
+ * When the draft edits a saved family (opened with `get({ id })` /
412
+ * `get({ component })`), or its name is already taken in that library, the
413
+ * APP asks the user in its own window whether to save a new version of that
414
+ * family — every placed copy in this project moves to it, and families
415
+ * nesting it may be offered theirs — or a new family ("new family" is the
416
+ * default). The agent never asks this itself and cannot answer the window.
417
+ * `decision` reports what the user chose; `"cancelled"` means nothing was
418
+ * saved (`ok: false`) and nothing will auto-save until `create` is called again.
419
+ *
279
420
  * @param args Optional library name and team scope
280
- * @returns The catalogued definition's id and version
421
+ * @returns The catalogued definition's id and version, and what the user chose
281
422
  *
282
423
  * @examplePrompt Save this as a family called Vertical Louvre
283
424
  * @examplePrompt Add it to my library
@@ -322,6 +463,22 @@ export abstract class PluginDesignFamilyApi {
322
463
  // Result types
323
464
  // ---------------------------------------------------------------------------
324
465
 
466
+ /** One worked example in the catalogue returned by `getExamples`. */
467
+ export interface PluginFamilyExampleSummary {
468
+ id: string
469
+ label: string
470
+ discipline: "architecture" | "structure" | "mep" | "furniture"
471
+ /** The patterns it demonstrates, e.g. "nesting", "slot", "path", "union", "trim". */
472
+ teaches: string[]
473
+ /** Size of the bundle as compact JSON, kilobytes — what fetching it costs. */
474
+ sizeKB: number
475
+ }
476
+
477
+ /** One worked example with its full bundle, from `getExamples({ id })`. */
478
+ export interface PluginFamilyExample extends PluginFamilyExampleSummary {
479
+ bundle: PluginFamilyBundle
480
+ }
481
+
325
482
  /** The family-spec DSL reference returned by `getReference`. */
326
483
  export interface PluginFamilyReference {
327
484
  /** Grammar, op table, expression whitelist, repair contract, worked example. */
@@ -340,6 +497,34 @@ export interface PluginFamilyReference {
340
497
  export interface PluginFamilyBundle {
341
498
  root: Record<string, unknown>
342
499
  deps?: Record<string, Record<string, unknown>>
500
+ /** Connector metadata saved with the recipe; network placement evaluates these expressions. */
501
+ network?: NetworkFamilyContract
502
+ }
503
+
504
+ /** Which saved family a draft edits. */
505
+ export interface PluginFamilyEditing {
506
+ /** Library id of the family being edited. */
507
+ familyId: string
508
+ /** The version that was opened. */
509
+ version: number
510
+ /** Its library name — an update saves under this name. */
511
+ name: string
512
+ /** "your library", or the team library's name. */
513
+ library: string
514
+ }
515
+
516
+ /** What `get` returns: the bundle, plus `editing` when the draft edits a saved family. */
517
+ export interface PluginFamilyGetResult extends PluginFamilyBundle {
518
+ editing?: PluginFamilyEditing
519
+ }
520
+
521
+ /** A family at a version, as `create` reports parents it did or did not update. */
522
+ export interface PluginFamilyVersionRef {
523
+ id: string
524
+ name: string
525
+ version: number
526
+ /** Set when the user chose to update it but the update failed. */
527
+ error?: string
343
528
  }
344
529
 
345
530
  /** Axis-aligned extents of a built family. Snaptrude units (1 SU = 254 mm), +Y up. */
@@ -387,6 +572,10 @@ export interface PluginFamilyDiagnostic {
387
572
  | "depth"
388
573
  | "bad-dimension"
389
574
  | "proportion"
575
+ /** A part the geometry kernel could not build exactly; drawn approximately. */
576
+ | "kernel-fallback"
577
+ /** A part whose hole loop crosses its outline. */
578
+ | "profile-hole"
390
579
  message: string
391
580
  hint?: string
392
581
  }
@@ -487,6 +676,49 @@ export interface PluginFamilySummary {
487
676
  kind: "native" | "revit"
488
677
  }
489
678
 
679
+ /** Exact catalog pin. Unsaved session definitions are not catalog inventory. */
680
+ export const PluginDesignFamilyGetDefinitionArgs = z.object({
681
+ id: z.string().min(1),
682
+ version: z.number().int().positive(),
683
+ })
684
+ export type PluginDesignFamilyGetDefinitionArgs = z.infer<
685
+ typeof PluginDesignFamilyGetDefinitionArgs
686
+ >
687
+
688
+ /** Saved-family metadata. Physical lengths use mm; angle entries retain degrees. */
689
+ export const PluginFamilyDefinitionParameter = z.object({
690
+ key: z.string(),
691
+ label: z.string(),
692
+ type: z.enum(["length", "integer", "enum", "text", "boolean"]),
693
+ /** Angles use the legacy length type. Check unit before interpreting numeric values. */
694
+ unit: z.enum(["mm", "degrees"]).optional(),
695
+ default: z.union([z.number().finite(), z.string()]),
696
+ min: z.number().finite().optional(),
697
+ max: z.number().finite().optional(),
698
+ options: z.array(z.string()).optional(),
699
+ drivesGeometry: z.boolean(),
700
+ readOnly: z.boolean(),
701
+ formulaDriven: z.boolean(),
702
+ /** False means shared/type: placement cannot change it for one instance. */
703
+ isInstance: z.boolean(),
704
+ })
705
+ export type PluginFamilyDefinitionParameter = z.infer<
706
+ typeof PluginFamilyDefinitionParameter
707
+ >
708
+
709
+ /** Read-only catalog inventory. Boolean defaults retain the family DSL's Yes/No strings. */
710
+ export const PluginFamilyDefinition = z.object({
711
+ id: z.string(),
712
+ version: z.number().int().positive(),
713
+ label: z.string(),
714
+ category: z.string().optional(),
715
+ lengthUnit: z.literal("mm"),
716
+ parameters: z.array(PluginFamilyDefinitionParameter),
717
+ /** Pinned connector metadata. Absence means this family cannot build a topology network. */
718
+ network: NetworkFamilyContract.optional(),
719
+ })
720
+ export type PluginFamilyDefinition = z.infer<typeof PluginFamilyDefinition>
721
+
490
722
  /** What `place` reports. */
491
723
  export interface PluginFamilyPlaceResult {
492
724
  ok: boolean
@@ -515,6 +747,99 @@ export interface PluginFamilyInstanceParameters {
515
747
  parts?: PluginFamilyPart[]
516
748
  /** Present when `getParameters` was asked for a `snapshot`. */
517
749
  snapshot?: PluginFamilySnapshot
750
+ /** The family's `outputs`, as last built. */
751
+ reports?: Record<string, number | string>
752
+ /** Every declared nested child, as last built. */
753
+ children?: PluginFamilyNestedChild[]
754
+ /** Parameters that follow another object (links) or the host. */
755
+ follows?: PluginFamilyFollow[]
756
+ /** Where a hosted family sits: a system wall, or a family by id. */
757
+ host?: ({ kind: "wall" } | { kind: "family"; id: string }) & {
758
+ /** True when the host no longer has room for it: it is out of the model until it fits again. */
759
+ parked?: boolean
760
+ }
761
+ /** Guests hosted in THIS family that no longer fit it (parked); they return when they fit. */
762
+ parkedGuests?: { id: string; family: string }[]
763
+ /** How it sits against other placed objects (attach, join, span, path), with current reports. */
764
+ relations?: PluginFamilyRelation[]
765
+ /** setParameters only: the edits refused, each with what to change instead. */
766
+ refused?: string[]
767
+ }
768
+
769
+ /** A nested child of a placed family. */
770
+ export interface PluginFamilyNestedChild {
771
+ /** "chair", or "chair/cushion" for a child of a child — the key for `children` edits. */
772
+ path: string
773
+ label: string
774
+ /** The family it is now (a slot's current pick). */
775
+ family: string
776
+ familyLabel: string
777
+ /** How many copies the parent places. */
778
+ copies: number
779
+ slot?: { expr: string; options: string[] }
780
+ values: Record<string, number | string>
781
+ /** Values the parent sets, with the expression it sets them by. */
782
+ setBy: Record<string, string | undefined>
783
+ reports: Record<string, number | string>
784
+ }
785
+
786
+ /** A placed relation as stored, plus what it measures now. */
787
+ export type PluginFamilyRelation = PluginFamilyRelationInput & {
788
+ id: string
789
+ /** "Attached to", "Joins", "Spans between", "Along a path", "Cuts host". */
790
+ label: string
791
+ summary: string
792
+ /** False when a target is gone: the family stayed where it was. */
793
+ live: boolean
794
+ reports: Record<string, number | string>
795
+ }
796
+
797
+ /** One relation to add (`relations.add`). Targets are handles, except a cut's symbolic "host". */
798
+ export type PluginFamilyRelationInput = {
799
+ /** Replaces the relation with this id; a new one gets "r1", "r2"… */
800
+ id?: string
801
+ /** This family's parameter → an expression over the relation's reports and own parameters. */
802
+ bind?: Record<string, string>
803
+ } & (
804
+ | {
805
+ kind: "attach"
806
+ side: "base" | "top"
807
+ to: ComponentHandle | { level: number; face?: "base" | "top" }
808
+ offset?: number
809
+ }
810
+ | {
811
+ kind: "join"
812
+ end: "start" | "end"
813
+ to: ComponentHandle
814
+ toEnd: "start" | "end"
815
+ /** The other family's parameters, over the same reports. */
816
+ bindOther?: Record<string, string>
817
+ }
818
+ | { kind: "span"; from: ComponentHandle; to: ComponentHandle; at?: "top" | "centre" | "base" }
819
+ | {
820
+ kind: "path"
821
+ from?: ComponentHandle
822
+ points?: [number, number, number][]
823
+ closed?: boolean
824
+ }
825
+ | {
826
+ /** Apply recipe voids to the existing host without adding another host ID. */
827
+ kind: "cut"
828
+ to: "host"
829
+ bind?: never
830
+ }
831
+ )
832
+
833
+ /** One parameter that follows something else. */
834
+ export interface PluginFamilyFollow {
835
+ key: string
836
+ /** "relation": set by a placed relation — `from` is the relation's id. */
837
+ kind: "link" | "host" | "relation"
838
+ /** The source's id. */
839
+ from: string
840
+ fromLabel: string
841
+ /** The expression over the source's reports (for a host: the report name). */
842
+ expr: string
518
843
  }
519
844
 
520
845
  /** What `create` reports. */
@@ -527,6 +852,26 @@ export interface PluginFamilyCreateResult {
527
852
  placed?: boolean
528
853
  /** Host guidance for the caller, e.g. that an instance was placed at the view centre. */
529
854
  hint?: string
855
+ /**
856
+ * What the user chose in the app's window: `"version"` (a new version of
857
+ * the saved family), `"new"` (a new family), `"cancelled"` (nothing saved).
858
+ * `"saved"` = no question was needed.
859
+ */
860
+ decision?: "version" | "new" | "cancelled" | "saved"
861
+ /** The library name it was saved under. */
862
+ name?: string
863
+ /** Placed copies in this project moved to the new version (the family's and updated parents'). */
864
+ movedCopies?: number
865
+ /** What the moved copies lost because the new version no longer has it. */
866
+ dropped?: {
867
+ values: Array<{ copy: string; key: string }>
868
+ childValues: Array<{ copy: string; path: string }>
869
+ follows: Array<{ copy: string; key: string; kind: "link" | "relation" }>
870
+ }
871
+ /** Families nesting this one that the user chose to update, at their new versions. */
872
+ parentsUpdated?: PluginFamilyVersionRef[]
873
+ /** Families nesting this one that keep their version. */
874
+ parentsKept?: PluginFamilyVersionRef[]
530
875
  error?: string
531
876
  }
532
877
 
@@ -555,6 +900,7 @@ export interface PluginFamilyDeleteResult {
555
900
  export const PluginFamilyBundleSchema = z.object({
556
901
  root: z.record(z.string(), z.unknown()),
557
902
  deps: z.record(z.string(), z.record(z.string(), z.unknown())).optional(),
903
+ network: NetworkFamilyContract.optional(),
558
904
  })
559
905
 
560
906
  /**
@@ -576,16 +922,39 @@ export const PluginFamilyBundleSchema = z.object({
576
922
  * the viewport is not moved — and comes back as a PNG data URL in the result's
577
923
  * `snapshot.image`. Through the agent's `run_worker`, return the result and the
578
924
  * image reaches the model as an image.
925
+ *
926
+ * `focus` makes it a close-up: `{ parts: ["seat", "back", "slat@*"] }` frames
927
+ * those parts (`*` matches any run of characters; nested names are `a/b`) with
928
+ * a margin of a quarter of their size (at least 50 mm), so a joint is "the two
929
+ * parts that meet"; `{ box: { min, max } }` frames a region in family space
930
+ * (Snaptrude units, +Y up). Every other part is still drawn; the dimension line
931
+ * then gives the framed region and the result says `closeUp: true`. A part
932
+ * name that matches nothing fails the snapshot with a list of real names.
579
933
  */
934
+ export const PluginFamilySnapshotFocus = z.union([
935
+ z.object({ parts: z.array(z.string()).min(1) }),
936
+ z.object({
937
+ box: z.object({
938
+ min: z.tuple([z.number(), z.number(), z.number()]),
939
+ max: z.tuple([z.number(), z.number(), z.number()]),
940
+ }),
941
+ }),
942
+ ])
943
+ export type PluginFamilySnapshotFocus = z.infer<typeof PluginFamilySnapshotFocus>
944
+
580
945
  export const PluginFamilySnapshotOption = z.union([
581
946
  z.boolean(),
582
947
  z.object({
583
948
  views: z.array(z.enum(["iso", "front", "right", "top"])).optional(),
584
949
  /** Pixels per tile (square), 64–2048; default 512. */
585
950
  tileSize: z.number().optional(),
951
+ /** A close-up of some parts or a region instead of the whole family. */
952
+ focus: PluginFamilySnapshotFocus.optional(),
586
953
  }),
587
954
  ])
588
- export type PluginFamilySnapshotOption = z.infer<typeof PluginFamilySnapshotOption>
955
+ export type PluginFamilySnapshotOption = z.infer<
956
+ typeof PluginFamilySnapshotOption
957
+ >
589
958
 
590
959
  /** The picture block on prepare / flex / getParameters results when `snapshot` was asked for. */
591
960
  export interface PluginFamilySnapshot {
@@ -593,9 +962,11 @@ export interface PluginFamilySnapshot {
593
962
  /** PNG data URL of the composed sheet. */
594
963
  image?: string
595
964
  views?: ("iso" | "front" | "right" | "top")[]
596
- /** [width, height, depth] in Snaptrude units. */
965
+ /** [width, height, depth] in Snaptrude units — of the framed region when `closeUp`. */
597
966
  size?: [number, number, number]
598
967
  ms?: number
968
+ /** True when the sheet is a close-up (`focus`), not the whole family. */
969
+ closeUp?: boolean
599
970
  /** Set when the picture could not be taken (e.g. the legacy renderer); the call itself still succeeded. */
600
971
  error?: string
601
972
  }
@@ -605,7 +976,9 @@ export const PluginDesignFamilyPrepareArgs = z.object({
605
976
  spec: PluginFamilyBundleSchema.optional(),
606
977
  snapshot: PluginFamilySnapshotOption.optional(),
607
978
  })
608
- export type PluginDesignFamilyPrepareArgs = z.infer<typeof PluginDesignFamilyPrepareArgs>
979
+ export type PluginDesignFamilyPrepareArgs = z.infer<
980
+ typeof PluginDesignFamilyPrepareArgs
981
+ >
609
982
 
610
983
  /**
611
984
  * Arguments for {@linkcode PluginDesignFamilyApi.flex}.
@@ -619,7 +992,42 @@ export const PluginDesignFamilyFlexArgs = z.object({
619
992
  values: z.record(z.string(), z.union([z.number(), z.string()])),
620
993
  snapshot: PluginFamilySnapshotOption.optional(),
621
994
  })
622
- export type PluginDesignFamilyFlexArgs = z.infer<typeof PluginDesignFamilyFlexArgs>
995
+ export type PluginDesignFamilyFlexArgs = z.infer<
996
+ typeof PluginDesignFamilyFlexArgs
997
+ >
998
+
999
+ /**
1000
+ * Arguments for {@linkcode PluginDesignFamilyApi.get}. All optional — no
1001
+ * arguments returns the prepared draft.
1002
+ *
1003
+ * | Property | Type | Description |
1004
+ * |---|---|---|
1005
+ * | `id` | `string` | A saved family to open for editing, from {@linkcode PluginDesignFamilyApi.list} |
1006
+ * | `version` | `number` | Exact version to open; defaults to the newest |
1007
+ * | `component` | {@linkcode ComponentHandle} | Open the family of this placed copy, at its version |
1008
+ */
1009
+ export const PluginDesignFamilyGetArgs = z.object({
1010
+ id: z.string().optional(),
1011
+ version: z.number().int().positive().optional(),
1012
+ component: ComponentHandle.optional(),
1013
+ })
1014
+ export type PluginDesignFamilyGetArgs = z.infer<typeof PluginDesignFamilyGetArgs>
1015
+
1016
+ /**
1017
+ * Arguments for {@linkcode PluginDesignFamilyApi.getExamples}.
1018
+ *
1019
+ * | Property | Type | Description |
1020
+ * |---|---|---|
1021
+ * | `id` | `string` | One example's id — returns its bundle |
1022
+ * | `discipline` | `string` | List only this discipline: architecture, structure, mep, furniture |
1023
+ * | `tag` | `string` | List only examples teaching this pattern (e.g. "path", "union") |
1024
+ */
1025
+ export const PluginDesignFamilyGetExamplesArgs = z.object({
1026
+ id: z.string().optional(),
1027
+ discipline: z.enum(["architecture", "structure", "mep", "furniture"]).optional(),
1028
+ tag: z.string().optional(),
1029
+ })
1030
+ export type PluginDesignFamilyGetExamplesArgs = z.infer<typeof PluginDesignFamilyGetExamplesArgs>
623
1031
 
624
1032
  /**
625
1033
  * Arguments for {@linkcode PluginDesignFamilyApi.create}.
@@ -633,7 +1041,9 @@ export const PluginDesignFamilyCreateArgs = z.object({
633
1041
  name: z.string().optional(),
634
1042
  teamId: z.string().optional(),
635
1043
  })
636
- export type PluginDesignFamilyCreateArgs = z.infer<typeof PluginDesignFamilyCreateArgs>
1044
+ export type PluginDesignFamilyCreateArgs = z.infer<
1045
+ typeof PluginDesignFamilyCreateArgs
1046
+ >
637
1047
 
638
1048
  /**
639
1049
  * Arguments for {@linkcode PluginDesignFamilyApi.delete}.
@@ -647,7 +1057,9 @@ export const PluginDesignFamilyDeleteArgs = z.object({
647
1057
  id: z.string().optional(),
648
1058
  instances: z.boolean().optional(),
649
1059
  })
650
- export type PluginDesignFamilyDeleteArgs = z.infer<typeof PluginDesignFamilyDeleteArgs>
1060
+ export type PluginDesignFamilyDeleteArgs = z.infer<
1061
+ typeof PluginDesignFamilyDeleteArgs
1062
+ >
651
1063
 
652
1064
  /**
653
1065
  * Arguments for {@linkcode PluginDesignFamilyApi.place}.
@@ -668,8 +1080,12 @@ export const PluginDesignFamilyPlaceArgs = z.object({
668
1080
  rotation: z.number().optional(),
669
1081
  values: z.record(z.string(), z.union([z.number(), z.string()])).optional(),
670
1082
  storey: z.number().int().optional(),
1083
+ /** Hosted families: the wall or family to set it into. */
1084
+ host: ComponentHandle.optional(),
671
1085
  })
672
- export type PluginDesignFamilyPlaceArgs = z.infer<typeof PluginDesignFamilyPlaceArgs>
1086
+ export type PluginDesignFamilyPlaceArgs = z.infer<
1087
+ typeof PluginDesignFamilyPlaceArgs
1088
+ >
673
1089
 
674
1090
  /**
675
1091
  * Arguments for {@linkcode PluginDesignFamilyApi.getParameters}.
@@ -697,7 +1113,22 @@ export type PluginDesignFamilyGetParametersArgs = z.infer<
697
1113
  */
698
1114
  export const PluginDesignFamilySetParametersArgs = z.object({
699
1115
  component: ComponentHandle,
700
- values: z.record(z.string(), z.union([z.number(), z.string()])),
1116
+ values: z.record(z.string(), z.union([z.number(), z.string()])).optional().default({}),
1117
+ /** Nested children's own values: { "<path>": { key: value } }. */
1118
+ children: z
1119
+ .record(z.string(), z.record(z.string(), z.union([z.number(), z.string()])))
1120
+ .optional(),
1121
+ /** Links: { key: { from, expr } | null }. */
1122
+ links: z
1123
+ .record(z.string(), z.union([z.object({ from: ComponentHandle, expr: z.string() }), z.null()]))
1124
+ .optional(),
1125
+ /** Placed relations: { add?: [{ kind, … }], remove?: ["r1"] } — checked by the host. */
1126
+ relations: z
1127
+ .object({
1128
+ add: z.array(z.record(z.string(), z.unknown())).optional(),
1129
+ remove: z.array(z.string()).optional(),
1130
+ })
1131
+ .optional(),
701
1132
  })
702
1133
  export type PluginDesignFamilySetParametersArgs = z.infer<
703
1134
  typeof PluginDesignFamilySetParametersArgs