@snaptrude/plugin-core 1.0.0 → 1.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snaptrude/plugin-core",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -1406,20 +1406,24 @@ export abstract class PluginDesignCreateApi {
1406
1406
 
1407
1407
  /**
1408
1408
  * **Copy** existing entities, offsetting each copy by `displacement * i` (i = 1…count),
1409
- * source positions preserved. Migrated from the removed `tools.copy`. In `"instance"`
1410
- * mode copies stay in the source's instance family where possible; `"unique"` makes
1411
- * independent geometry. The created copies become the active selection. Undoable.
1409
+ * source positions preserved. Migrated from the removed `tools.copy`.
1410
+ * The created copies become the active selection. Undoable.
1412
1411
  *
1413
- * Runs on the engine's array-copy tool, so its semantics carry over: an
1414
- * `"instance"` copy of a source that is not yet an instance **replaces that
1415
- * source** with a fresh instance component — the handle you passed in stops
1416
- * resolving after the call. Re-query (e.g. `design.query.listSpaces`) to pick
1417
- * up the replacement before operating on that entity again; copying the stale
1418
- * handle throws. `"unique"` mode never replaces sources.
1412
+ * Agents should omit `mode`. Never ask Instance/Unique for recipe family or
1413
+ * parametric door/window copies. The field remains for legacy geometry-only
1414
+ * compatibility: `"instance"` shares geometry where possible; `"unique"` does not.
1415
+ * Recipe families (including recipe stairs) and parametric doors/windows keep
1416
+ * their project family in BOTH modes. Use `design.family.duplicate` for an
1417
+ * independent family variant; do not create a group to simulate family sharing.
1418
+ * Door/window sill, hand/swing and host-bound dimensions remain placement-local.
1419
+ *
1420
+ * Uses the engine's array-copy tool. Recipe and graph-registered source handles
1421
+ * stay live. Legacy catalog geometry can still replace its source when converting
1422
+ * it to a Babylon instance; re-query if a legacy source handle becomes stale.
1419
1423
  *
1420
1424
  * @param components - Entities to copy (≥1)
1421
1425
  * @param displacement - Per-copy offset as a {@linkcode Vec3Handle} (copy `i` at `displacement * i`)
1422
- * @param options - `count` (copies per component, default 1), `mode` (`"instance"` | `"unique"`, default `"instance"`)
1426
+ * @param options - `count` (copies per component, default 1). Omit `mode`; it is legacy geometry-only compatibility.
1423
1427
  * @returns the created copies as {@linkcode ComponentHandle}`[]`
1424
1428
  * @throws if a component handle is unknown or stale (replaced by an earlier
1425
1429
  * `"instance"` copy), `count` is not a positive integer, or the engine fails
@@ -1428,7 +1432,6 @@ export abstract class PluginDesignCreateApi {
1428
1432
  * @examplePrompt Duplicate the selected furniture along the X axis
1429
1433
  * @examplePrompt Make 5 copies of this column at 4 metre intervals
1430
1434
  * @examplePrompt Array these walls with a 3m offset
1431
- * @examplePrompt Clone the desk as a unique copy, not an instance
1432
1435
  *
1433
1436
  * @performance To place N repeats of the same geometry, bulk-create the seed(s) once then
1434
1437
  * call this ONCE with `{ count: N - 1 }` — one host round-trip for all copies. Never
@@ -2167,12 +2170,15 @@ export type PluginDesignCreateReferenceLinesArgs = z.infer<
2167
2170
  // ---------------------------------------------------------------------------
2168
2171
 
2169
2172
  /**
2170
- * Copy mode for {@linkcode PluginDesignCreateApi.copy}.
2173
+ * Legacy geometry-only copy mode for {@linkcode PluginDesignCreateApi.copy}.
2174
+ * Retained for runtime compatibility. Agents should omit it and never ask
2175
+ * Instance/Unique for recipe families or parametric openings: both modes retain
2176
+ * their project family. Use `design.family.duplicate` for independence.
2171
2177
  *
2172
2178
  * | Value | Description |
2173
2179
  * |---|---|
2174
- * | `"instance"` | Keep copies in the source's instance family where possible |
2175
- * | `"unique"` | Independent geometry copies |
2180
+ * | `"instance"` | Share legacy geometry where possible |
2181
+ * | `"unique"` | Independent legacy geometry copies |
2176
2182
  */
2177
2183
  export const PluginCopyMode = z.enum(["instance", "unique"])
2178
2184
  export type PluginCopyMode = z.infer<typeof PluginCopyMode>
@@ -2185,7 +2191,7 @@ export type PluginCopyMode = z.infer<typeof PluginCopyMode>
2185
2191
  * | `components` | {@linkcode ComponentHandle}`[]` | Entities to copy (≥1) |
2186
2192
  * | `displacement` | {@linkcode Vec3Handle} | Per-copy offset (copy `i` at `displacement * i`) |
2187
2193
  * | `count` | `number`? | Copies per component (default 1) |
2188
- * | `mode` | {@linkcode PluginCopyMode}? | `"instance"` (default) or `"unique"` |
2194
+ * | `mode` | {@linkcode PluginCopyMode}? | Legacy geometry-only compatibility; agents should omit |
2189
2195
  */
2190
2196
  export const PluginDesignCreateCopyArgs = z.object({
2191
2197
  components: z.array(ComponentHandle).min(1),
@@ -335,8 +335,8 @@ export abstract class PluginDesignFamilyApi {
335
335
  * and returns immediately — no geometry yet. The USER clicks to drop it, so
336
336
  * tell them to. Prefer this when the location is a judgement call.
337
337
  *
338
- * To COPY a placed component, place its family again at the new position with
339
- * the same `values` — there is no separate copy call.
338
+ * To copy a placement, use `design.create.copy` to retain its project family.
339
+ * Placing a library definition again does not select an existing project variant.
340
340
  *
341
341
  * HOSTED families (doors, windows — anything with an `opening`, a `host`
342
342
  * block, or a void with `cuts: "host"`) take `host`: a wall or a placed
@@ -403,7 +403,12 @@ export abstract class PluginDesignFamilyApi {
403
403
  ): PluginApiReturn<PluginFamilyInstanceParameters | null>
404
404
 
405
405
  /**
406
- * Change a placed component's parameters and rebuild its geometry in place.
406
+ * Change authored parameters across the selected component's project family.
407
+ * Use `duplicate({ component, name })` first for an independent project variant.
408
+ * This never publishes or modifies a library. Only changed authored values
409
+ * propagate; placement, hosting, links and relations remain local. A locked
410
+ * family member or host refuses a shared edit. Mixed requests containing links,
411
+ * relations and parameters are not one atomic transaction.
407
412
  *
408
413
  * Partial: pass only what changes. The component keeps its position,
409
414
  * rotation and identity — this is an edit, not a replace, and it is undoable.
@@ -469,6 +474,47 @@ export abstract class PluginDesignFamilyApi {
469
474
  args: PluginDesignFamilySetParametersArgs,
470
475
  ): PluginApiReturn<PluginFamilyInstanceParameters>
471
476
 
477
+ /**
478
+ * Assign one placed recipe family or parametric door/window to a new project
479
+ * family. Does not copy geometry, publish a library or alter native system families.
480
+ * Subsequent authored parameter edits no longer affect the original family.
481
+ * Undo restores the original family identity and name together.
482
+ * This is project-only. If the user requests both an independent project family
483
+ * and a library asset, call duplicate first, then saveToLibrary with the same
484
+ * component. If they request a library save later, call saveToLibrary alone;
485
+ * do not duplicate again.
486
+ * @param args The selected component and a nonblank project-family name.
487
+ * @returns The unchanged component handle, new project-family id and trimmed name.
488
+ * @example await snaptrude.design.family.duplicate({ component, name: "Wide door" })
489
+ */
490
+ public abstract duplicate(
491
+ args: PluginDesignFamilyDuplicateArgs,
492
+ ): PluginApiReturn<PluginFamilyDuplicateResult>
493
+
494
+ /**
495
+ * Save a placed recipe family or parametric door/window's current authored
496
+ * configuration as a separate library asset. Does not place geometry, reassign
497
+ * any model component, overwrite the original library asset or publish publicly.
498
+ *
499
+ * This is an explicit shared-library write: call only when the user requests
500
+ * a library save, never as a side effect of copying or project-only duplication.
501
+ * Choose the destination explicitly: teamId is a team id, or null for personal.
502
+ * Preserve libraryKey across retries of this save; a new UUID means a new save.
503
+ * For project independence plus a library asset, call duplicate first, then
504
+ * saveToLibrary on that component. To save later, call saveToLibrary alone.
505
+ *
506
+ * @param args Component, nonblank asset name, explicit library scope and retry key.
507
+ * @returns The saved asset's id, guid, version and name.
508
+ * @example
509
+ * const libraryKey = crypto.randomUUID() // Retain this key if the save needs retrying.
510
+ * const saved = await snaptrude.design.family.saveToLibrary({
511
+ * component, name: "Wide door", teamId: null, libraryKey,
512
+ * })
513
+ */
514
+ public abstract saveToLibrary(
515
+ args: PluginDesignFamilySaveToLibraryArgs,
516
+ ): PluginApiReturn<PluginFamilySaveToLibraryResult>
517
+
472
518
  /**
473
519
  * Save the prepared bundle to the library: the open project's team library in a team
474
520
  * project, else the user's personal library. `teamId` picks another team, and `null` the
@@ -1301,6 +1347,45 @@ export type PluginDesignFamilyGetParametersArgs = z.infer<
1301
1347
  typeof PluginDesignFamilyGetParametersArgs
1302
1348
  >
1303
1349
 
1350
+ /** Input to project-only family duplication; names never determine membership. */
1351
+ export const PluginDesignFamilyDuplicateArgs = z.object({
1352
+ component: ComponentHandle,
1353
+ name: z.string().trim().min(1),
1354
+ })
1355
+ export type PluginDesignFamilyDuplicateArgs = z.infer<typeof PluginDesignFamilyDuplicateArgs>
1356
+
1357
+ /** The selected placement now belongs to this project family, not a new library entry. */
1358
+ export const PluginFamilyDuplicateResult = z.object({
1359
+ component: ComponentHandle,
1360
+ projectFamilyId: z.string().min(1),
1361
+ name: z.string().min(1),
1362
+ })
1363
+ export type PluginFamilyDuplicateResult = z.infer<typeof PluginFamilyDuplicateResult>
1364
+
1365
+ /** Explicit library destination and stable retry identity for a separate asset save. */
1366
+ export const PluginDesignFamilySaveToLibraryArgs = z.object({
1367
+ component: ComponentHandle,
1368
+ name: z.string().trim().min(1),
1369
+ /** Required: a team id, or null for the personal library. Never inferred from the project. */
1370
+ teamId: z.string().nullable(),
1371
+ /** Reuse for retries of this save; generate a new UUID only for a new save. */
1372
+ libraryKey: z.string().uuid(),
1373
+ })
1374
+ export type PluginDesignFamilySaveToLibraryArgs = z.infer<
1375
+ typeof PluginDesignFamilySaveToLibraryArgs
1376
+ >
1377
+
1378
+ /** Saved library asset identity; no component was placed or reassigned. */
1379
+ export const PluginFamilySaveToLibraryResult = z.object({
1380
+ id: z.string(),
1381
+ guid: z.string(),
1382
+ version: z.number().int().positive(),
1383
+ name: z.string(),
1384
+ })
1385
+ export type PluginFamilySaveToLibraryResult = z.infer<
1386
+ typeof PluginFamilySaveToLibraryResult
1387
+ >
1388
+
1304
1389
  /**
1305
1390
  * Arguments for {@linkcode PluginDesignFamilyApi.setParameters}.
1306
1391
  *