@snaptrude/plugin-core 0.10.0 → 1.0.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 (229) hide show
  1. package/AGENTS.md +4 -0
  2. package/CHANGELOG.md +187 -3
  3. package/api-manifest.json +929 -25
  4. package/dist/api/analysis/daylight.d.ts +5 -5
  5. package/dist/api/analysis/solar.d.ts +2 -2
  6. package/dist/api/core/camera/index.d.ts +463 -0
  7. package/dist/api/core/camera/index.d.ts.map +1 -1
  8. package/dist/api/core/geom/create/index.d.ts +942 -101
  9. package/dist/api/core/geom/create/index.d.ts.map +1 -1
  10. package/dist/api/core/geom/query/brep.d.ts +59 -2
  11. package/dist/api/core/geom/query/brep.d.ts.map +1 -1
  12. package/dist/api/core/geom/query/curve.d.ts +59 -35
  13. package/dist/api/core/geom/query/curve.d.ts.map +1 -1
  14. package/dist/api/core/geom/query/edge.d.ts +2 -2
  15. package/dist/api/core/geom/query/face.d.ts +105 -0
  16. package/dist/api/core/geom/query/face.d.ts.map +1 -1
  17. package/dist/api/core/geom/update/contour.d.ts +10 -2
  18. package/dist/api/core/geom/update/contour.d.ts.map +1 -1
  19. package/dist/api/core/geom/update/curve.d.ts +5 -5
  20. package/dist/api/core/geom/update/profile.d.ts +3 -1
  21. package/dist/api/core/geom/update/profile.d.ts.map +1 -1
  22. package/dist/api/core/handles/index.d.ts +4 -2
  23. package/dist/api/core/handles/index.d.ts.map +1 -1
  24. package/dist/api/core/index.d.ts +4 -4
  25. package/dist/api/core/index.d.ts.map +1 -1
  26. package/dist/api/core/io/import/index.d.ts +3 -1
  27. package/dist/api/core/io/import/index.d.ts.map +1 -1
  28. package/dist/api/core/layers.d.ts +377 -255
  29. package/dist/api/core/layers.d.ts.map +1 -1
  30. package/dist/api/core/mode/index.d.ts +19 -7
  31. package/dist/api/core/mode/index.d.ts.map +1 -1
  32. package/dist/api/core/project/index.d.ts +68 -1
  33. package/dist/api/core/project/index.d.ts.map +1 -1
  34. package/dist/api/core/proposals/index.d.ts +4 -3
  35. package/dist/api/core/proposals/index.d.ts.map +1 -1
  36. package/dist/api/core/storeys/index.d.ts +31 -2
  37. package/dist/api/core/storeys/index.d.ts.map +1 -1
  38. package/dist/api/core/storeys/layers.d.ts +408 -0
  39. package/dist/api/core/storeys/layers.d.ts.map +1 -0
  40. package/dist/api/core/tags.d.ts +84 -3
  41. package/dist/api/core/tags.d.ts.map +1 -1
  42. package/dist/api/core/zoom/index.d.ts +13 -0
  43. package/dist/api/core/zoom/index.d.ts.map +1 -1
  44. package/dist/api/design/bim.d.ts +299 -0
  45. package/dist/api/design/bim.d.ts.map +1 -0
  46. package/dist/api/design/boolean/index.d.ts +2 -0
  47. package/dist/api/design/boolean/index.d.ts.map +1 -1
  48. package/dist/api/design/catalog.d.ts +320 -0
  49. package/dist/api/design/catalog.d.ts.map +1 -0
  50. package/dist/api/design/create/bulk-items.d.ts +269 -0
  51. package/dist/api/design/create/bulk-items.d.ts.map +1 -0
  52. package/dist/api/design/create/grids.d.ts +132 -0
  53. package/dist/api/design/create/grids.d.ts.map +1 -0
  54. package/dist/api/design/create/index.d.ts +752 -78
  55. package/dist/api/design/create/index.d.ts.map +1 -1
  56. package/dist/api/design/create/opening-fields.d.ts +38 -0
  57. package/dist/api/design/create/opening-fields.d.ts.map +1 -0
  58. package/dist/api/design/delete/index.d.ts +5 -0
  59. package/dist/api/design/delete/index.d.ts.map +1 -1
  60. package/dist/api/design/dimensions.d.ts +427 -0
  61. package/dist/api/design/dimensions.d.ts.map +1 -0
  62. package/dist/api/design/doors/index.d.ts +24 -23
  63. package/dist/api/design/doors/index.d.ts.map +1 -1
  64. package/dist/api/design/family.d.ts +1446 -0
  65. package/dist/api/design/family.d.ts.map +1 -0
  66. package/dist/api/design/furniture/index.d.ts +33 -0
  67. package/dist/api/design/furniture/index.d.ts.map +1 -1
  68. package/dist/api/design/grids.d.ts +237 -0
  69. package/dist/api/design/grids.d.ts.map +1 -0
  70. package/dist/api/design/index.d.ts +30 -0
  71. package/dist/api/design/index.d.ts.map +1 -1
  72. package/dist/api/design/networks.d.ts +1703 -0
  73. package/dist/api/design/networks.d.ts.map +1 -0
  74. package/dist/api/design/query/geometry/index.d.ts +33 -5
  75. package/dist/api/design/query/geometry/index.d.ts.map +1 -1
  76. package/dist/api/design/query/grids.d.ts +287 -0
  77. package/dist/api/design/query/grids.d.ts.map +1 -0
  78. package/dist/api/design/query/index.d.ts +143 -34
  79. package/dist/api/design/query/index.d.ts.map +1 -1
  80. package/dist/api/design/query/spaces.d.ts +5 -5
  81. package/dist/api/design/selection/index.d.ts +12 -12
  82. package/dist/api/design/types/index.d.ts +40 -24
  83. package/dist/api/design/types/index.d.ts.map +1 -1
  84. package/dist/api/design/update/index.d.ts +412 -8
  85. package/dist/api/design/update/index.d.ts.map +1 -1
  86. package/dist/api/design/visibility.d.ts +28 -0
  87. package/dist/api/design/visibility.d.ts.map +1 -1
  88. package/dist/api/design/windows/index.d.ts +8 -8
  89. package/dist/api/design/windows/index.d.ts.map +1 -1
  90. package/dist/api/entity/buildableEnvelope.d.ts +84 -2
  91. package/dist/api/entity/buildableEnvelope.d.ts.map +1 -1
  92. package/dist/api/entity/referenceLine.d.ts +13 -1
  93. package/dist/api/entity/referenceLine.d.ts.map +1 -1
  94. package/dist/api/entity/space.d.ts +12 -12
  95. package/dist/api/entity/story.d.ts +4 -4
  96. package/dist/api/presentation/aiInspiration.d.ts +24 -24
  97. package/dist/api/presentation/annotate.d.ts +38 -22
  98. package/dist/api/presentation/annotate.d.ts.map +1 -1
  99. package/dist/api/presentation/camera.d.ts +265 -0
  100. package/dist/api/presentation/camera.d.ts.map +1 -0
  101. package/dist/api/presentation/drawingContract.d.ts +32 -0
  102. package/dist/api/presentation/drawingContract.d.ts.map +1 -0
  103. package/dist/api/presentation/drawings.d.ts +3523 -0
  104. package/dist/api/presentation/drawings.d.ts.map +1 -0
  105. package/dist/api/presentation/export.d.ts +23 -5
  106. package/dist/api/presentation/export.d.ts.map +1 -1
  107. package/dist/api/presentation/graphics.d.ts +419 -0
  108. package/dist/api/presentation/graphics.d.ts.map +1 -0
  109. package/dist/api/presentation/index.d.ts +66 -12
  110. package/dist/api/presentation/index.d.ts.map +1 -1
  111. package/dist/api/presentation/placedViews.d.ts +116 -16
  112. package/dist/api/presentation/placedViews.d.ts.map +1 -1
  113. package/dist/api/presentation/sections.d.ts +461 -0
  114. package/dist/api/presentation/sections.d.ts.map +1 -0
  115. package/dist/api/presentation/shapes.d.ts +2 -2
  116. package/dist/api/presentation/sheets.d.ts +175 -19
  117. package/dist/api/presentation/sheets.d.ts.map +1 -1
  118. package/dist/api/presentation/standards.d.ts +590 -0
  119. package/dist/api/presentation/standards.d.ts.map +1 -0
  120. package/dist/api/presentation/viewPresets.d.ts +222 -0
  121. package/dist/api/presentation/viewPresets.d.ts.map +1 -0
  122. package/dist/api/presentation/views.d.ts +144 -9
  123. package/dist/api/presentation/views.d.ts.map +1 -1
  124. package/dist/api/presentation/walkthroughs.d.ts +1350 -0
  125. package/dist/api/presentation/walkthroughs.d.ts.map +1 -0
  126. package/dist/api/program/departments.d.ts +58 -2
  127. package/dist/api/program/departments.d.ts.map +1 -1
  128. package/dist/api/program/index.d.ts +16 -6
  129. package/dist/api/program/index.d.ts.map +1 -1
  130. package/dist/api/program/labels.d.ts +355 -0
  131. package/dist/api/program/labels.d.ts.map +1 -0
  132. package/dist/api/program/metadata.d.ts +328 -0
  133. package/dist/api/program/metadata.d.ts.map +1 -0
  134. package/dist/api/program/site.d.ts +4 -4
  135. package/dist/api/program/spreadsheet.d.ts +267 -4
  136. package/dist/api/program/spreadsheet.d.ts.map +1 -1
  137. package/dist/api/workspace/index.d.ts +49 -2
  138. package/dist/api/workspace/index.d.ts.map +1 -1
  139. package/dist/errors/codes.d.ts +3 -1
  140. package/dist/errors/codes.d.ts.map +1 -1
  141. package/dist/handles.d.ts +32 -3
  142. package/dist/handles.d.ts.map +1 -1
  143. package/dist/index.cjs +6539 -2804
  144. package/dist/index.cjs.map +1 -1
  145. package/dist/index.d.ts +2 -0
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js +6121 -2801
  148. package/dist/index.js.map +1 -1
  149. package/dist/massParameters.d.ts +287 -0
  150. package/dist/massParameters.d.ts.map +1 -0
  151. package/dist/massParameters.ops.d.ts +138 -0
  152. package/dist/massParameters.ops.d.ts.map +1 -0
  153. package/package.json +14 -13
  154. package/scripts/check-dist-fresh.mjs +22 -0
  155. package/scripts/generate-manifest.mjs +5 -0
  156. package/scripts/generate-manifest.test.mjs +14 -3
  157. package/src/api/core/camera/index.ts +446 -1
  158. package/src/api/core/geom/create/index.ts +1037 -102
  159. package/src/api/core/geom/query/brep.ts +60 -2
  160. package/src/api/core/geom/query/curve.ts +26 -2
  161. package/src/api/core/geom/query/edge.ts +2 -2
  162. package/src/api/core/geom/query/face.ts +109 -4
  163. package/src/api/core/geom/update/contour.ts +10 -2
  164. package/src/api/core/geom/update/profile.ts +2 -0
  165. package/src/api/core/handles/index.ts +3 -2
  166. package/src/api/core/index.ts +4 -4
  167. package/src/api/core/io/import/index.ts +11 -3
  168. package/src/api/core/layers.ts +380 -173
  169. package/src/api/core/mode/index.ts +19 -7
  170. package/src/api/core/project/index.ts +62 -1
  171. package/src/api/core/proposals/index.ts +4 -3
  172. package/src/api/core/storeys/index.ts +34 -2
  173. package/src/api/core/storeys/layers.ts +339 -0
  174. package/src/api/core/tags.ts +76 -0
  175. package/src/api/core/zoom/index.ts +13 -0
  176. package/src/api/design/bim.ts +210 -0
  177. package/src/api/design/boolean/index.ts +2 -0
  178. package/src/api/design/catalog.ts +203 -0
  179. package/src/api/design/create/bulk-items.ts +294 -0
  180. package/src/api/design/create/grids.ts +137 -0
  181. package/src/api/design/create/index.ts +823 -114
  182. package/src/api/design/create/opening-fields.ts +40 -0
  183. package/src/api/design/delete/index.ts +5 -0
  184. package/src/api/design/dimensions.ts +453 -0
  185. package/src/api/design/doors/index.ts +64 -41
  186. package/src/api/design/family.ts +1342 -0
  187. package/src/api/design/furniture/index.ts +34 -0
  188. package/src/api/design/grids.ts +237 -0
  189. package/src/api/design/index.ts +49 -4
  190. package/src/api/design/networks.ts +607 -0
  191. package/src/api/design/query/geometry/index.ts +35 -4
  192. package/src/api/design/query/grids.ts +197 -0
  193. package/src/api/design/query/index.ts +105 -3
  194. package/src/api/design/types/index.ts +31 -24
  195. package/src/api/design/update/index.ts +398 -4
  196. package/src/api/design/visibility.ts +34 -0
  197. package/src/api/design/windows/index.ts +40 -22
  198. package/src/api/entity/buildableEnvelope.ts +77 -2
  199. package/src/api/entity/referenceLine.ts +25 -12
  200. package/src/api/presentation/annotate.ts +90 -58
  201. package/src/api/presentation/camera.ts +289 -0
  202. package/src/api/presentation/drawingContract.ts +70 -0
  203. package/src/api/presentation/drawings.ts +1179 -0
  204. package/src/api/presentation/export.ts +32 -6
  205. package/src/api/presentation/graphics.ts +324 -0
  206. package/src/api/presentation/index.ts +73 -14
  207. package/src/api/presentation/placedViews.ts +119 -28
  208. package/src/api/presentation/sections.ts +420 -0
  209. package/src/api/presentation/sheets.ts +181 -20
  210. package/src/api/presentation/standards.ts +445 -0
  211. package/src/api/presentation/viewPresets.ts +241 -0
  212. package/src/api/presentation/views.ts +81 -10
  213. package/src/api/presentation/walkthroughs.ts +1201 -0
  214. package/src/api/program/departments.ts +54 -2
  215. package/src/api/program/index.ts +16 -6
  216. package/src/api/program/labels.ts +332 -0
  217. package/src/api/program/metadata.ts +364 -0
  218. package/src/api/program/spreadsheet.ts +282 -0
  219. package/src/api/workspace/index.ts +51 -2
  220. package/src/errors/codes.ts +7 -0
  221. package/src/handles.ts +42 -3
  222. package/src/index.ts +2 -0
  223. package/src/massParameters.ops.ts +246 -0
  224. package/src/massParameters.ts +427 -0
  225. package/test/drawingContract.test.mjs +288 -0
  226. package/test/errors.test.mjs +1 -0
  227. package/test/massParameters.test.mjs +952 -0
  228. package/test/paperPensContract.test.mjs +75 -0
  229. package/test/presentationViewSettings.test.mjs +85 -0
@@ -0,0 +1,607 @@
1
+ import * as z from "zod"
2
+ import type { PluginApiReturn } from "../../types"
3
+
4
+ const id = z.string().regex(/^[A-Za-z][A-Za-z0-9_-]{0,79}$/)
5
+ const vector = z.tuple([
6
+ z.number().finite(),
7
+ z.number().finite(),
8
+ z.number().finite(),
9
+ ])
10
+ const positive = z.number().finite().positive()
11
+ const endpoint = z.object({ node: id, port: id }).strict()
12
+ const relationships = z
13
+ .array(
14
+ z
15
+ .object({
16
+ node: id,
17
+ relation: z.enum(["mountedOn", "within", "serves", "penetrates"]),
18
+ target: z
19
+ .object({
20
+ kind: z.enum(["component", "topology"]),
21
+ id: z.string().min(1).max(200),
22
+ })
23
+ .strict(),
24
+ })
25
+ .strict(),
26
+ )
27
+ .max(4000)
28
+
29
+ /** Versioned metadata stored in a pinned FamilySpecBundle.network. Expressions use the
30
+ * family's parameter scope and SU, just like its geometry. No world-space port overrides.
31
+ * Pipe recipes run from local [0,0,0] to [length,0,0] along +X.
32
+ */
33
+ export const NetworkFamilyContract = z
34
+ .object({
35
+ schemaVersion: z.literal(1),
36
+ role: z.enum([
37
+ "pipe",
38
+ "duct",
39
+ "wire",
40
+ "cable",
41
+ "conduit",
42
+ "cableDuct",
43
+ "cableTray",
44
+ "connector",
45
+ "equipment",
46
+ ]),
47
+ lengthParameter: z.string().min(1).optional(),
48
+ ports: z
49
+ .array(
50
+ z
51
+ .object({
52
+ id,
53
+ position: z.tuple([z.string(), z.string(), z.string()]),
54
+ direction: z.tuple([z.string(), z.string(), z.string()]),
55
+ size: z.string(),
56
+ height: z.string().optional(),
57
+ /** Unit width axis in family coordinates, perpendicular to direction.
58
+ * Required with height for mechanical rectangular ports; prevents guessed roll.
59
+ */
60
+ widthDirection: z
61
+ .tuple([z.string(), z.string(), z.string()])
62
+ .optional(),
63
+ systems: z.array(z.string().min(1)).min(1),
64
+ })
65
+ .strict(),
66
+ )
67
+ .min(1)
68
+ .max(64),
69
+ })
70
+ .strict()
71
+ export type NetworkFamilyContract = z.infer<typeof NetworkFamilyContract>
72
+
73
+ const familyPin = z
74
+ .object({
75
+ definitionId: z.string().min(1).max(200),
76
+ version: z.number().int().positive(),
77
+ values: z.record(
78
+ z.string(),
79
+ z.union([z.number().finite(), z.string(), z.boolean()]),
80
+ ),
81
+ })
82
+ .strict()
83
+
84
+ /** Authored topology: vertices are pinned connector/equipment families; each edge is one
85
+ * line and one pinned straight-pipe family. Curved runs are not yet supported by this
86
+ * contract; bends require explicit connector vertices. World mm, Y up, rotations in degrees.
87
+ * Plumbing v2 accepts round ports only. Rectangular ports need a section-roll contract.
88
+ * The resolved component graph is retained for existing persistence, replay and queries.
89
+ */
90
+ export const NetworkTopologyDocument = z
91
+ .object({
92
+ schemaVersion: z.literal(2),
93
+ domain: z.literal("plumbing"),
94
+ label: z.string().min(1).max(120),
95
+ architectureRevision: z.string().min(1).max(80),
96
+ relationships,
97
+ vertices: z
98
+ .array(
99
+ z
100
+ .object({
101
+ id,
102
+ kind: z.enum([
103
+ "fixture",
104
+ "equipment",
105
+ "tank",
106
+ "pump",
107
+ "valve",
108
+ "manifold",
109
+ "fitting",
110
+ ]),
111
+ storey: z.number().int(),
112
+ family: familyPin,
113
+ positionMm: vector,
114
+ rotationDeg: vector,
115
+ systems: z.record(z.string(), z.string().min(1)),
116
+ })
117
+ .strict(),
118
+ )
119
+ .min(1)
120
+ .max(1000),
121
+ edges: z
122
+ .array(
123
+ z
124
+ .object({
125
+ id,
126
+ kind: z.enum(["pipe", "riser"]),
127
+ storey: z.number().int(),
128
+ family: familyPin,
129
+ from: endpoint,
130
+ to: endpoint,
131
+ system: z.string().min(1),
132
+ /** One line between evaluated connector ports, never a corner-bearing polyline. */
133
+ curve: z.object({ kind: z.literal("line") }).strict(),
134
+ })
135
+ .strict(),
136
+ )
137
+ .max(1000),
138
+ })
139
+ .strict()
140
+ export type NetworkTopologyDocument = z.infer<typeof NetworkTopologyDocument>
141
+
142
+ export const MechanicalAirSystem = z.enum([
143
+ "supplyAir",
144
+ "returnAir",
145
+ "exhaustAir",
146
+ "outsideAir",
147
+ "reliefAir",
148
+ ])
149
+ const mechanicalVertexKind = z.enum([
150
+ "equipment",
151
+ "terminal",
152
+ "damper",
153
+ "fitting",
154
+ ])
155
+ const mechanicalEdgeKind = z.enum(["duct", "riser"])
156
+
157
+ /** Air-side HVAC intent. Reuses the graph transport, not plumbing domain semantics.
158
+ * Every vertex and edge pins an existing family. Edges run from upstream to downstream.
159
+ * Rectangular duct ports use local +Y as their width axis. The host aligns this axis to
160
+ * the upstream connector's widthDirection and rejects incompatible downstream roll.
161
+ * Bends, branches and transitions are explicit fitting vertices. No flexible curves,
162
+ * hydronic/refrigerant design, airflow sizing or code-compliance inference is provided.
163
+ */
164
+ export const MechanicalTopologyDocument = NetworkTopologyDocument.extend({
165
+ domain: z.literal("mechanical"),
166
+ vertices: z
167
+ .array(
168
+ NetworkTopologyDocument.shape.vertices.element.extend({
169
+ kind: mechanicalVertexKind,
170
+ systems: z.record(z.string(), MechanicalAirSystem),
171
+ }),
172
+ )
173
+ .min(1)
174
+ .max(1000),
175
+ edges: z
176
+ .array(
177
+ NetworkTopologyDocument.shape.edges.element.extend({
178
+ kind: mechanicalEdgeKind,
179
+ system: MechanicalAirSystem,
180
+ }),
181
+ )
182
+ .max(1000),
183
+ })
184
+ export type MechanicalTopologyDocument = z.infer<
185
+ typeof MechanicalTopologyDocument
186
+ >
187
+
188
+ const electricalVertexKind = z.enum([
189
+ "transformer",
190
+ "mainBoard",
191
+ "distributionBoard",
192
+ "mcbBox",
193
+ "junctionBox",
194
+ "switch",
195
+ "outlet",
196
+ "light",
197
+ "load",
198
+ "equipment",
199
+ "fitting",
200
+ ])
201
+ const electricalEdgeKind = z.enum([
202
+ "wire",
203
+ "cable",
204
+ "conduit",
205
+ "cableDuct",
206
+ "cableTray",
207
+ ])
208
+ const electricalCircuits = z
209
+ .array(
210
+ z
211
+ .object({
212
+ id,
213
+ source: id,
214
+ loads: z.array(id).min(1).max(1000),
215
+ routes: z.array(id).max(1000),
216
+ voltage: positive,
217
+ phases: z.number().int().min(1).max(3),
218
+ ratedAmps: positive,
219
+ })
220
+ .strict(),
221
+ )
222
+ .max(1000)
223
+
224
+ /** Physical electrical topology, independent of logical circuit membership.
225
+ * Each straight edge pins a family whose role matches its kind. Bends and branches
226
+ * require connector vertices. Junction boxes are equipment with explicit named ports.
227
+ * Rectangular containment uses the same section frame as mechanical ducts.
228
+ * Systems are authored labels which must be supported by the pinned ports.
229
+ * Direction is explicit, never inferred from circuits or spatial contact.
230
+ */
231
+ export const ElectricalTopologyDocument = NetworkTopologyDocument.extend({
232
+ domain: z.literal("electrical"),
233
+ vertices: z
234
+ .array(
235
+ NetworkTopologyDocument.shape.vertices.element.extend({
236
+ kind: electricalVertexKind,
237
+ }),
238
+ )
239
+ .min(1)
240
+ .max(1000),
241
+ edges: z
242
+ .array(
243
+ NetworkTopologyDocument.shape.edges.element.extend({
244
+ kind: electricalEdgeKind,
245
+ directed: z.boolean(),
246
+ }),
247
+ )
248
+ .max(1000),
249
+ circuits: electricalCircuits,
250
+ })
251
+ export type ElectricalTopologyDocument = z.infer<
252
+ typeof ElectricalTopologyDocument
253
+ >
254
+
255
+ export const AuthoredNetworkTopologyDocument = z.discriminatedUnion("domain", [
256
+ NetworkTopologyDocument,
257
+ MechanicalTopologyDocument,
258
+ ElectricalTopologyDocument,
259
+ ])
260
+ export type AuthoredNetworkTopologyDocument = z.infer<
261
+ typeof AuthoredNetworkTopologyDocument
262
+ >
263
+
264
+ /** Authored world-space connector. This is not a claim of certified family connector metadata. */
265
+ export const NetworkPort = z
266
+ .object({
267
+ id,
268
+ positionMm: vector,
269
+ direction: vector,
270
+ system: z.string().min(1).max(80),
271
+ /** Diameter for round ports; width when heightMm is supplied (rectangular port). */
272
+ sizeMm: positive,
273
+ heightMm: positive.optional(),
274
+ /** World-space unit width axis. Mechanical rectangular ports require this section frame. */
275
+ widthDirection: vector.optional(),
276
+ })
277
+ .strict()
278
+
279
+ /** The agent supplies every route and section. The host does not size or route a network. */
280
+ export const NetworkGeometry = z.discriminatedUnion("kind", [
281
+ z
282
+ .object({
283
+ kind: z.literal("family"),
284
+ definitionId: z.string().min(1).max(200),
285
+ version: z.number().int().positive(),
286
+ values: z.record(
287
+ z.string(),
288
+ z.union([z.number().finite(), z.string(), z.boolean()]),
289
+ ),
290
+ positionMm: vector,
291
+ rotationDeg: vector,
292
+ ports: z.array(NetworkPort).max(64),
293
+ })
294
+ .strict(),
295
+ z
296
+ .object({
297
+ kind: z.literal("route"),
298
+ pathMm: z.array(vector).min(2).max(256),
299
+ system: z.string().min(1).max(80),
300
+ section: z.discriminatedUnion("kind", [
301
+ z
302
+ .object({
303
+ kind: z.literal("round"),
304
+ diameterMm: positive,
305
+ wallMm: z.number().finite().nonnegative(),
306
+ })
307
+ .strict(),
308
+ z
309
+ .object({
310
+ kind: z.literal("rectangular"),
311
+ widthMm: positive,
312
+ heightMm: positive,
313
+ wallMm: z.number().finite().nonnegative(),
314
+ })
315
+ .strict(),
316
+ ]),
317
+ })
318
+ .strict(),
319
+ ])
320
+
321
+ // Storey is scene membership, not a coordinate offset. A route can span multiple elevations.
322
+ const node = z
323
+ .object({ id, storey: z.number().int(), geometry: NetworkGeometry })
324
+ .strict()
325
+ const electricalNode = node.extend({
326
+ kind: z.enum([
327
+ ...electricalVertexKind.options,
328
+ ...electricalEdgeKind.options,
329
+ ]),
330
+ })
331
+ const plumbingNode = node.extend({
332
+ kind: z.enum([
333
+ "fixture",
334
+ "equipment",
335
+ "tank",
336
+ "pump",
337
+ "valve",
338
+ "manifold",
339
+ "fitting",
340
+ "pipe",
341
+ "riser",
342
+ ]),
343
+ })
344
+ const document = z
345
+ .object({
346
+ schemaVersion: z.literal(1),
347
+ label: z.string().min(1).max(120),
348
+ /** Revision from context(); invalidated when the source architectural graph changes. */
349
+ architectureRevision: z.string().min(1).max(80),
350
+ connections: z
351
+ .array(
352
+ z
353
+ .object({ id, from: endpoint, to: endpoint, directed: z.boolean() })
354
+ .strict(),
355
+ )
356
+ .max(4000),
357
+ relationships,
358
+ })
359
+ .strict()
360
+
361
+ /** Electrical connectivity and circuit membership are distinct from physical route occupancy. */
362
+ export const ElectricalGraphDocument = document.extend({
363
+ domain: z.literal("electrical"),
364
+ nodes: z.array(electricalNode).min(1).max(1000),
365
+ circuits: electricalCircuits,
366
+ /** Required for new authoring. Omitted only on saved legacy snapshots. */
367
+ authored: ElectricalTopologyDocument.optional(),
368
+ })
369
+
370
+ /** Plumbing uses explicit directed port connections; touching geometry never creates a connection. */
371
+ export const PlumbingGraphDocument = document.extend({
372
+ domain: z.literal("plumbing"),
373
+ nodes: z.array(plumbingNode).min(1).max(1000),
374
+ /** Original v2 intent. Nodes/connections are the resolved snapshot for replay.
375
+ * Kept inside the document so existing save services preserve it without envelope changes.
376
+ * Legacy v1 saves omit this field; new plumbing submissions require v2.
377
+ */
378
+ authored: NetworkTopologyDocument.optional(),
379
+ })
380
+ /** Resolved HVAC snapshot for queries, commands and load. New submissions require v2 intent. */
381
+ export const MechanicalGraphDocument = document.extend({
382
+ domain: z.literal("mechanical"),
383
+ nodes: z
384
+ .array(
385
+ node.extend({
386
+ kind: z.enum([
387
+ ...mechanicalVertexKind.options,
388
+ ...mechanicalEdgeKind.options,
389
+ ]),
390
+ geometry: NetworkGeometry.options[0],
391
+ }),
392
+ )
393
+ .min(1)
394
+ .max(1000),
395
+ authored: MechanicalTopologyDocument,
396
+ })
397
+ export const NetworkGraphDocument = z.discriminatedUnion("domain", [
398
+ ElectricalGraphDocument,
399
+ PlumbingGraphDocument,
400
+ MechanicalGraphDocument,
401
+ ])
402
+ export type NetworkGraphDocument = z.infer<typeof NetworkGraphDocument>
403
+ export type NetworkNode = NetworkGraphDocument["nodes"][number]
404
+ export type NetworkPort = z.infer<typeof NetworkPort>
405
+ export type NetworkDomain = NetworkGraphDocument["domain"]
406
+
407
+ /** JSON is an authored document, not a transport for live geometry handles. Maximum 2 MB characters. */
408
+ export const NetworkValidateArgs = z
409
+ .object({ graphJson: z.string().max(2_000_000) })
410
+ .strict()
411
+ export const NetworkCreateArgs = NetworkValidateArgs.extend({
412
+ requestId: z.string().uuid(),
413
+ })
414
+ export const NetworkGetArgs = z.object({ graphId: z.string().uuid() }).strict()
415
+ export const NetworkContextArgs = z
416
+ .object({
417
+ offset: z.number().int().nonnegative().default(0),
418
+ limit: z.number().int().min(1).max(100).default(30),
419
+ /** Omit the legacy PG2 dump when using the routing view, to avoid duplicate geometry tokens. */
420
+ includeTopology: z.boolean().default(true),
421
+ })
422
+ .strict()
423
+ export const NetworkQueryArgs = NetworkGetArgs.extend({
424
+ revision: z.number().int().positive(),
425
+ queryJson: z.string().max(16000),
426
+ })
427
+ export const NetworkQuery = z
428
+ .object({
429
+ kind: z.enum([
430
+ "nodes",
431
+ "connected",
432
+ "upstream",
433
+ "downstream",
434
+ "ports",
435
+ "nearestPort",
436
+ "circuits",
437
+ "relationships",
438
+ ]),
439
+ node: id.optional(),
440
+ pointMm: vector.optional(),
441
+ radiusMm: positive.optional(),
442
+ /** Filter nodes, ports and traversal links by system. Traversal always includes its start node. */
443
+ system: z.string().optional(),
444
+ sizeMm: positive.optional(),
445
+ heightMm: positive.optional(),
446
+ offset: z.number().int().nonnegative().default(0),
447
+ limit: z.number().int().min(1).max(100).default(30),
448
+ })
449
+ .strict()
450
+ export type NetworkQuery = z.infer<typeof NetworkQuery>
451
+
452
+ export interface NetworkDiagnostic {
453
+ path: string
454
+ message: string
455
+ }
456
+ export interface NetworkValidation {
457
+ ok: boolean
458
+ diagnostics: NetworkDiagnostic[]
459
+ nodeCount: number
460
+ }
461
+ /** Durable node-to-scene mapping. New family-backed nodes retain the original authorized pin;
462
+ * parameters and placement belong to the component, not a generated library family.
463
+ * Existing wrapper pins and v1 raw-route recipe pins remain readable, not newly authored.
464
+ */
465
+ export const NetworkComponentBinding = z
466
+ .object({
467
+ nodeId: id,
468
+ componentId: z.string().min(1),
469
+ definitionId: z.string().min(1),
470
+ definitionVersion: z.number().int().positive(),
471
+ })
472
+ .strict()
473
+ export type NetworkComponentBinding = z.infer<typeof NetworkComponentBinding>
474
+ export interface SavedNetworkGraph {
475
+ id: string
476
+ domain: NetworkDomain
477
+ revision: 1
478
+ requestId: string
479
+ document: NetworkGraphDocument
480
+ components: NetworkComponentBinding[]
481
+ }
482
+ export interface NetworkCreateResult {
483
+ graphId: string
484
+ revision: number
485
+ components: NetworkComponentBinding[]
486
+ alreadyCreated: boolean
487
+ /** Queue acceptance is not a durable database acknowledgement. */
488
+ saveStatus: "queued" | "existing"
489
+ }
490
+
491
+ /** Exact TopoGraph routing evidence, not a route planner or inferred enclosure.
492
+ * World mm, Y-up. A curve's axis is a unit direction, not a length.
493
+ * Shared region IDs mean a shared boundary, not permission to penetrate a wall.
494
+ * Regions preserve directed outer/hole loops. No vertical connectivity is inferred;
495
+ * use their curves and storey elevations to plan risers and explicit penetrations.
496
+ */
497
+ export interface NetworkRoutingContext {
498
+ units: "mm"
499
+ edges: {
500
+ id: string
501
+ curve:
502
+ | { type: "line"; start: number[]; end: number[] }
503
+ | {
504
+ type: "arc"
505
+ start: number[]
506
+ end: number[]
507
+ centre: number[]
508
+ axis: number[]
509
+ }
510
+ wallId: string | null
511
+ regionIds: string[]
512
+ }[]
513
+ regions: {
514
+ id: string
515
+ /** Stable source component IDs; read label/storey from context.components. */
516
+ ownerIds: string[]
517
+ boundary: {
518
+ outer: { edgeId: string; direction: "forward" | "reverse" }[]
519
+ holes: { edgeId: string; direction: "forward" | "reverse" }[][]
520
+ }
521
+ floorId: string | null
522
+ slabId: string | null
523
+ }[]
524
+ /** Hosting is exact; bounds are only coarse avoidance data, not a penetration clearance check. */
525
+ openings: {
526
+ id: string
527
+ wallId: string
528
+ type: string
529
+ minMm: number[]
530
+ maxMm: number[]
531
+ }[]
532
+ }
533
+ export interface NetworkContextResult {
534
+ proposalId: string
535
+ buildingId: string | null
536
+ revision: string
537
+ /** PG2's native serialization uses Snaptrude units (1 SU = 254 mm). */
538
+ topologyUnits: "SU"
539
+ /** JSON array; empty when includeTopology is false. Prefer routing for network planning. */
540
+ topologyJson: string
541
+ /** Each collection uses offset/limit independently. Follow nextOffset before resolving references.
542
+ * Empty regions mean topology is unavailable, not that bounds can substitute for room boundaries.
543
+ */
544
+ routing: NetworkRoutingContext
545
+ nextOffset: number | null
546
+ total: number
547
+ storeys: { number: number; elevationMm: number }[]
548
+ components: {
549
+ id: string
550
+ type: string
551
+ label: string
552
+ storey: number
553
+ minMm: number[]
554
+ maxMm: number[]
555
+ }[]
556
+ /** Saved graph inventory, paged with topology/components. Use these IDs after reopening a project. */
557
+ networks: {
558
+ graphId: string
559
+ domain: NetworkDomain
560
+ revision: number
561
+ label: string
562
+ componentCount: number
563
+ }[]
564
+ }
565
+ export interface NetworkQueryResult {
566
+ revision: number
567
+ resultJson: string
568
+ nextOffset: number | null
569
+ total: number
570
+ }
571
+
572
+ /** Shared transport only. Each domain retains a separate schema and semantics. */
573
+ export abstract class PluginDomainNetworkApi {
574
+ /** Read the active proposal's architectural context before authoring. Keep one revision across pages. */
575
+ abstract context(
576
+ args?: z.input<typeof NetworkContextArgs>,
577
+ ): PluginApiReturn<NetworkContextResult>
578
+ /** Validate authored JSON, pinned families and recipe evaluation without placing or saving anything.
579
+ * This does not build kernel meshes or certify engineering/code compliance.
580
+ */
581
+ abstract validate(
582
+ args: z.input<typeof NetworkValidateArgs>,
583
+ ): PluginApiReturn<NetworkValidation>
584
+ /** Construct each node on its authored storey, with one undo command and queued project persistence.
585
+ * Networks can span storeys. Reuse requestId on retry; components returns the durable node mapping.
586
+ * Coordinates are world millimetres, Y-up; rotations are degrees. No engineering rules are inferred.
587
+ * Throws for stale architecture, invalid references/geometry or conflicting retries.
588
+ */
589
+ abstract create(
590
+ args: z.input<typeof NetworkCreateArgs>,
591
+ ): PluginApiReturn<NetworkCreateResult>
592
+ /** Read one saved graph in the active proposal; null on miss. IDs are durable, not worker handles. */
593
+ abstract get(
594
+ args: z.input<typeof NetworkGetArgs>,
595
+ ): PluginApiReturn<SavedNetworkGraph | null>
596
+ /** Read bounded authored topology in graph revision. Crossings do not connect. No clash/compliance claims. */
597
+ abstract query(
598
+ args: z.input<typeof NetworkQueryArgs>,
599
+ ): PluginApiReturn<NetworkQueryResult>
600
+ }
601
+
602
+ /** Electrical equipment, physical routes and logical circuits, constructed from agent-authored intent. */
603
+ export abstract class PluginElectricalApi extends PluginDomainNetworkApi {}
604
+ /** Plumbing equipment and routed pipe networks, constructed from agent-authored intent. */
605
+ export abstract class PluginPlumbingApi extends PluginDomainNetworkApi {}
606
+ /** Family-backed air-side HVAC networks. The agent authors engineering intent; the host constructs it. */
607
+ export abstract class PluginMechanicalApi extends PluginDomainNetworkApi {}
@@ -19,8 +19,8 @@ import {
19
19
  * {@linkcode PluginDesignQueryGeometryApi.getCenterline} mints a
20
20
  * {@linkcode CurveHandle} for a wall's core curve.
21
21
  * {@linkcode PluginDesignQueryGeometryApi.getTriangulatedMeshes} is the
22
- * handle-free exception: it returns the triangulated render meshes as plain
23
- * world-space arrays.
22
+ * handle-free exception: it returns `{ meshes }` — the triangulated render
23
+ * meshes as plain world-space arrays, wrapped in an object, never bare.
24
24
  *
25
25
  * Returned handles are session-ephemeral and go stale on undo/redo/re-topologize
26
26
  * — re-fetch a fresh one after edits.
@@ -34,10 +34,26 @@ export abstract class PluginDesignQueryGeometryApi {
34
34
  * scene entity, as a reusable {@linkcode BrepHandle} for the
35
35
  * `core.geom.query.brep.*` reads.
36
36
  *
37
+ * **Frame.** By default the brep comes back in the component's own LOCAL frame
38
+ * (its vertices are relative to the mesh origin, exactly as stored). This is
39
+ * the long-standing default, and existing callers rely on it. Pass
40
+ * `{ frame: "world" }` to get the same solid placed where it sits in the
41
+ * scene (world coordinates, like
42
+ * `getBottomContour` / `getTriangulatedMeshes` and every `design.query.measure`
43
+ * read). Use `"world"` whenever the handle will be COMBINED with other scene
44
+ * geometry or committed back beside its source: booleans between two scene
45
+ * masses, `brepFromFillet` / `brepFromShell` / `brepFromOffset` /
46
+ * `brepFromChamfer` on a scene mass followed by `massFromBrep`, or comparing
47
+ * its vertices with a bounding box. A world-frame handle from a parametrically
48
+ * live mass also carries that mass's recipe, so an operation result keeps an
49
+ * editable record.
50
+ *
37
51
  * @param component - The scene component to read
38
- * @returns The brep as a {@linkcode BrepHandle}, or `null` if the component has no brep geometry
52
+ * @param options - `frame`: `"local"` (default, mesh-local coordinates) or `"world"` (scene coordinates)
53
+ * @returns The brep as a {@linkcode BrepHandle}, or `null` if the component has no brep geometry — also `null` for a parametric family: read `design.family.getParameters({ component }).parts[].brep`
39
54
  *
40
55
  * @examplePrompt Get the 3D solid geometry of the selected wall
56
+ * @examplePrompt Round the top rim of this column and put it back where it is
41
57
  * @examplePrompt How many faces does this mass have?
42
58
  * @examplePrompt Inspect the mesh of this slab so I can look at its faces and edges
43
59
  * @examplePrompt Read the vertices of the selected column's geometry
@@ -48,10 +64,16 @@ export abstract class PluginDesignQueryGeometryApi {
48
64
  * if (brep) {
49
65
  * const faces = await snaptrude.core.geom.query.brep.listFaces(brep)
50
66
  * }
67
+ *
68
+ * // Operate on a scene mass and commit the result where the mass sits:
69
+ * const placed = await snaptrude.design.query.geometry.getBrep(mass, { frame: "world" })
70
+ * const rounded = await snaptrude.core.geom.create.brepFromFillet(placed, edges, 0.2)
71
+ * await snaptrude.design.create.massFromBrep(rounded, "Rounded")
51
72
  * ```
52
73
  */
53
74
  public abstract getBrep(
54
75
  component: ComponentHandle,
76
+ options?: PluginDesignQueryGeometryGetBrepOptions,
55
77
  ): PluginApiReturn<BrepHandle | null>
56
78
 
57
79
  /**
@@ -126,7 +148,8 @@ export abstract class PluginDesignQueryGeometryApi {
126
148
  * Get the **triangulated render meshes** of scene components as plain,
127
149
  * serializable arrays — flat world-space vertex `positions` (`[x0, y0, z0,
128
150
  * x1, …]`) and triangle `indices` (three per triangle), one record per
129
- * requested component.
151
+ * requested component. The result is `{ meshes }`, not a bare array —
152
+ * destructure it before iterating.
130
153
  *
131
154
  * Unlike the other `geometry.*` reads this does NOT mint kernel handles — the
132
155
  * raw arrays cross the boundary directly, ready for export, custom analysis,
@@ -189,8 +212,16 @@ export abstract class PluginDesignQueryGeometryApi {
189
212
  * |---|---|---|
190
213
  * | `component` | {@linkcode ComponentHandle} | The scene component to read |
191
214
  */
215
+ /** Options for {@linkcode PluginDesignQueryGeometryApi.getBrep}: `frame` defaults to `"local"`. */
216
+ export type PluginDesignQueryGeometryGetBrepOptions = { frame?: "local" | "world" }
217
+ export const PluginDesignQueryGeometryGetBrepOptions: z.ZodType<
218
+ PluginDesignQueryGeometryGetBrepOptions,
219
+ PluginDesignQueryGeometryGetBrepOptions
220
+ > = z.object({ frame: z.enum(["local", "world"]).optional() })
221
+
192
222
  export const PluginDesignQueryGeometryGetBrepArgs = z.object({
193
223
  component: ComponentHandle,
224
+ options: PluginDesignQueryGeometryGetBrepOptions.optional(),
194
225
  })
195
226
 
196
227
  export type PluginDesignQueryGeometryGetBrepArgs = z.infer<