@snaptrude/plugin-core 0.6.0 → 0.7.0

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 (83) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/api-manifest.full.json +511 -10
  3. package/api-manifest.json +529 -4
  4. package/dist/api/core/index.d.ts +5 -0
  5. package/dist/api/core/index.d.ts.map +1 -1
  6. package/dist/api/core/proposals/index.d.ts +475 -0
  7. package/dist/api/core/proposals/index.d.ts.map +1 -0
  8. package/dist/api/design/create/index.d.ts +209 -0
  9. package/dist/api/design/create/index.d.ts.map +1 -1
  10. package/dist/api/design/doors/index.d.ts +159 -0
  11. package/dist/api/design/doors/index.d.ts.map +1 -1
  12. package/dist/api/design/furniture/index.d.ts +34 -5
  13. package/dist/api/design/furniture/index.d.ts.map +1 -1
  14. package/dist/api/design/index.d.ts +83 -0
  15. package/dist/api/design/index.d.ts.map +1 -1
  16. package/dist/api/design/lock.d.ts +26 -0
  17. package/dist/api/design/lock.d.ts.map +1 -1
  18. package/dist/api/design/materials/index.d.ts +159 -2
  19. package/dist/api/design/materials/index.d.ts.map +1 -1
  20. package/dist/api/design/query/index.d.ts +115 -5
  21. package/dist/api/design/query/index.d.ts.map +1 -1
  22. package/dist/api/design/selection/index.d.ts +2 -2
  23. package/dist/api/design/transform/index.d.ts +83 -2
  24. package/dist/api/design/transform/index.d.ts.map +1 -1
  25. package/dist/api/design/update/index.d.ts +168 -0
  26. package/dist/api/design/update/index.d.ts.map +1 -1
  27. package/dist/api/design/windows/index.d.ts +113 -2
  28. package/dist/api/design/windows/index.d.ts.map +1 -1
  29. package/dist/api/entity/story.d.ts +101 -7
  30. package/dist/api/entity/story.d.ts.map +1 -1
  31. package/dist/api/presentation/import.d.ts +49 -4
  32. package/dist/api/presentation/import.d.ts.map +1 -1
  33. package/dist/api/presentation/views.d.ts +210 -5
  34. package/dist/api/presentation/views.d.ts.map +1 -1
  35. package/dist/api/program/areas.d.ts +42 -12
  36. package/dist/api/program/areas.d.ts.map +1 -1
  37. package/dist/api/program/index.d.ts +3 -11
  38. package/dist/api/program/index.d.ts.map +1 -1
  39. package/dist/api/program/layout.d.ts +186 -11
  40. package/dist/api/program/layout.d.ts.map +1 -1
  41. package/dist/errors/codes.d.ts +34 -0
  42. package/dist/errors/codes.d.ts.map +1 -0
  43. package/dist/errors/envelope.d.ts +56 -0
  44. package/dist/errors/envelope.d.ts.map +1 -0
  45. package/dist/errors/index.d.ts +6 -0
  46. package/dist/errors/index.d.ts.map +1 -0
  47. package/dist/errors/plugin-error.d.ts +69 -0
  48. package/dist/errors/plugin-error.d.ts.map +1 -0
  49. package/dist/host-utils.d.ts +4 -0
  50. package/dist/host-utils.d.ts.map +1 -1
  51. package/dist/index.cjs +1624 -1028
  52. package/dist/index.cjs.map +1 -1
  53. package/dist/index.d.ts +1 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +1546 -1025
  56. package/dist/index.js.map +1 -1
  57. package/package.json +2 -1
  58. package/scripts/generate-manifest.test.mjs +77 -0
  59. package/src/api/core/index.ts +5 -0
  60. package/src/api/core/proposals/index.ts +509 -0
  61. package/src/api/design/create/index.ts +249 -0
  62. package/src/api/design/doors/index.ts +168 -0
  63. package/src/api/design/furniture/index.ts +36 -5
  64. package/src/api/design/index.ts +87 -0
  65. package/src/api/design/lock.ts +27 -0
  66. package/src/api/design/materials/index.ts +182 -2
  67. package/src/api/design/query/index.ts +81 -0
  68. package/src/api/design/transform/index.ts +74 -2
  69. package/src/api/design/update/index.ts +167 -0
  70. package/src/api/design/windows/index.ts +128 -2
  71. package/src/api/entity/story.ts +105 -7
  72. package/src/api/presentation/import.ts +45 -4
  73. package/src/api/presentation/views.ts +221 -5
  74. package/src/api/program/areas.ts +34 -12
  75. package/src/api/program/index.ts +3 -11
  76. package/src/api/program/layout.ts +195 -11
  77. package/src/errors/codes.ts +136 -0
  78. package/src/errors/envelope.ts +75 -0
  79. package/src/errors/index.ts +21 -0
  80. package/src/errors/plugin-error.ts +134 -0
  81. package/src/host-utils.ts +4 -0
  82. package/src/index.ts +1 -0
  83. package/test/errors.test.mjs +184 -0
@@ -24,6 +24,13 @@ import {
24
24
  * Footprint objects take a {@linkcode ContourHandle} (outer profile + holes);
25
25
  * build one with `snaptrude.core.geom.create.contourFromProfile(s)`.
26
26
  *
27
+ * `door`/`window` place a catalog item into a host wall (the world point is
28
+ * projected onto the wall face to locate the opening; the wall is re-cut in one
29
+ * undo entry) and throw if the point projects outside the wall. `smartLayout` is
30
+ * the one creator returning a grouped {@linkcode PluginSmartLayoutResult} rather
31
+ * than a handle — it clones an in-scene template into one or more target spaces,
32
+ * merging every target into a single undo batch (see its JSDoc).
33
+ *
27
34
  * Accessed via `snaptrude.design.create`.
28
35
  */
29
36
  export abstract class PluginDesignCreateApi {
@@ -433,6 +440,142 @@ export abstract class PluginDesignCreateApi {
433
440
  options?: { label?: string; createNewSourceMesh?: boolean },
434
441
  ): PluginApiReturn<ComponentHandle>
435
442
 
443
+ /**
444
+ * Place a **door** from the catalog into a host wall.
445
+ *
446
+ * Identified by a **catalog (library) id** — the team object's stable `_id`
447
+ * or the general library's `fullName`. Discover ids with
448
+ * {@linkcode PluginDesignDoorsApi.listCatalog}. The world `position` is
449
+ * projected onto the host wall's face to locate the opening; the wall is
450
+ * re-cut and the door hosted in a single undo entry. Placement is
451
+ * asynchronous (the source mesh is fetched if not cached).
452
+ *
453
+ * @param catalogId - Library id: team `_id` or general `fullName`
454
+ * @param hostWall - The wall to host the door
455
+ * @param position - World point projected onto the wall to locate the opening
456
+ * @param options - Optional placement options: `label` — instance name
457
+ * @returns the {@linkcode ComponentHandle} of the placed door
458
+ * @throws if the catalog id is unknown, the host is not a wall, the source
459
+ * mesh fails to load, or the projected point falls **outside** the host wall
460
+ *
461
+ * @examplePrompt Add a door to this wall here
462
+ * @examplePrompt Place a sliding door on the selected wall
463
+ * @examplePrompt Put a door from the catalog at this point on the wall
464
+ * @examplePrompt Insert the entrance door into this wall
465
+ * @examplePrompt Add a door to the wall and call it Entry-01
466
+ *
467
+ * # Example
468
+ * ```ts
469
+ * const [wall] = await snaptrude.design.query.listWalls({ isSelected: true })
470
+ * const [entry] = await snaptrude.design.doors.listCatalog()
471
+ * const door = await snaptrude.design.create.door(
472
+ * entry.id,
473
+ * wall,
474
+ * await snaptrude.core.math.vec3.new(3, 0, 5),
475
+ * { label: "Entry-01" },
476
+ * )
477
+ * ```
478
+ */
479
+ public abstract door(
480
+ catalogId: string,
481
+ hostWall: ComponentHandle,
482
+ position: Vec3Handle,
483
+ options?: { label?: string },
484
+ ): PluginApiReturn<ComponentHandle>
485
+
486
+ /**
487
+ * Place a **window** from the catalog into a host wall.
488
+ *
489
+ * Identified by a **catalog (library) id** — the team object's stable `_id`
490
+ * or the general library's `fullName`. Discover ids with
491
+ * {@linkcode PluginDesignWindowsApi.listCatalog}. The world `position` is
492
+ * projected onto the host wall's face to locate the opening; the wall is
493
+ * re-cut and the window hosted in a single undo entry. Placement is
494
+ * asynchronous (the source mesh is fetched if not cached).
495
+ *
496
+ * @param catalogId - Library id: team `_id` or general `fullName`
497
+ * @param hostWall - The wall to host the window
498
+ * @param position - World point projected onto the wall to locate the opening
499
+ * @param options - Optional placement options: `label` — instance name
500
+ * @returns the {@linkcode ComponentHandle} of the placed window
501
+ * @throws if the catalog id is unknown, the host is not a wall, the source
502
+ * mesh fails to load, or the projected point falls **outside** the host wall
503
+ *
504
+ * @examplePrompt Add a window to this wall here
505
+ * @examplePrompt Place a casement window on the selected wall
506
+ * @examplePrompt Put a window from the catalog at this point on the wall
507
+ * @examplePrompt Insert a window into the living room wall
508
+ * @examplePrompt Add a window to the wall and name it Win-01
509
+ *
510
+ * # Example
511
+ * ```ts
512
+ * const [wall] = await snaptrude.design.query.listWalls({ isSelected: true })
513
+ * const [entry] = await snaptrude.design.windows.listCatalog()
514
+ * const window = await snaptrude.design.create.window(
515
+ * entry.id,
516
+ * wall,
517
+ * await snaptrude.core.math.vec3.new(3, 0, 5),
518
+ * { label: "Win-01" },
519
+ * )
520
+ * ```
521
+ */
522
+ public abstract window(
523
+ catalogId: string,
524
+ hostWall: ComponentHandle,
525
+ position: Vec3Handle,
526
+ options?: { label?: string },
527
+ ): PluginApiReturn<ComponentHandle>
528
+
529
+ /**
530
+ * Place a **smart layout** — clone an in-scene template cluster (walls,
531
+ * furniture, doors, windows, floors) into one or more target ROOM/DEPARTMENT
532
+ * spaces, adapting orientation and fit to each target.
533
+ *
534
+ * Supply the template as **exactly one** of `templateGroup` (a group **id**
535
+ * from `snaptrude.core.groups.list` — groups are string-id entities, not
536
+ * component handles) or
537
+ * `templateComponents` (an explicit component set); every target in `targets`
538
+ * (≥1) receives its own placement. By default the target spaces are hidden
539
+ * after placement (`hideTargets`, mirroring the product) — pass `false` to keep
540
+ * them visible.
541
+ *
542
+ * The whole operation — every wall/opening/furniture/floor across every target,
543
+ * plus the target-hide — is committed as a **single undo step**. Because all
544
+ * targets merge into one creation batch, the created entities are returned
545
+ * **flat, grouped by kind** ({@linkcode PluginSmartLayoutResult}), NOT attributed
546
+ * per target (per-target attribution would break the single-undo guarantee).
547
+ * `skippedWalls` is the interior-optimizer's best-candidate count of walls it
548
+ * could not place. Synchronous and heavy — many targets can jank the main thread.
549
+ *
550
+ * @param options - {@linkcode PluginSmartLayoutArgs}: template source, targets, and `hideTargets`
551
+ * @returns a {@linkcode PluginSmartLayoutResult} — created entities grouped by kind + `skippedWalls`
552
+ * @throws if smart layout is not enabled, the template group is invalid, there
553
+ * is no template / no targets / no valid mappings, or component creation fails
554
+ *
555
+ * @examplePrompt Copy this room's layout into the selected rooms
556
+ * @examplePrompt Apply this furniture arrangement to the other offices
557
+ * @examplePrompt Replicate this template into these two spaces
558
+ * @examplePrompt Place this desk cluster into every meeting room
559
+ * @examplePrompt Use this room as a template for the rooms I selected but keep them visible
560
+ *
561
+ * # Example
562
+ * ```ts
563
+ * const [templateRoom, ...targets] = await snaptrude.design.query.listSpaces({ isSelected: true })
564
+ * const result = await snaptrude.design.create.smartLayout({
565
+ * templateComponents: [templateRoom],
566
+ * targets,
567
+ * })
568
+ * console.log("placed walls:", result.created.walls.length, "skipped:", result.skippedWalls)
569
+ *
570
+ * // …or clone a saved group by its id (from core.groups.list)
571
+ * const { groups } = await snaptrude.core.groups.list()
572
+ * await snaptrude.design.create.smartLayout({ templateGroup: groups[0].id, targets })
573
+ * ```
574
+ */
575
+ public abstract smartLayout(
576
+ options: PluginSmartLayoutArgs,
577
+ ): PluginApiReturn<PluginSmartLayoutResult>
578
+
436
579
  /**
437
580
  * **Copy** existing entities, offsetting each copy by `displacement * i` (i = 1…count),
438
581
  * source positions preserved. Migrated from the removed `tools.copy`. In `"instance"`
@@ -804,6 +947,112 @@ export type PluginDesignCreateFurnitureArgs = z.infer<
804
947
  // TRANSPORT: positional signature shipped —
805
948
  // furniture(catalogId: string, position: Vec3Handle, options?: { label?, createNewSourceMesh? })
806
949
 
950
+ // ---------------------------------------------------------------------------
951
+ // door
952
+ // ---------------------------------------------------------------------------
953
+
954
+ /**
955
+ * Arguments for {@linkcode PluginDesignCreateApi.door}.
956
+ *
957
+ * | Property | Type | Description |
958
+ * |---|---|---|
959
+ * | `catalogId` | `string` | Library id: team `_id` or general `fullName` |
960
+ * | `hostWall` | {@linkcode ComponentHandle} | The wall to host the door |
961
+ * | `position` | {@linkcode Vec3Handle} | World point projected onto the wall |
962
+ * | `label` | `string`? | Instance name (optional) |
963
+ */
964
+ export const PluginDesignCreateDoorArgs = z.object({
965
+ catalogId: z.string().min(1),
966
+ hostWall: ComponentHandle,
967
+ position: Vec3Handle,
968
+ label: z.string().optional(),
969
+ })
970
+ export type PluginDesignCreateDoorArgs = z.infer<typeof PluginDesignCreateDoorArgs>
971
+ // TRANSPORT: positional signature shipped —
972
+ // door(catalogId: string, hostWall: ComponentHandle, position: Vec3Handle, options?: { label? })
973
+
974
+ // ---------------------------------------------------------------------------
975
+ // window
976
+ // ---------------------------------------------------------------------------
977
+
978
+ /**
979
+ * Arguments for {@linkcode PluginDesignCreateApi.window}.
980
+ *
981
+ * | Property | Type | Description |
982
+ * |---|---|---|
983
+ * | `catalogId` | `string` | Library id: team `_id` or general `fullName` |
984
+ * | `hostWall` | {@linkcode ComponentHandle} | The wall to host the window |
985
+ * | `position` | {@linkcode Vec3Handle} | World point projected onto the wall |
986
+ * | `label` | `string`? | Instance name (optional) |
987
+ */
988
+ export const PluginDesignCreateWindowArgs = z.object({
989
+ catalogId: z.string().min(1),
990
+ hostWall: ComponentHandle,
991
+ position: Vec3Handle,
992
+ label: z.string().optional(),
993
+ })
994
+ export type PluginDesignCreateWindowArgs = z.infer<typeof PluginDesignCreateWindowArgs>
995
+ // TRANSPORT: positional signature shipped —
996
+ // window(catalogId: string, hostWall: ComponentHandle, position: Vec3Handle, options?: { label? })
997
+
998
+ // ---------------------------------------------------------------------------
999
+ // smartLayout
1000
+ // ---------------------------------------------------------------------------
1001
+
1002
+ /**
1003
+ * Arguments for {@linkcode PluginDesignCreateApi.smartLayout}. Provide **exactly
1004
+ * one** template source: `templateGroup` or a non-empty `templateComponents`.
1005
+ *
1006
+ * | Property | Type | Description |
1007
+ * |---|---|---|
1008
+ * | `templateGroup` | `string`? | Id of the group to clone as the template (a group id from `snaptrude.core.groups.list`, NOT a component handle) |
1009
+ * | `templateComponents` | {@linkcode ComponentHandle}`[]`? | Explicit component set to clone as the template |
1010
+ * | `targets` | {@linkcode ComponentHandle}`[]` | ROOM/DEPARTMENT spaces to place into (≥1) |
1011
+ * | `hideTargets` | `boolean`? | Hide the target spaces after placement (default `true`) |
1012
+ */
1013
+ export const PluginSmartLayoutArgs = z
1014
+ .object({
1015
+ templateGroup: z.string().min(1).optional(),
1016
+ templateComponents: z.array(ComponentHandle).optional(),
1017
+ targets: z.array(ComponentHandle).min(1),
1018
+ hideTargets: z.boolean().optional(),
1019
+ })
1020
+ .refine(
1021
+ (v) =>
1022
+ (v.templateGroup != null) !== ((v.templateComponents?.length ?? 0) > 0),
1023
+ {
1024
+ message:
1025
+ "Provide exactly one template source: templateGroup or a non-empty templateComponents",
1026
+ },
1027
+ )
1028
+ export type PluginSmartLayoutArgs = z.infer<typeof PluginSmartLayoutArgs>
1029
+
1030
+ /**
1031
+ * Result of {@linkcode PluginDesignCreateApi.smartLayout} — the entities the run
1032
+ * created, grouped by kind (flat across all targets; see the method JSDoc for why
1033
+ * this is not attributed per target).
1034
+ *
1035
+ * | Property | Type | Description |
1036
+ * |---|---|---|
1037
+ * | `created.walls` | {@linkcode ComponentHandle}`[]` | Created walls |
1038
+ * | `created.furniture` | {@linkcode ComponentHandle}`[]` | Created furniture |
1039
+ * | `created.doors` | {@linkcode ComponentHandle}`[]` | Created doors |
1040
+ * | `created.windows` | {@linkcode ComponentHandle}`[]` | Created windows |
1041
+ * | `created.floors` | {@linkcode ComponentHandle}`[]` | Created floors |
1042
+ * | `skippedWalls` | `number` | Walls the interior optimizer could not place |
1043
+ */
1044
+ export const PluginSmartLayoutResult = z.object({
1045
+ created: z.object({
1046
+ walls: z.array(ComponentHandle),
1047
+ furniture: z.array(ComponentHandle),
1048
+ doors: z.array(ComponentHandle),
1049
+ windows: z.array(ComponentHandle),
1050
+ floors: z.array(ComponentHandle),
1051
+ }),
1052
+ skippedWalls: z.number(),
1053
+ })
1054
+ export type PluginSmartLayoutResult = z.infer<typeof PluginSmartLayoutResult>
1055
+
807
1056
  // ---------------------------------------------------------------------------
808
1057
  // referenceLines
809
1058
  // ---------------------------------------------------------------------------
@@ -12,6 +12,13 @@ import { PluginDesignChangeResult } from "../lock"
12
12
  * mesh reflection state. `mirror` reflects the door across an axis (undoable, one
13
13
  * command); `setType` is intentionally absent (the engine has no in-place re-type —
14
14
  * it would require delete+recreate).
15
+ *
16
+ * The **catalog** reads (`listCatalogGroups`/`listCatalog`/`getCatalogItem`/`exists`)
17
+ * browse the placeable door library — plain value records keyed by a library id
18
+ * (team `_id` or general `fullName`), NOT handles — and feed `design.create.door`.
19
+ * They are network-backed but carry no `Async` suffix (§6.6) and never throw
20
+ * (`[]`/`null`). The {@linkcode PluginObjectCatalogGroup} / {@linkcode PluginObjectCatalogItem}
21
+ * DTOs are shared with `design.windows`.
15
22
  */
16
23
  export abstract class PluginDesignDoorsApi {
17
24
  constructor() {}
@@ -224,6 +231,90 @@ export abstract class PluginDesignDoorsApi {
224
231
  doors: ComponentHandle[],
225
232
  height: number
226
233
  ): PluginApiReturn<PluginDesignChangeResult>
234
+
235
+ /**
236
+ * List the door catalog **groups** — the placement families (Swing, Sliding,
237
+ * Folding, Pivot, …), built-in groups merged with any team-custom types.
238
+ *
239
+ * @returns The groups as {@linkcode PluginObjectCatalogGroup}`[]` (`[]` when empty)
240
+ *
241
+ * @examplePrompt What kinds of doors can I place?
242
+ * @examplePrompt List the door groups in the catalog
243
+ * @examplePrompt Show me the door families available in this project
244
+ * @examplePrompt Which door types does this project support?
245
+ *
246
+ * # Example
247
+ * ```ts
248
+ * const groups = await snaptrude.design.doors.listCatalogGroups()
249
+ * for (const g of groups) console.log(g.dbType, g.label, g.source)
250
+ * ```
251
+ */
252
+ public abstract listCatalogGroups(): PluginApiReturn<PluginObjectCatalogGroup[]>
253
+
254
+ /**
255
+ * List the placeable door catalog (team + general libraries), optionally
256
+ * restricted to one group by its `dbType` token.
257
+ *
258
+ * @param group Restrict to one group's `dbType` (optional; e.g. `"singleDoor"`)
259
+ * @returns The catalog as {@linkcode PluginObjectCatalogItem}`[]` (`[]` when empty)
260
+ *
261
+ * @examplePrompt What doors can I place in this project?
262
+ * @examplePrompt List every door in the library
263
+ * @examplePrompt Show me the sliding doors I can add
264
+ * @examplePrompt Browse the door catalog
265
+ *
266
+ * # Example
267
+ * ```ts
268
+ * const [group] = await snaptrude.design.doors.listCatalogGroups()
269
+ * const items = await snaptrude.design.doors.listCatalog(group?.dbType)
270
+ * for (const item of items) console.log(item.id, item.name, item.thumbnailUrl)
271
+ * ```
272
+ */
273
+ public abstract listCatalog(
274
+ group?: string
275
+ ): PluginApiReturn<PluginObjectCatalogItem[]>
276
+
277
+ /**
278
+ * Get a single door catalog item by its library id.
279
+ *
280
+ * @param id The catalog library id
281
+ * @returns The item, or `null` if no catalog item matches
282
+ *
283
+ * @examplePrompt Get the details of this door from the library
284
+ * @examplePrompt Look up the catalog entry for this door id
285
+ * @examplePrompt What is the cost of this door item?
286
+ * @examplePrompt Show me the family name of this door in the catalog
287
+ *
288
+ * # Example
289
+ * ```ts
290
+ * const [entry] = await snaptrude.design.doors.listCatalog()
291
+ * const item = await snaptrude.design.doors.getCatalogItem(entry.id)
292
+ * if (item) console.log(item.name, item.source, item.dbType)
293
+ * ```
294
+ */
295
+ public abstract getCatalogItem(
296
+ id: string
297
+ ): PluginApiReturn<PluginObjectCatalogItem | null>
298
+
299
+ /**
300
+ * Test whether a door catalog item exists for the given library id.
301
+ *
302
+ * @param id The catalog library id
303
+ * @returns `true` if it exists, otherwise `false`
304
+ *
305
+ * @examplePrompt Is this door still in the library?
306
+ * @examplePrompt Check whether a door catalog item with this id exists
307
+ * @examplePrompt Does my team library have this door?
308
+ * @examplePrompt Verify the door is available before placing it
309
+ *
310
+ * # Example
311
+ * ```ts
312
+ * const [entry] = await snaptrude.design.doors.listCatalog()
313
+ * const stillAvailable = await snaptrude.design.doors.exists(entry.id)
314
+ * console.log(stillAvailable ? "safe to place" : "removed from library")
315
+ * ```
316
+ */
317
+ public abstract exists(id: string): PluginApiReturn<boolean>
227
318
  }
228
319
 
229
320
  /**
@@ -283,3 +374,80 @@ export const PluginDesignDoorSetHeightArgs = z.object({
283
374
  height: z.number(),
284
375
  })
285
376
  export type PluginDesignDoorSetHeightArgs = z.infer<typeof PluginDesignDoorSetHeightArgs>
377
+
378
+ // ---------------------------------------------------------------------------
379
+ // catalog (shared by doors + windows)
380
+ // ---------------------------------------------------------------------------
381
+
382
+ /**
383
+ * A door/window catalog **group** — a placement family (e.g. Swing, Sliding,
384
+ * Casement). Shared by `design.doors` and `design.windows`.
385
+ *
386
+ * | Property | Type | Description |
387
+ * |---|---|---|
388
+ * | `dbType` | `string` | Stable engine token (e.g. `singleDoor`, `casementDouble`) |
389
+ * | `label` | `string` | Human-readable group label (e.g. `"Swing - 1 Shutter"`) |
390
+ * | `source` | `'default' \| 'team'` | Built-in group or a team-custom type |
391
+ */
392
+ export const PluginObjectCatalogGroup = z.object({
393
+ dbType: z.string(),
394
+ label: z.string(),
395
+ source: z.enum(["default", "team"]),
396
+ })
397
+ export type PluginObjectCatalogGroup = z.infer<typeof PluginObjectCatalogGroup>
398
+
399
+ /**
400
+ * A placeable door/window catalog **item** (value record — not a scene
401
+ * entity/handle). Shared by `design.doors` and `design.windows`; symmetric with
402
+ * {@linkcode PluginFurnitureCatalogItem}. The raw mesh (`.babylon`) URL is never
403
+ * exposed — only `thumbnailUrl`.
404
+ *
405
+ * | Property | Type | Description |
406
+ * |---|---|---|
407
+ * | `id` | `string` | Library id (team `_id` or general `fullName`) — pass to `design.create.door`/`window` |
408
+ * | `name` | `string` | Display name |
409
+ * | `source` | `'team' \| 'general'` | Which library it came from |
410
+ * | `dbType` | `string` | The group token it belongs to (e.g. `singleDoor`) |
411
+ * | `subType` | `string`? | Engine sub-type (optional) |
412
+ * | `thumbnailUrl` | `string`? | Preview thumbnail URL (optional) |
413
+ * | `cost` | `number`? | Cost (optional; team items) |
414
+ * | `familyName` | `string`? | Family name (optional) |
415
+ */
416
+ export const PluginObjectCatalogItem = z.object({
417
+ id: z.string(),
418
+ name: z.string(),
419
+ source: z.enum(["team", "general"]),
420
+ dbType: z.string(),
421
+ subType: z.string().optional(),
422
+ thumbnailUrl: z.string().optional(),
423
+ cost: z.number().optional(),
424
+ familyName: z.string().optional(),
425
+ })
426
+ export type PluginObjectCatalogItem = z.infer<typeof PluginObjectCatalogItem>
427
+
428
+ /**
429
+ * Arguments for {@linkcode PluginDesignDoorsApi.listCatalog}.
430
+ *
431
+ * | Property | Type | Description |
432
+ * |---|---|---|
433
+ * | `group` | `string`? | Restrict to one group's `dbType` token (optional) |
434
+ */
435
+ export const PluginDesignDoorListCatalogArgs = z.object({
436
+ group: z.string().optional(),
437
+ })
438
+ export type PluginDesignDoorListCatalogArgs = z.infer<typeof PluginDesignDoorListCatalogArgs>
439
+
440
+ /**
441
+ * Arguments for {@linkcode PluginDesignDoorsApi.getCatalogItem} / {@linkcode PluginDesignDoorsApi.exists}.
442
+ *
443
+ * | Property | Type | Description |
444
+ * |---|---|---|
445
+ * | `id` | `string` | The catalog library id |
446
+ */
447
+ export const PluginDesignDoorGetCatalogItemArgs = z.object({
448
+ id: z.string(),
449
+ })
450
+ export type PluginDesignDoorGetCatalogItemArgs = z.infer<typeof PluginDesignDoorGetCatalogItemArgs>
451
+
452
+ export const PluginDesignDoorExistsArgs = PluginDesignDoorGetCatalogItemArgs
453
+ export type PluginDesignDoorExistsArgs = z.infer<typeof PluginDesignDoorExistsArgs>
@@ -15,24 +15,49 @@ export abstract class PluginDesignFurnitureApi {
15
15
  constructor() {}
16
16
 
17
17
  /**
18
- * List the placeable furniture catalog (team + general libraries).
18
+ * List the categories (Living, Bedroom, Kitchen, …) that organize the
19
+ * furniture catalog — the picker taxonomy, merged from built-in categories,
20
+ * team types, and backend user types. Pass a value to
21
+ * {@linkcode PluginDesignFurnitureApi.listCatalog}'s `category` filter.
22
+ * @returns The category names (`[]` when empty)
23
+ *
24
+ * @examplePrompt What furniture categories are available?
25
+ * @examplePrompt List the furniture categories
26
+ * @examplePrompt Show me the furniture groups like living and bedroom
27
+ * @examplePrompt Which furniture categories can I filter by?
28
+ *
29
+ * # Example
30
+ * ```ts
31
+ * const categories = await snaptrude.design.furniture.listCategories()
32
+ * const [first] = categories
33
+ * const items = await snaptrude.design.furniture.listCatalog(undefined, first)
34
+ * console.log(first, "→", items.length, "items")
35
+ * ```
36
+ */
37
+ public abstract listCategories(): PluginApiReturn<string[]>
38
+
39
+ /**
40
+ * List the placeable furniture catalog (team + general libraries),
41
+ * optionally filtered by library `source` and/or `category`.
19
42
  * @param source Restrict to one library (optional)
43
+ * @param category Restrict to one category (optional; see {@linkcode PluginDesignFurnitureApi.listCategories})
20
44
  * @returns The catalog as {@linkcode PluginFurnitureCatalogItem}`[]` (`[]` when empty)
21
45
  *
22
46
  * @examplePrompt What furniture can I place in this project?
23
47
  * @examplePrompt Show me all the furniture available in the library
24
48
  * @examplePrompt List the chairs and tables I can add to the model
25
49
  * @examplePrompt Which furniture items are in my team's library?
26
- * @examplePrompt Browse the furniture catalog
50
+ * @examplePrompt Show me the bedroom furniture I can place
27
51
  *
28
52
  * # Example
29
53
  * ```ts
30
- * const teamItems = await snaptrude.design.furniture.listCatalog("team")
31
- * for (const item of teamItems) console.log(item.id, item.name, item.cost)
54
+ * const bedroom = await snaptrude.design.furniture.listCatalog("team", "bedroom")
55
+ * for (const item of bedroom) console.log(item.id, item.name, item.thumbnailUrl)
32
56
  * ```
33
57
  */
34
58
  public abstract listCatalog(
35
- source?: "team" | "general"
59
+ source?: "team" | "general",
60
+ category?: string
36
61
  ): PluginApiReturn<PluginFurnitureCatalogItem[]>
37
62
 
38
63
  /**
@@ -85,6 +110,8 @@ export abstract class PluginDesignFurnitureApi {
85
110
  * | `name` | `string` | Display name |
86
111
  * | `source` | `'team' \| 'general'` | Which library it came from |
87
112
  * | `subType` | `string` | Sub-type (optional) |
113
+ * | `category` | `string` | Category the item belongs to (optional) |
114
+ * | `thumbnailUrl` | `string` | Preview thumbnail URL (optional; never the mesh URL) |
88
115
  * | `cost` | `number` | Cost (optional; team items) |
89
116
  * | `familyName` | `string` | Family name (optional) |
90
117
  */
@@ -93,6 +120,8 @@ export const PluginFurnitureCatalogItem = z.object({
93
120
  name: z.string(),
94
121
  source: z.enum(["team", "general"]),
95
122
  subType: z.string().optional(),
123
+ category: z.string().optional(),
124
+ thumbnailUrl: z.string().optional(),
96
125
  cost: z.number().optional(),
97
126
  familyName: z.string().optional(),
98
127
  })
@@ -104,9 +133,11 @@ export type PluginFurnitureCatalogItem = z.infer<typeof PluginFurnitureCatalogIt
104
133
  * | Property | Type | Description |
105
134
  * |---|---|---|
106
135
  * | `source` | `'team' \| 'general'` | Restrict to one library (optional) |
136
+ * | `category` | `string` | Restrict to one category (optional) |
107
137
  */
108
138
  export const PluginDesignFurnitureListCatalogArgs = z.object({
109
139
  source: z.enum(["team", "general"]).optional(),
140
+ category: z.string().optional(),
110
141
  })
111
142
  export type PluginDesignFurnitureListCatalogArgs = z.infer<
112
143
  typeof PluginDesignFurnitureListCatalogArgs
@@ -28,6 +28,7 @@ import { PluginDesignChangeResult } from "./lock"
28
28
  * - {@linkcode PluginDesignApi.erase} — plan-level adjacency-edge erase (NOT hard delete)
29
29
  * - {@linkcode PluginDesignApi.delete} — hard entity removal
30
30
  * - {@linkcode PluginDesignApi.lock} / {@linkcode PluginDesignApi.unlock} / {@linkcode PluginDesignApi.isLocked} / {@linkcode PluginDesignApi.listLocked} — lock state (top-level design verbs, §2A.1)
31
+ * - {@linkcode PluginDesignApi.lockArea} / {@linkcode PluginDesignApi.unlockArea} / {@linkcode PluginDesignApi.isAreaLocked} / {@linkcode PluginDesignApi.listAreaLocked} — footprint-area lock for Room/Department spaces
31
32
  *
32
33
  * Transform and edit operation namespaces land here in later waves.
33
34
  */
@@ -142,6 +143,92 @@ export abstract class PluginDesignApi {
142
143
  */
143
144
  public abstract listLocked(): PluginApiReturn<ComponentHandle[]>
144
145
 
146
+ /**
147
+ * Lock the **footprint area** of one or more Room / Department mass spaces —
148
+ * geometry edits that would change the bottom-face area are then refused or
149
+ * auto-resized to preserve it. Distinct from {@linkcode PluginDesignApi.lock}
150
+ * (which freezes the whole object): this locks only the area. Cascades to the
151
+ * space's instances and its source mass. Undoable.
152
+ *
153
+ * Only Room / Department masses are area-lockable; any other handle throws.
154
+ * Requires the `area_lock` feature to be enabled — when it is off this throws.
155
+ *
156
+ * @param spaces Room / Department mass spaces to area-lock
157
+ * @returns The affected spaces (post-cascade, echoing the input handles)
158
+ *
159
+ * @examplePrompt Lock the floor area of the selected rooms
160
+ * @examplePrompt Freeze the footprint area of this department so its size can't drift
161
+ * @examplePrompt Area-lock every room on this storey
162
+ * @examplePrompt Pin the area of this space while I edit its walls
163
+ *
164
+ * # Example
165
+ * ```ts
166
+ * const rooms = await snaptrude.design.selection.get()
167
+ * const { affected } = await snaptrude.design.lockArea(rooms)
168
+ * console.log("area-locked", affected.length, "spaces")
169
+ * ```
170
+ */
171
+ public abstract lockArea(spaces: ComponentHandle[]): PluginApiReturn<PluginDesignChangeResult>
172
+
173
+ /**
174
+ * Unlock the footprint area of previously area-locked spaces so their area can
175
+ * change again. Paired with {@linkcode PluginDesignApi.lockArea}; cascades to
176
+ * instances + source; undoable. Requires the `area_lock` feature (throws if off).
177
+ *
178
+ * @param spaces Room / Department mass spaces to area-unlock
179
+ * @returns The affected spaces (post-cascade)
180
+ *
181
+ * @examplePrompt Unlock the area of the selected rooms
182
+ * @examplePrompt Remove the footprint-area lock from this department
183
+ * @examplePrompt Let me resize this space again by unlocking its area
184
+ * @examplePrompt Area-unlock everything I locked earlier
185
+ *
186
+ * # Example
187
+ * ```ts
188
+ * const locked = await snaptrude.design.listAreaLocked()
189
+ * const { affected } = await snaptrude.design.unlockArea(locked)
190
+ * ```
191
+ */
192
+ public abstract unlockArea(spaces: ComponentHandle[]): PluginApiReturn<PluginDesignChangeResult>
193
+
194
+ /**
195
+ * Test whether a space's footprint area is locked. A read — never throws;
196
+ * returns `false` for non-space handles and when the `area_lock` feature is off.
197
+ *
198
+ * @param space The space to test
199
+ * @returns `true` if the space's area is locked, otherwise `false`
200
+ *
201
+ * @examplePrompt Is the area of this room locked?
202
+ * @examplePrompt Check whether this department's footprint is pinned
203
+ * @examplePrompt Tell me if the selected space has its area locked
204
+ *
205
+ * # Example
206
+ * ```ts
207
+ * const [room] = await snaptrude.design.selection.get()
208
+ * const locked = await snaptrude.design.isAreaLocked(room)
209
+ * console.log(locked ? "area is locked" : "area is free")
210
+ * ```
211
+ */
212
+ public abstract isAreaLocked(space: ComponentHandle): PluginApiReturn<boolean>
213
+
214
+ /**
215
+ * List the spaces whose footprint area is locked. A read — never throws; returns
216
+ * `[]` when the `area_lock` feature is off.
217
+ *
218
+ * @returns The area-locked spaces as {@linkcode ComponentHandle}`[]`
219
+ *
220
+ * @examplePrompt Which spaces have their area locked?
221
+ * @examplePrompt List every room with a locked footprint
222
+ * @examplePrompt Show me all the area-locked departments
223
+ *
224
+ * # Example
225
+ * ```ts
226
+ * const locked = await snaptrude.design.listAreaLocked()
227
+ * console.log(locked.length, "area-locked spaces")
228
+ * ```
229
+ */
230
+ public abstract listAreaLocked(): PluginApiReturn<ComponentHandle[]>
231
+
145
232
  constructor() {}
146
233
  }
147
234
 
@@ -40,3 +40,30 @@ export const PluginDesignIsLockedArgs = z.object({
40
40
  includeInherited: z.boolean().optional(),
41
41
  })
42
42
  export type PluginDesignIsLockedArgs = z.infer<typeof PluginDesignIsLockedArgs>
43
+
44
+ /**
45
+ * Arguments for {@linkcode PluginDesignApi.lockArea} / {@linkcode PluginDesignApi.unlockArea}.
46
+ *
47
+ * | Property | Type | Description |
48
+ * |---|---|---|
49
+ * | `spaces` | {@linkcode ComponentHandle}`[]` | Room / Department mass spaces to area-lock / unlock |
50
+ */
51
+ export const PluginDesignLockAreaArgs = z.object({
52
+ spaces: z.array(ComponentHandle),
53
+ })
54
+ export type PluginDesignLockAreaArgs = z.infer<typeof PluginDesignLockAreaArgs>
55
+
56
+ export const PluginDesignUnlockAreaArgs = PluginDesignLockAreaArgs
57
+ export type PluginDesignUnlockAreaArgs = z.infer<typeof PluginDesignUnlockAreaArgs>
58
+
59
+ /**
60
+ * Arguments for {@linkcode PluginDesignApi.isAreaLocked}.
61
+ *
62
+ * | Property | Type | Description |
63
+ * |---|---|---|
64
+ * | `space` | {@linkcode ComponentHandle} | The space to test |
65
+ */
66
+ export const PluginDesignIsAreaLockedArgs = z.object({
67
+ space: ComponentHandle,
68
+ })
69
+ export type PluginDesignIsAreaLockedArgs = z.infer<typeof PluginDesignIsAreaLockedArgs>