@snaptrude/plugin-core 0.0.0-dev-20260911064951 → 0.0.0-surfaces-20260921090636

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 (79) hide show
  1. package/CHANGELOG.md +0 -25
  2. package/api-manifest.json +151 -12
  3. package/dist/api/core/geom/create/index.d.ts +918 -93
  4. package/dist/api/core/geom/create/index.d.ts.map +1 -1
  5. package/dist/api/core/geom/query/brep.d.ts +50 -0
  6. package/dist/api/core/geom/query/brep.d.ts.map +1 -1
  7. package/dist/api/core/geom/query/curve.d.ts +58 -35
  8. package/dist/api/core/geom/query/curve.d.ts.map +1 -1
  9. package/dist/api/core/geom/query/edge.d.ts +2 -2
  10. package/dist/api/core/geom/query/face.d.ts +105 -0
  11. package/dist/api/core/geom/query/face.d.ts.map +1 -1
  12. package/dist/api/core/geom/update/curve.d.ts +5 -5
  13. package/dist/api/core/geom/update/profile.d.ts +1 -1
  14. package/dist/api/core/io/import/index.d.ts +1 -3
  15. package/dist/api/core/io/import/index.d.ts.map +1 -1
  16. package/dist/api/design/create/index.d.ts +22 -310
  17. package/dist/api/design/create/index.d.ts.map +1 -1
  18. package/dist/api/design/delete/index.d.ts +0 -4
  19. package/dist/api/design/delete/index.d.ts.map +1 -1
  20. package/dist/api/design/doors/index.d.ts +13 -20
  21. package/dist/api/design/doors/index.d.ts.map +1 -1
  22. package/dist/api/design/index.d.ts +0 -5
  23. package/dist/api/design/index.d.ts.map +1 -1
  24. package/dist/api/design/query/geometry/index.d.ts +27 -1
  25. package/dist/api/design/query/geometry/index.d.ts.map +1 -1
  26. package/dist/api/design/query/index.d.ts +47 -0
  27. package/dist/api/design/query/index.d.ts.map +1 -1
  28. package/dist/api/design/query/spaces.d.ts +5 -5
  29. package/dist/api/design/update/index.d.ts +87 -0
  30. package/dist/api/design/update/index.d.ts.map +1 -1
  31. package/dist/api/entity/referenceLine.d.ts +2 -2
  32. package/dist/api/entity/referenceLine.d.ts.map +1 -1
  33. package/dist/api/entity/space.d.ts +5 -4
  34. package/dist/api/entity/space.d.ts.map +1 -1
  35. package/dist/api/entity/story.d.ts +4 -4
  36. package/dist/api/presentation/annotate.d.ts +2 -2
  37. package/dist/api/presentation/shapes.d.ts +2 -2
  38. package/dist/api/program/site.d.ts +4 -4
  39. package/dist/api/program/spreadsheet.d.ts +4 -4
  40. package/dist/handles.d.ts +12 -22
  41. package/dist/handles.d.ts.map +1 -1
  42. package/dist/index.cjs +2773 -2445
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.d.ts +2 -0
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +2726 -2422
  47. package/dist/index.js.map +1 -1
  48. package/dist/massParameters.d.ts +280 -0
  49. package/dist/massParameters.d.ts.map +1 -0
  50. package/dist/massParameters.ops.d.ts +135 -0
  51. package/dist/massParameters.ops.d.ts.map +1 -0
  52. package/package.json +1 -1
  53. package/src/api/core/geom/create/index.ts +1011 -94
  54. package/src/api/core/geom/query/brep.ts +51 -0
  55. package/src/api/core/geom/query/curve.ts +25 -2
  56. package/src/api/core/geom/query/edge.ts +2 -2
  57. package/src/api/core/geom/query/face.ts +109 -4
  58. package/src/api/core/io/import/index.ts +3 -11
  59. package/src/api/design/create/index.ts +38 -335
  60. package/src/api/design/delete/index.ts +0 -4
  61. package/src/api/design/doors/index.ts +13 -20
  62. package/src/api/design/index.ts +0 -5
  63. package/src/api/design/query/geometry/index.ts +29 -0
  64. package/src/api/design/query/index.ts +50 -0
  65. package/src/api/design/update/index.ts +94 -0
  66. package/src/handles.ts +15 -27
  67. package/src/index.ts +2 -0
  68. package/src/massParameters.ops.ts +243 -0
  69. package/src/massParameters.ts +418 -0
  70. package/test/massParameters.test.mjs +936 -0
  71. package/dist/api/design/create/bulk-items.d.ts +0 -185
  72. package/dist/api/design/create/bulk-items.d.ts.map +0 -1
  73. package/dist/api/design/create/opening-fields.d.ts +0 -37
  74. package/dist/api/design/create/opening-fields.d.ts.map +0 -1
  75. package/dist/api/design/dimensions.d.ts +0 -427
  76. package/dist/api/design/dimensions.d.ts.map +0 -1
  77. package/src/api/design/create/bulk-items.ts +0 -186
  78. package/src/api/design/create/opening-fields.ts +0 -37
  79. package/src/api/design/dimensions.ts +0 -453
@@ -18,21 +18,12 @@ import {
18
18
  PluginBuildableEnvelopeVerticalCap,
19
19
  PluginBuildableEnvelopeCreateResult,
20
20
  } from "../../entity/buildableEnvelope"
21
- import { PluginOpeningBaseOptions } from "./opening-fields"
22
- import type {
23
- PluginCreateDoorItem,
24
- PluginCreateFloorItem,
25
- PluginCreateFurnitureItem,
26
- PluginCreateWallRunItem,
27
- PluginCreateWindowItem,
28
- } from "./bulk-items"
29
21
 
30
22
  /**
31
23
  * `design.create.*` — author new scene-committed BIM entities.
32
24
  *
33
25
  * Every creator takes geometry handles + scalars and returns a
34
- * {@linkcode ComponentHandle} (or `ComponentHandle[]` for plural creators;
35
- * `wallRuns` returns one `ComponentHandle[]` per run) — the
26
+ * {@linkcode ComponentHandle} (or `ComponentHandle[]` for plural creators) — the
36
27
  * `Component.id` of the created entity, resolvable across the rest of the
37
28
  * `design.*` surface. Creation is an undoable host call; it **throws** on failure
38
29
  * (no `Result` wrapper — consistent with `core.geom.create.*`).
@@ -243,10 +234,6 @@ export abstract class PluginDesignCreateApi {
243
234
  * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)
244
235
  * const floor = await snaptrude.design.create.floor(contour, 0.1)
245
236
  * ```
246
- *
247
- * @performance For MORE THAN ONE floor, call `design.create.floors(items[])` — one
248
- * host round-trip and ONE undo entry for the whole batch. Looping this single-floor
249
- * creator is N round-trips and N undo entries.
250
237
  */
251
238
  public abstract floor(
252
239
  contour: ContourHandle,
@@ -254,46 +241,6 @@ export abstract class PluginDesignCreateApi {
254
241
  position?: Vec3Handle,
255
242
  ): PluginApiReturn<ComponentHandle>
256
243
 
257
- /**
258
- * Create **many floors** in one undoable operation (bulk plural of
259
- * {@linkcode floor}). Each item extrudes its own footprint contour (outer
260
- * profile + holes) upward by its own `thickness`, at its own optional
261
- * position offset. Validate-all-or-throw; one command.
262
- *
263
- * @param items - One {@linkcode PluginCreateFloorItem} per floor to create
264
- * (≥1, ≤1000)
265
- * @returns the created floors as {@linkcode ComponentHandle}`[]`, in input order
266
- * @throws `VALIDATION` if the items fail the schema (empty array, more than
267
- * 1000 items, a non-positive thickness) or a contour is degenerate;
268
- * `HANDLE_INVALID` if a `contour` or `position` handle is unknown or
269
- * released; `OPERATION_FAILED` if extrusion fails (nothing is created —
270
- * `details.itemIndex` is the failing item); `METHOD_NOT_PERMITTED` if the
271
- * plugin may not write.
272
- *
273
- * @examplePrompt Create floors for all these room outlines at once
274
- * @examplePrompt Add a floor to every space in one operation
275
- * @examplePrompt Bulk create the floor plates for this building
276
- * @examplePrompt Lay 100mm floors across these five footprints in one undo step
277
- * @examplePrompt Create the ground and first floor slabs together
278
- *
279
- * @performance Bulk creator — the whole batch is ONE host round-trip and ONE undo
280
- * entry. Always prefer this over calling `design.create.floor` in a loop: build the
281
- * full `items[]` array first (all contours and offsets up front), then make one call.
282
- *
283
- * # Example
284
- * ```ts
285
- * const rect = await snaptrude.core.geom.create.profileRect(5, 4)
286
- * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)
287
- * const [ground, upper] = await snaptrude.design.create.floors([
288
- * { contour, thickness: 0.1 },
289
- * { contour, thickness: 0.1, position: await snaptrude.core.math.vec3.new(0, 3, 0) },
290
- * ])
291
- * ```
292
- */
293
- public abstract floors(
294
- items: PluginCreateFloorItem[],
295
- ): PluginApiReturn<ComponentHandle[]>
296
-
297
244
  /**
298
245
  * Create a **roof** by extruding a footprint contour by `thickness`. Created
299
246
  * flat (extruded downward); pitch/slope is a separate post-creation edit.
@@ -475,10 +422,6 @@ export abstract class PluginDesignCreateApi {
475
422
  * centerlines, 3, undefined, brick.label,
476
423
  * )
477
424
  * ```
478
- *
479
- * @performance For MORE THAN ONE run, call `design.create.wallRuns(items[])` — one
480
- * host round-trip and ONE undo entry for every run in the batch. Looping this
481
- * single-run creator is N round-trips and N undo entries.
482
425
  */
483
426
  public abstract walls(
484
427
  profile: ProfileHandle,
@@ -488,60 +431,6 @@ export abstract class PluginDesignCreateApi {
488
431
  storey?: number,
489
432
  ): PluginApiReturn<ComponentHandle[]>
490
433
 
491
- /**
492
- * Create **many wall runs** in one undoable operation (bulk plural of
493
- * {@linkcode walls}). Each item is one full run — a wall per curve in that
494
- * item's profile chain, mitred at shared endpoints. Junctions resolve within a
495
- * run, not between runs. Each item carries its own
496
- * dimensions, `wallType` and `storey`, so a single call can build a whole
497
- * floor plate or several storeys at once. Validate-all-or-throw; one command.
498
- *
499
- * @param items - One {@linkcode PluginCreateWallRunItem} per run to create
500
- * (≥1, ≤1000)
501
- * @returns one {@linkcode ComponentHandle}`[]` per item, in input order —
502
- * each inner array holding that run's walls in profile-curve order
503
- * @throws `VALIDATION` if the items fail the schema (empty array, more than
504
- * 1000 items, a non-positive height or thickness, a zero-length curve);
505
- * `HANDLE_INVALID` if a `profile` handle is unknown or released;
506
- * `PRECONDITION_FAILED` if a profile has no curves, `wallType` names no
507
- * wall type in the project, or `storey` does not exist;
508
- * `OPERATION_FAILED` if wall creation fails (nothing is created —
509
- * `details.itemIndex` is the failing item); `METHOD_NOT_PERMITTED` if the
510
- * plugin may not write.
511
- *
512
- * @examplePrompt Draw all the walls of this floor plan at once
513
- * @examplePrompt Build every room's perimeter in one operation
514
- * @examplePrompt Bulk create wall runs from these centerlines
515
- * @examplePrompt Create the walls for both storeys in a single undo step
516
- * @examplePrompt Turn these polylines into 200mm brick walls together
517
- *
518
- * @performance Bulk creator — the whole batch is ONE host round-trip and ONE undo
519
- * entry. Always prefer this over calling `design.create.walls` in a loop: build the
520
- * full `items[]` array first (all profiles up front), then make one call.
521
- *
522
- * # Example
523
- * ```ts
524
- * const v = snaptrude.core.math.vec3
525
- * const ground = await snaptrude.core.geom.create.profileFromLinePoints([
526
- * await v.new(0, 0, 0),
527
- * await v.new(8, 0, 0),
528
- * await v.new(8, 0, 6),
529
- * ])
530
- * const upper = await snaptrude.core.geom.create.profileFromLinePoints([
531
- * await v.new(0, 0, 0),
532
- * await v.new(8, 0, 0),
533
- * ])
534
- * const [groundWalls, upperWalls] = await snaptrude.design.create.wallRuns([
535
- * { profile: ground, height: 3, thickness: 0.2 },
536
- * { profile: upper, height: 3, storey: 2 },
537
- * ])
538
- * console.log(groundWalls.length, "walls on the ground floor")
539
- * ```
540
- */
541
- public abstract wallRuns(
542
- items: PluginCreateWallRunItem[],
543
- ): PluginApiReturn<ComponentHandle[][]>
544
-
545
434
  /**
546
435
  * Create a **staircase** from a parametric preset, placed at a point.
547
436
  *
@@ -646,19 +535,15 @@ export abstract class PluginDesignCreateApi {
646
535
  * `position.y` (the same surface-flush contract as interactive drag-drop) —
647
536
  * pass the floor/storey elevation to stand furniture on it; never add half
648
537
  * the item's height yourself.
649
- * @param options - Optional placement options: `label` — instance name and
650
- * readable label (`design.query.getLabel`; default auto `${name}Ins${n}`);
651
- * `createNewSourceMesh` — legacy flag, kept for compatibility. The host owns
652
- * source-mesh persistence: a catalog source is recorded exactly once, by the
653
- * first placement that brings it in, and this flag cannot skip that record.
538
+ * @param options - Optional placement options: `label` — instance name
539
+ * (default auto `${name}Ins${n}`); `createNewSourceMesh` — emit a
540
+ * source-mesh creation command (default `true`)
654
541
  * @param angleInDegrees - Optional signed rotation about the vertical axis, in
655
542
  * degrees (same convention as {@linkcode PluginDesignTransformApi.rotate}).
656
543
  * Applied at creation time so it is part of the placement's single undo entry.
657
544
  * Default: the item's own (unrotated) orientation.
658
545
  * @returns the {@linkcode ComponentHandle} of the placed furniture instance
659
- * @throws if the catalog id is unknown, the source mesh fails to load, or placement fails;
660
- * `PRECONDITION_FAILED` (`details.engineCode: "TOOL_ACTIVE"`) while the interactive
661
- * furniture tool is active — finish or cancel it first
546
+ * @throws if the catalog id is unknown, the source mesh fails to load, or placement fails
662
547
  *
663
548
  * @examplePrompt Place a chair from the library at this spot
664
549
  * @examplePrompt Add a sofa from the furniture catalog to the living room
@@ -682,10 +567,6 @@ export abstract class PluginDesignCreateApi {
682
567
  * 90,
683
568
  * )
684
569
  * ```
685
- *
686
- * @performance For MORE THAN ONE item, call `design.create.furnitureItems(items[])` —
687
- * one host round-trip and ONE undo entry, and a catalog source shared by several
688
- * items is fetched once. Looping this single-item creator is N round-trips.
689
570
  */
690
571
  public abstract furniture(
691
572
  catalogId: string,
@@ -694,52 +575,6 @@ export abstract class PluginDesignCreateApi {
694
575
  angleInDegrees?: number,
695
576
  ): PluginApiReturn<ComponentHandle>
696
577
 
697
- /**
698
- * Place **many furniture items** in one undoable operation (bulk plural of
699
- * {@linkcode furniture}). Each item names a catalog id and an absolute world
700
- * position (`position.y` is the REST elevation — the same grounding contract
701
- * as the singular creator), with optional label and rotation. A catalog
702
- * source shared by several items is fetched and recorded once for the whole
703
- * batch, so the host — not the caller — owns source persistence (there is no
704
- * per-item `createNewSourceMesh`). Validate-all-or-throw; one command.
705
- *
706
- * @param items - One {@linkcode PluginCreateFurnitureItem} per instance to
707
- * place (≥1, ≤1000)
708
- * @returns the placed instances as {@linkcode ComponentHandle}`[]`, in input order
709
- * @throws `VALIDATION` if the items fail the schema (empty array, more than
710
- * 1000 items); `HANDLE_INVALID` if a `position` handle is unknown or
711
- * released; `PRECONDITION_FAILED` if a catalog id is unknown, a furniture
712
- * tool is active, or a parametric group is active (exit it first, or place
713
- * items one at a time with `furniture`); `OPERATION_FAILED` if a source
714
- * mesh fails to load or placement fails (nothing is created —
715
- * `details.itemIndex` is the failing item); `METHOD_NOT_PERMITTED` if the plugin may not write.
716
- *
717
- * @examplePrompt Place all the desks for this office at once
718
- * @examplePrompt Furnish every bedroom in one operation
719
- * @examplePrompt Bulk place these chairs around the table
720
- * @examplePrompt Add the whole furniture layout in a single undo step
721
- * @examplePrompt Drop twenty copies of this chair at these positions
722
- *
723
- * @performance Bulk creator — the whole batch is ONE host round-trip and ONE undo
724
- * entry, and each distinct catalog source loads once. Always prefer this over
725
- * calling `design.create.furniture` in a loop: build the full `items[]` array
726
- * first (all positions and angles up front), then make one call.
727
- *
728
- * # Example
729
- * ```ts
730
- * const v = snaptrude.core.math.vec3
731
- * const [chair] = await snaptrude.design.furniture.listCatalog()
732
- * const placed = await snaptrude.design.create.furnitureItems([
733
- * { catalogId: chair.id, position: await v.new(3, 0, 5), label: "Chair-01" },
734
- * { catalogId: chair.id, position: await v.new(4, 0, 5), angleInDegrees: 90 },
735
- * { catalogId: chair.id, position: await v.new(5, 0, 5), angleInDegrees: 180 },
736
- * ])
737
- * ```
738
- */
739
- public abstract furnitureItems(
740
- items: PluginCreateFurnitureItem[],
741
- ): PluginApiReturn<ComponentHandle[]>
742
-
743
578
  /**
744
579
  * Place a **door** from the catalog into a host wall.
745
580
  *
@@ -753,15 +588,12 @@ export abstract class PluginDesignCreateApi {
753
588
  * @param catalogId - Library id: team `_id` or general `fullName`
754
589
  * @param hostWall - The wall to host the door
755
590
  * @param position - World point projected onto the wall to locate the opening
756
- * @param options - Optional placement options: `label` — instance name;
757
- * `hinge` — world point near the jamb the door is hinged on (the door is
758
- * reflected along the wall so its hinged jamb is the one nearer this
759
- * point; default the catalog item's authored hinge side)
760
- * @param facing - World point on the side of the wall the door swings open
761
- * to (the room it opens into — where the plan symbol draws the swing arc).
762
- * Any point clearly on that side works (e.g. the room's center). Default:
763
- * the engine picks a side (nondeterministic when `position` sits on the
764
- * wall centerline).
591
+ * @param options - Optional placement options: `label` — instance name
592
+ * @param facing - World point selecting which side of the wall the door faces
593
+ * (the room it opens into) — the same convention as approaching the wall
594
+ * from that side with the cursor in the interactive tool. Any point clearly
595
+ * on that side works (e.g. the room's center). Default: the engine picks a
596
+ * side (nondeterministic when `position` sits on the wall centerline).
765
597
  * @returns the {@linkcode ComponentHandle} of the placed door
766
598
  * @throws if the catalog id is unknown, the host is not a wall, the source
767
599
  * mesh fails to load, or the projected point falls **outside** the host wall
@@ -772,7 +604,6 @@ export abstract class PluginDesignCreateApi {
772
604
  * @examplePrompt Insert the entrance door into this wall
773
605
  * @examplePrompt Add a door to the wall and call it Entry-01
774
606
  * @examplePrompt Add a door that opens into the living room
775
- * @examplePrompt Put a door here hinged on the north jamb
776
607
  *
777
608
  * # Example
778
609
  * ```ts
@@ -792,83 +623,16 @@ export abstract class PluginDesignCreateApi {
792
623
  * undefined,
793
624
  * await snaptrude.core.math.vec3.new(3, 0, 9),
794
625
  * )
795
- * // …and hinged on the jamb nearest a point (here the +x end of the opening):
796
- * const hingedRight = await snaptrude.design.create.door(
797
- * entry.id,
798
- * wall,
799
- * await snaptrude.core.math.vec3.new(3, 0, 5),
800
- * { hinge: await snaptrude.core.math.vec3.new(3.5, 0, 5) },
801
- * await snaptrude.core.math.vec3.new(3, 0, 9),
802
- * )
803
626
  * ```
804
- *
805
- * @performance For MORE THAN ONE door, call `design.create.doors(items[])` — one host
806
- * round-trip and ONE undo entry, with each host wall re-cut once per opening inside
807
- * that single call. Looping this single-door creator is N round-trips.
808
627
  */
809
628
  public abstract door(
810
629
  catalogId: string,
811
630
  hostWall: ComponentHandle,
812
631
  position: Vec3Handle,
813
- options?: { label?: string; hinge?: Vec3Handle },
632
+ options?: { label?: string },
814
633
  facing?: Vec3Handle,
815
634
  ): PluginApiReturn<ComponentHandle>
816
635
 
817
- /**
818
- * Place **many doors** into host walls in one undoable operation (bulk plural
819
- * of {@linkcode door}). Each item carries its own catalog id, host wall,
820
- * world position, and optional `facing`, `label`, `width` and `height` — the
821
- * same fields as a `"door"` {@linkcode PluginDesignCreateOpeningOptions}.
822
- * Validate-all-or-throw; one command.
823
- *
824
- * As with the singular creator, placing an opening **re-cuts its host wall**:
825
- * the wall you passed as `hostWall` stops resolving once the call returns.
826
- * Several items MAY name the same original wall handle in one call — the host
827
- * chains the re-cuts internally — but after the call recover the surviving
828
- * wall with `design.query.getHost(opening)`, never by reusing the handle you
829
- * passed in.
830
- *
831
- * @param items - One {@linkcode PluginCreateDoorItem} per door to place
832
- * (≥1, ≤1000)
833
- * @returns the placed doors as {@linkcode ComponentHandle}`[]`, in input order
834
- * @throws `VALIDATION` if the items fail the schema (empty array, more than
835
- * 1000 items, unknown fields, a non-positive `width`/`height`);
836
- * `HANDLE_INVALID` if a `position`, `facing` or `hinge` handle is unknown
837
- * or released; `PRECONDITION_FAILED` if a catalog id is unknown, a `hostWall`
838
- * is not a wall / is locked / is not in the active proposal, the projected
839
- * point falls outside its host wall, or a door tool is active;
840
- * `OPERATION_FAILED` if a source mesh fails to load or placement fails
841
- * (nothing is created — `details.itemIndex` is the failing item);
842
- * `METHOD_NOT_PERMITTED` if the plugin may not write.
843
- *
844
- * @examplePrompt Add doors to all of these walls at once
845
- * @examplePrompt Place a door in every room in one operation
846
- * @examplePrompt Bulk add the entrance doors from this schedule
847
- * @examplePrompt Put three doors on this wall in a single undo step
848
- * @examplePrompt Add all the doors for this floor plan together
849
- *
850
- * @performance Bulk creator — the whole batch is ONE host round-trip and ONE undo
851
- * entry, and each distinct catalog source loads once. Always prefer this over
852
- * calling `design.create.door` in a loop: build the full `items[]` array first
853
- * (all host walls and points up front), then make one call.
854
- *
855
- * # Example
856
- * ```ts
857
- * const v = snaptrude.core.math.vec3
858
- * const [wall] = await snaptrude.design.query.listWalls({ isSelected: true })
859
- * const [entry] = await snaptrude.design.doors.listCatalog()
860
- * const [front, side] = await snaptrude.design.create.doors([
861
- * { catalogId: entry.id, hostWall: wall, position: await v.new(2, 0, 5), label: "D-01" },
862
- * { catalogId: entry.id, hostWall: wall, position: await v.new(6, 0, 5), width: 1.2 },
863
- * ])
864
- * // `wall` was re-cut twice and no longer resolves — ask the opening for its host:
865
- * const currentWall = await snaptrude.design.query.getHost(front)
866
- * ```
867
- */
868
- public abstract doors(
869
- items: PluginCreateDoorItem[],
870
- ): PluginApiReturn<ComponentHandle[]>
871
-
872
636
  /**
873
637
  * Place a **window** from the catalog into a host wall.
874
638
  *
@@ -882,14 +646,11 @@ export abstract class PluginDesignCreateApi {
882
646
  * @param catalogId - Library id: team `_id` or general `fullName`
883
647
  * @param hostWall - The wall to host the window
884
648
  * @param position - World point projected onto the wall to locate the opening
885
- * @param options - Optional placement options: `label` — instance name;
886
- * `hinge` — world point near the jamb the window is hinged on (same rule
887
- * as {@linkcode PluginDesignCreateApi.door}; default the catalog item's
888
- * authored side)
889
- * @param facing - World point on the side of the wall the window opens to
890
- * (matters for asymmetric windows, e.g. casement swing) — same convention
891
- * as {@linkcode PluginDesignCreateApi.door}. Default: the engine picks a
892
- * side (nondeterministic when `position` sits on the centerline).
649
+ * @param options - Optional placement options: `label` — instance name
650
+ * @param facing - World point selecting which side of the wall the window
651
+ * faces (matters for asymmetric windows, e.g. casement swing) — same
652
+ * convention as {@linkcode PluginDesignCreateApi.door}. Default: the engine
653
+ * picks a side (nondeterministic when `position` sits on the centerline).
893
654
  * @returns the {@linkcode ComponentHandle} of the placed window
894
655
  * @throws if the catalog id is unknown, the host is not a wall, the source
895
656
  * mesh fails to load, or the projected point falls **outside** the host wall
@@ -912,75 +673,15 @@ export abstract class PluginDesignCreateApi {
912
673
  * { label: "Win-01" },
913
674
  * )
914
675
  * ```
915
- *
916
- * @performance For MORE THAN ONE window, call `design.create.windows(items[])` — one
917
- * host round-trip and ONE undo entry, with each host wall re-cut once per opening
918
- * inside that single call. Looping this single-window creator is N round-trips.
919
676
  */
920
677
  public abstract window(
921
678
  catalogId: string,
922
679
  hostWall: ComponentHandle,
923
680
  position: Vec3Handle,
924
- options?: { label?: string; hinge?: Vec3Handle },
681
+ options?: { label?: string },
925
682
  facing?: Vec3Handle,
926
683
  ): PluginApiReturn<ComponentHandle>
927
684
 
928
- /**
929
- * Place **many windows** into host walls in one undoable operation (bulk
930
- * plural of {@linkcode window}). Each item carries its own catalog id, host
931
- * wall, world position, and optional `facing`, `label`, `width`, `height` and
932
- * `sillHeight` — the same fields as a `"window"`
933
- * {@linkcode PluginDesignCreateOpeningOptions}. `sillHeight` is measured from
934
- * the host wall base to the BOTTOM of the window. Validate-all-or-throw; one
935
- * command.
936
- *
937
- * As with the singular creator, placing an opening **re-cuts its host wall**:
938
- * the wall you passed as `hostWall` stops resolving once the call returns.
939
- * Several items MAY name the same original wall handle in one call — the host
940
- * chains the re-cuts internally — but after the call recover the surviving
941
- * wall with `design.query.getHost(opening)`, never by reusing the handle you
942
- * passed in.
943
- *
944
- * @param items - One {@linkcode PluginCreateWindowItem} per window to place
945
- * (≥1, ≤1000)
946
- * @returns the placed windows as {@linkcode ComponentHandle}`[]`, in input order
947
- * @throws `VALIDATION` if the items fail the schema (empty array, more than
948
- * 1000 items, unknown fields, a non-positive `width`/`height`, a negative
949
- * `sillHeight`); `HANDLE_INVALID` if a `position`, `facing` or `hinge`
950
- * handle is unknown or released; `PRECONDITION_FAILED` if a catalog id is unknown, a
951
- * `hostWall` is not a wall / is locked / is not in the active proposal, the
952
- * projected point falls outside its host wall, or a window tool is active;
953
- * `OPERATION_FAILED` if a source mesh fails to load or placement fails
954
- * (nothing is created — `details.itemIndex` is the failing item);
955
- * `METHOD_NOT_PERMITTED` if the plugin may not write.
956
- *
957
- * @examplePrompt Add windows along this whole facade at once
958
- * @examplePrompt Place a window in every bedroom in one operation
959
- * @examplePrompt Bulk add the windows from this schedule
960
- * @examplePrompt Put four windows on this wall in a single undo step
961
- * @examplePrompt Add all the windows with a 0.9m sill height together
962
- *
963
- * @performance Bulk creator — the whole batch is ONE host round-trip and ONE undo
964
- * entry, and each distinct catalog source loads once. Always prefer this over
965
- * calling `design.create.window` in a loop: build the full `items[]` array first
966
- * (all host walls and points up front), then make one call.
967
- *
968
- * # Example
969
- * ```ts
970
- * const v = snaptrude.core.math.vec3
971
- * const [wall] = await snaptrude.design.query.listWalls({ isSelected: true })
972
- * const [casement] = await snaptrude.design.windows.listCatalog()
973
- * const placed = await snaptrude.design.create.windows([
974
- * { catalogId: casement.id, hostWall: wall, position: await v.new(2, 0, 5), sillHeight: 0.9 },
975
- * { catalogId: casement.id, hostWall: wall, position: await v.new(5, 0, 5), sillHeight: 0.9 },
976
- * ])
977
- * const currentWall = await snaptrude.design.query.getHost(placed[0])
978
- * ```
979
- */
980
- public abstract windows(
981
- items: PluginCreateWindowItem[],
982
- ): PluginApiReturn<ComponentHandle[]>
983
-
984
685
  /**
985
686
  * Place a catalog **door or window** into a host wall with optional size overrides.
986
687
  *
@@ -990,7 +691,7 @@ export abstract class PluginDesignCreateApi {
990
691
  * its center. Placement is asynchronous and committed as one undoable creation.
991
692
  *
992
693
  * @param options - Opening kind, catalog and host references, placement, and
993
- * optional facing (swing side), hinge (hinged jamb), label, and dimensions
694
+ * optional facing, label, and dimensions
994
695
  * @returns the {@linkcode ComponentHandle} of the placed door or window
995
696
  * @throws if the options are invalid, the catalog id is unknown, the host is
996
697
  * not a wall, loading fails, or the opening cannot be placed on the host
@@ -998,9 +699,8 @@ export abstract class PluginDesignCreateApi {
998
699
  * @examplePrompt Add a 1m wide door to this wall here
999
700
  * @examplePrompt Place a window with a 0.9m sill height on the selected wall
1000
701
  *
1001
- * @performance Single-opening creator — use it for one hosted opening. For MORE
1002
- * THAN ONE, call `design.create.doors(items[])` / `design.create.windows(items[])`:
1003
- * the same per-item fields, one host round-trip, ONE undo entry.
702
+ * @performance Single-opening creator — use it for one hosted opening. A plural
703
+ * API is intentionally unavailable until host placement can be atomic.
1004
704
  *
1005
705
  * # Example
1006
706
  * ```ts
@@ -1562,7 +1262,7 @@ export type PluginDesignCreateStaircaseArgs = z.infer<
1562
1262
  * | `catalogId` | `string` | Library id: team `_id` or general `fullName` |
1563
1263
  * | `position` | {@linkcode Vec3Handle} | Absolute world placement point |
1564
1264
  * | `label` | `string`? | Instance name (default auto `${name}Ins${n}`) |
1565
- * | `createNewSourceMesh` | `boolean`? | Legacy, kept for compatibility; the host records a catalog source exactly once and this flag cannot skip it |
1265
+ * | `createNewSourceMesh` | `boolean`? | Emit a source-mesh creation command (default `true`) |
1566
1266
  * | `angleInDegrees` | `number`? | Rotation about the vertical axis, in degrees (default unrotated) |
1567
1267
  */
1568
1268
  export const PluginDesignCreateFurnitureArgs = z.object({
@@ -1591,8 +1291,7 @@ export type PluginDesignCreateFurnitureArgs = z.infer<
1591
1291
  * | `hostWall` | {@linkcode ComponentHandle} | The wall to host the door |
1592
1292
  * | `position` | {@linkcode Vec3Handle} | World point projected onto the wall |
1593
1293
  * | `label` | `string`? | Instance name (optional) |
1594
- * | `facing` | {@linkcode Vec3Handle}? | World point on the side of the wall the door swings open to (default engine-chosen) |
1595
- * | `hinge` | {@linkcode Vec3Handle}? | World point near the jamb the door is hinged on (default the catalog item's authored side) |
1294
+ * | `facing` | {@linkcode Vec3Handle}? | World point on the side of the wall the door faces (default engine-chosen) |
1596
1295
  */
1597
1296
  export const PluginDesignCreateDoorArgs = z.object({
1598
1297
  catalogId: z.string().min(1),
@@ -1600,13 +1299,12 @@ export const PluginDesignCreateDoorArgs = z.object({
1600
1299
  position: Vec3Handle,
1601
1300
  label: z.string().optional(),
1602
1301
  facing: Vec3Handle.optional(),
1603
- hinge: Vec3Handle.optional(),
1604
1302
  })
1605
1303
  export type PluginDesignCreateDoorArgs = z.infer<
1606
1304
  typeof PluginDesignCreateDoorArgs
1607
1305
  >
1608
- // TRANSPORT: positional signature shipped — facing is the trailing 5th arg, hinge rides in options:
1609
- // door(catalogId: string, hostWall: ComponentHandle, position: Vec3Handle, options?: { label?, hinge? }, facing?: Vec3Handle)
1306
+ // TRANSPORT: positional signature shipped — facing is the trailing 5th arg:
1307
+ // door(catalogId: string, hostWall: ComponentHandle, position: Vec3Handle, options?: { label? }, facing?: Vec3Handle)
1610
1308
 
1611
1309
  // ---------------------------------------------------------------------------
1612
1310
  // window
@@ -1621,8 +1319,7 @@ export type PluginDesignCreateDoorArgs = z.infer<
1621
1319
  * | `hostWall` | {@linkcode ComponentHandle} | The wall to host the window |
1622
1320
  * | `position` | {@linkcode Vec3Handle} | World point projected onto the wall |
1623
1321
  * | `label` | `string`? | Instance name (optional) |
1624
- * | `facing` | {@linkcode Vec3Handle}? | World point on the side of the wall the window opens to (default engine-chosen) |
1625
- * | `hinge` | {@linkcode Vec3Handle}? | World point near the jamb the window is hinged on (default the catalog item's authored side) |
1322
+ * | `facing` | {@linkcode Vec3Handle}? | World point on the side of the wall the window faces (default engine-chosen) |
1626
1323
  */
1627
1324
  export const PluginDesignCreateWindowArgs = z.object({
1628
1325
  catalogId: z.string().min(1),
@@ -1630,18 +1327,27 @@ export const PluginDesignCreateWindowArgs = z.object({
1630
1327
  position: Vec3Handle,
1631
1328
  label: z.string().optional(),
1632
1329
  facing: Vec3Handle.optional(),
1633
- hinge: Vec3Handle.optional(),
1634
1330
  })
1635
1331
  export type PluginDesignCreateWindowArgs = z.infer<
1636
1332
  typeof PluginDesignCreateWindowArgs
1637
1333
  >
1638
- // TRANSPORT: positional signature shipped — facing is the trailing 5th arg, hinge rides in options:
1639
- // window(catalogId: string, hostWall: ComponentHandle, position: Vec3Handle, options?: { label?, hinge? }, facing?: Vec3Handle)
1334
+ // TRANSPORT: positional signature shipped — facing is the trailing 5th arg:
1335
+ // window(catalogId: string, hostWall: ComponentHandle, position: Vec3Handle, options?: { label? }, facing?: Vec3Handle)
1640
1336
 
1641
1337
  // ---------------------------------------------------------------------------
1642
1338
  // opening
1643
1339
  // ---------------------------------------------------------------------------
1644
1340
 
1341
+ const PluginOpeningBaseOptions = {
1342
+ catalogId: z.string().min(1),
1343
+ hostWall: ComponentHandle,
1344
+ position: Vec3Handle,
1345
+ facing: Vec3Handle.optional(),
1346
+ label: z.string().optional(),
1347
+ width: z.number().finite().positive().optional(),
1348
+ height: z.number().finite().positive().optional(),
1349
+ }
1350
+
1645
1351
  /**
1646
1352
  * Options for {@linkcode PluginDesignCreateApi.opening}, discriminated by
1647
1353
  * `kind`. Width and height are positive Snaptrude engine-unit values.
@@ -1818,6 +1524,3 @@ export const PluginCreateSpaceItem = z.object({
1818
1524
  storey: z.number().int().optional(),
1819
1525
  })
1820
1526
  export type PluginCreateSpaceItem = z.infer<typeof PluginCreateSpaceItem>
1821
-
1822
- export * from "./opening-fields"
1823
- export * from "./bulk-items"
@@ -39,10 +39,6 @@ export abstract class PluginDesignDeleteApi {
39
39
  * children are removed and linked-list neighbours are fixed up — not caller-tunable
40
40
  * in v1. Undoable — commits as a **single** undo entry.
41
41
  *
42
- * Dimension lines anchored to a deleted entity are removed with it, in the same undo
43
- * entry, exactly as the editor's Delete key does — see `design.dimensions`. They are
44
- * NOT listed in `affected`, which reports component handles only.
45
- *
46
42
  * @param components - Entities to delete. Unknown/forged handles reject the whole call (fail-fast).
47
43
  * @returns The entities that were deleted, as {@linkcode ComponentHandle}`[]`
48
44
  * (echoed host-side from the resolved+deleted set — the engine returns no ids).
@@ -8,11 +8,9 @@ import { PluginDesignChangeResult } from "../lock"
8
8
  * `design.create.door`; generic reads (`listDoors`/`getHost`/`getProperties`) live at
9
9
  * `design.query.*`. Door targets are {@linkcode ComponentHandle}s.
10
10
  *
11
- * `getSwingDirection` is a DERIVED read (no persisted field) — computed from the placed
12
- * mesh's orientation: stand on the side the door swings open to, facing it; `'left'`
13
- * when the hinged jamb is on your left. `mirror` reflects the door about one of its own
14
- * axes (undoable, one command): `'x'` flips the swing side, `'z'` swaps the hinged jamb;
15
- * `setType` is intentionally absent (the engine has no in-place re-type —
11
+ * `getSwingDirection` is a DERIVED read (no persisted field) — computed from the door
12
+ * mesh reflection state. `mirror` reflects the door across an axis (undoable, one
13
+ * command); `setType` is intentionally absent (the engine has no in-place re-type —
16
14
  * it would require delete+recreate).
17
15
  *
18
16
  * The **catalog** reads (`listCatalogGroups`/`listCatalog`/`getCatalogItem`/`exists`)
@@ -156,11 +154,9 @@ export abstract class PluginDesignDoorsApi {
156
154
  public abstract getSupportFloor(door: ComponentHandle): PluginApiReturn<ComponentHandle | null>
157
155
 
158
156
  /**
159
- * Get a door's swing (hinge) handedness. Stand on the side the door swings
160
- * open to, facing the door: `'left'` when the hinged jamb is on your left,
161
- * `'right'` when it is on your right. Derived from the placed mesh's
162
- * orientation (the same reflection `mirror` and the `hinge` placement option
163
- * apply), not a persisted field.
157
+ * Get a door's swing (hinge) handedness — whether it opens as a left-hand or
158
+ * right-hand door. Derived from the door mesh's reflection state, not a
159
+ * persisted field.
164
160
  *
165
161
  * @param door The door to query
166
162
  * @returns `'left'` / `'right'`, or `null` if indeterminate
@@ -182,13 +178,11 @@ export abstract class PluginDesignDoorsApi {
182
178
  ): PluginApiReturn<"left" | "right" | null>
183
179
 
184
180
  /**
185
- * Mirror a door about its own axes. `'x'` (default) flips the swing side —
186
- * the door opens to the other side of the wall; `'z'` swaps the hinged jamb
187
- * (left-hand ↔ right-hand, same swing side); `'y'` turns it upside down.
188
- * Undoable as a single command.
181
+ * Mirror a door across an axis, flipping its swing so it opens from the
182
+ * other side. Undoable as a single command.
189
183
  *
190
184
  * @param door The door to mirror
191
- * @param axis Reflection axis (optional; default `'x'` — the swing-flip axis; `'z'` — the hinge-jamb axis)
185
+ * @param axis Reflection axis (optional; default `'x'` — the swing-flip axis)
192
186
  * @returns The affected door(s)
193
187
  *
194
188
  * @examplePrompt Flip this door's swing
@@ -199,11 +193,10 @@ export abstract class PluginDesignDoorsApi {
199
193
  *
200
194
  * # Example
201
195
  * ```ts
202
- * // Flip the selected door's swing side — axis defaults to "x"
196
+ * // Flip the selected door's swing — axis defaults to "x" (the swing-flip
197
+ * // axis); pass "y" or "z" to reflect across a different axis
203
198
  * const [door] = await snaptrude.design.query.listDoors({ isSelected: true })
204
199
  * const { affected } = await snaptrude.design.doors.mirror(door)
205
- * // …or swap the hinged jamb instead (keeps the swing side)
206
- * await snaptrude.design.doors.mirror(door, "z")
207
200
  * ```
208
201
  */
209
202
  public abstract mirror(
@@ -376,7 +369,7 @@ export const PluginDoorDimensions = z.object({
376
369
  })
377
370
  export type PluginDoorDimensions = z.infer<typeof PluginDoorDimensions>
378
371
 
379
- /** Mirror axis — tokens mirror the engine `FlipDirection` verbatim: `'x'` flips the swing side, `'z'` swaps the hinged jamb, `'y'` turns the door upside down. */
372
+ /** Mirror axis — tokens mirror the engine `FlipDirection` verbatim (§6.1). */
380
373
  export const PluginMirrorAxis = z.enum(["x", "y", "z"])
381
374
  export type PluginMirrorAxis = z.infer<typeof PluginMirrorAxis>
382
375
 
@@ -386,7 +379,7 @@ export type PluginMirrorAxis = z.infer<typeof PluginMirrorAxis>
386
379
  * | Property | Type | Description |
387
380
  * |---|---|---|
388
381
  * | `door` | {@linkcode ComponentHandle} | The door to mirror |
389
- * | `axis` | {@linkcode PluginMirrorAxis} | Reflection axis (optional; default `'x'` — the swing-flip axis; `'z'` — the hinge-jamb axis) |
382
+ * | `axis` | {@linkcode PluginMirrorAxis} | Reflection axis (optional; default `'x'` — the swing-flip axis) |
390
383
  */
391
384
  export const PluginDesignDoorMirrorArgs = z.object({
392
385
  door: ComponentHandle,