@snaptrude/plugin-core 1.0.2-plainpoint-20261102.2 → 1.0.3

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 (106) hide show
  1. package/AGENTS.md +17 -0
  2. package/CHANGELOG.md +6 -0
  3. package/api-manifest.json +35 -31
  4. package/dist/api/core/camera/index.d.ts +6 -16
  5. package/dist/api/core/camera/index.d.ts.map +1 -1
  6. package/dist/api/core/comment/index.d.ts +3 -7
  7. package/dist/api/core/comment/index.d.ts.map +1 -1
  8. package/dist/api/core/geom/create/index.d.ts +21 -69
  9. package/dist/api/core/geom/create/index.d.ts.map +1 -1
  10. package/dist/api/core/geom/query/circle.d.ts +11 -31
  11. package/dist/api/core/geom/query/circle.d.ts.map +1 -1
  12. package/dist/api/core/geom/query/contour.d.ts +7 -19
  13. package/dist/api/core/geom/query/contour.d.ts.map +1 -1
  14. package/dist/api/core/geom/query/curve.d.ts +24 -72
  15. package/dist/api/core/geom/query/curve.d.ts.map +1 -1
  16. package/dist/api/core/geom/query/profile.d.ts +7 -19
  17. package/dist/api/core/geom/query/profile.d.ts.map +1 -1
  18. package/dist/api/core/geom/update/contour.d.ts +12 -36
  19. package/dist/api/core/geom/update/contour.d.ts.map +1 -1
  20. package/dist/api/core/geom/update/curve.d.ts +5 -13
  21. package/dist/api/core/geom/update/curve.d.ts.map +1 -1
  22. package/dist/api/core/geom/update/profile.d.ts +12 -36
  23. package/dist/api/core/geom/update/profile.d.ts.map +1 -1
  24. package/dist/api/core/io/import/index.d.ts +3 -7
  25. package/dist/api/core/io/import/index.d.ts.map +1 -1
  26. package/dist/api/core/math/quat.d.ts +2 -2
  27. package/dist/api/core/math/quat.d.ts.map +1 -1
  28. package/dist/api/core/math/vec3.d.ts +15 -15
  29. package/dist/api/core/math/vec3.d.ts.map +1 -1
  30. package/dist/api/core/storeys/index.d.ts +0 -5
  31. package/dist/api/core/storeys/index.d.ts.map +1 -1
  32. package/dist/api/design/boolean/index.d.ts +4 -20
  33. package/dist/api/design/boolean/index.d.ts.map +1 -1
  34. package/dist/api/design/create/bulk-items.d.ts +20 -85
  35. package/dist/api/design/create/bulk-items.d.ts.map +1 -1
  36. package/dist/api/design/create/index.d.ts +71 -134
  37. package/dist/api/design/create/index.d.ts.map +1 -1
  38. package/dist/api/design/create/opening-fields.d.ts +6 -18
  39. package/dist/api/design/create/opening-fields.d.ts.map +1 -1
  40. package/dist/api/design/delete/index.d.ts +7 -14
  41. package/dist/api/design/delete/index.d.ts.map +1 -1
  42. package/dist/api/design/dimensions.d.ts +4 -12
  43. package/dist/api/design/dimensions.d.ts.map +1 -1
  44. package/dist/api/design/doors/index.d.ts +37 -0
  45. package/dist/api/design/doors/index.d.ts.map +1 -1
  46. package/dist/api/design/edit/index.d.ts +3 -7
  47. package/dist/api/design/edit/index.d.ts.map +1 -1
  48. package/dist/api/design/family.d.ts +1 -5
  49. package/dist/api/design/family.d.ts.map +1 -1
  50. package/dist/api/design/index.d.ts +1 -0
  51. package/dist/api/design/index.d.ts.map +1 -1
  52. package/dist/api/design/lock.d.ts +0 -3
  53. package/dist/api/design/lock.d.ts.map +1 -1
  54. package/dist/api/design/opening-placement.d.ts +123 -0
  55. package/dist/api/design/opening-placement.d.ts.map +1 -0
  56. package/dist/api/design/transform/index.d.ts +8 -28
  57. package/dist/api/design/transform/index.d.ts.map +1 -1
  58. package/dist/api/design/windows/index.d.ts +23 -0
  59. package/dist/api/design/windows/index.d.ts.map +1 -1
  60. package/dist/api/entity/space.d.ts +6 -30
  61. package/dist/api/entity/space.d.ts.map +1 -1
  62. package/dist/api/entity/story.d.ts +0 -4
  63. package/dist/api/entity/story.d.ts.map +1 -1
  64. package/dist/api/presentation/drawings.d.ts +1 -1
  65. package/dist/api/presentation/views.d.ts +1 -5
  66. package/dist/api/presentation/views.d.ts.map +1 -1
  67. package/dist/handles.d.ts +0 -8
  68. package/dist/handles.d.ts.map +1 -1
  69. package/dist/index.cjs +2496 -2441
  70. package/dist/index.cjs.map +1 -1
  71. package/dist/index.js +2493 -2440
  72. package/dist/index.js.map +1 -1
  73. package/package.json +13 -13
  74. package/src/api/core/camera/index.ts +7 -9
  75. package/src/api/core/comment/index.ts +3 -3
  76. package/src/api/core/geom/create/index.ts +25 -25
  77. package/src/api/core/geom/query/circle.ts +11 -11
  78. package/src/api/core/geom/query/contour.ts +6 -7
  79. package/src/api/core/geom/query/curve.ts +25 -25
  80. package/src/api/core/geom/query/profile.ts +6 -7
  81. package/src/api/core/geom/update/contour.ts +11 -12
  82. package/src/api/core/geom/update/curve.ts +5 -5
  83. package/src/api/core/geom/update/profile.ts +11 -12
  84. package/src/api/core/io/import/index.ts +2 -3
  85. package/src/api/core/math/quat.ts +2 -2
  86. package/src/api/core/math/vec3.ts +16 -16
  87. package/src/api/core/storeys/index.ts +0 -5
  88. package/src/api/design/boolean/index.ts +2 -2
  89. package/src/api/design/create/bulk-items.ts +6 -8
  90. package/src/api/design/create/index.ts +58 -34
  91. package/src/api/design/create/opening-fields.ts +7 -7
  92. package/src/api/design/delete/index.ts +7 -14
  93. package/src/api/design/dimensions.ts +4 -5
  94. package/src/api/design/doors/index.ts +43 -0
  95. package/src/api/design/edit/index.ts +3 -3
  96. package/src/api/design/family.ts +1 -2
  97. package/src/api/design/index.ts +1 -0
  98. package/src/api/design/lock.ts +0 -6
  99. package/src/api/design/opening-placement.ts +79 -0
  100. package/src/api/design/transform/index.ts +7 -15
  101. package/src/api/design/windows/index.ts +29 -0
  102. package/src/api/entity/space.ts +4 -5
  103. package/src/api/entity/story.ts +0 -4
  104. package/src/api/presentation/views.ts +2 -7
  105. package/src/handles.ts +0 -11
  106. package/test/handles.test.mjs +0 -36
@@ -8,7 +8,6 @@ import {
8
8
  CurveHandle,
9
9
  ProfileHandle,
10
10
  Vec3Handle,
11
- Vec3Input,
12
11
  } from "../../../handles"
13
12
  import { PluginGridStyleOptions } from "./grids"
14
13
  import { PluginGraphicsLineStyleId } from "../../presentation/graphics"
@@ -131,7 +130,7 @@ export abstract class PluginDesignCreateApi {
131
130
  contour: ContourHandle,
132
131
  height: number,
133
132
  label?: string,
134
- position?: Vec3Input,
133
+ position?: Vec3Handle,
135
134
  spaceType?: PluginSpaceType,
136
135
  massType?: PluginMassType,
137
136
  departmentId?: PluginDepartmentId,
@@ -166,7 +165,7 @@ export abstract class PluginDesignCreateApi {
166
165
  contour: ContourHandle,
167
166
  height: number,
168
167
  label?: string,
169
- position?: Vec3Input,
168
+ position?: Vec3Handle,
170
169
  massType?: PluginMassType,
171
170
  ): PluginApiReturn<ComponentHandle>
172
171
 
@@ -289,7 +288,7 @@ export abstract class PluginDesignCreateApi {
289
288
  */
290
289
  public abstract floor(
291
290
  ...args:
292
- | [contour: ContourHandle, thickness: number, position?: Vec3Input]
291
+ | [contour: ContourHandle, thickness: number, position?: Vec3Handle]
293
292
  | [contour: ContourHandle, options: PluginFamilyFloorOptions]
294
293
  ): PluginApiReturn<ComponentHandle[]>
295
294
 
@@ -423,10 +422,10 @@ export abstract class PluginDesignCreateApi {
423
422
  public abstract column(
424
423
  ...args:
425
424
  | [
426
- position: Vec3Input,
425
+ position: Vec3Handle,
427
426
  crossSection: ContourHandle,
428
427
  height: number,
429
- directionUp?: Vec3Input,
428
+ directionUp?: Vec3Handle,
430
429
  ]
431
430
  | [axis: LineHandle, options: PluginFamilyPlacement]
432
431
  ): PluginApiReturn<ComponentHandle>
@@ -473,7 +472,7 @@ export abstract class PluginDesignCreateApi {
473
472
  */
474
473
  public abstract beam(
475
474
  ...args:
476
- | [section: ContourHandle, length: number, direction?: Vec3Input]
475
+ | [section: ContourHandle, length: number, direction?: Vec3Handle]
477
476
  | [axis: LineHandle, options: PluginFamilyPlacement]
478
477
  ): PluginApiReturn<ComponentHandle>
479
478
 
@@ -497,10 +496,10 @@ export abstract class PluginDesignCreateApi {
497
496
  * single undo entry. The storey must already exist
498
497
  * (`core.storeys.create` first). Default: geometry-derived — a profile
499
498
  * drawn at y = 0 lands on storey 1; the ACTIVE storey is NOT consulted.
500
- * @param topProfiles - Top edge per curve of `profile` (same order). Current
501
- * hosts build walls with one height: any non-null entry is refused with
502
- * `PRECONDITION_FAILED` ("sloped or stepped tops are not supported") and
503
- * nothing is created. Pass `null` entries or omit it.
499
+ * @param topProfiles - Optional top edge per curve of `profile` (same order):
500
+ * a {@linkcode PluginWallTopProfile} makes that wall raked / gabled; `null`
501
+ * or a missing entry keeps it flat at `height`. Straight curves only. Such
502
+ * walls are non-parametric (see {@linkcode PluginWallTopProfile}).
504
503
  * @returns a {@linkcode ComponentHandle}`[]` (one per wall), in profile order
505
504
  * @throws if the profile is empty, `wallType` names no wall type in the
506
505
  * project, `storey` does not exist, `topProfiles` has more entries than the
@@ -514,6 +513,9 @@ export abstract class PluginDesignCreateApi {
514
513
  * @examplePrompt Add walls with mitred corners along the path
515
514
  * @examplePrompt Draw brick walls with plaster along this outline
516
515
  * @examplePrompt Build the perimeter in exposed concrete walls
516
+ * @examplePrompt Create a raked wall that slopes from 3m to 5m
517
+ * @examplePrompt Build a gable end wall with the ridge in the middle
518
+ * @examplePrompt Draw a wall with a sloped top edge
517
519
  *
518
520
  * # Example
519
521
  * ```ts
@@ -529,6 +531,13 @@ export abstract class PluginDesignCreateApi {
529
531
  * const brickWalls = await snaptrude.design.create.walls(
530
532
  * centerlines, 3, undefined, brick.label,
531
533
  * )
534
+ * // …a raked wall (3m at the start, 5m at the end) on a single centerline:
535
+ * const run = await snaptrude.core.geom.create.profileFromCurves([
536
+ * await snaptrude.core.geom.create.line(await v.new(0, 0, 10), await v.new(8, 0, 10)),
537
+ * ])
538
+ * const [raked] = await snaptrude.design.create.walls(run, undefined, 0.2, undefined, undefined, [
539
+ * [{ along: 0, height: 3 }, { along: 1, height: 5 }],
540
+ * ])
532
541
  * ```
533
542
  *
534
543
  * @performance For MORE THAN ONE run, call `design.create.wallRuns(items[])` — one
@@ -551,9 +560,8 @@ export abstract class PluginDesignCreateApi {
551
560
  * item's profile chain, mitred at shared endpoints. Junctions resolve within a
552
561
  * run, not between runs. Each item carries its own
553
562
  * dimensions, `wallType` and `storey`, so a single call can build a whole
554
- * floor plate or several storeys at once. Walls have one height: a non-null
555
- * `topProfiles` entry is refused with `PRECONDITION_FAILED` (`details.itemIndex`
556
- * names the item) and nothing is created. Validate-all-or-throw;
563
+ * floor plate or several storeys at once. Per-curve `topProfiles` make raked
564
+ * or gabled walls (see {@linkcode PluginWallTopProfile}). Validate-all-or-throw;
557
565
  * one command.
558
566
  *
559
567
  * @param items - One {@linkcode PluginCreateWallRunItem} per run to create
@@ -574,6 +582,7 @@ export abstract class PluginDesignCreateApi {
574
582
  * @examplePrompt Bulk create wall runs from these centerlines
575
583
  * @examplePrompt Create the walls for both storeys in a single undo step
576
584
  * @examplePrompt Turn these polylines into 200mm brick walls together
585
+ * @examplePrompt Import these raked walls with their top profiles in one go
577
586
  *
578
587
  * @performance Bulk creator — the whole batch is ONE host round-trip and ONE undo
579
588
  * entry. Always prefer this over calling `design.create.walls` in a loop: build the
@@ -594,6 +603,9 @@ export abstract class PluginDesignCreateApi {
594
603
  * const [groundWalls, upperWalls] = await snaptrude.design.create.wallRuns([
595
604
  * { profile: ground, height: 3, thickness: 0.2 },
596
605
  * { profile: upper, height: 3, storey: 2 },
606
+ * // one gable wall: 3m at both ends, 5m ridge in the middle
607
+ * { profile: upper, thickness: 0.2, storey: 3,
608
+ * topProfiles: [[{ along: 0, height: 3 }, { along: 0.5, height: 5 }, { along: 1, height: 3 }]] },
597
609
  * ])
598
610
  * console.log(groundWalls.length, "walls on the ground floor")
599
611
  * ```
@@ -637,7 +649,7 @@ export abstract class PluginDesignCreateApi {
637
649
  */
638
650
  public abstract staircase(
639
651
  preset: PluginStaircasePreset,
640
- position: Vec3Input,
652
+ position: Vec3Handle,
641
653
  label?: string,
642
654
  structureId?: string,
643
655
  level?: string,
@@ -728,7 +740,7 @@ export abstract class PluginDesignCreateApi {
728
740
  spacingY: number,
729
741
  options?: {
730
742
  angle?: number
731
- center?: Vec3Input
743
+ center?: Vec3Handle
732
744
  } & PluginGridStyleOptions,
733
745
  ): PluginApiReturn<ComponentHandle>
734
746
 
@@ -883,7 +895,7 @@ export abstract class PluginDesignCreateApi {
883
895
  */
884
896
  public abstract furniture(
885
897
  catalogId: string,
886
- position: Vec3Input,
898
+ position: Vec3Handle,
887
899
  options?: { label?: string; createNewSourceMesh?: boolean },
888
900
  angleInDegrees?: number,
889
901
  ): PluginApiReturn<ComponentHandle>
@@ -996,14 +1008,14 @@ export abstract class PluginDesignCreateApi {
996
1008
  public abstract door(
997
1009
  familyId: string,
998
1010
  hostWall: ComponentHandle,
999
- position: Vec3Input,
1011
+ position: Vec3Handle,
1000
1012
  options?: {
1001
1013
  label?: string
1002
- hinge?: Vec3Input
1014
+ hinge?: Vec3Handle
1003
1015
  version?: number
1004
1016
  values?: Record<string, number | string>
1005
1017
  },
1006
- facing?: Vec3Input,
1018
+ facing?: Vec3Handle,
1007
1019
  ): PluginApiReturn<ComponentHandle>
1008
1020
 
1009
1021
  /**
@@ -1035,6 +1047,12 @@ export abstract class PluginDesignCreateApi {
1035
1047
  * @examplePrompt Put three doors on this wall in a single undo step
1036
1048
  * @examplePrompt Add all the doors for this floor plan together
1037
1049
  *
1050
+ * A width/height (top-level, or under the family's own keys in `values`) on a family that
1051
+ * takes its size from its Type while Custom Sizes is off is never dropped: the Type within
1052
+ * 25 mm is chosen, else Custom Sizes is turned on and that size is built, leaving the Type
1053
+ * label stale. `design.doors.checkPlacement` reports which as `sizing`; `customSizes: "No"`
1054
+ * with a size no Type matches is refused.
1055
+ *
1038
1056
  * @performance Bulk creator — the whole batch is ONE host round-trip and ONE undo
1039
1057
  * entry. Always prefer this over
1040
1058
  * calling `design.create.door` in a loop: build the full `items[]` array first
@@ -1100,14 +1118,14 @@ export abstract class PluginDesignCreateApi {
1100
1118
  public abstract window(
1101
1119
  familyId: string,
1102
1120
  hostWall: ComponentHandle,
1103
- position: Vec3Input,
1121
+ position: Vec3Handle,
1104
1122
  options?: {
1105
1123
  label?: string
1106
- hinge?: Vec3Input
1124
+ hinge?: Vec3Handle
1107
1125
  version?: number
1108
1126
  values?: Record<string, number | string>
1109
1127
  },
1110
- facing?: Vec3Input,
1128
+ facing?: Vec3Handle,
1111
1129
  ): PluginApiReturn<ComponentHandle>
1112
1130
 
1113
1131
  /**
@@ -1140,6 +1158,12 @@ export abstract class PluginDesignCreateApi {
1140
1158
  * @examplePrompt Put four windows on this wall in a single undo step
1141
1159
  * @examplePrompt Add all the windows with a 0.9m sill height together
1142
1160
  *
1161
+ * A width/height (top-level, or under the family's own keys in `values`) on a family that
1162
+ * takes its size from its Type while Custom Sizes is off is never dropped: the Type within
1163
+ * 25 mm is chosen, else Custom Sizes is turned on and that size is built, leaving the Type
1164
+ * label stale. `design.windows.checkPlacement` reports which as `sizing`; `customSizes: "No"`
1165
+ * with a size no Type matches is refused.
1166
+ *
1143
1167
  * @performance Bulk creator — the whole batch is ONE host round-trip and ONE undo
1144
1168
  * entry. Always prefer this over
1145
1169
  * calling `design.create.window` in a loop: build the full `items[]` array first
@@ -1434,7 +1458,7 @@ export abstract class PluginDesignCreateApi {
1434
1458
  */
1435
1459
  public abstract copy(
1436
1460
  components: ComponentHandle[],
1437
- displacement: Vec3Input,
1461
+ displacement: Vec3Handle,
1438
1462
  options?: { count?: number; mode?: PluginCopyMode },
1439
1463
  ): PluginApiReturn<ComponentHandle[]>
1440
1464
 
@@ -1657,7 +1681,7 @@ export const PluginDesignCreateSpaceArgs = z.object({
1657
1681
  contour: ContourHandle,
1658
1682
  height: z.number(),
1659
1683
  label: z.string().optional(),
1660
- position: Vec3Input.optional(),
1684
+ position: Vec3Handle.optional(),
1661
1685
  spaceType: PluginSpaceType.optional(),
1662
1686
  massType: PluginMassType.optional(),
1663
1687
  departmentId: PluginDepartmentId.optional(),
@@ -1688,7 +1712,7 @@ export const PluginDesignCreateMassArgs = z.object({
1688
1712
  contour: ContourHandle,
1689
1713
  height: z.number(),
1690
1714
  label: z.string().optional(),
1691
- position: Vec3Input.optional(),
1715
+ position: Vec3Handle.optional(),
1692
1716
  massType: PluginMassType.optional(),
1693
1717
  })
1694
1718
  export type PluginDesignCreateMassArgs = z.infer<
@@ -1755,7 +1779,7 @@ export type PluginDesignCreateSlabArgs = z.infer<
1755
1779
  export const PluginDesignCreateFloorArgs = z.object({
1756
1780
  contour: ContourHandle,
1757
1781
  thickness: z.number(),
1758
- position: Vec3Input.optional(),
1782
+ position: Vec3Handle.optional(),
1759
1783
  })
1760
1784
  export type PluginDesignCreateFloorArgs = z.infer<
1761
1785
  typeof PluginDesignCreateFloorArgs
@@ -1818,10 +1842,10 @@ export type PluginDesignCreateCeilingArgs = z.infer<
1818
1842
  * | `directionUp` | {@linkcode Vec3Handle}? | Up axis (default world up) |
1819
1843
  */
1820
1844
  export const PluginDesignCreateColumnArgs = z.object({
1821
- position: Vec3Input,
1845
+ position: Vec3Handle,
1822
1846
  crossSection: ContourHandle,
1823
1847
  height: z.number(),
1824
- directionUp: Vec3Input.optional(),
1848
+ directionUp: Vec3Handle.optional(),
1825
1849
  })
1826
1850
  export type PluginDesignCreateColumnArgs = z.infer<
1827
1851
  typeof PluginDesignCreateColumnArgs
@@ -1843,7 +1867,7 @@ export type PluginDesignCreateColumnArgs = z.infer<
1843
1867
  export const PluginDesignCreateBeamArgs = z.object({
1844
1868
  section: ContourHandle,
1845
1869
  length: z.number(),
1846
- direction: Vec3Input.optional(),
1870
+ direction: Vec3Handle.optional(),
1847
1871
  })
1848
1872
  export type PluginDesignCreateBeamArgs = z.infer<
1849
1873
  typeof PluginDesignCreateBeamArgs
@@ -1898,7 +1922,7 @@ export type PluginDesignCreateWallsArgs = z.infer<
1898
1922
  */
1899
1923
  export const PluginDesignCreateStaircaseArgs = z.object({
1900
1924
  preset: PluginStaircasePreset,
1901
- position: Vec3Input,
1925
+ position: Vec3Handle,
1902
1926
  label: z.string().optional(),
1903
1927
  structureId: z.string().optional(),
1904
1928
  level: z.string().optional(),
@@ -1925,7 +1949,7 @@ export type PluginDesignCreateStaircaseArgs = z.infer<
1925
1949
  */
1926
1950
  export const PluginDesignCreateFurnitureArgs = z.object({
1927
1951
  catalogId: z.string().min(1),
1928
- position: Vec3Input,
1952
+ position: Vec3Handle,
1929
1953
  label: z.string().optional(),
1930
1954
  createNewSourceMesh: z.boolean().optional(),
1931
1955
  angleInDegrees: z.number().optional(),
@@ -2183,7 +2207,7 @@ export type PluginCopyMode = z.infer<typeof PluginCopyMode>
2183
2207
  */
2184
2208
  export const PluginDesignCreateCopyArgs = z.object({
2185
2209
  components: z.array(ComponentHandle).min(1),
2186
- displacement: Vec3Input,
2210
+ displacement: Vec3Handle,
2187
2211
  count: z.number().int().positive().optional(),
2188
2212
  mode: PluginCopyMode.optional(),
2189
2213
  })
@@ -2216,7 +2240,7 @@ export const PluginCreateSpaceItem = z.object({
2216
2240
  contour: ContourHandle,
2217
2241
  height: z.number(),
2218
2242
  label: z.string().optional(),
2219
- position: Vec3Input.optional(),
2243
+ position: Vec3Handle.optional(),
2220
2244
  spaceType: PluginSpaceType.optional(),
2221
2245
  massType: PluginMassType.optional(),
2222
2246
  departmentId: PluginDepartmentId.optional(),
@@ -1,5 +1,5 @@
1
1
  import * as z from "zod"
2
- import { ComponentHandle, Vec3Input } from "../../../handles"
2
+ import { ComponentHandle, Vec3Handle } from "../../../handles"
3
3
 
4
4
  /**
5
5
  * The shared field bag every hosted opening (door / window) carries — used by
@@ -17,9 +17,9 @@ import { ComponentHandle, Vec3Input } from "../../../handles"
17
17
  * | `version` | `number`? | Exact family version; newest known by default |
18
18
  * | `values` | `Record<string, number \| string>`? | Authored instance parameters; numeric lengths use Snaptrude units |
19
19
  * | `hostWall` | {@linkcode ComponentHandle} | The wall to host the opening |
20
- * | `position` | {@linkcode Vec3Input} | World point projected onto the wall (a vec3 handle or a plain `{ x, y, z }`) |
21
- * | `facing` | {@linkcode Vec3Input}? | World point on the side of the wall the opening swings open to (default engine-chosen) |
22
- * | `hinge` | {@linkcode Vec3Input}? | World point near the hinged jamb; requires the recipe's hand parameter |
20
+ * | `position` | {@linkcode Vec3Handle} | World point projected onto the wall |
21
+ * | `facing` | {@linkcode Vec3Handle}? | World point on the side of the wall the opening swings open to (default engine-chosen) |
22
+ * | `hinge` | {@linkcode Vec3Handle}? | World point near the hinged jamb; requires the recipe's hand parameter |
23
23
  * | `label` | `string`? | Instance name (optional) |
24
24
  * | `width` | `number`? | Sets the declared width parameter, not the opening's evaluated envelope |
25
25
  * | `height` | `number`? | Sets the declared height parameter |
@@ -31,9 +31,9 @@ export const PluginOpeningBaseOptions = {
31
31
  .record(z.string(), z.union([z.number().finite(), z.string()]))
32
32
  .optional(),
33
33
  hostWall: ComponentHandle,
34
- position: Vec3Input,
35
- facing: Vec3Input.optional(),
36
- hinge: Vec3Input.optional(),
34
+ position: Vec3Handle,
35
+ facing: Vec3Handle.optional(),
36
+ hinge: Vec3Handle.optional(),
37
37
  label: z.string().optional(),
38
38
  width: z.number().finite().positive().optional(),
39
39
  height: z.number().finite().positive().optional(),
@@ -8,7 +8,7 @@ import { PluginDesignChangeResult } from "../lock"
8
8
  *
9
9
  * | Property | Type | Description |
10
10
  * |---|---|---|
11
- * | `components` | {@linkcode ComponentHandle}`[]` | Entities to delete. Handles that no longer resolve are skipped and returned as `missing`; the call rejects only when none resolve. |
11
+ * | `components` | {@linkcode ComponentHandle}`[]` | Entities to delete. Unknown/forged handles reject the whole call (fail-fast). |
12
12
  *
13
13
  * `.min(1)` is deliberately stricter than the shipped `PluginDesignLockArgs`
14
14
  * (which allows an empty array) — an empty delete is a caller error, not a no-op.
@@ -43,19 +43,12 @@ export abstract class PluginDesignDeleteApi {
43
43
  * entry, exactly as the editor's Delete key does — see `design.dimensions`. They are
44
44
  * NOT listed in `affected`, which reports component handles only.
45
45
  *
46
- * @param components - Entities to delete. A handle that no longer resolves (already
47
- * removed by an earlier edit — a door deleted with its wall, a wall replaced by an
48
- * opening's re-cut) is skipped, not fatal.
49
- * @returns `{ affected, cascaded?, missing?, rejected? }`: the entities you asked to delete
50
- * (echoed host-side — the engine returns no ids); `cascaded`, the derived walls/floors/slabs
51
- * the parametric graph removed with a deleted space; `missing`, handles that no longer
52
- * resolved and were skipped; `rejected`, handles the engine accepted but left standing
53
- * (delete them again, one at a time, or report them). Deleted handles are **stale**
54
- * afterwards: re-resolving one throws `HANDLE_STALE`.
55
- * @throws `HANDLE_INVALID` when NONE of the supplied handles resolve (nothing is deleted);
56
- * hosts before Oct 2026 reject the whole call when any handle is gone. `OPERATION_FAILED`
57
- * when the parametric engine rejects the deletion — the batch is rolled back.
58
- * `HANDLE_STALE` for a handle the caller held that has since been retired.
46
+ * @param components - Entities to delete. Unknown/forged handles reject the whole call (fail-fast).
47
+ * @returns The entities that were deleted, as {@linkcode ComponentHandle}`[]`
48
+ * (echoed host-side from the resolved+deleted set — the engine returns no ids).
49
+ * Deleted handles are **stale** afterwards: re-resolving one throws `HANDLE_INVALID`.
50
+ * @throws If any supplied handle is unknown/forged (`HANDLE_INVALID`) — the whole
51
+ * call rejects before anything is deleted (all-or-nothing resolution).
59
52
  *
60
53
  * @examplePrompt Delete the selected objects
61
54
  * @examplePrompt Remove these walls from the model
@@ -5,7 +5,6 @@ import {
5
5
  DimensionHandle,
6
6
  Vec3Components,
7
7
  Vec3Handle,
8
- Vec3Input,
9
8
  } from "../../handles"
10
9
 
11
10
  /**
@@ -95,8 +94,8 @@ export abstract class PluginDesignDimensionsApi {
95
94
  * ```
96
95
  */
97
96
  public abstract create(
98
- from: Vec3Input,
99
- to: Vec3Input,
97
+ from: Vec3Handle,
98
+ to: Vec3Handle,
100
99
  anchor: ComponentHandle,
101
100
  options?: { anchorTo?: ComponentHandle; offset?: number },
102
101
  ): PluginApiReturn<DimensionHandle>
@@ -363,8 +362,8 @@ export type PluginDimensionsChangeResult = z.infer<
363
362
  * TRANSPORT: positional args — validated host-side as this object.
364
363
  */
365
364
  export const PluginDesignDimensionsCreateArgs = z.object({
366
- from: Vec3Input,
367
- to: Vec3Input,
365
+ from: Vec3Handle,
366
+ to: Vec3Handle,
368
367
  anchor: ComponentHandle,
369
368
  anchorTo: ComponentHandle.optional(),
370
369
  offset: z.number().finite().optional(),
@@ -2,6 +2,10 @@ import * as z from "zod"
2
2
  import { PluginApiReturn } from "../../../types"
3
3
  import { ComponentHandle } from "../../../handles"
4
4
  import { PluginDesignChangeResult } from "../lock"
5
+ import type {
6
+ PluginOpeningPlacementArgs,
7
+ PluginOpeningPlacementResult,
8
+ } from "../opening-placement"
5
9
 
6
10
  /**
7
11
  * `snaptrude.design.doors` — door-specific reads + edits. Creation lives at
@@ -22,6 +26,33 @@ import { PluginDesignChangeResult } from "../lock"
22
26
  export abstract class PluginDesignDoorsApi {
23
27
  constructor() {}
24
28
 
29
+ /**
30
+ * Trial the native host fit and geometry rebuild, then restore the model without publishing commands.
31
+ * The first result uses the creation options; subsequent results use candidateWidths in input order.
32
+ * Widths are authored parameters, not physical envelopes or a guaranteed maximum width.
33
+ * No catalog save, history entry or model persistence occurs. Rerun after model edits.
34
+ * A failed fit leaves cut unverified. Family-host cuts are also unverified: their rebuild
35
+ * cannot supply a reversible, authoritative trial result. Material clearance checks evaluated
36
+ * parts against physical neighbours across hosts. The intended host uses native fit/cut checks.
37
+ * Creation repeats clearance immediately before placement; this report grants no later approval.
38
+ * Editing permission is required; locked hosts and an active opening tool reject.
39
+ * It does not say which room the door opens into: read `opensInto` from
40
+ * `design.validation.access()` after placement (see {@linkcode getSwingDirection}).
41
+ * A requested width/height on a family sized by its Type (Custom Sizes off) picks the Type
42
+ * within 25 mm, else turns Custom Sizes on; each result's `sizing` says which. Creation
43
+ * (`design.create.door(s)`) applies the same rule, so the size you pass is the size built.
44
+ * @param options Door creation options and up to 12 alternative widths; numeric lengths use SU.
45
+ * @returns Separate fit/cut/clearance evidence, evaluated bounds and explicit limitations.
46
+ * @examplePrompt Check which door widths fit this host without placing a door
47
+ * @example
48
+ * const result = await snaptrude.design.doors.checkPlacement({
49
+ * familyId, version, hostWall, position, width: 3.5, candidateWidths: [3, 2.5],
50
+ * })
51
+ */
52
+ public abstract checkPlacement(
53
+ options: PluginOpeningPlacementArgs,
54
+ ): PluginApiReturn<PluginOpeningPlacementResult>
55
+
25
56
  /**
26
57
  * Get the type name of a door — the label that identifies which kind of door
27
58
  * the instance is, as shown in its properties.
@@ -163,6 +194,12 @@ export abstract class PluginDesignDoorsApi {
163
194
  * Read the saved hand parameter. left means local -X; right means local +X.
164
195
  * Returns null when the recipe does not declare that control.
165
196
  *
197
+ * Hand is the hinge side, not the room the leaf swings into. For that, read
198
+ * `(await design.validation.access()).topology.rooms[].doors[].opensInto`: a
199
+ * converted room or `"outside"`. A door whose wall is not a room edge is listed
200
+ * in `topology.unresolvedDoors` instead. To make it open into the other room,
201
+ * call {@linkcode mirror} (axis `x`) and read access again.
202
+ *
166
203
  * @param door The door to query
167
204
  * @returns `'left'` / `'right'`, or `null` if indeterminate
168
205
  *
@@ -170,12 +207,16 @@ export abstract class PluginDesignDoorsApi {
170
207
  * @examplePrompt Is the selected door left-handed or right-handed?
171
208
  * @examplePrompt Check the hinge side of this door
172
209
  * @examplePrompt Does the entrance door open to the left or the right?
210
+ * @examplePrompt Which room does this door open into?
173
211
  *
174
212
  * # Example
175
213
  * ```ts
176
214
  * const [door] = await snaptrude.design.query.listDoors({ isSelected: true })
177
215
  * const swing = await snaptrude.design.doors.getSwingDirection(door)
178
216
  * if (swing) console.log("opens as a", swing, "hand door")
217
+ * // Which room it opens into
218
+ * const { topology } = await snaptrude.design.validation.access()
219
+ * const side = topology.rooms.flatMap((r) => r.doors).find((d) => d.door === door)?.opensInto
179
220
  * ```
180
221
  */
181
222
  public abstract getSwingDirection(
@@ -188,6 +229,8 @@ export abstract class PluginDesignDoorsApi {
188
229
  * Axis y rejects. No mesh reflection occurs. Recipes use outputs.swingSide
189
230
  * (1 or -1) for the adjoining room's floor ownership. A door between two
190
231
  * room floors requires this output; a missing or invalid report rejects the cut.
232
+ * The same side decides `opensInto` in `design.validation.access()`; read it
233
+ * there to confirm which room the door now opens into.
191
234
  *
192
235
  * @param door The door to mirror
193
236
  * @param axis Reflection axis (optional; default `'x'` — the swing-flip axis; `'z'` — the hinge-jamb axis)
@@ -1,6 +1,6 @@
1
1
  import * as z from "zod"
2
2
  import { PluginApiReturn } from "../../../types"
3
- import { ComponentHandle, Vec3Input } from "../../../handles"
3
+ import { ComponentHandle, Vec3Handle } from "../../../handles"
4
4
  import { OffsetSplitOutcome } from "../shared"
5
5
 
6
6
  /**
@@ -41,7 +41,7 @@ export abstract class PluginDesignEditApi {
41
41
  public abstract offsetSplit(
42
42
  component: ComponentHandle,
43
43
  offsetDistance: number,
44
- options?: { profilePickPoint?: Vec3Input },
44
+ options?: { profilePickPoint?: Vec3Handle },
45
45
  ): PluginApiReturn<OffsetSplitOutcome>
46
46
  }
47
47
 
@@ -49,6 +49,6 @@ export abstract class PluginDesignEditApi {
49
49
  export const PluginDesignOffsetSplitArgs = z.object({
50
50
  component: ComponentHandle,
51
51
  offsetDistance: z.number(),
52
- profilePickPoint: Vec3Input.optional(),
52
+ profilePickPoint: Vec3Handle.optional(),
53
53
  })
54
54
  export type PluginDesignOffsetSplitArgs = z.infer<typeof PluginDesignOffsetSplitArgs>
@@ -5,7 +5,6 @@ import {
5
5
  ComponentHandle,
6
6
  Vec3Components,
7
7
  Vec3Handle,
8
- Vec3Input,
9
8
  } from "../../handles"
10
9
  import { NetworkFamilyContract } from "./networks"
11
10
 
@@ -1320,7 +1319,7 @@ export type PluginDesignFamilyDeleteArgs = z.infer<
1320
1319
  export const PluginDesignFamilyPlaceArgs = z.object({
1321
1320
  id: z.string().optional(),
1322
1321
  version: z.number().int().positive().optional(),
1323
- position: z.union([Vec3Input, Vec3Components]).optional(),
1322
+ position: z.union([Vec3Handle, Vec3Components]).optional(),
1324
1323
  rotation: z.number().optional(),
1325
1324
  values: z.record(z.string(), z.union([z.number(), z.string()])).optional(),
1326
1325
  storey: z.number().int().optional(),
@@ -288,6 +288,7 @@ export * from "./query"
288
288
  export * from "./selection"
289
289
  export * from "./doors"
290
290
  export * from "./windows"
291
+ export * from "./opening-placement"
291
292
  export * from "./furniture"
292
293
  export * from "./materials"
293
294
  export * from "./lock"
@@ -9,12 +9,6 @@ import { ComponentHandle } from "../../handles"
9
9
  */
10
10
  export const PluginDesignChangeResult = z.object({
11
11
  affected: z.array(ComponentHandle),
12
- /** Derived BIM (walls/floors/slabs) the parametric engine removed with its host. Delete only. */
13
- cascaded: z.array(ComponentHandle).optional(),
14
- /** Handles that no longer resolved when the call ran and were skipped. Delete only. */
15
- missing: z.array(ComponentHandle).optional(),
16
- /** Handles the engine accepted but left standing; delete them again or report them. Delete only. */
17
- rejected: z.array(ComponentHandle).optional(),
18
12
  })
19
13
  export type PluginDesignChangeResult = z.infer<typeof PluginDesignChangeResult>
20
14
 
@@ -0,0 +1,79 @@
1
+ import * as z from "zod"
2
+ import { PluginOpeningBaseOptions } from "./create/opening-fields"
3
+
4
+ /** Creation options plus bounded alternative widths. Each trial starts from the same model. */
5
+ export const PluginOpeningPlacementArgs = z.object({
6
+ ...PluginOpeningBaseOptions,
7
+ sillHeight: z.number().finite().nonnegative().optional(),
8
+ candidateWidths: z.array(z.number().finite().positive()).max(12).optional(),
9
+ })
10
+ export type PluginOpeningPlacementArgs = z.infer<
11
+ typeof PluginOpeningPlacementArgs
12
+ >
13
+
14
+ /** A failed fit leaves the cut unverified: no geometry trial was attempted. */
15
+ export const PluginOpeningPlacementCheck = z.object({
16
+ status: z.enum(["pass", "fail", "unverified"]),
17
+ reason: z.string(),
18
+ })
19
+
20
+ const bounds = z.object({
21
+ min: z.object({ x: z.number().finite(), y: z.number().finite(), z: z.number().finite() }),
22
+ max: z.object({ x: z.number().finite(), y: z.number().finite(), z: z.number().finite() }),
23
+ })
24
+
25
+ /** Fit, cut and material clearance are independent checks, not an accessibility certificate. */
26
+ export const PluginOpeningPlacementResult = z.object({
27
+ units: z.literal("SU"),
28
+ results: z.array(
29
+ z.object({
30
+ requestedWidth: z.number().finite().positive().optional(),
31
+ requestedHeight: z.number().finite().positive().optional(),
32
+ /** Original parameter overrides. Evaluated bounds can differ from these values. */
33
+ requestedValues: z.record(z.string(), z.union([z.number().finite(), z.string()])).optional(),
34
+ /**
35
+ * Present when the family sizes itself from a Type unless Custom Sizes is on, and a width
36
+ * or height was requested. `type`: the Type within 25 mm was chosen (its label stays true).
37
+ * `custom`: none matched, so Custom Sizes was turned on and the requested size is built;
38
+ * `type` then names the Type that no longer describes it. Creation applies the same rule.
39
+ */
40
+ sizing: z
41
+ .object({
42
+ mode: z.enum(["type", "custom"]),
43
+ typeKey: z.string(),
44
+ type: z.string(),
45
+ note: z.string(),
46
+ })
47
+ .optional(),
48
+ /** World-space bounds of evaluated material parts, never the opening proxy. */
49
+ physicalBounds: bounds.optional(),
50
+ /** Only present when the recipe declares semantic frame anatomy. */
51
+ frameBounds: bounds.optional(),
52
+ /** World enclosure of the authored cut envelope; not frame or clear passage width. */
53
+ roughOpeningBounds: bounds.optional(),
54
+ dimensions: z
55
+ .object({
56
+ width: z.number().finite().positive(),
57
+ height: z.number().finite().positive(),
58
+ thickness: z.number().finite().nonnegative(),
59
+ })
60
+ .optional(),
61
+ fit: PluginOpeningPlacementCheck,
62
+ cut: PluginOpeningPlacementCheck,
63
+ clearance: PluginOpeningPlacementCheck.extend({
64
+ candidatePairs: z.number().int().nonnegative(),
65
+ checkedPairs: z.number().int().nonnegative(),
66
+ obstructions: z.array(z.object({
67
+ entity: z.string(),
68
+ status: z.enum(["fail", "unverified"]),
69
+ reason: z.string(),
70
+ intersectionVolume: z.number().finite().nonnegative().optional(),
71
+ })),
72
+ }),
73
+ }),
74
+ ),
75
+ limitations: z.array(z.string()),
76
+ })
77
+ export type PluginOpeningPlacementResult = z.infer<
78
+ typeof PluginOpeningPlacementResult
79
+ >