@snaptrude/plugin-core 0.9.13 → 0.9.14-networks-20261001.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.
- package/CHANGELOG.md +39 -0
- package/dist/api/core/geom/create/index.d.ts +935 -100
- package/dist/api/core/geom/create/index.d.ts.map +1 -1
- package/dist/api/core/geom/query/brep.d.ts +59 -2
- package/dist/api/core/geom/query/brep.d.ts.map +1 -1
- package/dist/api/core/geom/query/curve.d.ts +59 -35
- package/dist/api/core/geom/query/curve.d.ts.map +1 -1
- package/dist/api/core/geom/query/edge.d.ts +2 -2
- package/dist/api/core/geom/query/face.d.ts +105 -0
- package/dist/api/core/geom/query/face.d.ts.map +1 -1
- package/dist/api/core/geom/update/contour.d.ts +2 -0
- package/dist/api/core/geom/update/contour.d.ts.map +1 -1
- package/dist/api/core/geom/update/curve.d.ts +5 -5
- package/dist/api/core/geom/update/profile.d.ts +3 -1
- package/dist/api/core/geom/update/profile.d.ts.map +1 -1
- package/dist/api/core/handles/index.d.ts +4 -2
- package/dist/api/core/handles/index.d.ts.map +1 -1
- package/dist/api/design/create/grids.d.ts +1 -1
- package/dist/api/design/family.d.ts +145 -10
- package/dist/api/design/family.d.ts.map +1 -1
- package/dist/api/design/index.d.ts +6 -0
- package/dist/api/design/index.d.ts.map +1 -1
- package/dist/api/design/networks.d.ts +530 -0
- package/dist/api/design/networks.d.ts.map +1 -0
- package/dist/api/design/query/geometry/index.d.ts +33 -5
- package/dist/api/design/query/geometry/index.d.ts.map +1 -1
- package/dist/api/design/query/grids.d.ts +3 -3
- package/dist/api/design/query/index.d.ts +47 -0
- package/dist/api/design/query/index.d.ts.map +1 -1
- package/dist/api/design/query/spaces.d.ts +5 -5
- package/dist/api/design/update/index.d.ts +99 -0
- package/dist/api/design/update/index.d.ts.map +1 -1
- package/dist/api/entity/referenceLine.d.ts +1 -1
- package/dist/api/entity/space.d.ts +4 -4
- package/dist/api/entity/story.d.ts +4 -4
- package/dist/api/presentation/aiInspiration.d.ts +5 -5
- package/dist/api/program/site.d.ts +4 -4
- package/dist/api/program/spreadsheet.d.ts +4 -4
- package/dist/handles.d.ts +13 -3
- package/dist/handles.d.ts.map +1 -1
- package/dist/index.cjs +3656 -2950
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3591 -2950
- package/dist/index.js.map +1 -1
- package/dist/massParameters.d.ts +287 -0
- package/dist/massParameters.d.ts.map +1 -0
- package/dist/massParameters.ops.d.ts +138 -0
- package/dist/massParameters.ops.d.ts.map +1 -0
- package/package.json +2 -1
- package/scripts/check-dist-fresh.mjs +22 -0
- package/src/api/core/geom/create/index.ts +1030 -101
- package/src/api/core/geom/query/brep.ts +60 -2
- package/src/api/core/geom/query/curve.ts +26 -2
- package/src/api/core/geom/query/edge.ts +2 -2
- package/src/api/core/geom/query/face.ts +109 -4
- package/src/api/core/geom/update/contour.ts +2 -0
- package/src/api/core/geom/update/profile.ts +2 -0
- package/src/api/core/handles/index.ts +3 -2
- package/src/api/design/family.ts +145 -16
- package/src/api/design/index.ts +7 -0
- package/src/api/design/networks.ts +315 -0
- package/src/api/design/query/geometry/index.ts +35 -4
- package/src/api/design/query/index.ts +50 -0
- package/src/api/design/update/index.ts +106 -0
- package/src/handles.ts +18 -3
- package/src/index.ts +2 -0
- package/src/massParameters.ops.ts +246 -0
- package/src/massParameters.ts +427 -0
- package/test/massParameters.test.mjs +952 -0
- package/api-manifest.json +0 -8371
package/src/api/design/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { PluginDesignTypesApi } from "./types"
|
|
|
19
19
|
import { PluginDesignFamilyApi } from "./family"
|
|
20
20
|
import { PluginDesignGridsApi } from "./grids"
|
|
21
21
|
import { PluginDesignChangeResult } from "./lock"
|
|
22
|
+
import { PluginElectricalApi, PluginPlumbingApi } from "./networks"
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* `snaptrude.design.*` — author, query & edit BIM geometry.
|
|
@@ -80,6 +81,11 @@ export abstract class PluginDesignApi {
|
|
|
80
81
|
/** Structural edits on a grid's line structure. See {@linkcode PluginDesignGridsApi}. */
|
|
81
82
|
public abstract grids: PluginDesignGridsApi
|
|
82
83
|
|
|
84
|
+
/** Persistent electrical equipment, routes and circuit graphs. */
|
|
85
|
+
public abstract electrical: PluginElectricalApi
|
|
86
|
+
/** Persistent plumbing equipment and directed pipe graphs. */
|
|
87
|
+
public abstract plumbing: PluginPlumbingApi
|
|
88
|
+
|
|
83
89
|
/**
|
|
84
90
|
* Lock entities so they cannot be edited. Undoable. Cascades to void/door/window
|
|
85
91
|
* children, stacked-wall companions, and terrain neighborhood; skips
|
|
@@ -272,3 +278,4 @@ export * from "./dimensions"
|
|
|
272
278
|
export * from "./types"
|
|
273
279
|
export * from "./family"
|
|
274
280
|
export * from "./grids"
|
|
281
|
+
export * from "./networks"
|
|
@@ -0,0 +1,315 @@
|
|
|
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
|
+
|
|
13
|
+
/** Authored world-space connector. This is not a claim of certified family connector metadata. */
|
|
14
|
+
export const NetworkPort = z
|
|
15
|
+
.object({
|
|
16
|
+
id,
|
|
17
|
+
positionMm: vector,
|
|
18
|
+
direction: vector,
|
|
19
|
+
system: z.string().min(1).max(80),
|
|
20
|
+
/** Diameter for round ports; width when heightMm is supplied (rectangular port). */
|
|
21
|
+
sizeMm: positive,
|
|
22
|
+
heightMm: positive.optional(),
|
|
23
|
+
})
|
|
24
|
+
.strict()
|
|
25
|
+
|
|
26
|
+
/** The agent supplies every route and section. The host does not size or route a network. */
|
|
27
|
+
export const NetworkGeometry = z.discriminatedUnion("kind", [
|
|
28
|
+
z
|
|
29
|
+
.object({
|
|
30
|
+
kind: z.literal("family"),
|
|
31
|
+
definitionId: z.string().min(1).max(200),
|
|
32
|
+
version: z.number().int().positive(),
|
|
33
|
+
values: z.record(
|
|
34
|
+
z.string(),
|
|
35
|
+
z.union([z.number().finite(), z.string(), z.boolean()]),
|
|
36
|
+
),
|
|
37
|
+
positionMm: vector,
|
|
38
|
+
rotationDeg: vector,
|
|
39
|
+
ports: z.array(NetworkPort).max(64),
|
|
40
|
+
})
|
|
41
|
+
.strict(),
|
|
42
|
+
z
|
|
43
|
+
.object({
|
|
44
|
+
kind: z.literal("route"),
|
|
45
|
+
pathMm: z.array(vector).min(2).max(256),
|
|
46
|
+
system: z.string().min(1).max(80),
|
|
47
|
+
section: z.discriminatedUnion("kind", [
|
|
48
|
+
z
|
|
49
|
+
.object({
|
|
50
|
+
kind: z.literal("round"),
|
|
51
|
+
diameterMm: positive,
|
|
52
|
+
wallMm: z.number().finite().nonnegative(),
|
|
53
|
+
})
|
|
54
|
+
.strict(),
|
|
55
|
+
z
|
|
56
|
+
.object({
|
|
57
|
+
kind: z.literal("rectangular"),
|
|
58
|
+
widthMm: positive,
|
|
59
|
+
heightMm: positive,
|
|
60
|
+
wallMm: z.number().finite().nonnegative(),
|
|
61
|
+
})
|
|
62
|
+
.strict(),
|
|
63
|
+
]),
|
|
64
|
+
})
|
|
65
|
+
.strict(),
|
|
66
|
+
])
|
|
67
|
+
|
|
68
|
+
// Storey is scene membership, not a coordinate offset. A route can span multiple elevations.
|
|
69
|
+
const node = z
|
|
70
|
+
.object({ id, storey: z.number().int(), geometry: NetworkGeometry })
|
|
71
|
+
.strict()
|
|
72
|
+
const electricalNode = node.extend({
|
|
73
|
+
kind: z.enum([
|
|
74
|
+
"transformer",
|
|
75
|
+
"mainBoard",
|
|
76
|
+
"distributionBoard",
|
|
77
|
+
"mcbBox",
|
|
78
|
+
"junctionBox",
|
|
79
|
+
"switch",
|
|
80
|
+
"outlet",
|
|
81
|
+
"light",
|
|
82
|
+
"load",
|
|
83
|
+
"equipment",
|
|
84
|
+
"fitting",
|
|
85
|
+
"wire",
|
|
86
|
+
"cable",
|
|
87
|
+
"conduit",
|
|
88
|
+
"cableDuct",
|
|
89
|
+
"cableTray",
|
|
90
|
+
]),
|
|
91
|
+
})
|
|
92
|
+
const plumbingNode = node.extend({
|
|
93
|
+
kind: z.enum([
|
|
94
|
+
"fixture",
|
|
95
|
+
"equipment",
|
|
96
|
+
"tank",
|
|
97
|
+
"pump",
|
|
98
|
+
"valve",
|
|
99
|
+
"manifold",
|
|
100
|
+
"fitting",
|
|
101
|
+
"pipe",
|
|
102
|
+
"riser",
|
|
103
|
+
]),
|
|
104
|
+
})
|
|
105
|
+
const document = z
|
|
106
|
+
.object({
|
|
107
|
+
schemaVersion: z.literal(1),
|
|
108
|
+
label: z.string().min(1).max(120),
|
|
109
|
+
/** Revision from context(); invalidated when the source architectural graph changes. */
|
|
110
|
+
architectureRevision: z.string().min(1).max(80),
|
|
111
|
+
connections: z
|
|
112
|
+
.array(
|
|
113
|
+
z
|
|
114
|
+
.object({ id, from: endpoint, to: endpoint, directed: z.boolean() })
|
|
115
|
+
.strict(),
|
|
116
|
+
)
|
|
117
|
+
.max(4000),
|
|
118
|
+
relationships: z
|
|
119
|
+
.array(
|
|
120
|
+
z
|
|
121
|
+
.object({
|
|
122
|
+
node: id,
|
|
123
|
+
relation: z.enum(["mountedOn", "within", "serves", "penetrates"]),
|
|
124
|
+
target: z
|
|
125
|
+
.object({
|
|
126
|
+
kind: z.enum(["component", "topology"]),
|
|
127
|
+
id: z.string().min(1).max(200),
|
|
128
|
+
})
|
|
129
|
+
.strict(),
|
|
130
|
+
})
|
|
131
|
+
.strict(),
|
|
132
|
+
)
|
|
133
|
+
.max(4000),
|
|
134
|
+
})
|
|
135
|
+
.strict()
|
|
136
|
+
|
|
137
|
+
/** Electrical connectivity and circuit membership are distinct from physical route occupancy. */
|
|
138
|
+
export const ElectricalGraphDocument = document.extend({
|
|
139
|
+
domain: z.literal("electrical"),
|
|
140
|
+
nodes: z.array(electricalNode).min(1).max(1000),
|
|
141
|
+
circuits: z
|
|
142
|
+
.array(
|
|
143
|
+
z
|
|
144
|
+
.object({
|
|
145
|
+
id,
|
|
146
|
+
source: id,
|
|
147
|
+
loads: z.array(id).min(1).max(1000),
|
|
148
|
+
routes: z.array(id).max(1000),
|
|
149
|
+
voltage: positive,
|
|
150
|
+
phases: z.number().int().min(1).max(3),
|
|
151
|
+
ratedAmps: positive,
|
|
152
|
+
})
|
|
153
|
+
.strict(),
|
|
154
|
+
)
|
|
155
|
+
.max(1000),
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
/** Plumbing uses explicit directed port connections; touching geometry never creates a connection. */
|
|
159
|
+
export const PlumbingGraphDocument = document.extend({
|
|
160
|
+
domain: z.literal("plumbing"),
|
|
161
|
+
nodes: z.array(plumbingNode).min(1).max(1000),
|
|
162
|
+
})
|
|
163
|
+
export const NetworkGraphDocument = z.discriminatedUnion("domain", [
|
|
164
|
+
ElectricalGraphDocument,
|
|
165
|
+
PlumbingGraphDocument,
|
|
166
|
+
])
|
|
167
|
+
export type NetworkGraphDocument = z.infer<typeof NetworkGraphDocument>
|
|
168
|
+
export type NetworkNode = NetworkGraphDocument["nodes"][number]
|
|
169
|
+
export type NetworkPort = z.infer<typeof NetworkPort>
|
|
170
|
+
export type NetworkDomain = NetworkGraphDocument["domain"]
|
|
171
|
+
|
|
172
|
+
/** JSON is an authored document, not a transport for live geometry handles. Maximum 2 MB characters. */
|
|
173
|
+
export const NetworkValidateArgs = z
|
|
174
|
+
.object({ graphJson: z.string().max(2_000_000) })
|
|
175
|
+
.strict()
|
|
176
|
+
export const NetworkCreateArgs = NetworkValidateArgs.extend({
|
|
177
|
+
requestId: z.string().uuid(),
|
|
178
|
+
})
|
|
179
|
+
export const NetworkGetArgs = z.object({ graphId: z.string().uuid() }).strict()
|
|
180
|
+
export const NetworkContextArgs = z
|
|
181
|
+
.object({
|
|
182
|
+
offset: z.number().int().nonnegative().default(0),
|
|
183
|
+
limit: z.number().int().min(1).max(100).default(30),
|
|
184
|
+
})
|
|
185
|
+
.strict()
|
|
186
|
+
export const NetworkQueryArgs = NetworkGetArgs.extend({
|
|
187
|
+
revision: z.number().int().positive(),
|
|
188
|
+
queryJson: z.string().max(16000),
|
|
189
|
+
})
|
|
190
|
+
export const NetworkQuery = z
|
|
191
|
+
.object({
|
|
192
|
+
kind: z.enum([
|
|
193
|
+
"nodes",
|
|
194
|
+
"connected",
|
|
195
|
+
"upstream",
|
|
196
|
+
"downstream",
|
|
197
|
+
"ports",
|
|
198
|
+
"nearestPort",
|
|
199
|
+
"circuits",
|
|
200
|
+
"relationships",
|
|
201
|
+
]),
|
|
202
|
+
node: id.optional(),
|
|
203
|
+
pointMm: vector.optional(),
|
|
204
|
+
radiusMm: positive.optional(),
|
|
205
|
+
system: z.string().optional(),
|
|
206
|
+
sizeMm: positive.optional(),
|
|
207
|
+
heightMm: positive.optional(),
|
|
208
|
+
offset: z.number().int().nonnegative().default(0),
|
|
209
|
+
limit: z.number().int().min(1).max(100).default(30),
|
|
210
|
+
})
|
|
211
|
+
.strict()
|
|
212
|
+
export type NetworkQuery = z.infer<typeof NetworkQuery>
|
|
213
|
+
|
|
214
|
+
export interface NetworkDiagnostic {
|
|
215
|
+
path: string
|
|
216
|
+
message: string
|
|
217
|
+
}
|
|
218
|
+
export interface NetworkValidation {
|
|
219
|
+
ok: boolean
|
|
220
|
+
diagnostics: NetworkDiagnostic[]
|
|
221
|
+
nodeCount: number
|
|
222
|
+
}
|
|
223
|
+
/** Durable node-to-scene mapping. Each node owns one component and its pinned generated recipe. */
|
|
224
|
+
export const NetworkComponentBinding = z
|
|
225
|
+
.object({
|
|
226
|
+
nodeId: id,
|
|
227
|
+
componentId: z.string().min(1),
|
|
228
|
+
definitionId: z.string().min(1),
|
|
229
|
+
definitionVersion: z.number().int().positive(),
|
|
230
|
+
})
|
|
231
|
+
.strict()
|
|
232
|
+
export type NetworkComponentBinding = z.infer<typeof NetworkComponentBinding>
|
|
233
|
+
export interface SavedNetworkGraph {
|
|
234
|
+
id: string
|
|
235
|
+
domain: NetworkDomain
|
|
236
|
+
revision: 1
|
|
237
|
+
requestId: string
|
|
238
|
+
document: NetworkGraphDocument
|
|
239
|
+
components: NetworkComponentBinding[]
|
|
240
|
+
}
|
|
241
|
+
export interface NetworkCreateResult {
|
|
242
|
+
graphId: string
|
|
243
|
+
revision: number
|
|
244
|
+
components: NetworkComponentBinding[]
|
|
245
|
+
alreadyCreated: boolean
|
|
246
|
+
/** Queue acceptance is not a durable database acknowledgement. */
|
|
247
|
+
saveStatus: "queued" | "existing"
|
|
248
|
+
}
|
|
249
|
+
export interface NetworkContextResult {
|
|
250
|
+
proposalId: string
|
|
251
|
+
buildingId: string | null
|
|
252
|
+
revision: string
|
|
253
|
+
/** PG2's native serialization uses Snaptrude units (1 SU = 254 mm). */
|
|
254
|
+
topologyUnits: "SU"
|
|
255
|
+
topologyJson: string
|
|
256
|
+
nextOffset: number | null
|
|
257
|
+
total: number
|
|
258
|
+
storeys: { number: number; elevationMm: number }[]
|
|
259
|
+
components: {
|
|
260
|
+
id: string
|
|
261
|
+
type: string
|
|
262
|
+
storey: number
|
|
263
|
+
minMm: number[]
|
|
264
|
+
maxMm: number[]
|
|
265
|
+
}[]
|
|
266
|
+
/** Saved graph inventory, paged with topology/components. Use these IDs after reopening a project. */
|
|
267
|
+
networks: {
|
|
268
|
+
graphId: string
|
|
269
|
+
domain: NetworkDomain
|
|
270
|
+
revision: number
|
|
271
|
+
label: string
|
|
272
|
+
componentCount: number
|
|
273
|
+
}[]
|
|
274
|
+
}
|
|
275
|
+
export interface NetworkQueryResult {
|
|
276
|
+
revision: number
|
|
277
|
+
resultJson: string
|
|
278
|
+
nextOffset: number | null
|
|
279
|
+
total: number
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Shared transport only. Electrical and plumbing documents retain separate schemas and semantics. */
|
|
283
|
+
export abstract class PluginDomainNetworkApi {
|
|
284
|
+
/** Read the active proposal's architectural context before authoring. Keep one revision across pages. */
|
|
285
|
+
abstract context(
|
|
286
|
+
args?: z.input<typeof NetworkContextArgs>,
|
|
287
|
+
): PluginApiReturn<NetworkContextResult>
|
|
288
|
+
/** Validate authored JSON, pinned families and recipe evaluation without placing or saving anything.
|
|
289
|
+
* This does not build kernel meshes or certify engineering/code compliance.
|
|
290
|
+
*/
|
|
291
|
+
abstract validate(
|
|
292
|
+
args: z.input<typeof NetworkValidateArgs>,
|
|
293
|
+
): PluginApiReturn<NetworkValidation>
|
|
294
|
+
/** Construct each node on its authored storey, with one undo command and queued project persistence.
|
|
295
|
+
* Networks can span storeys. Reuse requestId on retry; components returns the durable node mapping.
|
|
296
|
+
* Coordinates are world millimetres, Y-up; rotations are degrees. No engineering rules are inferred.
|
|
297
|
+
* Throws for stale architecture, invalid references/geometry or conflicting retries.
|
|
298
|
+
*/
|
|
299
|
+
abstract create(
|
|
300
|
+
args: z.input<typeof NetworkCreateArgs>,
|
|
301
|
+
): PluginApiReturn<NetworkCreateResult>
|
|
302
|
+
/** Read one saved graph in the active proposal; null on miss. IDs are durable, not worker handles. */
|
|
303
|
+
abstract get(
|
|
304
|
+
args: z.input<typeof NetworkGetArgs>,
|
|
305
|
+
): PluginApiReturn<SavedNetworkGraph | null>
|
|
306
|
+
/** Read bounded authored topology in graph revision. Crossings do not connect. No clash/compliance claims. */
|
|
307
|
+
abstract query(
|
|
308
|
+
args: z.input<typeof NetworkQueryArgs>,
|
|
309
|
+
): PluginApiReturn<NetworkQueryResult>
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Electrical equipment, physical routes and logical circuits, constructed from agent-authored intent. */
|
|
313
|
+
export abstract class PluginElectricalApi extends PluginDomainNetworkApi {}
|
|
314
|
+
/** Plumbing equipment and routed pipe networks, constructed from agent-authored intent. */
|
|
315
|
+
export abstract class PluginPlumbingApi 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
|
|
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
|
-
* @
|
|
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<
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as z from "zod"
|
|
2
2
|
import { PluginApiReturn } from "../../../types"
|
|
3
3
|
import { ComponentHandle, BBoxComponents } from "../../../handles"
|
|
4
|
+
import type { MassParametersRecord } from "../../../massParameters"
|
|
4
5
|
import {
|
|
5
6
|
PluginSpaceType,
|
|
6
7
|
PluginMassType,
|
|
@@ -1040,6 +1041,55 @@ export abstract class PluginDesignQueryApi {
|
|
|
1040
1041
|
staircase: ComponentHandle,
|
|
1041
1042
|
): PluginApiReturn<PluginStaircaseParams | null>
|
|
1042
1043
|
|
|
1044
|
+
/**
|
|
1045
|
+
* Read a mass's creation-parameter record — the recipe it was built from
|
|
1046
|
+
* (`kind` plus the values that constructor was called with). Present on
|
|
1047
|
+
* masses committed from a `core.geom.create` brep constructor that carries a
|
|
1048
|
+
* recipe: {@linkcode MassParametersRecord} covers the four primitives
|
|
1049
|
+
* (sphere, cylinder, cone, torus), extrusions, revolutions, lofts and sweeps.
|
|
1050
|
+
*
|
|
1051
|
+
* A fillet, chamfer, shell or offset of a recorded solid keeps the record
|
|
1052
|
+
* too: `kind` stays the base constructor and the operation is appended to the
|
|
1053
|
+
* record's `operations` list. Edit any of it with `design.update.parameters`.
|
|
1054
|
+
*
|
|
1055
|
+
* `null` for everything else — drawn masses, imported geometry, booleans,
|
|
1056
|
+
* split parts, `brepFromFaces` / `brepFromMesh`, and results built on a
|
|
1057
|
+
* source that carried no record — and for a mass whose record no longer
|
|
1058
|
+
* matches what is on
|
|
1059
|
+
* screen (a later free-form edit dropped the recipe, or the mass carries a
|
|
1060
|
+
* baked scale). `null` too when the recipe was not recordable: the record
|
|
1061
|
+
* schema has its own caps (a sweep path of at most 512 points, a loft of at
|
|
1062
|
+
* most 32 sections, at most 8 chained `operations`, at most 256 curves in one
|
|
1063
|
+
* profile loop and 64 holes in one contour), and a mass built past one of
|
|
1064
|
+
* them — through a route that does not enforce the matching creator cap —
|
|
1065
|
+
* commits as ordinary geometry carrying no record. The `operations` cap is
|
|
1066
|
+
* the one an agent can walk into by accident: a 9th chained
|
|
1067
|
+
* fillet/chamfer/shell/offset drops the record silently, so the result
|
|
1068
|
+
* commits as ordinary geometry and reads back `null` here. A record is never
|
|
1069
|
+
* reported unless it is still the truth.
|
|
1070
|
+
*
|
|
1071
|
+
* Points and axes come back in **world** coordinates. Pure read — nothing is
|
|
1072
|
+
* created or modified.
|
|
1073
|
+
*
|
|
1074
|
+
* @param component - the mass's {@linkcode ComponentHandle}
|
|
1075
|
+
* @returns the {@linkcode MassParametersRecord}, or `null`
|
|
1076
|
+
*
|
|
1077
|
+
* @examplePrompt What radius is this sphere?
|
|
1078
|
+
* @examplePrompt Is this mass parametric?
|
|
1079
|
+
* @examplePrompt Read back the dome's creation parameters
|
|
1080
|
+
* @examplePrompt How tall was this cylinder made?
|
|
1081
|
+
*
|
|
1082
|
+
* # Example
|
|
1083
|
+
* ```ts
|
|
1084
|
+
* const [mass] = await snaptrude.design.query.listMasses({ isSelected: true })
|
|
1085
|
+
* const params = await snaptrude.design.query.getParameters(mass)
|
|
1086
|
+
* if (params?.kind === "sphere") console.log("radius:", params.values.radius)
|
|
1087
|
+
* ```
|
|
1088
|
+
*/
|
|
1089
|
+
public abstract getParameters(
|
|
1090
|
+
component: ComponentHandle,
|
|
1091
|
+
): PluginApiReturn<MassParametersRecord | null>
|
|
1092
|
+
|
|
1043
1093
|
/**
|
|
1044
1094
|
* Get the **union** axis-aligned bounding box enclosing a set of entities.
|
|
1045
1095
|
*
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
PluginSpaceUpdateResult,
|
|
10
10
|
} from "../../entity/space"
|
|
11
11
|
import { PluginDesignChangeResult } from "../lock"
|
|
12
|
+
import { MassParametersPatch, type MassParametersRecord } from "../../../massParameters"
|
|
12
13
|
import {
|
|
13
14
|
PluginBuildableEnvelopePolygonVertex,
|
|
14
15
|
PluginBuildableEnvelopeSetbackTier,
|
|
@@ -137,6 +138,22 @@ export type PluginDesignUpdateStaircaseArgs = z.infer<
|
|
|
137
138
|
typeof PluginDesignUpdateStaircaseArgs
|
|
138
139
|
>
|
|
139
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Arguments for {@linkcode PluginDesignUpdateApi.parameters}.
|
|
143
|
+
*
|
|
144
|
+
* | Property | Type | Description |
|
|
145
|
+
* |---|---|---|
|
|
146
|
+
* | `component` | {@linkcode ComponentHandle} | The parametric mass to edit |
|
|
147
|
+
* | `patch` | {@linkcode MassParametersPatch} | Partial `values` (+ optional `operations`), or the full edited record |
|
|
148
|
+
*/
|
|
149
|
+
export const PluginDesignUpdateParametersArgs = z.object({
|
|
150
|
+
component: ComponentHandle,
|
|
151
|
+
patch: MassParametersPatch,
|
|
152
|
+
})
|
|
153
|
+
export type PluginDesignUpdateParametersArgs = z.infer<
|
|
154
|
+
typeof PluginDesignUpdateParametersArgs
|
|
155
|
+
>
|
|
156
|
+
|
|
140
157
|
/**
|
|
141
158
|
* Sparse edits for a wall (all optional; at least one required). Only the
|
|
142
159
|
* fields you provide change. Dimensions are in **engine units** (the same
|
|
@@ -462,6 +479,95 @@ export abstract class PluginDesignUpdateApi {
|
|
|
462
479
|
params: PluginStaircaseParamUpdates,
|
|
463
480
|
): PluginApiReturn<PluginDesignChangeResult>
|
|
464
481
|
|
|
482
|
+
/**
|
|
483
|
+
* Edit a parametric mass by patching its creation recipe and rebuilding the
|
|
484
|
+
* solid in place — the mass keeps its id, storey, materials, position and
|
|
485
|
+
* linked copies; only the geometry changes. Read the current record with
|
|
486
|
+
* `design.query.getParameters` first.
|
|
487
|
+
*
|
|
488
|
+
* `patch` is a partial of `record.values` — or the whole record you read
|
|
489
|
+
* back, edited. A key you leave out is unchanged (that includes
|
|
490
|
+
* `operations`). Each key you send replaces that value whole — a scalar, a
|
|
491
|
+
* point, a list or a record (`contour`, `profile`, `sections`, `path`,
|
|
492
|
+
* `options`, `operations`) alike — so send the complete value you read,
|
|
493
|
+
* edited (a partial `options` drops the switches it leaves out);
|
|
494
|
+
* `{ operations: [] }` drops every operation. An optional key
|
|
495
|
+
* the record does not carry yet may be patched in when the kind allows it:
|
|
496
|
+
* a straight extrusion acquires `twistInDegrees` or `topScale`, an
|
|
497
|
+
* options-less loft or sweep acquires `options` (`levels` is not one — send
|
|
498
|
+
* the angle, not the section count). A patch that changes nothing
|
|
499
|
+
* returns the current record and records no edit (no undo step). `kind` and
|
|
500
|
+
* `version` are immutable: to change the kind, build a new mass. Keys are the
|
|
501
|
+
* record's `values` keys for its `kind` (sphere: `centre`, `radius`;
|
|
502
|
+
* cylinder: `base`, `axis`, `radius`, `height`; torus: `centre`, `axis`,
|
|
503
|
+
* `majorRadius`, `minorRadius`; …) plus `operations`. To change a fillet
|
|
504
|
+
* radius, edit `operations[i].radius` in the record you read and send
|
|
505
|
+
* `{ operations }` back. Points and axes are WORLD coordinates in internal
|
|
506
|
+
* units, exactly as `getParameters` reports them.
|
|
507
|
+
*
|
|
508
|
+
* Rebuilds in memory first, then swaps onto the SAME component; every linked
|
|
509
|
+
* copy regenerates together. One undo step; saves and syncs to collaborators
|
|
510
|
+
* like any geometry edit. Kernel kinds (revolution, sweep, loft, primitives,
|
|
511
|
+
* any `operations`) run on OpenCascade — expect a short pause.
|
|
512
|
+
*
|
|
513
|
+
* @param component - a mass whose `getParameters` is non-null
|
|
514
|
+
* @param patch - partial `values` (+ optional `operations`), or the full edited record
|
|
515
|
+
* @returns the complete post-edit record, world coordinates (what
|
|
516
|
+
* `getParameters` now returns)
|
|
517
|
+
* @throws PRECONDITION_FAILED when the component is not a mass (a family
|
|
518
|
+
* instance: use `design.family.setParameters`).
|
|
519
|
+
* Also PRECONDITION_FAILED when the mass carries no live recipe — built
|
|
520
|
+
* from a boolean, split, `brepFromFaces`/`brepFromMesh` or drawn; edited
|
|
521
|
+
* free-form since (push/pull, move face, boolean); or scaled with the
|
|
522
|
+
* transform tool. `getParameters` returns `null` for exactly these.
|
|
523
|
+
* Do not retry — rebuild it with a recipe constructor and say so.
|
|
524
|
+
* A scaled mass is the exception: undo the scale and call again.
|
|
525
|
+
* Also PRECONDITION_FAILED when the mass (or a linked copy) is **locked**
|
|
526
|
+
* (the message says so). Locking is invisible to `getParameters` — a
|
|
527
|
+
* locked parametric mass still reads back a full record. Unlock it (and
|
|
528
|
+
* every linked copy) and call again; never rebuild it, the recipe is intact.
|
|
529
|
+
* Also PRECONDITION_FAILED when an `operations` entry's edge/face selectors
|
|
530
|
+
* no longer resolve on the rebuilt base (the message names the operation
|
|
531
|
+
* and edge): re-select the edges on the new shape and re-apply that
|
|
532
|
+
* operation.
|
|
533
|
+
* Also PRECONDITION_FAILED when the edit is the first twist or taper — or
|
|
534
|
+
* the straightening — of an extrusion that carries a shell, fillet or
|
|
535
|
+
* chamfer (the kernel numbers its faces differently): send the twist/taper
|
|
536
|
+
* keys with `operations: []` first (the message names them), re-select on
|
|
537
|
+
* the new shape, then send `{ operations }` back.
|
|
538
|
+
* @throws VALIDATION for a key the kind does not have (the message lists the
|
|
539
|
+
* kind's keys), `kind` or `version` in a partial patch, or a value outside
|
|
540
|
+
* the constructor's own rules (torus tube ≥ ring, revolution angle ∉
|
|
541
|
+
* (0, 360], a profile crossing its axis, a spline with fewer than
|
|
542
|
+
* degree + 1 poles).
|
|
543
|
+
* @throws OPERATION_FAILED when the kernel cannot build the edited recipe;
|
|
544
|
+
* the model is left unchanged.
|
|
545
|
+
*
|
|
546
|
+
* @examplePrompt Make the dome's radius 8 metres
|
|
547
|
+
* @examplePrompt Raise the podium to 5 m
|
|
548
|
+
* @examplePrompt Widen the vase — move its profile points 20 cm outward
|
|
549
|
+
* @examplePrompt Change the fillet on the rounded column to 300 mm
|
|
550
|
+
*
|
|
551
|
+
* # Example
|
|
552
|
+
* ```ts
|
|
553
|
+
* // { kind: "sphere", values: { centre, radius: 19.685 }, … }
|
|
554
|
+
* const before = await snaptrude.design.query.getParameters(dome)
|
|
555
|
+
* const after = await snaptrude.design.update.parameters(dome, { radius: 31.496 }) // 8 m in internal units
|
|
556
|
+
*
|
|
557
|
+
* // Fillet radius on a recorded result: read → edit → send the array back
|
|
558
|
+
* const rec = await snaptrude.design.query.getParameters(column)
|
|
559
|
+
* const operations = rec?.operations ?? []
|
|
560
|
+
* if (operations[0]?.op === "fillet") {
|
|
561
|
+
* operations[0].radius = 1.181
|
|
562
|
+
* await snaptrude.design.update.parameters(column, { operations })
|
|
563
|
+
* }
|
|
564
|
+
* ```
|
|
565
|
+
*/
|
|
566
|
+
public abstract parameters(
|
|
567
|
+
component: ComponentHandle,
|
|
568
|
+
patch: MassParametersPatch,
|
|
569
|
+
): PluginApiReturn<MassParametersRecord>
|
|
570
|
+
|
|
465
571
|
/**
|
|
466
572
|
* Edit a **wall** — its thickness and/or height (the same command the
|
|
467
573
|
* properties panel's Thickness/Height fields drive) and/or its **wall type**
|
package/src/handles.ts
CHANGED
|
@@ -145,8 +145,17 @@ export type Vec3Handle = Handle<"vec3">
|
|
|
145
145
|
export type QuatHandle = Handle<"quat">
|
|
146
146
|
export type LineHandle = Handle<"line">
|
|
147
147
|
export type ArcHandle = Handle<"arc">
|
|
148
|
-
/**
|
|
149
|
-
|
|
148
|
+
/**
|
|
149
|
+
* A NURBS curve value handle (open B-spline curve). Minted by
|
|
150
|
+
* `core.geom.create.splineFromPoints` / `splineFromControlPoints`, and returned by
|
|
151
|
+
* `edge.getCurve`, `profile.listCurves` and `update.curve.reverse` for a spline run;
|
|
152
|
+
* accepted everywhere a {@linkcode CurveHandle} is (profileFromCurves, query.curve.*).
|
|
153
|
+
* Closed spline loops never exist as a single handle — a closed run is built
|
|
154
|
+
* directly as a profile via `core.geom.create.profileFromSplinePoints`.
|
|
155
|
+
*/
|
|
156
|
+
export type SplineHandle = Handle<"spline">
|
|
157
|
+
/** A curve is a line, an arc, or a spline — its handle is one of those kinds. */
|
|
158
|
+
export type CurveHandle = LineHandle | ArcHandle | SplineHandle
|
|
150
159
|
/**
|
|
151
160
|
* A circle value handle. A circle is a CLOSED curve — it deliberately does NOT
|
|
152
161
|
* join {@linkcode CurveHandle} (it has no stable start/end and would break the
|
|
@@ -262,7 +271,13 @@ export const Vec3Handle = handleSchema("vec3")
|
|
|
262
271
|
export const QuatHandle = handleSchema("quat")
|
|
263
272
|
export const LineHandle = handleSchema("line")
|
|
264
273
|
export const ArcHandle = handleSchema("arc")
|
|
265
|
-
export const
|
|
274
|
+
export const SplineHandle = handleSchema("spline")
|
|
275
|
+
// Explicitly typed (rather than inferred): the inferred union type prints its
|
|
276
|
+
// three fully-expanded member schemas into handles.d.ts, which is served to
|
|
277
|
+
// agents whole and has a size budget. ZodUnion (not ZodType) keeps `.options`.
|
|
278
|
+
export const CurveHandle: z.ZodUnion<
|
|
279
|
+
readonly [typeof LineHandle, typeof ArcHandle, typeof SplineHandle]
|
|
280
|
+
> = z.union([LineHandle, ArcHandle, SplineHandle])
|
|
266
281
|
export const CircleHandle = handleSchema("circle")
|
|
267
282
|
|
|
268
283
|
// BREP topology handle schemas (§2A.2.1). Prefix-shape validation only; existence,
|