@snaptrude/plugin-core 0.9.17 → 0.9.18-networks-20261016.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/AGENTS.md +231 -231
  2. package/CHANGELOG.md +408 -408
  3. package/CLAUDE.md +11 -11
  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 +29 -0
  7. package/dist/api/core/camera/index.d.ts.map +1 -1
  8. package/dist/api/core/geom/create/index.d.ts +7 -1
  9. package/dist/api/core/geom/create/index.d.ts.map +1 -1
  10. package/dist/api/core/geom/update/contour.d.ts +8 -2
  11. package/dist/api/core/geom/update/contour.d.ts.map +1 -1
  12. package/dist/api/core/zoom/index.d.ts +13 -0
  13. package/dist/api/core/zoom/index.d.ts.map +1 -1
  14. package/dist/api/design/bim.d.ts +209 -0
  15. package/dist/api/design/bim.d.ts.map +1 -0
  16. package/dist/api/design/boolean/index.d.ts +2 -0
  17. package/dist/api/design/boolean/index.d.ts.map +1 -1
  18. package/dist/api/design/catalog.d.ts +320 -0
  19. package/dist/api/design/catalog.d.ts.map +1 -0
  20. package/dist/api/design/create/bulk-items.d.ts +65 -25
  21. package/dist/api/design/create/bulk-items.d.ts.map +1 -1
  22. package/dist/api/design/create/index.d.ts +272 -99
  23. package/dist/api/design/create/index.d.ts.map +1 -1
  24. package/dist/api/design/create/opening-fields.d.ts +13 -12
  25. package/dist/api/design/create/opening-fields.d.ts.map +1 -1
  26. package/dist/api/design/delete/index.d.ts +1 -0
  27. package/dist/api/design/delete/index.d.ts.map +1 -1
  28. package/dist/api/design/doors/index.d.ts +18 -24
  29. package/dist/api/design/doors/index.d.ts.map +1 -1
  30. package/dist/api/design/family.d.ts +797 -27
  31. package/dist/api/design/family.d.ts.map +1 -1
  32. package/dist/api/design/index.d.ts +16 -0
  33. package/dist/api/design/index.d.ts.map +1 -1
  34. package/dist/api/design/networks.d.ts +1763 -0
  35. package/dist/api/design/networks.d.ts.map +1 -0
  36. package/dist/api/design/query/index.d.ts +42 -32
  37. package/dist/api/design/query/index.d.ts.map +1 -1
  38. package/dist/api/design/selection/index.d.ts +10 -10
  39. package/dist/api/design/types/index.d.ts +40 -24
  40. package/dist/api/design/types/index.d.ts.map +1 -1
  41. package/dist/api/design/update/index.d.ts +43 -3
  42. package/dist/api/design/update/index.d.ts.map +1 -1
  43. package/dist/api/design/windows/index.d.ts +8 -8
  44. package/dist/api/design/windows/index.d.ts.map +1 -1
  45. package/dist/api/presentation/aiInspiration.d.ts +10 -10
  46. package/dist/api/presentation/annotate.d.ts +38 -22
  47. package/dist/api/presentation/annotate.d.ts.map +1 -1
  48. package/dist/api/presentation/drawingContract.d.ts +32 -0
  49. package/dist/api/presentation/drawingContract.d.ts.map +1 -0
  50. package/dist/api/presentation/drawings.d.ts +3523 -0
  51. package/dist/api/presentation/drawings.d.ts.map +1 -0
  52. package/dist/api/presentation/export.d.ts +23 -5
  53. package/dist/api/presentation/export.d.ts.map +1 -1
  54. package/dist/api/presentation/index.d.ts +32 -12
  55. package/dist/api/presentation/index.d.ts.map +1 -1
  56. package/dist/api/presentation/placedViews.d.ts +106 -6
  57. package/dist/api/presentation/placedViews.d.ts.map +1 -1
  58. package/dist/api/presentation/shapes.d.ts +2 -2
  59. package/dist/api/presentation/sheets.d.ts +175 -19
  60. package/dist/api/presentation/sheets.d.ts.map +1 -1
  61. package/dist/api/presentation/viewPresets.d.ts +222 -0
  62. package/dist/api/presentation/viewPresets.d.ts.map +1 -0
  63. package/dist/api/presentation/views.d.ts +103 -5
  64. package/dist/api/presentation/views.d.ts.map +1 -1
  65. package/dist/api/presentation/walkthroughs.d.ts +6 -6
  66. package/dist/errors/codes.d.ts +3 -1
  67. package/dist/errors/codes.d.ts.map +1 -1
  68. package/dist/index.cjs +3732 -2383
  69. package/dist/index.cjs.map +1 -1
  70. package/dist/index.js +3607 -2383
  71. package/dist/index.js.map +1 -1
  72. package/package.json +42 -42
  73. package/scripts/check-dist-fresh.mjs +22 -22
  74. package/scripts/generate-manifest.mjs +243 -238
  75. package/scripts/generate-manifest.test.mjs +303 -303
  76. package/src/api/analysis/daylight.ts +470 -470
  77. package/src/api/analysis/heatmaps.ts +683 -683
  78. package/src/api/analysis/illuminance.ts +155 -155
  79. package/src/api/analysis/index.ts +61 -61
  80. package/src/api/analysis/shadows.ts +183 -183
  81. package/src/api/analysis/solar.ts +237 -237
  82. package/src/api/analysis/sunlightHours.ts +211 -211
  83. package/src/api/analysis/sunpath.ts +83 -83
  84. package/src/api/analysis/weather.ts +179 -179
  85. package/src/api/core/buildings.ts +485 -485
  86. package/src/api/core/camera/index.ts +696 -667
  87. package/src/api/core/comment/index.ts +376 -376
  88. package/src/api/core/geom/create/index.ts +2139 -2133
  89. package/src/api/core/geom/delete/index.ts +84 -84
  90. package/src/api/core/geom/index.ts +35 -35
  91. package/src/api/core/geom/query/arc.ts +169 -169
  92. package/src/api/core/geom/query/brep.ts +621 -621
  93. package/src/api/core/geom/query/circle.ts +404 -404
  94. package/src/api/core/geom/query/contour.ts +540 -540
  95. package/src/api/core/geom/query/curve.ts +1102 -1102
  96. package/src/api/core/geom/query/edge.ts +130 -130
  97. package/src/api/core/geom/query/face.ts +508 -508
  98. package/src/api/core/geom/query/halfedge.ts +210 -210
  99. package/src/api/core/geom/query/index.ts +63 -63
  100. package/src/api/core/geom/query/profile.ts +473 -473
  101. package/src/api/core/geom/query/vertex.ts +205 -205
  102. package/src/api/core/geom/update/contour.ts +323 -317
  103. package/src/api/core/geom/update/curve.ts +209 -209
  104. package/src/api/core/geom/update/index.ts +29 -29
  105. package/src/api/core/geom/update/profile.ts +379 -379
  106. package/src/api/core/groups.ts +521 -521
  107. package/src/api/core/handles/index.ts +234 -234
  108. package/src/api/core/history/index.ts +84 -84
  109. package/src/api/core/index.ts +99 -99
  110. package/src/api/core/io/export/index.ts +126 -126
  111. package/src/api/core/io/import/index.ts +504 -504
  112. package/src/api/core/io/index.ts +42 -42
  113. package/src/api/core/io/job/index.ts +140 -140
  114. package/src/api/core/io/query/index.ts +71 -71
  115. package/src/api/core/io/terrain/index.ts +360 -360
  116. package/src/api/core/io/underlay/index.ts +705 -705
  117. package/src/api/core/layers.ts +540 -540
  118. package/src/api/core/math/index.ts +20 -20
  119. package/src/api/core/math/quat.ts +172 -172
  120. package/src/api/core/math/vec3.ts +179 -179
  121. package/src/api/core/mode/index.ts +108 -108
  122. package/src/api/core/project/index.ts +479 -479
  123. package/src/api/core/proposals/index.ts +570 -570
  124. package/src/api/core/storeys/index.ts +308 -308
  125. package/src/api/core/storeys/layers.ts +339 -339
  126. package/src/api/core/tags.ts +911 -911
  127. package/src/api/core/units/index.ts +164 -164
  128. package/src/api/core/user.ts +46 -46
  129. package/src/api/core/zoom/index.ts +64 -51
  130. package/src/api/design/bim.ts +121 -0
  131. package/src/api/design/boolean/index.ts +181 -179
  132. package/src/api/design/catalog.ts +203 -0
  133. package/src/api/design/create/bulk-items.ts +294 -254
  134. package/src/api/design/create/grids.ts +137 -137
  135. package/src/api/design/create/index.ts +2148 -1980
  136. package/src/api/design/create/opening-fields.ts +40 -37
  137. package/src/api/design/delete/index.ts +75 -74
  138. package/src/api/design/dimensions.ts +453 -453
  139. package/src/api/design/doors/index.ts +516 -500
  140. package/src/api/design/edit/index.ts +54 -54
  141. package/src/api/design/erase/index.ts +151 -151
  142. package/src/api/design/family.ts +1334 -704
  143. package/src/api/design/furniture/index.ts +319 -319
  144. package/src/api/design/grids.ts +237 -237
  145. package/src/api/design/index.ts +304 -274
  146. package/src/api/design/lock.ts +69 -69
  147. package/src/api/design/materials/index.ts +608 -608
  148. package/src/api/design/networks.ts +667 -0
  149. package/src/api/design/query/geometry/index.ts +319 -319
  150. package/src/api/design/query/grids.ts +197 -197
  151. package/src/api/design/query/index.ts +1236 -1226
  152. package/src/api/design/query/referenceLines.ts +52 -52
  153. package/src/api/design/query/spaces.ts +275 -275
  154. package/src/api/design/selection/index.ts +273 -273
  155. package/src/api/design/shared.ts +40 -40
  156. package/src/api/design/transform/index.ts +254 -254
  157. package/src/api/design/types/index.ts +163 -156
  158. package/src/api/design/update/index.ts +1024 -983
  159. package/src/api/design/visibility.ts +143 -143
  160. package/src/api/design/windows/index.ts +351 -333
  161. package/src/api/entity/buildableEnvelope.ts +322 -322
  162. package/src/api/entity/index.ts +30 -30
  163. package/src/api/entity/referenceLine.ts +235 -235
  164. package/src/api/entity/space.ts +612 -612
  165. package/src/api/entity/story.ts +517 -517
  166. package/src/api/index.ts +47 -47
  167. package/src/api/presentation/aiInspiration.ts +1116 -1116
  168. package/src/api/presentation/annotate.ts +417 -385
  169. package/src/api/presentation/camera.ts +289 -289
  170. package/src/api/presentation/diagrams.ts +194 -194
  171. package/src/api/presentation/drawingContract.ts +70 -0
  172. package/src/api/presentation/drawings.ts +1179 -0
  173. package/src/api/presentation/export.ts +134 -108
  174. package/src/api/presentation/import.ts +159 -159
  175. package/src/api/presentation/index.ts +160 -135
  176. package/src/api/presentation/placedViews.ts +1211 -1120
  177. package/src/api/presentation/sections.ts +420 -420
  178. package/src/api/presentation/shapes.ts +274 -274
  179. package/src/api/presentation/sheets.ts +746 -585
  180. package/src/api/presentation/slideshow.ts +134 -134
  181. package/src/api/presentation/tables.ts +84 -84
  182. package/src/api/presentation/viewPresets.ts +241 -0
  183. package/src/api/presentation/views.ts +713 -673
  184. package/src/api/presentation/walkthroughs.ts +1201 -1201
  185. package/src/api/program/adjacency.ts +164 -164
  186. package/src/api/program/areas.ts +408 -408
  187. package/src/api/program/classification.ts +138 -138
  188. package/src/api/program/cores.ts +83 -83
  189. package/src/api/program/departments.ts +517 -517
  190. package/src/api/program/index.ts +73 -73
  191. package/src/api/program/labels.ts +332 -332
  192. package/src/api/program/layout.ts +422 -422
  193. package/src/api/program/metadata.ts +364 -364
  194. package/src/api/program/metrics.ts +120 -120
  195. package/src/api/program/site.ts +748 -748
  196. package/src/api/program/spreadsheet.ts +2698 -2698
  197. package/src/api/workspace/index.ts +612 -612
  198. package/src/errors/codes.ts +143 -136
  199. package/src/errors/envelope.ts +75 -75
  200. package/src/errors/index.ts +21 -21
  201. package/src/errors/plugin-error.ts +134 -134
  202. package/src/handles.ts +297 -297
  203. package/src/host-utils.ts +67 -67
  204. package/src/index.ts +7 -7
  205. package/src/massParameters.ops.ts +246 -246
  206. package/src/massParameters.ts +427 -427
  207. package/src/types.ts +16 -16
  208. package/test/drawingContract.test.mjs +288 -0
  209. package/test/errors.test.mjs +185 -184
  210. package/test/massParameters.test.mjs +952 -952
  211. package/tsconfig.json +22 -22
  212. package/tsup.config.ts +12 -12
  213. package/api-manifest.json +0 -9711
@@ -0,0 +1,667 @@
1
+ import * as z from "zod"
2
+ import type { PluginApiReturn } from "../../types"
3
+ import { Vec3Handle } from "../../handles"
4
+
5
+ const id = z.string().regex(/^[A-Za-z][A-Za-z0-9_-]{0,79}$/)
6
+ const vector = z.tuple([
7
+ z.number().finite(),
8
+ z.number().finite(),
9
+ z.number().finite(),
10
+ ])
11
+ const positive = z.number().finite().positive()
12
+ const endpoint = z.object({ node: id, port: id }).strict()
13
+ const relationships = z
14
+ .array(
15
+ z
16
+ .object({
17
+ node: id,
18
+ relation: z.enum(["mountedOn", "within", "serves", "penetrates"]),
19
+ target: z
20
+ .object({
21
+ kind: z.enum(["component", "topology"]),
22
+ id: z.string().min(1).max(200),
23
+ })
24
+ .strict(),
25
+ })
26
+ .strict(),
27
+ )
28
+ .max(4000)
29
+
30
+ /** Versioned metadata stored in a pinned FamilySpecBundle.network. Expressions use the
31
+ * family's parameter scope and SU, just like its geometry. No world-space port overrides.
32
+ * Pipe recipes run from local [0,0,0] to [length,0,0] along +X.
33
+ */
34
+ export const NetworkFamilyContract = z
35
+ .object({
36
+ schemaVersion: z.literal(1),
37
+ role: z.enum([
38
+ "pipe",
39
+ "duct",
40
+ "wire",
41
+ "cable",
42
+ "conduit",
43
+ "cableDuct",
44
+ "cableTray",
45
+ "connector",
46
+ "equipment",
47
+ ]),
48
+ lengthParameter: z.string().min(1).optional(),
49
+ ports: z
50
+ .array(
51
+ z
52
+ .object({
53
+ id,
54
+ position: z.tuple([z.string(), z.string(), z.string()]),
55
+ direction: z.tuple([z.string(), z.string(), z.string()]),
56
+ size: z.string(),
57
+ height: z.string().optional(),
58
+ /** Unit width axis in family coordinates, perpendicular to direction.
59
+ * Required with height for mechanical rectangular ports; prevents guessed roll.
60
+ */
61
+ widthDirection: z
62
+ .tuple([z.string(), z.string(), z.string()])
63
+ .optional(),
64
+ systems: z.array(z.string().min(1)).min(1),
65
+ })
66
+ .strict(),
67
+ )
68
+ .min(1)
69
+ .max(64),
70
+ })
71
+ .strict()
72
+ export type NetworkFamilyContract = z.infer<typeof NetworkFamilyContract>
73
+
74
+ const familyPin = z
75
+ .object({
76
+ definitionId: z.string().min(1).max(200),
77
+ version: z.number().int().positive(),
78
+ values: z.record(
79
+ z.string(),
80
+ z.union([z.number().finite(), z.string(), z.boolean()]),
81
+ ),
82
+ })
83
+ .strict()
84
+
85
+ /** Authored topology: vertices are pinned connector/equipment families; each edge is one
86
+ * line and one pinned straight-pipe family. Curved runs are not yet supported by this
87
+ * contract; bends require explicit connector vertices. World mm, Y up, rotations in degrees.
88
+ * Plumbing v2 accepts round ports only. Rectangular ports need a section-roll contract.
89
+ * The resolved component graph is retained for existing persistence, replay and queries.
90
+ */
91
+ export const NetworkTopologyDocument = z
92
+ .object({
93
+ schemaVersion: z.literal(2),
94
+ domain: z.literal("plumbing"),
95
+ label: z.string().min(1).max(120),
96
+ architectureRevision: z.string().min(1).max(80),
97
+ relationships,
98
+ vertices: z
99
+ .array(
100
+ z
101
+ .object({
102
+ id,
103
+ kind: z.enum([
104
+ "fixture",
105
+ "equipment",
106
+ "tank",
107
+ "pump",
108
+ "valve",
109
+ "manifold",
110
+ "fitting",
111
+ ]),
112
+ storey: z.number().int(),
113
+ family: familyPin,
114
+ positionMm: vector,
115
+ rotationDeg: vector,
116
+ systems: z.record(z.string(), z.string().min(1)),
117
+ })
118
+ .strict(),
119
+ )
120
+ .min(1)
121
+ .max(1000),
122
+ edges: z
123
+ .array(
124
+ z
125
+ .object({
126
+ id,
127
+ kind: z.enum(["pipe", "riser"]),
128
+ storey: z.number().int(),
129
+ family: familyPin,
130
+ from: endpoint,
131
+ to: endpoint,
132
+ system: z.string().min(1),
133
+ /** One line between evaluated connector ports, never a corner-bearing polyline. */
134
+ curve: z.object({ kind: z.literal("line") }).strict(),
135
+ })
136
+ .strict(),
137
+ )
138
+ .max(1000),
139
+ })
140
+ .strict()
141
+ export type NetworkTopologyDocument = z.infer<typeof NetworkTopologyDocument>
142
+
143
+ export const MechanicalAirSystem = z.enum([
144
+ "supplyAir",
145
+ "returnAir",
146
+ "exhaustAir",
147
+ "outsideAir",
148
+ "reliefAir",
149
+ ])
150
+ const mechanicalVertexKind = z.enum([
151
+ "equipment",
152
+ "terminal",
153
+ "damper",
154
+ "fitting",
155
+ ])
156
+ const mechanicalEdgeKind = z.enum(["duct", "riser"])
157
+
158
+ /** Air-side HVAC intent. Reuses the graph transport, not plumbing domain semantics.
159
+ * Every vertex and edge pins an existing family. Edges run from upstream to downstream.
160
+ * Rectangular duct ports use local +Y as their width axis. The host aligns this axis to
161
+ * the upstream connector's widthDirection and rejects incompatible downstream roll.
162
+ * Bends, branches and transitions are explicit fitting vertices. No flexible curves,
163
+ * hydronic/refrigerant design, airflow sizing or code-compliance inference is provided.
164
+ */
165
+ export const MechanicalTopologyDocument = NetworkTopologyDocument.extend({
166
+ domain: z.literal("mechanical"),
167
+ vertices: z
168
+ .array(
169
+ NetworkTopologyDocument.shape.vertices.element.extend({
170
+ kind: mechanicalVertexKind,
171
+ systems: z.record(z.string(), MechanicalAirSystem),
172
+ }),
173
+ )
174
+ .min(1)
175
+ .max(1000),
176
+ edges: z
177
+ .array(
178
+ NetworkTopologyDocument.shape.edges.element.extend({
179
+ kind: mechanicalEdgeKind,
180
+ system: MechanicalAirSystem,
181
+ }),
182
+ )
183
+ .max(1000),
184
+ })
185
+ export type MechanicalTopologyDocument = z.infer<
186
+ typeof MechanicalTopologyDocument
187
+ >
188
+
189
+ const electricalVertexKind = z.enum([
190
+ "transformer",
191
+ "mainBoard",
192
+ "distributionBoard",
193
+ "mcbBox",
194
+ "junctionBox",
195
+ "switch",
196
+ "outlet",
197
+ "light",
198
+ "load",
199
+ "equipment",
200
+ "fitting",
201
+ ])
202
+ const electricalEdgeKind = z.enum([
203
+ "wire",
204
+ "cable",
205
+ "conduit",
206
+ "cableDuct",
207
+ "cableTray",
208
+ ])
209
+ const electricalCircuits = z
210
+ .array(
211
+ z
212
+ .object({
213
+ id,
214
+ source: id,
215
+ loads: z.array(id).min(1).max(1000),
216
+ routes: z.array(id).max(1000),
217
+ voltage: positive,
218
+ phases: z.number().int().min(1).max(3),
219
+ ratedAmps: positive,
220
+ })
221
+ .strict(),
222
+ )
223
+ .max(1000)
224
+
225
+ /** Physical electrical topology, independent of logical circuit membership.
226
+ * Each straight edge pins a family whose role matches its kind. Bends and branches
227
+ * require connector vertices. Junction boxes are equipment with explicit named ports.
228
+ * Rectangular containment uses the same section frame as mechanical ducts.
229
+ * Systems are authored labels which must be supported by the pinned ports.
230
+ * Direction is explicit, never inferred from circuits or spatial contact.
231
+ */
232
+ export const ElectricalTopologyDocument = NetworkTopologyDocument.extend({
233
+ domain: z.literal("electrical"),
234
+ vertices: z
235
+ .array(
236
+ NetworkTopologyDocument.shape.vertices.element.extend({
237
+ kind: electricalVertexKind,
238
+ }),
239
+ )
240
+ .min(1)
241
+ .max(1000),
242
+ edges: z
243
+ .array(
244
+ NetworkTopologyDocument.shape.edges.element.extend({
245
+ kind: electricalEdgeKind,
246
+ directed: z.boolean(),
247
+ }),
248
+ )
249
+ .max(1000),
250
+ circuits: electricalCircuits,
251
+ })
252
+ export type ElectricalTopologyDocument = z.infer<
253
+ typeof ElectricalTopologyDocument
254
+ >
255
+
256
+ export const AuthoredNetworkTopologyDocument = z.discriminatedUnion("domain", [
257
+ NetworkTopologyDocument,
258
+ MechanicalTopologyDocument,
259
+ ElectricalTopologyDocument,
260
+ ])
261
+ export type AuthoredNetworkTopologyDocument = z.infer<
262
+ typeof AuthoredNetworkTopologyDocument
263
+ >
264
+
265
+ /** Authored world-space connector. This is not a claim of certified family connector metadata. */
266
+ export const NetworkPort = z
267
+ .object({
268
+ id,
269
+ positionMm: vector,
270
+ direction: vector,
271
+ system: z.string().min(1).max(80),
272
+ /** Diameter for round ports; width when heightMm is supplied (rectangular port). */
273
+ sizeMm: positive,
274
+ heightMm: positive.optional(),
275
+ /** World-space unit width axis. Mechanical rectangular ports require this section frame. */
276
+ widthDirection: vector.optional(),
277
+ })
278
+ .strict()
279
+
280
+ /** The agent supplies every route and section. The host does not size or route a network. */
281
+ export const NetworkGeometry = z.discriminatedUnion("kind", [
282
+ z
283
+ .object({
284
+ kind: z.literal("family"),
285
+ definitionId: z.string().min(1).max(200),
286
+ version: z.number().int().positive(),
287
+ values: z.record(
288
+ z.string(),
289
+ z.union([z.number().finite(), z.string(), z.boolean()]),
290
+ ),
291
+ positionMm: vector,
292
+ rotationDeg: vector,
293
+ ports: z.array(NetworkPort).max(64),
294
+ })
295
+ .strict(),
296
+ z
297
+ .object({
298
+ kind: z.literal("route"),
299
+ pathMm: z.array(vector).min(2).max(256),
300
+ system: z.string().min(1).max(80),
301
+ section: z.discriminatedUnion("kind", [
302
+ z
303
+ .object({
304
+ kind: z.literal("round"),
305
+ diameterMm: positive,
306
+ wallMm: z.number().finite().nonnegative(),
307
+ })
308
+ .strict(),
309
+ z
310
+ .object({
311
+ kind: z.literal("rectangular"),
312
+ widthMm: positive,
313
+ heightMm: positive,
314
+ wallMm: z.number().finite().nonnegative(),
315
+ })
316
+ .strict(),
317
+ ]),
318
+ })
319
+ .strict(),
320
+ ])
321
+
322
+ // Storey is scene membership, not a coordinate offset. A route can span multiple elevations.
323
+ const node = z
324
+ .object({ id, storey: z.number().int(), geometry: NetworkGeometry })
325
+ .strict()
326
+ const electricalNode = node.extend({
327
+ kind: z.enum([
328
+ ...electricalVertexKind.options,
329
+ ...electricalEdgeKind.options,
330
+ ]),
331
+ })
332
+ const plumbingNode = node.extend({
333
+ kind: z.enum([
334
+ "fixture",
335
+ "equipment",
336
+ "tank",
337
+ "pump",
338
+ "valve",
339
+ "manifold",
340
+ "fitting",
341
+ "pipe",
342
+ "riser",
343
+ ]),
344
+ })
345
+ const document = z
346
+ .object({
347
+ schemaVersion: z.literal(1),
348
+ label: z.string().min(1).max(120),
349
+ /** Revision from context(); invalidated when the source architectural graph changes. */
350
+ architectureRevision: z.string().min(1).max(80),
351
+ connections: z
352
+ .array(
353
+ z
354
+ .object({ id, from: endpoint, to: endpoint, directed: z.boolean() })
355
+ .strict(),
356
+ )
357
+ .max(4000),
358
+ relationships,
359
+ })
360
+ .strict()
361
+
362
+ /** Electrical connectivity and circuit membership are distinct from physical route occupancy. */
363
+ export const ElectricalGraphDocument = document.extend({
364
+ domain: z.literal("electrical"),
365
+ nodes: z.array(electricalNode).min(1).max(1000),
366
+ circuits: electricalCircuits,
367
+ /** Required for new authoring. Omitted only on saved legacy snapshots. */
368
+ authored: ElectricalTopologyDocument.optional(),
369
+ })
370
+
371
+ /** Plumbing uses explicit directed port connections; touching geometry never creates a connection. */
372
+ export const PlumbingGraphDocument = document.extend({
373
+ domain: z.literal("plumbing"),
374
+ nodes: z.array(plumbingNode).min(1).max(1000),
375
+ /** Original v2 intent. Nodes/connections are the resolved snapshot for replay.
376
+ * Kept inside the document so existing save services preserve it without envelope changes.
377
+ * Legacy v1 saves omit this field; new plumbing submissions require v2.
378
+ */
379
+ authored: NetworkTopologyDocument.optional(),
380
+ })
381
+ /** Resolved HVAC snapshot for queries, commands and load. New submissions require v2 intent. */
382
+ export const MechanicalGraphDocument = document.extend({
383
+ domain: z.literal("mechanical"),
384
+ nodes: z
385
+ .array(
386
+ node.extend({
387
+ kind: z.enum([
388
+ ...mechanicalVertexKind.options,
389
+ ...mechanicalEdgeKind.options,
390
+ ]),
391
+ geometry: NetworkGeometry.options[0],
392
+ }),
393
+ )
394
+ .min(1)
395
+ .max(1000),
396
+ authored: MechanicalTopologyDocument,
397
+ })
398
+ export const NetworkGraphDocument = z.discriminatedUnion("domain", [
399
+ ElectricalGraphDocument,
400
+ PlumbingGraphDocument,
401
+ MechanicalGraphDocument,
402
+ ])
403
+ export type NetworkGraphDocument = z.infer<typeof NetworkGraphDocument>
404
+ export type NetworkNode = NetworkGraphDocument["nodes"][number]
405
+ export type NetworkPort = z.infer<typeof NetworkPort>
406
+ export type NetworkDomain = NetworkGraphDocument["domain"]
407
+
408
+ /** JSON is an authored document, not a transport for live geometry handles. Maximum 2 MB characters. */
409
+ export const NetworkValidateArgs = z
410
+ .object({ graphJson: z.string().max(2_000_000) })
411
+ .strict()
412
+ export const NetworkCreateArgs = NetworkValidateArgs.extend({
413
+ requestId: z.string().uuid(),
414
+ })
415
+ export const NetworkGetArgs = z.object({ graphId: z.string().uuid() }).strict()
416
+ /** Displacement components are world millimetres, Y up, not Snaptrude units. */
417
+ export const NetworkMoveEdgeArgs = NetworkGetArgs.extend({
418
+ expectedRevision: z.number().int().positive(),
419
+ edgeId: id,
420
+ displacement: Vec3Handle,
421
+ requestId: z.string().uuid(),
422
+ }).strict()
423
+ export type NetworkMoveEdgeArgs = z.input<typeof NetworkMoveEdgeArgs>
424
+
425
+ export const NetworkMoveEdgeResult = z.object({
426
+ graphId: z.string().uuid(),
427
+ revision: z.number().int().positive(),
428
+ affectedComponentIds: z.array(z.string().min(1)),
429
+ alreadyApplied: z.boolean(),
430
+ /** Queue acceptance does not acknowledge durable storage. */
431
+ saveStatus: z.enum(["queued", "existing"]),
432
+ }).strict()
433
+ export type NetworkMoveEdgeResult = z.infer<typeof NetworkMoveEdgeResult>
434
+
435
+ /** Immutable receipts survive undo. Retrying a move never executes it again. */
436
+ export const NetworkEditReceipt = z.object({
437
+ requestId: z.string().uuid(),
438
+ fingerprint: z.string().max(1000),
439
+ revision: z.number().int().positive(),
440
+ affectedComponentIds: z.array(z.string().min(1)).max(1000),
441
+ }).strict()
442
+ export type NetworkEditReceipt = z.infer<typeof NetworkEditReceipt>
443
+
444
+ /** Each intentional apply/undo/redo has its own identity. Retries reuse it. */
445
+ export const NetworkTransition = z.object({
446
+ id: z.string().uuid(),
447
+ expectedRevision: z.number().int().positive(),
448
+ forward: z.boolean(),
449
+ }).strict()
450
+ export type NetworkTransition = z.infer<typeof NetworkTransition>
451
+
452
+ export const NetworkTransitionReceipt = NetworkTransition.extend({
453
+ requestId: z.string().uuid(),
454
+ revision: z.number().int().positive(),
455
+ }).strict()
456
+ export type NetworkTransitionReceipt = z.infer<typeof NetworkTransitionReceipt>
457
+ export const NetworkContextArgs = z
458
+ .object({
459
+ offset: z.number().int().nonnegative().default(0),
460
+ limit: z.number().int().min(1).max(100).default(30),
461
+ /** Omit the legacy PG2 dump when using the routing view, to avoid duplicate geometry tokens. */
462
+ includeTopology: z.boolean().default(true),
463
+ })
464
+ .strict()
465
+ export const NetworkQueryArgs = NetworkGetArgs.extend({
466
+ revision: z.number().int().positive(),
467
+ queryJson: z.string().max(16000),
468
+ })
469
+ export const NetworkQuery = z
470
+ .object({
471
+ kind: z.enum([
472
+ "nodes",
473
+ "connected",
474
+ "upstream",
475
+ "downstream",
476
+ "ports",
477
+ "nearestPort",
478
+ "circuits",
479
+ "relationships",
480
+ ]),
481
+ node: id.optional(),
482
+ pointMm: vector.optional(),
483
+ radiusMm: positive.optional(),
484
+ /** Filter nodes, ports and traversal links by system. Traversal always includes its start node. */
485
+ system: z.string().optional(),
486
+ sizeMm: positive.optional(),
487
+ heightMm: positive.optional(),
488
+ offset: z.number().int().nonnegative().default(0),
489
+ limit: z.number().int().min(1).max(100).default(30),
490
+ })
491
+ .strict()
492
+ export type NetworkQuery = z.infer<typeof NetworkQuery>
493
+
494
+ export interface NetworkDiagnostic {
495
+ path: string
496
+ message: string
497
+ }
498
+ export interface NetworkValidation {
499
+ ok: boolean
500
+ diagnostics: NetworkDiagnostic[]
501
+ nodeCount: number
502
+ }
503
+ /** Durable node-to-scene mapping. New family-backed nodes retain the original authorized pin;
504
+ * parameters and placement belong to the component, not a generated library family.
505
+ * Existing wrapper pins and v1 raw-route recipe pins remain readable, not newly authored.
506
+ */
507
+ export const NetworkComponentBinding = z
508
+ .object({
509
+ nodeId: id,
510
+ componentId: z.string().min(1),
511
+ definitionId: z.string().min(1),
512
+ definitionVersion: z.number().int().positive(),
513
+ })
514
+ .strict()
515
+ export type NetworkComponentBinding = z.infer<typeof NetworkComponentBinding>
516
+ export interface SavedNetworkGraph {
517
+ id: string
518
+ domain: NetworkDomain
519
+ revision: number
520
+ requestId: string
521
+ document: NetworkGraphDocument
522
+ components: NetworkComponentBinding[]
523
+ edits?: NetworkEditReceipt[]
524
+ transitions?: NetworkTransitionReceipt[]
525
+ }
526
+ export interface NetworkCreateResult {
527
+ graphId: string
528
+ revision: number
529
+ components: NetworkComponentBinding[]
530
+ alreadyCreated: boolean
531
+ /** Queue acceptance is not a durable database acknowledgement. */
532
+ saveStatus: "queued" | "existing"
533
+ }
534
+
535
+ /** Exact TopoGraph routing evidence, not a route planner or inferred enclosure.
536
+ * World mm, Y-up. A curve's axis is a unit direction, not a length.
537
+ * Shared region IDs mean a shared boundary, not permission to penetrate a wall.
538
+ * Regions preserve directed outer/hole loops. No vertical connectivity is inferred;
539
+ * use their curves and storey elevations to plan risers and explicit penetrations.
540
+ */
541
+ export interface NetworkRoutingContext {
542
+ units: "mm"
543
+ edges: {
544
+ id: string
545
+ curve:
546
+ | { type: "line"; start: number[]; end: number[] }
547
+ | {
548
+ type: "arc"
549
+ start: number[]
550
+ end: number[]
551
+ centre: number[]
552
+ axis: number[]
553
+ }
554
+ wallId: string | null
555
+ regionIds: string[]
556
+ }[]
557
+ regions: {
558
+ id: string
559
+ /** Stable source component IDs; read label/storey from context.components. */
560
+ ownerIds: string[]
561
+ boundary: {
562
+ outer: { edgeId: string; direction: "forward" | "reverse" }[]
563
+ holes: { edgeId: string; direction: "forward" | "reverse" }[][]
564
+ }
565
+ floorId: string | null
566
+ slabId: string | null
567
+ }[]
568
+ /** Hosting is exact; bounds are only coarse avoidance data, not a penetration clearance check. */
569
+ openings: {
570
+ id: string
571
+ wallId: string
572
+ type: string
573
+ minMm: number[]
574
+ maxMm: number[]
575
+ }[]
576
+ }
577
+ export interface NetworkContextResult {
578
+ proposalId: string
579
+ buildingId: string | null
580
+ revision: string
581
+ /** PG2's native serialization uses Snaptrude units (1 SU = 254 mm). */
582
+ topologyUnits: "SU"
583
+ /** JSON array; empty when includeTopology is false. Prefer routing for network planning. */
584
+ topologyJson: string
585
+ /** Each collection uses offset/limit independently. Follow nextOffset before resolving references.
586
+ * Empty regions mean topology is unavailable, not that bounds can substitute for room boundaries.
587
+ */
588
+ routing: NetworkRoutingContext
589
+ nextOffset: number | null
590
+ total: number
591
+ storeys: { number: number; elevationMm: number }[]
592
+ components: {
593
+ id: string
594
+ type: string
595
+ label: string
596
+ storey: number
597
+ minMm: number[]
598
+ maxMm: number[]
599
+ }[]
600
+ /** Saved graph inventory, paged with topology/components. Use these IDs after reopening a project. */
601
+ networks: {
602
+ graphId: string
603
+ domain: NetworkDomain
604
+ revision: number
605
+ label: string
606
+ componentCount: number
607
+ }[]
608
+ }
609
+ export interface NetworkQueryResult {
610
+ revision: number
611
+ resultJson: string
612
+ nextOffset: number | null
613
+ total: number
614
+ }
615
+
616
+ /** Shared transport only. Each domain retains a separate schema and semantics. */
617
+ export abstract class PluginDomainNetworkApi {
618
+ /** Read the active proposal's architectural context before authoring. Keep one revision across pages. */
619
+ abstract context(
620
+ args?: z.input<typeof NetworkContextArgs>,
621
+ ): PluginApiReturn<NetworkContextResult>
622
+ /** Validate authored JSON, pinned families and recipe evaluation without placing or saving anything.
623
+ * This does not build kernel meshes or certify engineering/code compliance.
624
+ */
625
+ abstract validate(
626
+ args: z.input<typeof NetworkValidateArgs>,
627
+ ): PluginApiReturn<NetworkValidation>
628
+ /** Construct each node on its authored storey, with one undo command and queued project persistence.
629
+ * Networks can span storeys. Reuse requestId on retry; components returns the durable node mapping.
630
+ * Coordinates are world millimetres, Y-up; rotations are degrees. No engineering rules are inferred.
631
+ * Throws for stale architecture, invalid references/geometry or conflicting retries.
632
+ */
633
+ abstract create(
634
+ args: z.input<typeof NetworkCreateArgs>,
635
+ ): PluginApiReturn<NetworkCreateResult>
636
+ /** Read one saved graph in the active proposal; null on miss. IDs are durable, not worker handles. */
637
+ abstract get(
638
+ args: z.input<typeof NetworkGetArgs>,
639
+ ): PluginApiReturn<SavedNetworkGraph | null>
640
+ /** Read bounded authored topology in graph revision. Crossings do not connect. No clash/compliance claims. */
641
+ abstract query(
642
+ args: z.input<typeof NetworkQueryArgs>,
643
+ ): PluginApiReturn<NetworkQueryResult>
644
+ }
645
+
646
+ /** Electrical equipment, physical routes and logical circuits, constructed from agent-authored intent. */
647
+ export abstract class PluginElectricalApi extends PluginDomainNetworkApi {}
648
+ /** Plumbing equipment and routed pipe networks, constructed from agent-authored intent. */
649
+ export abstract class PluginPlumbingApi extends PluginDomainNetworkApi {
650
+ /** Offset one straight pipe perpendicular to its axis between two rigid 90-degree elbows.
651
+ * Resize adjacent pipes through instance length parameters. Out-of-plane offsets add
652
+ * one pinned elbow and straight pipe per affected end, preserving fixed outer ports
653
+ * and existing component identities. Rigid fittings only rotate and translate.
654
+ * Reject axial sliding, moving tees, constrained fittings,
655
+ * scene drift, stale revisions, and infeasible family geometry before mutation.
656
+ * @param args Graph/edge identity, current revision, displacement handle in world mm,
657
+ * and a unique retry identity. Identical retries return the original receipt after undo too.
658
+ * @returns Affected component IDs and revision in one undoable, queued transaction.
659
+ * @example
660
+ * const displacement = await snaptrude.core.math.vec3.new(0, 300, 0)
661
+ * await snaptrude.design.plumbing.moveEdge({ graphId, edgeId, expectedRevision,
662
+ * displacement, requestId: crypto.randomUUID() })
663
+ */
664
+ abstract moveEdge(args: NetworkMoveEdgeArgs): PluginApiReturn<NetworkMoveEdgeResult>
665
+ }
666
+ /** Family-backed air-side HVAC networks. The agent authors engineering intent; the host constructs it. */
667
+ export abstract class PluginMechanicalApi extends PluginDomainNetworkApi {}