@snaptrude/plugin-core 0.7.0 → 0.8.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 (133) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/api-manifest.full.json +360 -19
  3. package/api-manifest.json +915 -37
  4. package/dist/api/core/camera/index.d.ts +208 -0
  5. package/dist/api/core/camera/index.d.ts.map +1 -0
  6. package/dist/api/core/comment/index.d.ts +105 -2
  7. package/dist/api/core/comment/index.d.ts.map +1 -1
  8. package/dist/api/core/geom/create/index.d.ts +13 -13
  9. package/dist/api/core/geom/delete/index.d.ts +8 -2
  10. package/dist/api/core/geom/delete/index.d.ts.map +1 -1
  11. package/dist/api/core/geom/query/arc.d.ts +5 -5
  12. package/dist/api/core/geom/query/brep.d.ts +18 -18
  13. package/dist/api/core/geom/query/circle.d.ts +18 -18
  14. package/dist/api/core/geom/query/contour.d.ts +20 -20
  15. package/dist/api/core/geom/query/curve.d.ts +49 -49
  16. package/dist/api/core/geom/query/edge.d.ts +5 -5
  17. package/dist/api/core/geom/query/face.d.ts +16 -16
  18. package/dist/api/core/geom/query/halfedge.d.ts +8 -8
  19. package/dist/api/core/geom/query/profile.d.ts +19 -19
  20. package/dist/api/core/geom/query/vertex.d.ts +8 -8
  21. package/dist/api/core/geom/update/contour.d.ts +14 -14
  22. package/dist/api/core/geom/update/curve.d.ts +7 -7
  23. package/dist/api/core/geom/update/profile.d.ts +16 -16
  24. package/dist/api/core/handles/index.d.ts +210 -0
  25. package/dist/api/core/handles/index.d.ts.map +1 -0
  26. package/dist/api/core/index.d.ts +15 -0
  27. package/dist/api/core/index.d.ts.map +1 -1
  28. package/dist/api/core/io/export/index.d.ts +132 -0
  29. package/dist/api/core/io/export/index.d.ts.map +1 -0
  30. package/dist/api/core/io/import/index.d.ts +1 -1
  31. package/dist/api/core/io/index.d.ts +5 -0
  32. package/dist/api/core/io/index.d.ts.map +1 -1
  33. package/dist/api/core/layers.d.ts +7 -7
  34. package/dist/api/core/proposals/index.d.ts +65 -9
  35. package/dist/api/core/proposals/index.d.ts.map +1 -1
  36. package/dist/api/core/user.d.ts +44 -0
  37. package/dist/api/core/user.d.ts.map +1 -0
  38. package/dist/api/design/boolean/index.d.ts +4 -4
  39. package/dist/api/design/create/index.d.ts +139 -44
  40. package/dist/api/design/create/index.d.ts.map +1 -1
  41. package/dist/api/design/doors/index.d.ts +36 -0
  42. package/dist/api/design/doors/index.d.ts.map +1 -1
  43. package/dist/api/design/edit/index.d.ts +1 -1
  44. package/dist/api/design/erase/index.d.ts +2 -2
  45. package/dist/api/design/furniture/index.d.ts +114 -3
  46. package/dist/api/design/furniture/index.d.ts.map +1 -1
  47. package/dist/api/design/index.d.ts +10 -0
  48. package/dist/api/design/index.d.ts.map +1 -1
  49. package/dist/api/design/materials/index.d.ts +111 -14
  50. package/dist/api/design/materials/index.d.ts.map +1 -1
  51. package/dist/api/design/query/index.d.ts +31 -1
  52. package/dist/api/design/query/index.d.ts.map +1 -1
  53. package/dist/api/design/query/spaces.d.ts +5 -5
  54. package/dist/api/design/transform/index.d.ts +83 -14
  55. package/dist/api/design/transform/index.d.ts.map +1 -1
  56. package/dist/api/design/types/index.d.ts +181 -0
  57. package/dist/api/design/types/index.d.ts.map +1 -0
  58. package/dist/api/design/update/index.d.ts +335 -2
  59. package/dist/api/design/update/index.d.ts.map +1 -1
  60. package/dist/api/design/visibility.d.ts +98 -0
  61. package/dist/api/design/visibility.d.ts.map +1 -0
  62. package/dist/api/entity/referenceLine.d.ts +2 -2
  63. package/dist/api/entity/space.d.ts +19 -19
  64. package/dist/api/entity/story.d.ts +128 -15
  65. package/dist/api/entity/story.d.ts.map +1 -1
  66. package/dist/api/presentation/aiInspiration.d.ts +1967 -7
  67. package/dist/api/presentation/aiInspiration.d.ts.map +1 -1
  68. package/dist/api/presentation/annotate.d.ts +448 -0
  69. package/dist/api/presentation/annotate.d.ts.map +1 -0
  70. package/dist/api/presentation/diagrams.d.ts +49 -8
  71. package/dist/api/presentation/diagrams.d.ts.map +1 -1
  72. package/dist/api/presentation/export.d.ts +104 -0
  73. package/dist/api/presentation/export.d.ts.map +1 -0
  74. package/dist/api/presentation/index.d.ts +40 -3
  75. package/dist/api/presentation/index.d.ts.map +1 -1
  76. package/dist/api/presentation/sheets.d.ts +410 -13
  77. package/dist/api/presentation/sheets.d.ts.map +1 -1
  78. package/dist/api/presentation/views.d.ts +165 -10
  79. package/dist/api/presentation/views.d.ts.map +1 -1
  80. package/dist/api/program/areas.d.ts +63 -3
  81. package/dist/api/program/areas.d.ts.map +1 -1
  82. package/dist/api/program/cores.d.ts +3 -99
  83. package/dist/api/program/cores.d.ts.map +1 -1
  84. package/dist/api/program/index.d.ts +2 -2
  85. package/dist/api/program/index.d.ts.map +1 -1
  86. package/dist/api/program/layout.d.ts +172 -12
  87. package/dist/api/program/layout.d.ts.map +1 -1
  88. package/dist/api/program/site.d.ts +11 -8
  89. package/dist/api/program/site.d.ts.map +1 -1
  90. package/dist/api/program/spreadsheet.d.ts +105 -13
  91. package/dist/api/program/spreadsheet.d.ts.map +1 -1
  92. package/dist/handles.d.ts +64 -25
  93. package/dist/handles.d.ts.map +1 -1
  94. package/dist/index.cjs +2502 -1466
  95. package/dist/index.cjs.map +1 -1
  96. package/dist/index.js +2369 -1461
  97. package/dist/index.js.map +1 -1
  98. package/package.json +1 -1
  99. package/src/api/core/camera/index.ts +212 -0
  100. package/src/api/core/comment/index.ts +120 -2
  101. package/src/api/core/geom/delete/index.ts +6 -0
  102. package/src/api/core/handles/index.ts +233 -0
  103. package/src/api/core/index.ts +15 -0
  104. package/src/api/core/io/export/index.ts +124 -0
  105. package/src/api/core/io/index.ts +5 -0
  106. package/src/api/core/proposals/index.ts +71 -11
  107. package/src/api/core/user.ts +46 -0
  108. package/src/api/design/create/index.ts +166 -36
  109. package/src/api/design/doors/index.ts +40 -0
  110. package/src/api/design/furniture/index.ts +127 -3
  111. package/src/api/design/index.ts +10 -0
  112. package/src/api/design/materials/index.ts +157 -30
  113. package/src/api/design/query/index.ts +33 -7
  114. package/src/api/design/transform/index.ts +89 -12
  115. package/src/api/design/types/index.ts +156 -0
  116. package/src/api/design/update/index.ts +402 -6
  117. package/src/api/design/visibility.ts +109 -0
  118. package/src/api/entity/story.ts +141 -15
  119. package/src/api/presentation/aiInspiration.ts +761 -7
  120. package/src/api/presentation/annotate.ts +360 -0
  121. package/src/api/presentation/diagrams.ts +53 -8
  122. package/src/api/presentation/export.ts +104 -0
  123. package/src/api/presentation/index.ts +48 -3
  124. package/src/api/presentation/sheets.ts +346 -13
  125. package/src/api/presentation/views.ts +164 -12
  126. package/src/api/program/areas.ts +57 -6
  127. package/src/api/program/cores.ts +3 -91
  128. package/src/api/program/index.ts +2 -2
  129. package/src/api/program/layout.ts +182 -12
  130. package/src/api/program/site.ts +11 -8
  131. package/src/api/program/spreadsheet.ts +125 -29
  132. package/src/handles.ts +77 -13
  133. package/tsconfig.json +7 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snaptrude/plugin-core",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -0,0 +1,212 @@
1
+ import * as z from "zod"
2
+ import { PluginApiReturn } from "../../../types"
3
+ import { Vec3Handle } from "../../../handles"
4
+
5
+ /**
6
+ * Camera controls.
7
+ *
8
+ * Position the viewport camera, snap it to a standard orthographic/isometric
9
+ * view, and toggle between the 2D (plan) and 3D modelling modes — mirrors the
10
+ * canvas view menu and the 2D/3D toggle. Like {@linkcode PluginZoomApi}, these
11
+ * are transient VIEW-STATE changes: they are not model edits, so they are not
12
+ * write-gated and are not undoable. Accessed via `snaptrude.core.camera`.
13
+ */
14
+ export abstract class PluginCameraApi {
15
+ constructor() {}
16
+
17
+ /**
18
+ * Point the camera: place its eye at `eye` looking toward `target` (world
19
+ * coordinates, internal `babylon` units). View-state only — not undoable.
20
+ *
21
+ * @param eye - Camera position as a {@linkcode Vec3Handle}
22
+ * @param target - Point the camera looks at as a {@linkcode Vec3Handle}
23
+ * @returns `true` once the camera has been positioned.
24
+ *
25
+ * @examplePrompt Look at the model from above the north-east corner
26
+ * @examplePrompt Position the camera at (50, 30, 50) looking at the origin
27
+ * @examplePrompt Point the camera at the centre of the building from the side
28
+ * @examplePrompt Set the eye to this point and aim it at that point
29
+ *
30
+ * # Example
31
+ * ```ts
32
+ * const { vec3 } = snaptrude.core.math
33
+ * await snaptrude.core.camera.lookFrom(vec3.new(50, 30, 50), vec3.new(0, 0, 0))
34
+ * ```
35
+ */
36
+ public abstract lookFrom(
37
+ eye: Vec3Handle,
38
+ target: Vec3Handle,
39
+ ): PluginApiReturn<boolean>
40
+
41
+ /**
42
+ * Snap the camera to a standard view — the five orthographic elevations
43
+ * (`top` / `front` / `back` / `left` / `right`) or the 3D `iso` (isometric
44
+ * perspective) view. Mirrors the canvas view menu. View-state only — not
45
+ * undoable. The orthographic presets exit 2D mode first if needed.
46
+ *
47
+ * @param view - The standard view to snap to ({@linkcode PluginStandardView})
48
+ * @returns `true` once the view has been applied.
49
+ *
50
+ * @examplePrompt Switch to the top view
51
+ * @examplePrompt Show me the front elevation
52
+ * @examplePrompt Go to the isometric view
53
+ * @examplePrompt Look at the model from the left
54
+ * @examplePrompt Give me a plan view from the top
55
+ *
56
+ * # Example
57
+ * ```ts
58
+ * await snaptrude.core.camera.setStandardView("top")
59
+ * ```
60
+ */
61
+ public abstract setStandardView(
62
+ view: PluginStandardView,
63
+ ): PluginApiReturn<boolean>
64
+
65
+ /**
66
+ * Toggle the modelling mode between `2d` (plan) and `3d`. Mirrors the canvas
67
+ * 2D/3D toggle: `3d` enters the isometric perspective view, `2d` drops to the
68
+ * orthographic plan of the active storey. View-state only — not undoable.
69
+ *
70
+ * @param mode - `"2d"` or `"3d"` ({@linkcode PluginCameraMode})
71
+ * @returns `true` once the mode switch has been requested.
72
+ *
73
+ * @examplePrompt Switch to 3D view
74
+ * @examplePrompt Drop into 2D plan mode
75
+ * @examplePrompt Go back to 3D
76
+ * @examplePrompt Show the floor plan in 2D
77
+ *
78
+ * # Example
79
+ * ```ts
80
+ * await snaptrude.core.camera.setMode("3d")
81
+ * ```
82
+ */
83
+ public abstract setMode(mode: PluginCameraMode): PluginApiReturn<boolean>
84
+
85
+ /**
86
+ * Read the camera's current projection: `"perspective"` (the default 3D
87
+ * view) or `"orthographic"`. In 2D (plan) mode this always reports
88
+ * `"orthographic"` — a plan is an orthographic projection.
89
+ *
90
+ * @returns The current {@linkcode PluginCameraProjection}.
91
+ *
92
+ * @examplePrompt Is the camera in perspective or orthographic?
93
+ * @examplePrompt What projection is the 3D view using?
94
+ * @examplePrompt Check whether perspective is on
95
+ *
96
+ * # Example
97
+ * ```ts
98
+ * const projection = await snaptrude.core.camera.getProjection()
99
+ * console.log(projection) // "perspective" | "orthographic"
100
+ * ```
101
+ */
102
+ public abstract getProjection(): PluginApiReturn<PluginCameraProjection>
103
+
104
+ /**
105
+ * Switch the 3D camera between `"perspective"` and `"orthographic"`
106
+ * projection — the Perspective/Orthographic control in the Design tab's
107
+ * View Settings panel. Like {@linkcode PluginCameraApi.setMode}, this is a
108
+ * transient view-state change: not write-gated, not undoable.
109
+ *
110
+ * In 2D (plan) mode the canvas is always an orthographic plan:
111
+ * `"orthographic"` is accepted as a no-op, while `"perspective"` is
112
+ * rejected — switch to 3D first via `setMode("3d")`.
113
+ *
114
+ * @param projection - `"perspective"` or `"orthographic"` ({@linkcode PluginCameraProjection})
115
+ * @returns `true` once the projection has been applied.
116
+ *
117
+ * @examplePrompt Switch the camera to orthographic
118
+ * @examplePrompt Turn perspective off
119
+ * @examplePrompt Give me a perspective view of the model
120
+ * @examplePrompt Make the 3D view orthographic like an axonometric drawing
121
+ *
122
+ * # Example
123
+ * ```ts
124
+ * await snaptrude.core.camera.setProjection("orthographic")
125
+ * const projection = await snaptrude.core.camera.getProjection() // "orthographic"
126
+ * ```
127
+ */
128
+ public abstract setProjection(
129
+ projection: PluginCameraProjection,
130
+ ): PluginApiReturn<boolean>
131
+ }
132
+
133
+ /**
134
+ * A standard camera view: the five orthographic elevations (`top`/`front`/
135
+ * `back`/`left`/`right`) or the 3D isometric perspective (`iso`).
136
+ */
137
+ export const PluginStandardView = z.enum([
138
+ "top",
139
+ "front",
140
+ "back",
141
+ "left",
142
+ "right",
143
+ "iso",
144
+ ])
145
+ export type PluginStandardView = z.infer<typeof PluginStandardView>
146
+
147
+ /**
148
+ * The modelling mode: `2d` (orthographic plan) or `3d` (isometric perspective).
149
+ */
150
+ export const PluginCameraMode = z.enum(["2d", "3d"])
151
+ export type PluginCameraMode = z.infer<typeof PluginCameraMode>
152
+
153
+ /**
154
+ * The 3D camera projection: `perspective` (the default) or `orthographic` —
155
+ * the Perspective/Orthographic control in the Design tab's View Settings panel.
156
+ */
157
+ export const PluginCameraProjection = z.enum(["perspective", "orthographic"])
158
+ export type PluginCameraProjection = z.infer<typeof PluginCameraProjection>
159
+
160
+ /**
161
+ * Arguments for {@linkcode PluginCameraApi.lookFrom}.
162
+ *
163
+ * | Property | Type | Description |
164
+ * |---|---|---|
165
+ * | `eye` | {@linkcode Vec3Handle} | Camera position (world, `babylon` units) |
166
+ * | `target` | {@linkcode Vec3Handle} | Point the camera looks at |
167
+ */
168
+ export const PluginCameraLookFromArgs = z.object({
169
+ eye: Vec3Handle,
170
+ target: Vec3Handle,
171
+ })
172
+ export type PluginCameraLookFromArgs = z.infer<typeof PluginCameraLookFromArgs>
173
+
174
+ /**
175
+ * Arguments for {@linkcode PluginCameraApi.setStandardView}.
176
+ *
177
+ * | Property | Type | Description |
178
+ * |---|---|---|
179
+ * | `view` | {@linkcode PluginStandardView} | The standard view to snap to |
180
+ */
181
+ export const PluginCameraSetStandardViewArgs = z.object({
182
+ view: PluginStandardView,
183
+ })
184
+ export type PluginCameraSetStandardViewArgs = z.infer<
185
+ typeof PluginCameraSetStandardViewArgs
186
+ >
187
+
188
+ /**
189
+ * Arguments for {@linkcode PluginCameraApi.setMode}.
190
+ *
191
+ * | Property | Type | Description |
192
+ * |---|---|---|
193
+ * | `mode` | {@linkcode PluginCameraMode} | `"2d"` or `"3d"` |
194
+ */
195
+ export const PluginCameraSetModeArgs = z.object({
196
+ mode: PluginCameraMode,
197
+ })
198
+ export type PluginCameraSetModeArgs = z.infer<typeof PluginCameraSetModeArgs>
199
+
200
+ /**
201
+ * Arguments for {@linkcode PluginCameraApi.setProjection}.
202
+ *
203
+ * | Property | Type | Description |
204
+ * |---|---|---|
205
+ * | `projection` | {@linkcode PluginCameraProjection} | `"perspective"` or `"orthographic"` |
206
+ */
207
+ export const PluginCameraSetProjectionArgs = z.object({
208
+ projection: PluginCameraProjection,
209
+ })
210
+ export type PluginCameraSetProjectionArgs = z.infer<
211
+ typeof PluginCameraSetProjectionArgs
212
+ >
@@ -1,6 +1,6 @@
1
1
  import * as z from "zod"
2
2
  import { PluginApiReturn } from "../../../types"
3
- import { Vec3Handle, ComponentHandle } from "../../../handles"
3
+ import { Vec3Handle, ComponentHandle, Vec3Components } from "../../../handles"
4
4
 
5
5
  /**
6
6
  * Comments — create, edit, resolve, tag, and list scene/project comments.
@@ -140,7 +140,10 @@ export abstract class PluginCommentApi {
140
140
  * await snaptrude.core.comment.tag(comment, "alex@acme.com")
141
141
  * ```
142
142
  */
143
- public abstract tag(comment: CommentHandle, email: string): PluginApiReturn<boolean>
143
+ public abstract tag(
144
+ comment: CommentHandle,
145
+ email: string,
146
+ ): PluginApiReturn<boolean>
144
147
 
145
148
  /**
146
149
  * Permanently remove a comment from the project.
@@ -176,6 +179,57 @@ export abstract class PluginCommentApi {
176
179
  * ```
177
180
  */
178
181
  public abstract list(): PluginApiReturn<CommentHandle[]>
182
+
183
+ /**
184
+ * Read a comment's full detail — its text, author, pin location, resolved
185
+ * state, and reply thread. Comments are otherwise write-only; this is the read
186
+ * side. Pass a top-level comment handle or a reply handle (a reply has an empty
187
+ * `thread`). Returns `null` if the handle doesn't match a live comment.
188
+ *
189
+ * @param comment - The comment (or reply) to read.
190
+ * @returns the {@link PluginCommentDetails}, or `null` if not found.
191
+ *
192
+ * @examplePrompt Read the comment on this wall and tell me what it says
193
+ * @examplePrompt Who left this comment and where is it pinned?
194
+ * @examplePrompt Show me the full thread for this comment
195
+ * @examplePrompt Get the text and author of the selected comment
196
+ *
197
+ * # Example
198
+ * ```ts
199
+ * const [first] = await snaptrude.core.comment.list()
200
+ * const details = await snaptrude.core.comment.get(first)
201
+ * console.log(details?.content, "by", details?.author?.name)
202
+ * console.log(`${details?.thread.length ?? 0} replies`)
203
+ * ```
204
+ */
205
+ public abstract get(
206
+ comment: CommentHandle,
207
+ ): PluginApiReturn<PluginCommentDetails | null>
208
+
209
+ /**
210
+ * Reply to a comment — posts a new comment threaded under `comment` (which must
211
+ * be a top-level comment, not itself a reply). Notifies the thread's
212
+ * participants, mirroring the sidebar Reply action.
213
+ *
214
+ * @param comment - The top-level comment to reply to.
215
+ * @param text - The reply text (emojis = plain unicode).
216
+ * @returns the new reply's {@link CommentHandle}.
217
+ *
218
+ * @examplePrompt Reply to this comment saying I fixed the wall height
219
+ * @examplePrompt Add a reply to the review note confirming it's done
220
+ * @examplePrompt Respond to the comment about the door with "looks good now"
221
+ * @examplePrompt Post a follow-up on the selected comment thread
222
+ *
223
+ * # Example
224
+ * ```ts
225
+ * const [thread] = await snaptrude.core.comment.list()
226
+ * const reply = await snaptrude.core.comment.reply(thread, "Fixed 👍")
227
+ * ```
228
+ */
229
+ public abstract reply(
230
+ comment: CommentHandle,
231
+ text: string,
232
+ ): PluginApiReturn<CommentHandle>
179
233
  }
180
234
 
181
235
  /**
@@ -256,3 +310,67 @@ export const PluginCommentTagArgs = z.object({
256
310
  })
257
311
 
258
312
  export type PluginCommentTagArgs = z.infer<typeof PluginCommentTagArgs>
313
+
314
+ /**
315
+ * Arguments for {@link PluginCommentApi.reply}.
316
+ *
317
+ * | Property | Type | Description |
318
+ * |---|---|---|
319
+ * | `comment` | {@link CommentHandle} | The top-level comment to reply to |
320
+ * | `text` | `string` | The reply text (emojis = unicode) |
321
+ */
322
+ export const PluginCommentReplyArgs = z.object({
323
+ comment: CommentHandle,
324
+ text: z.string(),
325
+ })
326
+
327
+ export type PluginCommentReplyArgs = z.infer<typeof PluginCommentReplyArgs>
328
+
329
+ /**
330
+ * The author of a comment or reply — the display name and email of the teammate
331
+ * who posted it. `null` on the rare comment with no recorded author.
332
+ */
333
+ export interface PluginCommentAuthor {
334
+ /** The author's display name (falls back to their email). */
335
+ name: string
336
+ /** The author's email. */
337
+ email: string
338
+ }
339
+
340
+ /**
341
+ * One reply in a comment's thread (returned by {@link PluginCommentApi.get}).
342
+ */
343
+ export interface PluginCommentReply {
344
+ /** The reply's own {@link CommentHandle}. */
345
+ id: CommentHandle
346
+ /** The reply text. */
347
+ content: string
348
+ /** Who posted the reply. */
349
+ author: PluginCommentAuthor | null
350
+ /** ISO-8601 timestamp of when the reply was posted (empty if unknown). */
351
+ createdAt: string
352
+ }
353
+
354
+ /**
355
+ * The read model of a comment — returned by {@link PluginCommentApi.get}. The
356
+ * inverse of {@link PluginCommentApi.create}: `content`/`component`/`position`
357
+ * echo what was posted, plus the resolved state and reply `thread`.
358
+ */
359
+ export interface PluginCommentDetails {
360
+ /** The comment's {@link CommentHandle}. */
361
+ id: CommentHandle
362
+ /** The comment text. */
363
+ content: string
364
+ /** Who posted the comment. */
365
+ author: PluginCommentAuthor | null
366
+ /** The pin point (engine world coordinates), or `null` if un-anchored. */
367
+ position: Vec3Components | null
368
+ /** The component the comment is anchored to, or `null` if un-anchored. */
369
+ component: ComponentHandle | null
370
+ /** Whether the comment has been marked resolved. */
371
+ isResolved: boolean
372
+ /** ISO-8601 timestamp of when the comment was posted (empty if unknown). */
373
+ createdAt: string
374
+ /** The reply thread, oldest first. Empty for a comment with no replies. */
375
+ thread: PluginCommentReply[]
376
+ }
@@ -20,6 +20,9 @@ export abstract class PluginGeomDeleteApi {
20
20
  * reclaims its share of the plugin's resource quota. This is a handle-lifecycle
21
21
  * operation only and does NOT tear down any engine-side geometry.
22
22
  *
23
+ * @deprecated Use `core.handles.release([profile])` — the universal, batched
24
+ * release API for every arena handle kind.
25
+ *
23
26
  * @param profile The transient profile handle to release
24
27
  * @returns Nothing
25
28
  *
@@ -37,6 +40,9 @@ export abstract class PluginGeomDeleteApi {
37
40
  * reclaims its share of the plugin's resource quota. Handle-lifecycle only; does
38
41
  * NOT tear down engine-side geometry, nor the contour's child profile handles.
39
42
  *
43
+ * @deprecated Use `core.handles.release([contour])` — the universal, batched
44
+ * release API for every arena handle kind.
45
+ *
40
46
  * @param contour The transient contour handle to release
41
47
  * @returns Nothing
42
48
  *
@@ -0,0 +1,233 @@
1
+ import * as z from "zod"
2
+ import { PluginApiReturn } from "../../../types"
3
+ // Type-only: src/handles.ts type-only-imports ArenaKind from this file, so the
4
+ // cycle stays purely type-level and is erased at emit.
5
+ import type { AnyArenaHandle } from "../../../handles"
6
+ import { Handle } from "../../../handles"
7
+
8
+ /**
9
+ * The 12 registry-arena handle kinds — the releasable families. Entity-style
10
+ * handles (component / material / underlay / terrain / importJob / comment) are
11
+ * raw engine ids with no arena entry and therefore no lifecycle: passing one to
12
+ * `release` is a silent no-op like any other unknown id.
13
+ */
14
+ export const ArenaKind = z.enum([
15
+ "vec3",
16
+ "quat",
17
+ "line",
18
+ "arc",
19
+ "circle",
20
+ "profile",
21
+ "contour",
22
+ "brep",
23
+ "face",
24
+ "edge",
25
+ "halfedge",
26
+ "vertex",
27
+ ])
28
+ export type ArenaKind = z.infer<typeof ArenaKind>
29
+
30
+ /** Max handles accepted by a single `release` / `endScope(retain)` call. */
31
+ export const RELEASE_BATCH_MAX = 10_000
32
+
33
+ /**
34
+ * An opaque token identifying a handle scope opened by
35
+ * {@linkcode PluginCoreHandlesApi.beginScope}. It is NOT an arena handle —
36
+ * it pins no memory, and `release(token)` is a silent no-op; only
37
+ * {@linkcode PluginCoreHandlesApi.endScope} consumes it.
38
+ */
39
+ export type ScopeToken = string
40
+
41
+ /**
42
+ * Handle lifecycle — free host-side handle registry entries the plugin no
43
+ * longer needs, individually ({@linkcode release}), by kind
44
+ * ({@linkcode releaseAll}), or as a mint-recording region
45
+ * ({@linkcode beginScope} / {@linkcode endScope}).
46
+ *
47
+ * Releasing is a handle-lifecycle operation only: it frees the registry entry
48
+ * (host memory + quota room) and makes the handle unresolvable. It never
49
+ * deletes engine-side geometry or scene entities.
50
+ *
51
+ * Accessed via `snaptrude.core.handles`.
52
+ */
53
+ export abstract class PluginCoreHandlesApi {
54
+ constructor() {}
55
+
56
+ /**
57
+ * Free arena handles in bulk. Unknown, foreign, or already-released ids are
58
+ * silent no-ops — there is no per-id result and no count, so responses can
59
+ * never be used as an existence oracle.
60
+ *
61
+ * @param handles The handles to release (max {@linkcode RELEASE_BATCH_MAX} per call)
62
+ * @returns Nothing
63
+ *
64
+ * # Example
65
+ * ```ts
66
+ * const faces = await snaptrude.core.geom.query.brep.listFaces(brep)
67
+ * // ... read what you need ...
68
+ * await snaptrude.core.handles.release(faces)
69
+ * ```
70
+ */
71
+ public abstract release(handles: ReadonlyArray<AnyArenaHandle>): PluginApiReturn<void>
72
+
73
+ /**
74
+ * Free every live arena handle of one kind, or ALL arena handles when the
75
+ * kind is omitted. Useful at batch boundaries — e.g. a poll loop that mints
76
+ * a fresh footprint contour per tick.
77
+ *
78
+ * @param kind Optional arena kind to restrict the sweep to
79
+ * @returns Nothing
80
+ *
81
+ * # Example
82
+ * ```ts
83
+ * const footprint = await snaptrude.design.query.spaces.getFootprint(space)
84
+ * // ... measure ...
85
+ * await snaptrude.core.handles.releaseAll("contour")
86
+ * ```
87
+ */
88
+ public abstract releaseAll(kind?: ArenaKind): PluginApiReturn<void>
89
+
90
+ /**
91
+ * Open a mint-recording scope: every arena handle MINTED after this call
92
+ * (fresh mints only — identity-deduped re-registrations of pre-existing
93
+ * objects are not captured) is freed when the matching
94
+ * {@linkcode endScope} runs. Scopes nest, up to 64 deep — always pair
95
+ * begin/end in try/finally.
96
+ *
97
+ * @returns The scope token to pass to {@linkcode endScope}
98
+ *
99
+ * # Example
100
+ * ```ts
101
+ * for (const space of spaces) {
102
+ * const scope = await snaptrude.core.handles.beginScope()
103
+ * try {
104
+ * const footprint = await snaptrude.design.query.spaces.getFootprint(space)
105
+ * const area = await snaptrude.core.geom.query.contour.getArea(footprint)
106
+ * } finally {
107
+ * await snaptrude.core.handles.endScope(scope) // footprint (and intermediates) freed
108
+ * }
109
+ * }
110
+ * ```
111
+ */
112
+ public abstract beginScope(): PluginApiReturn<ScopeToken>
113
+
114
+ /**
115
+ * Close a scope by its token and free every handle it recorded, except those
116
+ * listed in `retain`. Retained handles are promoted to the enclosing scope
117
+ * (if any), so nesting composes like block scopes. Closing is
118
+ * token-addressed: an out-of-order close (interleaved async tasks) frees
119
+ * only that scope's mints and leaves other open scopes intact. Always pair
120
+ * begin/end in try/finally so a thrown body cannot leak the scope.
121
+ *
122
+ * @param scope The token returned by the matching {@linkcode beginScope}
123
+ * @param retain Handles minted inside the scope that must survive it
124
+ * @returns Nothing
125
+ *
126
+ * # Example
127
+ * ```ts
128
+ * const scope = await snaptrude.core.handles.beginScope()
129
+ * const outline = await snaptrude.design.query.spaces.getFootprint(space)
130
+ * const outer = await snaptrude.core.geom.query.contour.getOuterProfile(outline)
131
+ * await snaptrude.core.handles.endScope(scope, [outer]) // outline freed, outer survives
132
+ * ```
133
+ */
134
+ public abstract endScope(
135
+ scope: ScopeToken,
136
+ retain?: ReadonlyArray<AnyArenaHandle>
137
+ ): PluginApiReturn<void>
138
+
139
+ /**
140
+ * Live handle-registry statistics for the calling plugin, plus the host
141
+ * page's JS heap usage (when the browser exposes it). Diagnostic surface for
142
+ * verifying that releases actually shrink host memory.
143
+ *
144
+ * @returns Arena entry counts, open scope depth, and host heap bytes
145
+ *
146
+ * # Example
147
+ * ```ts
148
+ * const before = await snaptrude.core.handles.stats()
149
+ * await snaptrude.core.handles.releaseAll("vec3")
150
+ * const after = await snaptrude.core.handles.stats()
151
+ * console.log(`freed ${before.values - after.values} value entries`)
152
+ * ```
153
+ */
154
+ public abstract stats(): PluginApiReturn<PluginHandlesStatsResult>
155
+ }
156
+
157
+ /**
158
+ * Wire-normalize one handle argument: a raw id string passes through; a live
159
+ * `Handle` instance (direct host callers — console harness, host-internal) or a
160
+ * structured-clone-stripped `{id}` / tagged `{__h}` shape collapses to its id.
161
+ * Deliberately shape-lax beyond that (non-empty string) — a malformed id is
162
+ * just an id that resolves nowhere (anti-oracle).
163
+ */
164
+ const handleIdWire = z.preprocess((v) => {
165
+ if (typeof v === "string") return v
166
+ if (v instanceof Handle) return v.id
167
+ if (v && typeof v === "object") {
168
+ const o = v as Record<string, unknown>
169
+ if (typeof o.__h === "string") return o.__h
170
+ if (typeof o.id === "string") return o.id
171
+ }
172
+ return v
173
+ }, z.string().min(1))
174
+
175
+ /**
176
+ * Arguments for {@linkcode PluginCoreHandlesApi.release}.
177
+ *
178
+ * | Property | Type | Description |
179
+ * |---|---|---|
180
+ * | `handles` | `string[]` | Handles to release (≤ {@linkcode RELEASE_BATCH_MAX}) |
181
+ */
182
+ export const PluginHandlesReleaseArgs = z.object({
183
+ handles: z.array(handleIdWire).max(RELEASE_BATCH_MAX),
184
+ })
185
+ export type PluginHandlesReleaseArgs = z.infer<typeof PluginHandlesReleaseArgs>
186
+
187
+ /**
188
+ * Arguments for {@linkcode PluginCoreHandlesApi.releaseAll}.
189
+ *
190
+ * | Property | Type | Description |
191
+ * |---|---|---|
192
+ * | `kind` | {@linkcode ArenaKind} (optional) | Restrict the sweep to one kind |
193
+ */
194
+ export const PluginHandlesReleaseAllArgs = z.object({
195
+ kind: ArenaKind.optional(),
196
+ })
197
+ export type PluginHandlesReleaseAllArgs = z.infer<typeof PluginHandlesReleaseAllArgs>
198
+
199
+ /**
200
+ * Arguments for {@linkcode PluginCoreHandlesApi.endScope}.
201
+ *
202
+ * | Property | Type | Description |
203
+ * |---|---|---|
204
+ * | `scope` | {@linkcode ScopeToken} | Token from the matching `beginScope` |
205
+ * | `retain` | `string[]` (optional) | Scope-minted handles that must survive |
206
+ */
207
+ export const PluginHandlesEndScopeArgs = z.object({
208
+ scope: z.string().regex(/^scope_[A-Za-z0-9_-]+$/, 'expected a "scope" token'),
209
+ retain: z.array(handleIdWire).max(RELEASE_BATCH_MAX).optional(),
210
+ })
211
+ export type PluginHandlesEndScopeArgs = z.infer<typeof PluginHandlesEndScopeArgs>
212
+
213
+ /**
214
+ * Result of {@linkcode PluginCoreHandlesApi.stats}.
215
+ *
216
+ * | Property | Type | Description |
217
+ * |---|---|---|
218
+ * | `resources` | `number` | Live resource-arena entries (profile/contour) |
219
+ * | `values` | `number` | Live value-arena entries (vec3/quat/line/arc/circle) |
220
+ * | `topology` | `number` | Live topology-arena entries (brep/face/edge/halfedge/vertex) |
221
+ * | `scopes` | `number` | Currently open handle scopes |
222
+ * | `hostHeapUsedBytes` | `number \| null` | Host page `usedJSHeapSize` (null where unsupported) |
223
+ * | `hostHeapTotalBytes` | `number \| null` | Host page `totalJSHeapSize` (null where unsupported) |
224
+ */
225
+ export const PluginHandlesStatsResult = z.object({
226
+ resources: z.number().int().nonnegative(),
227
+ values: z.number().int().nonnegative(),
228
+ topology: z.number().int().nonnegative(),
229
+ scopes: z.number().int().nonnegative(),
230
+ hostHeapUsedBytes: z.number().nullable(),
231
+ hostHeapTotalBytes: z.number().nullable(),
232
+ })
233
+ export type PluginHandlesStatsResult = z.infer<typeof PluginHandlesStatsResult>
@@ -10,7 +10,10 @@ import { PluginCoreBuildingsApi } from "./buildings"
10
10
  import { PluginCoreLayersApi } from "./layers"
11
11
  import { PluginCoreTagsApi } from "./tags"
12
12
  import { PluginCoreIoApi } from "./io"
13
+ import { PluginCoreHandlesApi } from "./handles"
13
14
  import { PluginCoreProposalsApi } from "./proposals"
15
+ import { PluginCameraApi } from "./camera"
16
+ import { PluginUserApi } from "./user"
14
17
 
15
18
  /**
16
19
  * Core primitives and infrastructure used across the plugin API.
@@ -20,6 +23,8 @@ import { PluginCoreProposalsApi } from "./proposals"
20
23
  * - {@linkcode PluginCoreApi.history} — Undo / redo of committed history entries
21
24
  * - {@linkcode PluginCoreApi.units} — Unit-type queries and conversion
22
25
  * - {@linkcode PluginCoreApi.zoom} — Camera zoom controls (extents / selection)
26
+ * - {@linkcode PluginCoreApi.camera} — Camera controls — position, standard views, 2D/3D mode
27
+ * - {@linkcode PluginCoreApi.user} — User locale — timezone & language
23
28
  * - {@linkcode PluginCoreApi.project} — Project-level settings and info
24
29
  * - {@linkcode PluginCoreApi.comment} — Comments: create, edit, resolve, tag, list
25
30
  * - {@linkcode PluginCoreApi.groups} — Read & edit the group hierarchy
@@ -27,6 +32,7 @@ import { PluginCoreProposalsApi } from "./proposals"
27
32
  * - {@linkcode PluginCoreApi.layers} — Read & toggle storey drawing/reference layers
28
33
  * - {@linkcode PluginCoreApi.tags} — Tag catalog + assignment
29
34
  * - {@linkcode PluginCoreApi.io} — Import files (image/pdf/cad/model/terrain), poll jobs, manage underlays
35
+ * - {@linkcode PluginCoreApi.handles} — Handle lifecycle: release, releaseAll, scopes
30
36
  * - {@linkcode PluginCoreApi.proposals} — Design options: read, create, rename, switch, delete
31
37
  */
32
38
  export abstract class PluginCoreApi {
@@ -40,6 +46,10 @@ export abstract class PluginCoreApi {
40
46
  public abstract units: PluginUnitsApi
41
47
  /** Camera zoom controls. See {@linkcode PluginZoomApi}. */
42
48
  public abstract zoom: PluginZoomApi
49
+ /** Camera controls — position, standard views, 2D/3D mode. See {@linkcode PluginCameraApi}. */
50
+ public abstract camera: PluginCameraApi
51
+ /** User locale — timezone & language. See {@linkcode PluginUserApi}. */
52
+ public abstract user: PluginUserApi
43
53
  /** Project-level settings and info. See {@linkcode PluginProjectApi}. */
44
54
  public abstract project: PluginProjectApi
45
55
  /** Comments — create, edit, resolve, tag, list. See {@linkcode PluginCommentApi}. */
@@ -54,6 +64,8 @@ export abstract class PluginCoreApi {
54
64
  public abstract tags: PluginCoreTagsApi
55
65
  /** I/O — file import, import jobs, and underlay management. See {@linkcode PluginCoreIoApi}. */
56
66
  public abstract io: PluginCoreIoApi
67
+ /** Handle lifecycle — release, releaseAll, and mint-recording scopes. See {@linkcode PluginCoreHandlesApi}. */
68
+ public abstract handles: PluginCoreHandlesApi
57
69
  /** Proposals — design options: read, create, rename, switch, delete. See {@linkcode PluginCoreProposalsApi}. */
58
70
  public abstract proposals: PluginCoreProposalsApi
59
71
 
@@ -65,6 +77,8 @@ export * from "./geom"
65
77
  export * from "./history"
66
78
  export * from "./units"
67
79
  export * from "./zoom"
80
+ export * from "./camera"
81
+ export * from "./user"
68
82
  export * from "./project"
69
83
  export * from "./comment"
70
84
  export * from "./groups"
@@ -72,4 +86,5 @@ export * from "./buildings"
72
86
  export * from "./layers"
73
87
  export * from "./tags"
74
88
  export * from "./io"
89
+ export * from "./handles"
75
90
  export * from "./proposals"