@snaptrude/plugin-core 0.9.0 → 0.9.2

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 (45) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/api-manifest.full.json +411 -2
  3. package/api-manifest.json +411 -2
  4. package/dist/api/core/geom/create/index.d.ts +406 -11
  5. package/dist/api/core/geom/create/index.d.ts.map +1 -1
  6. package/dist/api/core/geom/query/brep.d.ts +44 -0
  7. package/dist/api/core/geom/query/brep.d.ts.map +1 -1
  8. package/dist/api/core/io/terrain/index.d.ts +135 -0
  9. package/dist/api/core/io/terrain/index.d.ts.map +1 -1
  10. package/dist/api/presentation/annotate.d.ts +23 -4
  11. package/dist/api/presentation/annotate.d.ts.map +1 -1
  12. package/dist/api/presentation/diagrams.d.ts +64 -2
  13. package/dist/api/presentation/diagrams.d.ts.map +1 -1
  14. package/dist/api/presentation/index.d.ts +11 -1
  15. package/dist/api/presentation/index.d.ts.map +1 -1
  16. package/dist/api/presentation/placedViews.d.ts +772 -3
  17. package/dist/api/presentation/placedViews.d.ts.map +1 -1
  18. package/dist/api/presentation/shapes.d.ts +2 -2
  19. package/dist/api/presentation/sheets.d.ts +42 -0
  20. package/dist/api/presentation/sheets.d.ts.map +1 -1
  21. package/dist/api/presentation/slideshow.d.ts +125 -0
  22. package/dist/api/presentation/slideshow.d.ts.map +1 -0
  23. package/dist/api/presentation/tables.d.ts +81 -0
  24. package/dist/api/presentation/tables.d.ts.map +1 -0
  25. package/dist/api/program/site.d.ts +166 -2
  26. package/dist/api/program/site.d.ts.map +1 -1
  27. package/dist/api/workspace/index.d.ts +46 -1
  28. package/dist/api/workspace/index.d.ts.map +1 -1
  29. package/dist/index.cjs +890 -554
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.js +850 -554
  32. package/dist/index.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/api/core/geom/create/index.ts +445 -10
  35. package/src/api/core/geom/query/brep.ts +45 -0
  36. package/src/api/core/io/terrain/index.ts +146 -0
  37. package/src/api/presentation/annotate.ts +27 -2
  38. package/src/api/presentation/diagrams.ts +67 -2
  39. package/src/api/presentation/index.ts +11 -1
  40. package/src/api/presentation/placedViews.ts +760 -3
  41. package/src/api/presentation/sheets.ts +54 -0
  42. package/src/api/presentation/slideshow.ts +134 -0
  43. package/src/api/presentation/tables.ts +84 -0
  44. package/src/api/program/site.ts +110 -2
  45. package/src/api/workspace/index.ts +48 -1
@@ -168,6 +168,77 @@ export abstract class PluginCoreIoTerrainApi {
168
168
  */
169
169
  public abstract disableSatellite(): PluginApiReturn<void>
170
170
 
171
+ /**
172
+ * Replace the terrain surface with a caller-supplied triangle mesh. Undoable
173
+ * (one step), and the replacement persists with the project. The returned
174
+ * promise resolves only after the surface is fully applied and recorded —
175
+ * it is safe to toggle terrain resolution or read the surface back the
176
+ * moment it resolves.
177
+ *
178
+ * `positions` is a flat `[x, y, z, …]` array in **world space** — the same
179
+ * frame `design.query.geometry.getTriangulatedMeshes` reads — so a
180
+ * read-modify-write round trip needs no conversion. `indices` is a flat
181
+ * triangle list into `positions` (three indices per triangle). A
182
+ * predominantly down-facing surface is flipped automatically (reported in
183
+ * `warnings`), and zero-area sliver triangles are tolerated and counted.
184
+ *
185
+ * **What survives the replacement:** the datum, geolocation, opacity, lock
186
+ * state, and the elevation/satellite toggles. The map/satellite texture is
187
+ * re-projected onto the new surface from above (best effort — alignment is
188
+ * approximate, reported in `warnings`). **What resets:** cut/fill history
189
+ * and the earthwork report — the replaced surface starts with a clean
190
+ * grading slate.
191
+ *
192
+ * Limits: at most 500,000 triangles and 500,000 vertices; every coordinate
193
+ * must be finite and within the scene bound.
194
+ *
195
+ * @param positions - Flat `[x, y, z, …]` world-space vertex positions.
196
+ * @param indices - Flat triangle list into `positions`.
197
+ * @param units - `"world"` (default): coordinates are raw internal units;
198
+ * `"meters"`: coordinates are metres and are converted on the way in.
199
+ * @param options - Optional {@linkcode PluginTerrainReplaceMeshOptions}:
200
+ * optimistic-concurrency expectations (`expectedTerrain`,
201
+ * `expectedModelRevision`), `preserve` opt-outs, idempotent replay via
202
+ * `clientMutationId`, and the `coordinateSpace`/`baseline` declarations.
203
+ * @throws if writes are disabled, there is no terrain (import one first),
204
+ * the terrain is **locked**, the mesh fails validation (non-finite or
205
+ * out-of-bound coordinates, bad indices, degenerate-only geometry, over
206
+ * the size caps), `expectedTerrain` does not match the live terrain, or
207
+ * `expectedModelRevision` is stale.
208
+ *
209
+ * @examplePrompt Replace the terrain with this surveyed mesh
210
+ * @examplePrompt Flatten a building pad into the site surface
211
+ * @examplePrompt Load a custom DEM surface onto the terrain
212
+ * @examplePrompt Regrade the terrain from these points
213
+ *
214
+ * # Example
215
+ * ```ts
216
+ * // Projects with no terrain yet: import first, then replace.
217
+ * if (!(await snaptrude.core.io.terrain.exists())) {
218
+ * await snaptrude.core.io.import.terrain(40.7128, -74.006, 300, 300)
219
+ * }
220
+ * const result = await snaptrude.core.io.terrain.replaceMesh(
221
+ * positions, // [x0, y0, z0, x1, y1, z1, …] — world space
222
+ * indices, // [a0, b0, c0, a1, b1, c1, …]
223
+ * "meters",
224
+ * {
225
+ * coordinateSpace: "world",
226
+ * expectedTerrain: await snaptrude.core.io.terrain.get(),
227
+ * baseline: "make-replacement-original",
228
+ * preserve: { datum: true, geolocation: true, satellite: true, material: true },
229
+ * clientMutationId: "survey-2026-08-05-r1",
230
+ * },
231
+ * )
232
+ * // result.modelRevision — pass back as expectedModelRevision on the next call
233
+ * ```
234
+ */
235
+ public abstract replaceMesh(
236
+ positions: number[],
237
+ indices: number[],
238
+ units?: "world" | "meters",
239
+ options?: PluginTerrainReplaceMeshOptions,
240
+ ): PluginApiReturn<PluginTerrainReplaceMeshResult>
241
+
171
242
  /** Read the terrain opacity, `0`..`1`, or `null` if no terrain. */
172
243
  public abstract getOpacity(): PluginApiReturn<number | null>
173
244
  /**
@@ -205,6 +276,81 @@ export const TerrainReport = z.object({
205
276
  })
206
277
  export type TerrainReport = z.infer<typeof TerrainReport>
207
278
 
279
+ /**
280
+ * Result of {@link PluginCoreIoTerrainApi.replaceMesh}.
281
+ *
282
+ * | Property | Type | Description |
283
+ * |---|---|---|
284
+ * | `terrain` | `TerrainHandle` | The terrain (unchanged singleton handle) |
285
+ * | `modelRevision` | `number` | Session-scoped terrain-mesh revision after this replace — pass back as `expectedModelRevision` for compare-and-swap semantics |
286
+ * | `vertexCount` | `number` | Vertices in the applied mesh |
287
+ * | `triangleCount` | `number` | Triangles in the applied mesh |
288
+ * | `warnings` | `string[]` | Non-fatal notes: winding flipped, sliver triangles, approximate texture re-projection |
289
+ */
290
+ export type PluginTerrainReplaceMeshResult = {
291
+ terrain: TerrainHandle
292
+ modelRevision: number
293
+ vertexCount: number
294
+ triangleCount: number
295
+ warnings: string[]
296
+ }
297
+
298
+ /**
299
+ * Options for {@link PluginCoreIoTerrainApi.replaceMesh}.
300
+ *
301
+ * | Property | Type | Description |
302
+ * |---|---|---|
303
+ * | `coordinateSpace` | `"world"`? | Declares the input frame. `world` is the only supported space (the same frame `getTriangulatedMeshes` reads) |
304
+ * | `expectedTerrain` | `TerrainHandle`? | Optimistic check: throws `HANDLE_INVALID` if the live terrain is not this handle (session-scoped — handles do not survive reloads) |
305
+ * | `expectedModelRevision` | `number`? | Optimistic check against the session terrain-mesh revision (starts at `0`, increments per successful `replaceMesh`; the result returns the new value). Stale → `PRECONDITION_FAILED` with the current revision in `details` |
306
+ * | `baseline` | `"make-replacement-original"`? | Declares replacement semantics. This is the only (and default) behavior: the replacement becomes the persisted surface; the app's terrain Reset restores the original map heightmap as the escape hatch |
307
+ * | `preserve` | object? | Opt-outs, all defaulting `true`. `datum: false` resets the terrain transform (authored coordinates become the local frame). `satellite: false` switches the drape off after the replace. `material: false` skips texture re-projection — the whole surface takes the ground material. `geolocation: false` is rejected (`VALIDATION`): geolocation is always preserved in this version |
308
+ * | `clientMutationId` | `string`? | Session-scoped idempotency: a repeated call with the same id returns the original result without re-executing |
309
+ */
310
+ export const PluginTerrainReplaceMeshOptions = z
311
+ .object({
312
+ coordinateSpace: z.literal("world").optional(),
313
+ expectedTerrain: z.string().min(1).optional(),
314
+ expectedModelRevision: z.number().int().nonnegative().optional(),
315
+ baseline: z.literal("make-replacement-original").optional(),
316
+ preserve: z
317
+ .object({
318
+ datum: z.boolean().optional(),
319
+ geolocation: z.boolean().optional(),
320
+ satellite: z.boolean().optional(),
321
+ material: z.boolean().optional(),
322
+ })
323
+ .strict()
324
+ .optional(),
325
+ clientMutationId: z.string().min(1).max(128).optional(),
326
+ })
327
+ .strict()
328
+ export type PluginTerrainReplaceMeshOptions = z.infer<typeof PluginTerrainReplaceMeshOptions>
329
+
330
+ /**
331
+ * Arguments for {@link PluginCoreIoTerrainApi.replaceMesh}. The caps are the
332
+ * plugin surface's explicit payload ceiling: 500k vertices / 500k triangles.
333
+ */
334
+ export const PluginTerrainReplaceMeshArgs = z.object({
335
+ positions: z
336
+ .array(z.number().finite())
337
+ .min(9)
338
+ .max(1_500_000)
339
+ .refine((a) => a.length % 3 === 0, {
340
+ message: "positions must be a flat [x, y, z, …] array (length a multiple of 3)",
341
+ }),
342
+ indices: z
343
+ .array(z.number().int().nonnegative())
344
+ .min(3)
345
+ .max(1_500_000)
346
+ .refine((a) => a.length % 3 === 0, {
347
+ message: "indices must be a flat triangle list (length a multiple of 3)",
348
+ }),
349
+ units: z.enum(["world", "meters"]).optional(),
350
+ options: PluginTerrainReplaceMeshOptions.optional(),
351
+ })
352
+ export type PluginTerrainReplaceMeshArgs = z.infer<typeof PluginTerrainReplaceMeshArgs>
353
+
208
354
  /** Arguments for {@link PluginCoreIoTerrainApi.setDatum}. */
209
355
  export const PluginTerrainSetDatumArgs = z.object({ offset: z.number().finite() })
210
356
  export type PluginTerrainSetDatumArgs = z.infer<typeof PluginTerrainSetDatumArgs>
@@ -26,12 +26,22 @@ export abstract class PluginPresentationAnnotateApi {
26
26
  * Creates a text shape on the given sheet and returns the id of the created
27
27
  * canvas shape. Requires Present mode to be open.
28
28
  *
29
+ * Typography is optional: `fontFamily` takes a system family or any Google
30
+ * Font (loaded automatically, the same loader as the Present text panel);
31
+ * `fontWeight` is a numeric weight string like `"400"` or `"700"`,
32
+ * optionally with the `-italic` suffix (e.g. `"700-italic"`) — the Present
33
+ * text panel's value domain; a weight the chosen font does not provide
34
+ * falls back to regular. `fontSizePx` sets an exact pixel size, overriding
35
+ * the `size` preset when both are given (`size` alone keeps working).
36
+ *
29
37
  * @param sheetId - The sheet to annotate.
30
38
  * @param text - The label text.
31
39
  * @param options - Optional `position` (`{ x, y }`, relative to the sheet's
32
40
  * top-left, in canvas units — defaults to the sheet's top-left corner),
33
- * `size` (`"s" | "m" | "l" | "xl"`, default `"m"`), and `color` (a named
34
- * palette color, default `"black"`).
41
+ * `size` (`"s" | "m" | "l" | "xl"`, default `"m"`), `color` (a named
42
+ * palette color, default `"black"`), and typography: `fontFamily`,
43
+ * `fontWeight` (numeric string, `-italic` suffix allowed), `fontSizePx`
44
+ * (positive px size overriding the `size` preset).
35
45
  * @returns A {@linkcode PluginPresentationAnnotateTextResult} with the created
36
46
  * `shapeId`.
37
47
  * @throws If Present mode is not open or the sheet id is invalid.
@@ -57,6 +67,9 @@ export abstract class PluginPresentationAnnotateApi {
57
67
  position?: { x: number; y: number }
58
68
  size?: PluginAnnotateTextSize
59
69
  color?: PluginAnnotateTextColor
70
+ fontFamily?: string
71
+ fontWeight?: string
72
+ fontSizePx?: number
60
73
  },
61
74
  ): PluginApiReturn<PluginPresentationAnnotateTextResult>
62
75
 
@@ -218,6 +231,9 @@ export type PluginAnnotateTextColor = z.infer<typeof PluginAnnotateTextColor>
218
231
  * | `position` | `{ x: number; y: number } \| undefined` | Where to place it (relative to the sheet) |
219
232
  * | `size` | {@linkcode PluginAnnotateTextSize}` \| undefined` | Text size preset |
220
233
  * | `color` | {@linkcode PluginAnnotateTextColor}` \| undefined` | Text color |
234
+ * | `fontFamily` | `string \| undefined` | System family or any Google Font (loaded automatically) |
235
+ * | `fontWeight` | `string \| undefined` | Numeric weight like `"400"`/`"700"`, `-italic` suffix allowed |
236
+ * | `fontSizePx` | `number \| undefined` | Exact px size (overrides the `size` preset) |
221
237
  */
222
238
  export const PluginPresentationAnnotateTextArgs = z.object({
223
239
  sheetId: z.string(),
@@ -225,6 +241,15 @@ export const PluginPresentationAnnotateTextArgs = z.object({
225
241
  position: z.object({ x: z.number(), y: z.number() }).optional(),
226
242
  size: PluginAnnotateTextSize.optional(),
227
243
  color: PluginAnnotateTextColor.optional(),
244
+ fontFamily: z.string().min(1).optional(),
245
+ fontWeight: z
246
+ .string()
247
+ .regex(
248
+ /^\d{3}(-italic)?$/,
249
+ 'fontWeight must be a numeric string like "400" or "700", optionally with the "-italic" suffix',
250
+ )
251
+ .optional(),
252
+ fontSizePx: z.number().finite().positive().optional(),
228
253
  })
229
254
  export type PluginPresentationAnnotateTextArgs = z.infer<
230
255
  typeof PluginPresentationAnnotateTextArgs
@@ -8,8 +8,10 @@ import { PluginApiReturn } from "../../types"
8
8
  * {@linkcode PluginPresentationDiagramsApi.generateProgram} runs the Present-mode
9
9
  * **Program** action: it reads the project's spaces and departments and creates
10
10
  * new layout sheets holding the generated diagram graphics.
11
+ * {@linkcode PluginPresentationDiagramsApi.generateSite} runs the site-diagram
12
+ * flow (location / streetview / climate sheets from the map terrain).
11
13
  * {@linkcode PluginPresentationDiagramsApi.place} drops ready diagram images (by
12
- * URL) onto an existing sheet and returns the created canvas shape ids. Both
14
+ * URL) onto an existing sheet and returns the created canvas shape ids. All
13
15
  * require Present mode to be open. Adjacency data itself is read and computed via
14
16
  * `program.adjacency` (see {@linkcode PluginProgramAdjacencyApi}).
15
17
  *
@@ -50,6 +52,44 @@ export abstract class PluginPresentationDiagramsApi {
50
52
  */
51
53
  public abstract generateProgram(): PluginApiReturn<PluginPresentationDiagramsGenerateResult>
52
54
 
55
+ /**
56
+ * Generate the site diagrams (location, streetview, climate) for the
57
+ * current model, creating new layout sheets.
58
+ *
59
+ * Runs the same auto-generation as the Present-mode site-diagram flow:
60
+ * each requested type renders its diagram from the project's map terrain
61
+ * and creates a new sheet holding it. `options.types` selects which
62
+ * diagrams to generate (default: all three). Generation is tracked
63
+ * per-proposal — a type whose diagram already exists for the active
64
+ * proposal is **skipped, not duplicated** (the same semantics as the
65
+ * mount-time flow; existing site-diagram sheets are never replaced), so
66
+ * `sheetIds` contains only the sheets created by this call and can be
67
+ * empty when everything already exists. Requires Present mode to be open
68
+ * and the project to have a map terrain to generate from.
69
+ *
70
+ * @param options - Optional `types` — which site diagrams to generate
71
+ * (default: `["location", "streetview", "climate"]`).
72
+ * @returns A {@linkcode PluginPresentationDiagramsGenerateResult} with the
73
+ * `sheetIds` of the sheets created by this call (empty when all requested
74
+ * types were already generated).
75
+ * @throws If Present mode is not open or the project has no map terrain to
76
+ * generate from.
77
+ *
78
+ * @examplePrompt Generate the site diagrams for this project
79
+ * @examplePrompt Create the location, streetview and climate sheets
80
+ * @examplePrompt Add a climate diagram sheet to the presentation
81
+ *
82
+ * # Example
83
+ * ```ts
84
+ * const { sheetIds } = await snaptrude.presentation.diagrams.generateSite({
85
+ * types: ["location", "climate"],
86
+ * })
87
+ * ```
88
+ */
89
+ public abstract generateSite(options?: {
90
+ types?: PluginPresentationSiteDiagramType[]
91
+ }): PluginApiReturn<PluginPresentationDiagramsGenerateResult>
92
+
53
93
  /**
54
94
  * Place diagram images onto a sheet.
55
95
  *
@@ -114,8 +154,33 @@ export type PluginPresentationDiagramsPlaceResult = z.infer<
114
154
  typeof PluginPresentationDiagramsPlaceResult
115
155
  >
116
156
 
157
+ /** A site diagram type — the three diagrams of the Present-mode site flow. */
158
+ export const PluginPresentationSiteDiagramType = z.enum([
159
+ "location",
160
+ "streetview",
161
+ "climate",
162
+ ])
163
+ export type PluginPresentationSiteDiagramType = z.infer<
164
+ typeof PluginPresentationSiteDiagramType
165
+ >
166
+
167
+ /**
168
+ * Arguments for {@linkcode PluginPresentationDiagramsApi.generateSite}.
169
+ *
170
+ * | Property | Type | Description |
171
+ * |---|---|---|
172
+ * | `types` | {@linkcode PluginPresentationSiteDiagramType}`[] \| undefined` | Which site diagrams to generate (default: all three) |
173
+ */
174
+ export const PluginPresentationDiagramsGenerateSiteArgs = z.object({
175
+ types: z.array(PluginPresentationSiteDiagramType).min(1).optional(),
176
+ })
177
+ export type PluginPresentationDiagramsGenerateSiteArgs = z.infer<
178
+ typeof PluginPresentationDiagramsGenerateSiteArgs
179
+ >
180
+
117
181
  /**
118
- * Result of {@linkcode PluginPresentationDiagramsApi.generateProgram}.
182
+ * Result of {@linkcode PluginPresentationDiagramsApi.generateProgram} and
183
+ * {@linkcode PluginPresentationDiagramsApi.generateSite}.
119
184
  *
120
185
  * | Property | Type | Description |
121
186
  * |---|---|---|
@@ -6,6 +6,8 @@ import { PluginPresentationPlacedViewsApi } from "./placedViews"
6
6
  import { PluginPresentationDiagramsApi } from "./diagrams"
7
7
  import { PluginPresentationAnnotateApi } from "./annotate"
8
8
  import { PluginPresentationShapesApi } from "./shapes"
9
+ import { PluginPresentationTablesApi } from "./tables"
10
+ import { PluginPresentationSlideshowApi } from "./slideshow"
9
11
  import { PluginPresentationImportApi } from "./import"
10
12
  import {
11
13
  PluginPresentationExportResult,
@@ -26,7 +28,9 @@ import {
26
28
  * - {@linkcode PluginPresentationApi.views} — Saved 2D/3D views (list, get, capture, activate, create)
27
29
  * - {@linkcode PluginPresentationApi.sheets} — Layout sheets (list, get, create, place views)
28
30
  * - {@linkcode PluginPresentationApi.placedViews} — Views already placed on the canvas (move, scale, crop)
29
- * - {@linkcode PluginPresentationApi.diagrams} — Place program/adjacency/site diagram images on sheets
31
+ * - {@linkcode PluginPresentationApi.diagrams} — Generate program/site diagram sheets, place diagram images
32
+ * - {@linkcode PluginPresentationApi.tables} — Place data tables on sheets
33
+ * - {@linkcode PluginPresentationApi.slideshow} — Run the Present-mode slideshow
30
34
  * - {@linkcode PluginPresentationApi.import} — Import reference images/PDFs onto the canvas
31
35
  * - {@linkcode PluginPresentationApi.aiInspiration} — Present-mode AI image/video generation
32
36
  *
@@ -45,6 +49,10 @@ export abstract class PluginPresentationApi {
45
49
  public abstract annotate: PluginPresentationAnnotateApi
46
50
  /** Plugin-owned keyed shapes — upsert/remove/list so reruns update instead of duplicating. See {@linkcode PluginPresentationShapesApi}. */
47
51
  public abstract shapes: PluginPresentationShapesApi
52
+ /** Data tables on Present sheets — place string rows as a table shape. See {@linkcode PluginPresentationTablesApi}. */
53
+ public abstract tables: PluginPresentationTablesApi
54
+ /** The Present-mode slideshow — start/stop/getState. See {@linkcode PluginPresentationSlideshowApi}. */
55
+ public abstract slideshow: PluginPresentationSlideshowApi
48
56
  /** Import reference images/PDFs onto the Present canvas. See {@linkcode PluginPresentationImportApi}. */
49
57
  public abstract import: PluginPresentationImportApi
50
58
  /**
@@ -97,6 +105,8 @@ export * from "./placedViews"
97
105
  export * from "./diagrams"
98
106
  export * from "./annotate"
99
107
  export * from "./shapes"
108
+ export * from "./tables"
109
+ export * from "./slideshow"
100
110
  export * from "./import"
101
111
  export * from "./aiInspiration"
102
112
  export * from "./export"