@snaptrude/plugin-core 0.9.16 → 0.9.18-edgesetbacks-20261021.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/AGENTS.md +231 -231
  2. package/CHANGELOG.md +408 -402
  3. package/CLAUDE.md +11 -11
  4. package/api-manifest.json +1510 -701
  5. package/dist/api/analysis/daylight.d.ts +5 -5
  6. package/dist/api/analysis/solar.d.ts +2 -2
  7. package/dist/api/core/camera/index.d.ts +29 -0
  8. package/dist/api/core/camera/index.d.ts.map +1 -1
  9. package/dist/api/core/geom/create/index.d.ts +7 -1
  10. package/dist/api/core/geom/create/index.d.ts.map +1 -1
  11. package/dist/api/core/geom/update/contour.d.ts +8 -2
  12. package/dist/api/core/geom/update/contour.d.ts.map +1 -1
  13. package/dist/api/core/storeys/index.d.ts +5 -2
  14. package/dist/api/core/storeys/index.d.ts.map +1 -1
  15. package/dist/api/core/zoom/index.d.ts +13 -0
  16. package/dist/api/core/zoom/index.d.ts.map +1 -1
  17. package/dist/api/design/bim.d.ts +299 -0
  18. package/dist/api/design/bim.d.ts.map +1 -0
  19. package/dist/api/design/boolean/index.d.ts +2 -0
  20. package/dist/api/design/boolean/index.d.ts.map +1 -1
  21. package/dist/api/design/catalog.d.ts +320 -0
  22. package/dist/api/design/catalog.d.ts.map +1 -0
  23. package/dist/api/design/create/bulk-items.d.ts +65 -25
  24. package/dist/api/design/create/bulk-items.d.ts.map +1 -1
  25. package/dist/api/design/create/index.d.ts +299 -100
  26. package/dist/api/design/create/index.d.ts.map +1 -1
  27. package/dist/api/design/create/opening-fields.d.ts +13 -12
  28. package/dist/api/design/create/opening-fields.d.ts.map +1 -1
  29. package/dist/api/design/delete/index.d.ts +1 -0
  30. package/dist/api/design/delete/index.d.ts.map +1 -1
  31. package/dist/api/design/doors/index.d.ts +18 -24
  32. package/dist/api/design/doors/index.d.ts.map +1 -1
  33. package/dist/api/design/family.d.ts +813 -35
  34. package/dist/api/design/family.d.ts.map +1 -1
  35. package/dist/api/design/index.d.ts +16 -0
  36. package/dist/api/design/index.d.ts.map +1 -1
  37. package/dist/api/design/networks.d.ts +1703 -0
  38. package/dist/api/design/networks.d.ts.map +1 -0
  39. package/dist/api/design/query/grids.d.ts +2 -2
  40. package/dist/api/design/query/index.d.ts +42 -32
  41. package/dist/api/design/query/index.d.ts.map +1 -1
  42. package/dist/api/design/selection/index.d.ts +10 -10
  43. package/dist/api/design/types/index.d.ts +40 -24
  44. package/dist/api/design/types/index.d.ts.map +1 -1
  45. package/dist/api/design/update/index.d.ts +111 -6
  46. package/dist/api/design/update/index.d.ts.map +1 -1
  47. package/dist/api/design/windows/index.d.ts +8 -8
  48. package/dist/api/design/windows/index.d.ts.map +1 -1
  49. package/dist/api/entity/buildableEnvelope.d.ts +84 -2
  50. package/dist/api/entity/buildableEnvelope.d.ts.map +1 -1
  51. package/dist/api/presentation/aiInspiration.d.ts +10 -10
  52. package/dist/api/presentation/annotate.d.ts +38 -22
  53. package/dist/api/presentation/annotate.d.ts.map +1 -1
  54. package/dist/api/presentation/drawingContract.d.ts +32 -0
  55. package/dist/api/presentation/drawingContract.d.ts.map +1 -0
  56. package/dist/api/presentation/drawings.d.ts +3523 -0
  57. package/dist/api/presentation/drawings.d.ts.map +1 -0
  58. package/dist/api/presentation/export.d.ts +23 -5
  59. package/dist/api/presentation/export.d.ts.map +1 -1
  60. package/dist/api/presentation/index.d.ts +41 -12
  61. package/dist/api/presentation/index.d.ts.map +1 -1
  62. package/dist/api/presentation/placedViews.d.ts +106 -6
  63. package/dist/api/presentation/placedViews.d.ts.map +1 -1
  64. package/dist/api/presentation/sections.d.ts +461 -0
  65. package/dist/api/presentation/sections.d.ts.map +1 -0
  66. package/dist/api/presentation/shapes.d.ts +2 -2
  67. package/dist/api/presentation/sheets.d.ts +175 -19
  68. package/dist/api/presentation/sheets.d.ts.map +1 -1
  69. package/dist/api/presentation/viewPresets.d.ts +222 -0
  70. package/dist/api/presentation/viewPresets.d.ts.map +1 -0
  71. package/dist/api/presentation/views.d.ts +141 -8
  72. package/dist/api/presentation/views.d.ts.map +1 -1
  73. package/dist/api/presentation/walkthroughs.d.ts +6 -6
  74. package/dist/errors/codes.d.ts +3 -1
  75. package/dist/errors/codes.d.ts.map +1 -1
  76. package/dist/index.cjs +3883 -2385
  77. package/dist/index.cjs.map +1 -1
  78. package/dist/index.js +3744 -2385
  79. package/dist/index.js.map +1 -1
  80. package/package.json +1 -1
  81. package/scripts/check-dist-fresh.mjs +22 -22
  82. package/scripts/generate-manifest.mjs +243 -238
  83. package/scripts/generate-manifest.test.mjs +303 -303
  84. package/src/api/analysis/daylight.ts +470 -470
  85. package/src/api/analysis/heatmaps.ts +683 -683
  86. package/src/api/analysis/illuminance.ts +155 -155
  87. package/src/api/analysis/index.ts +61 -61
  88. package/src/api/analysis/shadows.ts +183 -183
  89. package/src/api/analysis/solar.ts +237 -237
  90. package/src/api/analysis/sunlightHours.ts +211 -211
  91. package/src/api/analysis/sunpath.ts +83 -83
  92. package/src/api/analysis/weather.ts +179 -179
  93. package/src/api/core/buildings.ts +485 -485
  94. package/src/api/core/camera/index.ts +696 -667
  95. package/src/api/core/comment/index.ts +376 -376
  96. package/src/api/core/geom/create/index.ts +2139 -2133
  97. package/src/api/core/geom/delete/index.ts +84 -84
  98. package/src/api/core/geom/index.ts +35 -35
  99. package/src/api/core/geom/query/arc.ts +169 -169
  100. package/src/api/core/geom/query/brep.ts +621 -621
  101. package/src/api/core/geom/query/circle.ts +404 -404
  102. package/src/api/core/geom/query/contour.ts +540 -540
  103. package/src/api/core/geom/query/curve.ts +1102 -1102
  104. package/src/api/core/geom/query/edge.ts +130 -130
  105. package/src/api/core/geom/query/face.ts +508 -508
  106. package/src/api/core/geom/query/halfedge.ts +210 -210
  107. package/src/api/core/geom/query/index.ts +63 -63
  108. package/src/api/core/geom/query/profile.ts +473 -473
  109. package/src/api/core/geom/query/vertex.ts +205 -205
  110. package/src/api/core/geom/update/contour.ts +323 -317
  111. package/src/api/core/geom/update/curve.ts +209 -209
  112. package/src/api/core/geom/update/index.ts +29 -29
  113. package/src/api/core/geom/update/profile.ts +379 -379
  114. package/src/api/core/groups.ts +521 -521
  115. package/src/api/core/handles/index.ts +234 -234
  116. package/src/api/core/history/index.ts +84 -84
  117. package/src/api/core/index.ts +99 -99
  118. package/src/api/core/io/export/index.ts +126 -126
  119. package/src/api/core/io/import/index.ts +504 -504
  120. package/src/api/core/io/index.ts +42 -42
  121. package/src/api/core/io/job/index.ts +140 -140
  122. package/src/api/core/io/query/index.ts +71 -71
  123. package/src/api/core/io/terrain/index.ts +360 -360
  124. package/src/api/core/io/underlay/index.ts +705 -705
  125. package/src/api/core/layers.ts +540 -540
  126. package/src/api/core/math/index.ts +20 -20
  127. package/src/api/core/math/quat.ts +172 -172
  128. package/src/api/core/math/vec3.ts +179 -179
  129. package/src/api/core/mode/index.ts +108 -108
  130. package/src/api/core/project/index.ts +479 -479
  131. package/src/api/core/proposals/index.ts +570 -570
  132. package/src/api/core/storeys/index.ts +311 -308
  133. package/src/api/core/storeys/layers.ts +339 -339
  134. package/src/api/core/tags.ts +911 -911
  135. package/src/api/core/units/index.ts +164 -164
  136. package/src/api/core/user.ts +46 -46
  137. package/src/api/core/zoom/index.ts +64 -51
  138. package/src/api/design/bim.ts +210 -0
  139. package/src/api/design/boolean/index.ts +181 -179
  140. package/src/api/design/catalog.ts +203 -0
  141. package/src/api/design/create/bulk-items.ts +294 -254
  142. package/src/api/design/create/grids.ts +137 -137
  143. package/src/api/design/create/index.ts +2178 -1980
  144. package/src/api/design/create/opening-fields.ts +40 -37
  145. package/src/api/design/delete/index.ts +75 -74
  146. package/src/api/design/dimensions.ts +453 -453
  147. package/src/api/design/doors/index.ts +516 -500
  148. package/src/api/design/edit/index.ts +54 -54
  149. package/src/api/design/erase/index.ts +151 -151
  150. package/src/api/design/family.ts +1342 -704
  151. package/src/api/design/furniture/index.ts +319 -319
  152. package/src/api/design/grids.ts +237 -237
  153. package/src/api/design/index.ts +304 -274
  154. package/src/api/design/lock.ts +69 -69
  155. package/src/api/design/materials/index.ts +608 -608
  156. package/src/api/design/networks.ts +607 -0
  157. package/src/api/design/query/geometry/index.ts +319 -319
  158. package/src/api/design/query/grids.ts +197 -197
  159. package/src/api/design/query/index.ts +1236 -1226
  160. package/src/api/design/query/referenceLines.ts +52 -52
  161. package/src/api/design/query/spaces.ts +275 -275
  162. package/src/api/design/selection/index.ts +273 -273
  163. package/src/api/design/shared.ts +40 -40
  164. package/src/api/design/transform/index.ts +254 -254
  165. package/src/api/design/types/index.ts +163 -156
  166. package/src/api/design/update/index.ts +1096 -983
  167. package/src/api/design/visibility.ts +143 -143
  168. package/src/api/design/windows/index.ts +351 -333
  169. package/src/api/entity/buildableEnvelope.ts +397 -322
  170. package/src/api/entity/index.ts +30 -30
  171. package/src/api/entity/referenceLine.ts +235 -235
  172. package/src/api/entity/space.ts +612 -612
  173. package/src/api/entity/story.ts +517 -517
  174. package/src/api/index.ts +47 -47
  175. package/src/api/presentation/aiInspiration.ts +1116 -1116
  176. package/src/api/presentation/annotate.ts +417 -385
  177. package/src/api/presentation/camera.ts +289 -289
  178. package/src/api/presentation/diagrams.ts +194 -194
  179. package/src/api/presentation/drawingContract.ts +70 -0
  180. package/src/api/presentation/drawings.ts +1179 -0
  181. package/src/api/presentation/export.ts +134 -108
  182. package/src/api/presentation/import.ts +159 -159
  183. package/src/api/presentation/index.ts +160 -126
  184. package/src/api/presentation/placedViews.ts +1211 -1120
  185. package/src/api/presentation/sections.ts +420 -0
  186. package/src/api/presentation/shapes.ts +274 -274
  187. package/src/api/presentation/sheets.ts +746 -585
  188. package/src/api/presentation/slideshow.ts +134 -134
  189. package/src/api/presentation/tables.ts +84 -84
  190. package/src/api/presentation/viewPresets.ts +241 -0
  191. package/src/api/presentation/views.ts +742 -673
  192. package/src/api/presentation/walkthroughs.ts +1201 -1201
  193. package/src/api/program/adjacency.ts +164 -164
  194. package/src/api/program/areas.ts +408 -408
  195. package/src/api/program/classification.ts +138 -138
  196. package/src/api/program/cores.ts +83 -83
  197. package/src/api/program/departments.ts +517 -517
  198. package/src/api/program/index.ts +73 -73
  199. package/src/api/program/labels.ts +332 -332
  200. package/src/api/program/layout.ts +422 -422
  201. package/src/api/program/metadata.ts +364 -364
  202. package/src/api/program/metrics.ts +120 -120
  203. package/src/api/program/site.ts +748 -748
  204. package/src/api/program/spreadsheet.ts +2698 -2698
  205. package/src/api/workspace/index.ts +612 -612
  206. package/src/errors/codes.ts +143 -136
  207. package/src/errors/envelope.ts +75 -75
  208. package/src/errors/index.ts +21 -21
  209. package/src/errors/plugin-error.ts +134 -134
  210. package/src/handles.ts +297 -297
  211. package/src/host-utils.ts +67 -67
  212. package/src/index.ts +7 -7
  213. package/src/massParameters.ops.ts +246 -246
  214. package/src/massParameters.ts +427 -427
  215. package/src/types.ts +16 -16
  216. package/test/drawingContract.test.mjs +288 -0
  217. package/test/errors.test.mjs +185 -184
  218. package/test/massParameters.test.mjs +952 -952
  219. package/test/presentationViewSettings.test.mjs +85 -0
  220. package/tsconfig.json +22 -22
  221. package/tsup.config.ts +12 -12
@@ -1,504 +1,504 @@
1
- import * as z from "zod"
2
- import { PluginApiReturn } from "../../../../types"
3
- import {
4
- Vec3Handle,
5
- UnderlayHandle,
6
- TerrainHandle,
7
- ImportJobHandle,
8
- ComponentHandle,
9
- } from "../../../../handles"
10
-
11
- /**
12
- * A file `source` accepted by every importer: an `https://` URL or a `data:`
13
- * URL (base64). Validated for shape at the boundary — other schemes (`http:`,
14
- * `blob:`, `file:`) are rejected, since the host page (HTTPS) cannot fetch them.
15
- */
16
- export const ImportSource = z
17
- .string()
18
- .min(1)
19
- .refine((s) => s.startsWith("https://") || s.startsWith("data:"), {
20
- message: "source must be an https:// URL or a data: URL",
21
- })
22
-
23
- /**
24
- * File import — bring external files into the Snaptrude **scene**.
25
- *
26
- * `core.io.import.*` ingests images, PDFs, CAD drawings, 3D models, and site
27
- * terrain and commits them to the model as traceable underlays, placed geometry,
28
- * or topography. It is the scene counterpart to `presentation.import.*` (which
29
- * drops assets on the Present canvas, not the model).
30
- *
31
- * ## The `source` argument
32
- * Every file importer takes a single `source` **string** — either an `https://`
33
- * URL or a `data:` URL (base64). A plugin worker cannot hand a browser `File`
34
- * across the boundary, so the host fetches `source` and rebuilds the `File`
35
- * itself. Both forms go through the same path.
36
- *
37
- * ## Return values
38
- * - `image` / `pdf` / `cadJson` → an {@linkcode UnderlayHandle} (a placed 2D
39
- * reference plane; drive it with `core.io.underlay.*`).
40
- * - `dwg` → an {@linkcode ImportJobHandle} — DWG conversion is a long server job,
41
- * so `dwg` returns immediately and you poll it with `core.io.job.*`.
42
- * - `model` → a {@linkcode ComponentHandle} (a placed 3D component).
43
- * - `terrain` → a {@linkcode TerrainHandle} (the project's single site terrain).
44
- *
45
- * Every importer **throws** on failure (bad source, unsupported format, engine
46
- * error). Accessed via `snaptrude.core.io.import`.
47
- */
48
- export abstract class PluginCoreIoImportApi {
49
- constructor() {}
50
-
51
- /**
52
- * Import a raster image as a scene **underlay** — a flat, textured reference
53
- * plane placed on a storey that you can trace over.
54
- *
55
- * Supported formats: PNG, JPG/JPEG, BMP. The image is placed on the target
56
- * storey's "Image" layer at ~50% opacity by default.
57
- *
58
- * To place the image on a **Present-mode sheet** instead of the scene, use
59
- * `presentation.import.image`.
60
- *
61
- * The plane is placed at the storey origin (the engine's import placement);
62
- * there is no placement parameter — calibrate size with
63
- * {@link PluginCoreIoUnderlayApi.setScale} after import.
64
- *
65
- * @param source - The image to import: an `https://` URL or a `data:` URL.
66
- * @param storey - Target storey number. Defaults to the active storey.
67
- * @param scale - Initial uniform scale factor applied to the plane. Calibrate
68
- * precisely afterwards with {@link PluginCoreIoUnderlayApi.setScale}.
69
- * @param opacity - Plane opacity, `0`..`1` (default ~`0.5`).
70
- * @param label - A name for the created underlay.
71
- * @returns the created {@linkcode UnderlayHandle}.
72
- * @throws if writes are disabled, the source can't be loaded, the format is
73
- * unsupported, or the engine fails to place the plane.
74
- *
75
- * @examplePrompt Import this floor plan image onto the ground floor to trace over
76
- * @examplePrompt Drop this survey PNG in as a reference underlay at 30% opacity
77
- * @examplePrompt Add a site plan image on storey 2
78
- * @examplePrompt Bring in a sketch to trace, faded to half opacity
79
- *
80
- * # Example
81
- * ```ts
82
- * // Place a floor plan on storey 1, then calibrate it to a real-world size.
83
- * const plan = await snaptrude.core.io.import.image(
84
- * "https://example.com/floorplan.png",
85
- * 1, // storey
86
- * undefined, // scale
87
- * 0.4, // opacity
88
- * "Ground floor plan",
89
- * )
90
- * // Make the plan's longest side exactly 40 project-units:
91
- * await snaptrude.core.io.underlay.setScale(plan, { planSize: 40 })
92
- * ```
93
- */
94
- public abstract image(
95
- source: string,
96
- storey?: number,
97
- scale?: number,
98
- opacity?: number,
99
- label?: string,
100
- ): PluginApiReturn<UnderlayHandle>
101
-
102
- /**
103
- * Import one page of a PDF as a scene **underlay** (a traceable reference plane).
104
- *
105
- * One PDF per storey, one page per call. To bring in several pages, call once
106
- * per page onto different storeys.
107
- *
108
- * @param source - The PDF to import: an `https://` URL or a `data:` URL.
109
- * @param storey - Target storey number. Defaults to the active storey.
110
- * @param page - 1-based page number to import (multi-page PDFs). Default `1`.
111
- * @param scale - A drawing-scale **label** that sizes the page in real-world
112
- * units, matching the project's unit mode: metric `"1:10"`..`"1:1000"` (e.g.
113
- * `"1:100"`) or imperial `` `1/8" = 1'` `` / `` `1" = 10'` ``. Must be a valid
114
- * label for the current unit mode (invalid → throws). Defaults to `"1:100"`
115
- * in metric projects and `` `1" = 1'` `` in imperial projects. Re-calibrate
116
- * afterwards with {@link PluginCoreIoUnderlayApi.setScale}.
117
- * @param opacity - Plane opacity, `0`..`1` (default ~`0.5`).
118
- * @returns the created {@linkcode UnderlayHandle}.
119
- * @throws if writes are disabled, the source can't be loaded, the storey
120
- * already has a PDF, the page is out of range, the scale label is invalid for
121
- * the unit mode, or the engine fails.
122
- *
123
- * @examplePrompt Import page 1 of this PDF floor plan onto storey 1
124
- * @examplePrompt Bring in the second page of this PDF as an underlay at 1:50
125
- * @examplePrompt Add this PDF plan to the ground floor to trace over
126
- * @examplePrompt Place page 3 of the drawing set on storey 3
127
- *
128
- * # Example
129
- * ```ts
130
- * const sheet = await snaptrude.core.io.import.pdf(
131
- * "https://example.com/plans.pdf",
132
- * 1, // storey
133
- * 2, // page
134
- * "1:100", // drawing scale
135
- * )
136
- * ```
137
- */
138
- public abstract pdf(
139
- source: string,
140
- storey?: number,
141
- page?: number,
142
- scale?: string,
143
- opacity?: number,
144
- ): PluginApiReturn<UnderlayHandle>
145
-
146
- /**
147
- * Import a **DWG** CAD drawing as a scene underlay. `.dwg` only (no DXF).
148
- *
149
- * DWG conversion runs on a server and can take minutes, so this **starts a job
150
- * and returns immediately** with an {@linkcode ImportJobHandle}. One CAD
151
- * import runs at a time: starting a second while one is in flight throws —
152
- * poll the first job to completion first. Poll it with
153
- * {@link PluginCoreIoJobApi.isComplete} / {@link PluginCoreIoJobApi.getStatus}, then call
154
- * {@link PluginCoreIoJobApi.getResult} to get the placed {@linkcode UnderlayHandle}.
155
- * For CAD you already have parsed as JSON, use {@link PluginCoreIoImportApi.cadJson}
156
- * (synchronous, no server round-trip).
157
- *
158
- * All CAD layers are imported (there is no layer filter).
159
- *
160
- * @param source - The `.dwg` file: an `https://` URL or a `data:` URL.
161
- * @param storey - Target storey number. Defaults to the active storey.
162
- * @returns an {@linkcode ImportJobHandle} to poll via `core.io.job.*`.
163
- * @throws if writes are disabled or the upload cannot be started. (Conversion
164
- * failures surface later via {@link PluginCoreIoJobApi.getError}.)
165
- *
166
- * @examplePrompt Import this DWG floor plan onto storey 1
167
- * @examplePrompt Bring in a CAD drawing as an underlay and tell me when it's ready
168
- * @examplePrompt Start importing this CAD file onto the ground floor
169
- *
170
- * # Example
171
- * ```ts
172
- * const job = await snaptrude.core.io.import.dwg("https://example.com/plan.dwg", 1)
173
- * // Poll until the server-side conversion finishes — always with a timeout
174
- * // (a UI-cancelled import stays "processing" forever).
175
- * const deadline = Date.now() + 5 * 60_000
176
- * while (!(await snaptrude.core.io.job.isComplete(job))) {
177
- * if (Date.now() > deadline) throw new Error("DWG import timed out")
178
- * await new Promise((r) => setTimeout(r, 2000))
179
- * }
180
- * // The DWG lands as a CAD underlay. CAD scaling isn't supported; manage it
181
- * // (list / fade / delete) via core.io.underlay.*.
182
- * const cad = await snaptrude.core.io.job.getResult(job)
183
- * if (cad) console.log("DWG imported as CAD underlay:", cad)
184
- * ```
185
- */
186
- public abstract dwg(
187
- source: string,
188
- storey?: number,
189
- ): PluginApiReturn<ImportJobHandle>
190
-
191
- /**
192
- * Sketch a CAD underlay from **already-parsed CAD JSON** — synchronous, with no
193
- * server round-trip. Use this when you hold parsed CAD data (e.g. produced by
194
- * your own converter); use {@link PluginCoreIoImportApi.dwg} for raw `.dwg`
195
- * files.
196
- *
197
- * All layers in the JSON are sketched (there is no layer filter), at the
198
- * drawing's own coordinates.
199
- *
200
- * @param cad - The parsed CAD data: `{ unit, geometry: [...] }` describing the
201
- * drawing's lines/arcs/layers.
202
- * @param storey - Target storey number. Defaults to the active storey.
203
- * @returns the created {@linkcode UnderlayHandle}.
204
- * @throws if writes are disabled, the JSON yields no drawable geometry, or the
205
- * drawing is out of bounds.
206
- *
207
- * @examplePrompt Sketch this parsed CAD JSON onto the active storey
208
- * @examplePrompt Draw these CAD curves as an underlay on storey 2
209
- * @examplePrompt Create a CAD sketch from this geometry data
210
- * @examplePrompt Import my converted CAD JSON onto the ground floor
211
- *
212
- * # Example
213
- * ```ts
214
- * const cad = await snaptrude.core.io.import.cadJson(
215
- * { unit: "mm", geometry: [{ type: "line", layer: "WALLS", ... }] },
216
- * 1,
217
- * )
218
- * ```
219
- */
220
- public abstract cadJson(
221
- cad: CadJsonInput,
222
- storey?: number,
223
- ): PluginApiReturn<UnderlayHandle>
224
-
225
- /**
226
- * Import a **3D model** file and place it in the scene as a component.
227
- *
228
- * Supported formats: SketchUp (`.skp`), FBX (`.fbx`), OBJ (`.obj`), 3DS
229
- * (`.3ds`), and zipped bundles (`.zip`). The file is converted on the server,
230
- * then placed. `.glb`/`.gltf`/`.ifc`/`.stl` and Rhino/Revit are not supported.
231
- *
232
- * The model is placed at its source dimensions (interpreted in the file's own
233
- * units); there is no import-time scale override.
234
- *
235
- * Unlike `dwg`, this call **waits for the server conversion**: a large model
236
- * may exceed the 60-second call cap and reject at the API boundary while the
237
- * import still completes in the background (the model then appears without a
238
- * handle being returned — re-query the scene to find it). Keep sources small.
239
- *
240
- * @param source - The model file: an `https://` URL or a `data:` URL.
241
- * @param storey - Target storey number. Defaults to the active storey.
242
- * @param format - The file's format. Inferred from `source` when omitted.
243
- * @param position - Where to place the model. Defaults to the storey origin.
244
- * @param label - A name for the placed component.
245
- * @returns the placed {@linkcode ComponentHandle}.
246
- * @throws if writes are disabled, the source can't be loaded, the format is
247
- * unsupported, conversion/placement fails, or the converted model cannot be
248
- * found in the library after upload; `PRECONDITION_FAILED`
249
- * (`details.engineCode: "TOOL_ACTIVE"`) while the interactive furniture tool is
250
- * active — finish or cancel it first.
251
- *
252
- * @examplePrompt Import this SketchUp model onto storey 1
253
- * @examplePrompt Bring in this OBJ file and place it at the origin
254
- * @examplePrompt Add this 3D model to the ground floor
255
- *
256
- * # Example
257
- * ```ts
258
- * const model = await snaptrude.core.io.import.model(
259
- * "https://example.com/tree.skp",
260
- * 1, // storey
261
- * "skp",
262
- * )
263
- * ```
264
- */
265
- public abstract model(
266
- source: string,
267
- storey?: number,
268
- format?: ModelFormat,
269
- position?: Vec3Handle,
270
- label?: string,
271
- ): PluginApiReturn<ComponentHandle>
272
-
273
- /**
274
- * Import **site terrain** for a location — Mapbox topography (elevation +
275
- * satellite/streets imagery, optional neighborhood buildings and parcels).
276
- *
277
- * One terrain per project (a singleton). `width`/`length` describe the
278
- * real-world extent of the area to load **in metres** (regardless of the
279
- * project's unit mode); the host converts them to the right map zoom
280
- * internally — you do not pass a zoom level.
281
- *
282
- * @param lat - Site latitude (degrees), `-90`..`90`.
283
- * @param lng - Site longitude (degrees), `-180`..`180`.
284
- * @param width - East–west extent of the area to load, in metres.
285
- * @param length - North–south extent of the area to load, in metres.
286
- * @param elevation - Include DEM elevation (a real terrain surface). Default `true`.
287
- * @param satellite - Drape satellite imagery. Default `true`.
288
- * @param neighborhood - Include surrounding context buildings. Default `false`.
289
- * @param parcels - Include site parcel boundaries. Default `false`.
290
- * @returns the created {@linkcode TerrainHandle}.
291
- * @throws if writes are disabled, a terrain already exists, or the map/mesh
292
- * generation fails.
293
- *
294
- * @examplePrompt Import the site terrain for this location, about 500m across
295
- * @examplePrompt Load the topography at 40.7128, -74.0060 for a 300 by 300 meter site
296
- * @examplePrompt Bring in satellite terrain for the site with the surrounding buildings
297
- * @examplePrompt Add site elevation for a 1km by 1km area at these coordinates
298
- *
299
- * # Example
300
- * ```ts
301
- * // 300m × 300m site with elevation + satellite imagery.
302
- * const terrain = await snaptrude.core.io.import.terrain(
303
- * 40.7128, // lat
304
- * -74.006, // lng
305
- * 300, // width (metres)
306
- * 300, // length (metres)
307
- * true, // elevation
308
- * true, // satellite
309
- * )
310
- * ```
311
- */
312
- public abstract terrain(
313
- lat: number,
314
- lng: number,
315
- width: number,
316
- length: number,
317
- elevation?: boolean,
318
- satellite?: boolean,
319
- neighborhood?: boolean,
320
- parcels?: boolean,
321
- ): PluginApiReturn<TerrainHandle>
322
-
323
- /**
324
- * Import an **EPW weather file** into the project's weather catalog.
325
- *
326
- * Uploads an EnergyPlus Weather (`.epw`) file and registers it as the
327
- * project's site weather, returning its catalog `weatherFileId` — the same id
328
- * {@link PluginAnalysisWeatherApi.getSeries}, `program.site.getWeather`, and a
329
- * daylight run's provenance report. Uploading a user file makes the site's
330
- * `selectionMethod` `"user-upload"`, overriding nearest-station resolution.
331
- *
332
- * **Mutating**: this writes project state and is subject to the plugin
333
- * write-gate — it throws when plugin writes are disabled. Unlike the file
334
- * underlay/model importers it returns no scene handle (a weather file is not
335
- * a scene object), only the catalog `{ weatherFileId, label }`.
336
- *
337
- * @param source - The `.epw` file to import: an `https://` URL or a `data:` URL.
338
- * @returns A {@linkcode PluginImportEpwResult} — the catalog `weatherFileId`
339
- * and a human-readable `label` (the station/file name).
340
- * @throws if writes are disabled, the source can't be loaded, or the file is
341
- * not a valid EPW.
342
- *
343
- * @examplePrompt Import this EPW weather file for the site
344
- * @examplePrompt Upload a custom weather file for the daylight analysis
345
- * @examplePrompt Use this .epw as the project's weather source
346
- *
347
- * # Example
348
- * ```ts
349
- * const { weatherFileId, label } = await snaptrude.core.io.import.epw(
350
- * "https://example.com/Chicago-OHare.epw",
351
- * )
352
- * console.log(`Weather set to ${label} (${weatherFileId})`)
353
- * ```
354
- */
355
- public abstract epw(source: string): PluginApiReturn<PluginImportEpwResult>
356
- }
357
-
358
- /**
359
- * Supported 3D model import formats. `.glb`/`.gltf`/`.ifc`/`.stl` and Rhino/Revit
360
- * are intentionally excluded (no in-app import path).
361
- */
362
- export const ModelFormat = z.enum(["skp", "fbx", "obj", "3ds", "zip"])
363
- export type ModelFormat = z.infer<typeof ModelFormat>
364
-
365
- /**
366
- * Parsed CAD JSON accepted by {@link PluginCoreIoImportApi.cadJson}. `unit` sets
367
- * the drawing's length unit; `geometry` is the array of CAD entities (lines,
368
- * polylines, arcs, circles), each tagged with its CAD `layer`. Additional fields
369
- * are passed through untouched.
370
- */
371
- export const CadJsonInput = z
372
- .object({
373
- unit: z.string().optional(),
374
- geometry: z.array(z.record(z.string(), z.unknown())).optional(),
375
- })
376
- .passthrough()
377
- export type CadJsonInput = z.infer<typeof CadJsonInput>
378
-
379
- /**
380
- * Arguments for {@link PluginCoreIoImportApi.image}.
381
- *
382
- * | Property | Type | Description |
383
- * |---|---|---|
384
- * | `source` | `string` | Image `https://` URL or `data:` URL (png/jpg/jpeg/bmp) |
385
- * | `storey` | `number`? | Target storey (default: active) |
386
- * | `scale` | `number`? | Initial uniform scale |
387
- * | `opacity` | `number`? | `0`..`1` (default ~`0.5`) |
388
- * | `label` | `string`? | Name for the underlay |
389
- */
390
- export const PluginImportImageArgs = z.object({
391
- source: ImportSource,
392
- storey: z.number().int().optional(),
393
- scale: z.number().positive().optional(),
394
- opacity: z.number().min(0).max(1).optional(),
395
- label: z.string().optional(),
396
- })
397
- export type PluginImportImageArgs = z.infer<typeof PluginImportImageArgs>
398
-
399
- /**
400
- * Arguments for {@link PluginCoreIoImportApi.pdf}.
401
- *
402
- * | Property | Type | Description |
403
- * |---|---|---|
404
- * | `source` | `string` | PDF `https://` URL or `data:` URL |
405
- * | `storey` | `number`? | Target storey (default: active) |
406
- * | `page` | `number`? | 1-based page (default `1`) |
407
- * | `scale` | `string`? | Drawing-scale label, e.g. `"1:100"` |
408
- * | `opacity` | `number`? | `0`..`1` (default ~`0.5`) |
409
- */
410
- export const PluginImportPdfArgs = z.object({
411
- source: ImportSource,
412
- storey: z.number().int().optional(),
413
- page: z.number().int().positive().optional(),
414
- scale: z.string().optional(),
415
- opacity: z.number().min(0).max(1).optional(),
416
- })
417
- export type PluginImportPdfArgs = z.infer<typeof PluginImportPdfArgs>
418
-
419
- /** Arguments for {@link PluginCoreIoImportApi.dwg}. */
420
- export const PluginImportDwgArgs = z.object({
421
- source: ImportSource,
422
- storey: z.number().int().optional(),
423
- })
424
- export type PluginImportDwgArgs = z.infer<typeof PluginImportDwgArgs>
425
-
426
- /** Arguments for {@link PluginCoreIoImportApi.cadJson}. */
427
- export const PluginImportCadJsonArgs = z.object({
428
- cad: CadJsonInput,
429
- storey: z.number().int().optional(),
430
- })
431
- export type PluginImportCadJsonArgs = z.infer<typeof PluginImportCadJsonArgs>
432
-
433
- /**
434
- * Arguments for {@link PluginCoreIoImportApi.model}.
435
- *
436
- * | Property | Type | Description |
437
- * |---|---|---|
438
- * | `source` | `string` | Model `https://` URL or `data:` URL |
439
- * | `storey` | `number`? | Target storey (default: active) |
440
- * | `format` | {@link ModelFormat}? | File format (inferred when omitted) |
441
- * | `position` | {@link Vec3Handle}? | Placement (default: storey origin) |
442
- * | `label` | `string`? | Name for the component |
443
- */
444
- export const PluginImportModelArgs = z.object({
445
- source: ImportSource,
446
- storey: z.number().int().optional(),
447
- format: ModelFormat.optional(),
448
- position: Vec3Handle.optional(),
449
- label: z.string().optional(),
450
- })
451
- export type PluginImportModelArgs = z.infer<typeof PluginImportModelArgs>
452
-
453
- /**
454
- * Arguments for {@link PluginCoreIoImportApi.terrain}.
455
- *
456
- * | Property | Type | Description |
457
- * |---|---|---|
458
- * | `lat` | `number` | Latitude (degrees), `-90`..`90` |
459
- * | `lng` | `number` | Longitude (degrees), `-180`..`180` |
460
- * | `width` | `number` | East–west extent, metres |
461
- * | `length` | `number` | North–south extent, metres |
462
- * | `elevation` | `boolean`? | Include DEM elevation (default `true`) |
463
- * | `satellite` | `boolean`? | Drape satellite imagery (default `true`) |
464
- * | `neighborhood` | `boolean`? | Include context buildings (default `false`) |
465
- * | `parcels` | `boolean`? | Include parcel boundaries (default `false`) |
466
- */
467
- export const PluginImportTerrainArgs = z.object({
468
- lat: z.number().min(-90).max(90),
469
- lng: z.number().min(-180).max(180),
470
- width: z.number().positive().finite(),
471
- length: z.number().positive().finite(),
472
- elevation: z.boolean().optional(),
473
- satellite: z.boolean().optional(),
474
- neighborhood: z.boolean().optional(),
475
- parcels: z.boolean().optional(),
476
- })
477
- export type PluginImportTerrainArgs = z.infer<typeof PluginImportTerrainArgs>
478
-
479
- /**
480
- * Arguments for {@link PluginCoreIoImportApi.epw}.
481
- *
482
- * | Property | Type | Description |
483
- * |---|---|---|
484
- * | `source` | `string` | EPW `https://` URL or `data:` URL |
485
- */
486
- export const PluginImportEpwArgs = z.object({
487
- source: ImportSource,
488
- })
489
- export type PluginImportEpwArgs = z.infer<typeof PluginImportEpwArgs>
490
-
491
- /**
492
- * Result of {@link PluginCoreIoImportApi.epw} — the imported weather file's
493
- * catalog entry.
494
- *
495
- * | Property | Type | Description |
496
- * |---|---|---|
497
- * | `weatherFileId` | `string` | Catalog id (same key as `analysis.weather`'s `weatherFileId`) |
498
- * | `label` | `string` | Human-readable name (station / file name) |
499
- */
500
- export const PluginImportEpwResult = z.object({
501
- weatherFileId: z.string(),
502
- label: z.string(),
503
- })
504
- export type PluginImportEpwResult = z.infer<typeof PluginImportEpwResult>
1
+ import * as z from "zod"
2
+ import { PluginApiReturn } from "../../../../types"
3
+ import {
4
+ Vec3Handle,
5
+ UnderlayHandle,
6
+ TerrainHandle,
7
+ ImportJobHandle,
8
+ ComponentHandle,
9
+ } from "../../../../handles"
10
+
11
+ /**
12
+ * A file `source` accepted by every importer: an `https://` URL or a `data:`
13
+ * URL (base64). Validated for shape at the boundary — other schemes (`http:`,
14
+ * `blob:`, `file:`) are rejected, since the host page (HTTPS) cannot fetch them.
15
+ */
16
+ export const ImportSource = z
17
+ .string()
18
+ .min(1)
19
+ .refine((s) => s.startsWith("https://") || s.startsWith("data:"), {
20
+ message: "source must be an https:// URL or a data: URL",
21
+ })
22
+
23
+ /**
24
+ * File import — bring external files into the Snaptrude **scene**.
25
+ *
26
+ * `core.io.import.*` ingests images, PDFs, CAD drawings, 3D models, and site
27
+ * terrain and commits them to the model as traceable underlays, placed geometry,
28
+ * or topography. It is the scene counterpart to `presentation.import.*` (which
29
+ * drops assets on the Present canvas, not the model).
30
+ *
31
+ * ## The `source` argument
32
+ * Every file importer takes a single `source` **string** — either an `https://`
33
+ * URL or a `data:` URL (base64). A plugin worker cannot hand a browser `File`
34
+ * across the boundary, so the host fetches `source` and rebuilds the `File`
35
+ * itself. Both forms go through the same path.
36
+ *
37
+ * ## Return values
38
+ * - `image` / `pdf` / `cadJson` → an {@linkcode UnderlayHandle} (a placed 2D
39
+ * reference plane; drive it with `core.io.underlay.*`).
40
+ * - `dwg` → an {@linkcode ImportJobHandle} — DWG conversion is a long server job,
41
+ * so `dwg` returns immediately and you poll it with `core.io.job.*`.
42
+ * - `model` → a {@linkcode ComponentHandle} (a placed 3D component).
43
+ * - `terrain` → a {@linkcode TerrainHandle} (the project's single site terrain).
44
+ *
45
+ * Every importer **throws** on failure (bad source, unsupported format, engine
46
+ * error). Accessed via `snaptrude.core.io.import`.
47
+ */
48
+ export abstract class PluginCoreIoImportApi {
49
+ constructor() {}
50
+
51
+ /**
52
+ * Import a raster image as a scene **underlay** — a flat, textured reference
53
+ * plane placed on a storey that you can trace over.
54
+ *
55
+ * Supported formats: PNG, JPG/JPEG, BMP. The image is placed on the target
56
+ * storey's "Image" layer at ~50% opacity by default.
57
+ *
58
+ * To place the image on a **Present-mode sheet** instead of the scene, use
59
+ * `presentation.import.image`.
60
+ *
61
+ * The plane is placed at the storey origin (the engine's import placement);
62
+ * there is no placement parameter — calibrate size with
63
+ * {@link PluginCoreIoUnderlayApi.setScale} after import.
64
+ *
65
+ * @param source - The image to import: an `https://` URL or a `data:` URL.
66
+ * @param storey - Target storey number. Defaults to the active storey.
67
+ * @param scale - Initial uniform scale factor applied to the plane. Calibrate
68
+ * precisely afterwards with {@link PluginCoreIoUnderlayApi.setScale}.
69
+ * @param opacity - Plane opacity, `0`..`1` (default ~`0.5`).
70
+ * @param label - A name for the created underlay.
71
+ * @returns the created {@linkcode UnderlayHandle}.
72
+ * @throws if writes are disabled, the source can't be loaded, the format is
73
+ * unsupported, or the engine fails to place the plane.
74
+ *
75
+ * @examplePrompt Import this floor plan image onto the ground floor to trace over
76
+ * @examplePrompt Drop this survey PNG in as a reference underlay at 30% opacity
77
+ * @examplePrompt Add a site plan image on storey 2
78
+ * @examplePrompt Bring in a sketch to trace, faded to half opacity
79
+ *
80
+ * # Example
81
+ * ```ts
82
+ * // Place a floor plan on storey 1, then calibrate it to a real-world size.
83
+ * const plan = await snaptrude.core.io.import.image(
84
+ * "https://example.com/floorplan.png",
85
+ * 1, // storey
86
+ * undefined, // scale
87
+ * 0.4, // opacity
88
+ * "Ground floor plan",
89
+ * )
90
+ * // Make the plan's longest side exactly 40 project-units:
91
+ * await snaptrude.core.io.underlay.setScale(plan, { planSize: 40 })
92
+ * ```
93
+ */
94
+ public abstract image(
95
+ source: string,
96
+ storey?: number,
97
+ scale?: number,
98
+ opacity?: number,
99
+ label?: string,
100
+ ): PluginApiReturn<UnderlayHandle>
101
+
102
+ /**
103
+ * Import one page of a PDF as a scene **underlay** (a traceable reference plane).
104
+ *
105
+ * One PDF per storey, one page per call. To bring in several pages, call once
106
+ * per page onto different storeys.
107
+ *
108
+ * @param source - The PDF to import: an `https://` URL or a `data:` URL.
109
+ * @param storey - Target storey number. Defaults to the active storey.
110
+ * @param page - 1-based page number to import (multi-page PDFs). Default `1`.
111
+ * @param scale - A drawing-scale **label** that sizes the page in real-world
112
+ * units, matching the project's unit mode: metric `"1:10"`..`"1:1000"` (e.g.
113
+ * `"1:100"`) or imperial `` `1/8" = 1'` `` / `` `1" = 10'` ``. Must be a valid
114
+ * label for the current unit mode (invalid → throws). Defaults to `"1:100"`
115
+ * in metric projects and `` `1" = 1'` `` in imperial projects. Re-calibrate
116
+ * afterwards with {@link PluginCoreIoUnderlayApi.setScale}.
117
+ * @param opacity - Plane opacity, `0`..`1` (default ~`0.5`).
118
+ * @returns the created {@linkcode UnderlayHandle}.
119
+ * @throws if writes are disabled, the source can't be loaded, the storey
120
+ * already has a PDF, the page is out of range, the scale label is invalid for
121
+ * the unit mode, or the engine fails.
122
+ *
123
+ * @examplePrompt Import page 1 of this PDF floor plan onto storey 1
124
+ * @examplePrompt Bring in the second page of this PDF as an underlay at 1:50
125
+ * @examplePrompt Add this PDF plan to the ground floor to trace over
126
+ * @examplePrompt Place page 3 of the drawing set on storey 3
127
+ *
128
+ * # Example
129
+ * ```ts
130
+ * const sheet = await snaptrude.core.io.import.pdf(
131
+ * "https://example.com/plans.pdf",
132
+ * 1, // storey
133
+ * 2, // page
134
+ * "1:100", // drawing scale
135
+ * )
136
+ * ```
137
+ */
138
+ public abstract pdf(
139
+ source: string,
140
+ storey?: number,
141
+ page?: number,
142
+ scale?: string,
143
+ opacity?: number,
144
+ ): PluginApiReturn<UnderlayHandle>
145
+
146
+ /**
147
+ * Import a **DWG** CAD drawing as a scene underlay. `.dwg` only (no DXF).
148
+ *
149
+ * DWG conversion runs on a server and can take minutes, so this **starts a job
150
+ * and returns immediately** with an {@linkcode ImportJobHandle}. One CAD
151
+ * import runs at a time: starting a second while one is in flight throws —
152
+ * poll the first job to completion first. Poll it with
153
+ * {@link PluginCoreIoJobApi.isComplete} / {@link PluginCoreIoJobApi.getStatus}, then call
154
+ * {@link PluginCoreIoJobApi.getResult} to get the placed {@linkcode UnderlayHandle}.
155
+ * For CAD you already have parsed as JSON, use {@link PluginCoreIoImportApi.cadJson}
156
+ * (synchronous, no server round-trip).
157
+ *
158
+ * All CAD layers are imported (there is no layer filter).
159
+ *
160
+ * @param source - The `.dwg` file: an `https://` URL or a `data:` URL.
161
+ * @param storey - Target storey number. Defaults to the active storey.
162
+ * @returns an {@linkcode ImportJobHandle} to poll via `core.io.job.*`.
163
+ * @throws if writes are disabled or the upload cannot be started. (Conversion
164
+ * failures surface later via {@link PluginCoreIoJobApi.getError}.)
165
+ *
166
+ * @examplePrompt Import this DWG floor plan onto storey 1
167
+ * @examplePrompt Bring in a CAD drawing as an underlay and tell me when it's ready
168
+ * @examplePrompt Start importing this CAD file onto the ground floor
169
+ *
170
+ * # Example
171
+ * ```ts
172
+ * const job = await snaptrude.core.io.import.dwg("https://example.com/plan.dwg", 1)
173
+ * // Poll until the server-side conversion finishes — always with a timeout
174
+ * // (a UI-cancelled import stays "processing" forever).
175
+ * const deadline = Date.now() + 5 * 60_000
176
+ * while (!(await snaptrude.core.io.job.isComplete(job))) {
177
+ * if (Date.now() > deadline) throw new Error("DWG import timed out")
178
+ * await new Promise((r) => setTimeout(r, 2000))
179
+ * }
180
+ * // The DWG lands as a CAD underlay. CAD scaling isn't supported; manage it
181
+ * // (list / fade / delete) via core.io.underlay.*.
182
+ * const cad = await snaptrude.core.io.job.getResult(job)
183
+ * if (cad) console.log("DWG imported as CAD underlay:", cad)
184
+ * ```
185
+ */
186
+ public abstract dwg(
187
+ source: string,
188
+ storey?: number,
189
+ ): PluginApiReturn<ImportJobHandle>
190
+
191
+ /**
192
+ * Sketch a CAD underlay from **already-parsed CAD JSON** — synchronous, with no
193
+ * server round-trip. Use this when you hold parsed CAD data (e.g. produced by
194
+ * your own converter); use {@link PluginCoreIoImportApi.dwg} for raw `.dwg`
195
+ * files.
196
+ *
197
+ * All layers in the JSON are sketched (there is no layer filter), at the
198
+ * drawing's own coordinates.
199
+ *
200
+ * @param cad - The parsed CAD data: `{ unit, geometry: [...] }` describing the
201
+ * drawing's lines/arcs/layers.
202
+ * @param storey - Target storey number. Defaults to the active storey.
203
+ * @returns the created {@linkcode UnderlayHandle}.
204
+ * @throws if writes are disabled, the JSON yields no drawable geometry, or the
205
+ * drawing is out of bounds.
206
+ *
207
+ * @examplePrompt Sketch this parsed CAD JSON onto the active storey
208
+ * @examplePrompt Draw these CAD curves as an underlay on storey 2
209
+ * @examplePrompt Create a CAD sketch from this geometry data
210
+ * @examplePrompt Import my converted CAD JSON onto the ground floor
211
+ *
212
+ * # Example
213
+ * ```ts
214
+ * const cad = await snaptrude.core.io.import.cadJson(
215
+ * { unit: "mm", geometry: [{ type: "line", layer: "WALLS", ... }] },
216
+ * 1,
217
+ * )
218
+ * ```
219
+ */
220
+ public abstract cadJson(
221
+ cad: CadJsonInput,
222
+ storey?: number,
223
+ ): PluginApiReturn<UnderlayHandle>
224
+
225
+ /**
226
+ * Import a **3D model** file and place it in the scene as a component.
227
+ *
228
+ * Supported formats: SketchUp (`.skp`), FBX (`.fbx`), OBJ (`.obj`), 3DS
229
+ * (`.3ds`), and zipped bundles (`.zip`). The file is converted on the server,
230
+ * then placed. `.glb`/`.gltf`/`.ifc`/`.stl` and Rhino/Revit are not supported.
231
+ *
232
+ * The model is placed at its source dimensions (interpreted in the file's own
233
+ * units); there is no import-time scale override.
234
+ *
235
+ * Unlike `dwg`, this call **waits for the server conversion**: a large model
236
+ * may exceed the 60-second call cap and reject at the API boundary while the
237
+ * import still completes in the background (the model then appears without a
238
+ * handle being returned — re-query the scene to find it). Keep sources small.
239
+ *
240
+ * @param source - The model file: an `https://` URL or a `data:` URL.
241
+ * @param storey - Target storey number. Defaults to the active storey.
242
+ * @param format - The file's format. Inferred from `source` when omitted.
243
+ * @param position - Where to place the model. Defaults to the storey origin.
244
+ * @param label - A name for the placed component.
245
+ * @returns the placed {@linkcode ComponentHandle}.
246
+ * @throws if writes are disabled, the source can't be loaded, the format is
247
+ * unsupported, conversion/placement fails, or the converted model cannot be
248
+ * found in the library after upload; `PRECONDITION_FAILED`
249
+ * (`details.engineCode: "TOOL_ACTIVE"`) while the interactive furniture tool is
250
+ * active — finish or cancel it first.
251
+ *
252
+ * @examplePrompt Import this SketchUp model onto storey 1
253
+ * @examplePrompt Bring in this OBJ file and place it at the origin
254
+ * @examplePrompt Add this 3D model to the ground floor
255
+ *
256
+ * # Example
257
+ * ```ts
258
+ * const model = await snaptrude.core.io.import.model(
259
+ * "https://example.com/tree.skp",
260
+ * 1, // storey
261
+ * "skp",
262
+ * )
263
+ * ```
264
+ */
265
+ public abstract model(
266
+ source: string,
267
+ storey?: number,
268
+ format?: ModelFormat,
269
+ position?: Vec3Handle,
270
+ label?: string,
271
+ ): PluginApiReturn<ComponentHandle>
272
+
273
+ /**
274
+ * Import **site terrain** for a location — Mapbox topography (elevation +
275
+ * satellite/streets imagery, optional neighborhood buildings and parcels).
276
+ *
277
+ * One terrain per project (a singleton). `width`/`length` describe the
278
+ * real-world extent of the area to load **in metres** (regardless of the
279
+ * project's unit mode); the host converts them to the right map zoom
280
+ * internally — you do not pass a zoom level.
281
+ *
282
+ * @param lat - Site latitude (degrees), `-90`..`90`.
283
+ * @param lng - Site longitude (degrees), `-180`..`180`.
284
+ * @param width - East–west extent of the area to load, in metres.
285
+ * @param length - North–south extent of the area to load, in metres.
286
+ * @param elevation - Include DEM elevation (a real terrain surface). Default `true`.
287
+ * @param satellite - Drape satellite imagery. Default `true`.
288
+ * @param neighborhood - Include surrounding context buildings. Default `false`.
289
+ * @param parcels - Include site parcel boundaries. Default `false`.
290
+ * @returns the created {@linkcode TerrainHandle}.
291
+ * @throws if writes are disabled, a terrain already exists, or the map/mesh
292
+ * generation fails.
293
+ *
294
+ * @examplePrompt Import the site terrain for this location, about 500m across
295
+ * @examplePrompt Load the topography at 40.7128, -74.0060 for a 300 by 300 meter site
296
+ * @examplePrompt Bring in satellite terrain for the site with the surrounding buildings
297
+ * @examplePrompt Add site elevation for a 1km by 1km area at these coordinates
298
+ *
299
+ * # Example
300
+ * ```ts
301
+ * // 300m × 300m site with elevation + satellite imagery.
302
+ * const terrain = await snaptrude.core.io.import.terrain(
303
+ * 40.7128, // lat
304
+ * -74.006, // lng
305
+ * 300, // width (metres)
306
+ * 300, // length (metres)
307
+ * true, // elevation
308
+ * true, // satellite
309
+ * )
310
+ * ```
311
+ */
312
+ public abstract terrain(
313
+ lat: number,
314
+ lng: number,
315
+ width: number,
316
+ length: number,
317
+ elevation?: boolean,
318
+ satellite?: boolean,
319
+ neighborhood?: boolean,
320
+ parcels?: boolean,
321
+ ): PluginApiReturn<TerrainHandle>
322
+
323
+ /**
324
+ * Import an **EPW weather file** into the project's weather catalog.
325
+ *
326
+ * Uploads an EnergyPlus Weather (`.epw`) file and registers it as the
327
+ * project's site weather, returning its catalog `weatherFileId` — the same id
328
+ * {@link PluginAnalysisWeatherApi.getSeries}, `program.site.getWeather`, and a
329
+ * daylight run's provenance report. Uploading a user file makes the site's
330
+ * `selectionMethod` `"user-upload"`, overriding nearest-station resolution.
331
+ *
332
+ * **Mutating**: this writes project state and is subject to the plugin
333
+ * write-gate — it throws when plugin writes are disabled. Unlike the file
334
+ * underlay/model importers it returns no scene handle (a weather file is not
335
+ * a scene object), only the catalog `{ weatherFileId, label }`.
336
+ *
337
+ * @param source - The `.epw` file to import: an `https://` URL or a `data:` URL.
338
+ * @returns A {@linkcode PluginImportEpwResult} — the catalog `weatherFileId`
339
+ * and a human-readable `label` (the station/file name).
340
+ * @throws if writes are disabled, the source can't be loaded, or the file is
341
+ * not a valid EPW.
342
+ *
343
+ * @examplePrompt Import this EPW weather file for the site
344
+ * @examplePrompt Upload a custom weather file for the daylight analysis
345
+ * @examplePrompt Use this .epw as the project's weather source
346
+ *
347
+ * # Example
348
+ * ```ts
349
+ * const { weatherFileId, label } = await snaptrude.core.io.import.epw(
350
+ * "https://example.com/Chicago-OHare.epw",
351
+ * )
352
+ * console.log(`Weather set to ${label} (${weatherFileId})`)
353
+ * ```
354
+ */
355
+ public abstract epw(source: string): PluginApiReturn<PluginImportEpwResult>
356
+ }
357
+
358
+ /**
359
+ * Supported 3D model import formats. `.glb`/`.gltf`/`.ifc`/`.stl` and Rhino/Revit
360
+ * are intentionally excluded (no in-app import path).
361
+ */
362
+ export const ModelFormat = z.enum(["skp", "fbx", "obj", "3ds", "zip"])
363
+ export type ModelFormat = z.infer<typeof ModelFormat>
364
+
365
+ /**
366
+ * Parsed CAD JSON accepted by {@link PluginCoreIoImportApi.cadJson}. `unit` sets
367
+ * the drawing's length unit; `geometry` is the array of CAD entities (lines,
368
+ * polylines, arcs, circles), each tagged with its CAD `layer`. Additional fields
369
+ * are passed through untouched.
370
+ */
371
+ export const CadJsonInput = z
372
+ .object({
373
+ unit: z.string().optional(),
374
+ geometry: z.array(z.record(z.string(), z.unknown())).optional(),
375
+ })
376
+ .passthrough()
377
+ export type CadJsonInput = z.infer<typeof CadJsonInput>
378
+
379
+ /**
380
+ * Arguments for {@link PluginCoreIoImportApi.image}.
381
+ *
382
+ * | Property | Type | Description |
383
+ * |---|---|---|
384
+ * | `source` | `string` | Image `https://` URL or `data:` URL (png/jpg/jpeg/bmp) |
385
+ * | `storey` | `number`? | Target storey (default: active) |
386
+ * | `scale` | `number`? | Initial uniform scale |
387
+ * | `opacity` | `number`? | `0`..`1` (default ~`0.5`) |
388
+ * | `label` | `string`? | Name for the underlay |
389
+ */
390
+ export const PluginImportImageArgs = z.object({
391
+ source: ImportSource,
392
+ storey: z.number().int().optional(),
393
+ scale: z.number().positive().optional(),
394
+ opacity: z.number().min(0).max(1).optional(),
395
+ label: z.string().optional(),
396
+ })
397
+ export type PluginImportImageArgs = z.infer<typeof PluginImportImageArgs>
398
+
399
+ /**
400
+ * Arguments for {@link PluginCoreIoImportApi.pdf}.
401
+ *
402
+ * | Property | Type | Description |
403
+ * |---|---|---|
404
+ * | `source` | `string` | PDF `https://` URL or `data:` URL |
405
+ * | `storey` | `number`? | Target storey (default: active) |
406
+ * | `page` | `number`? | 1-based page (default `1`) |
407
+ * | `scale` | `string`? | Drawing-scale label, e.g. `"1:100"` |
408
+ * | `opacity` | `number`? | `0`..`1` (default ~`0.5`) |
409
+ */
410
+ export const PluginImportPdfArgs = z.object({
411
+ source: ImportSource,
412
+ storey: z.number().int().optional(),
413
+ page: z.number().int().positive().optional(),
414
+ scale: z.string().optional(),
415
+ opacity: z.number().min(0).max(1).optional(),
416
+ })
417
+ export type PluginImportPdfArgs = z.infer<typeof PluginImportPdfArgs>
418
+
419
+ /** Arguments for {@link PluginCoreIoImportApi.dwg}. */
420
+ export const PluginImportDwgArgs = z.object({
421
+ source: ImportSource,
422
+ storey: z.number().int().optional(),
423
+ })
424
+ export type PluginImportDwgArgs = z.infer<typeof PluginImportDwgArgs>
425
+
426
+ /** Arguments for {@link PluginCoreIoImportApi.cadJson}. */
427
+ export const PluginImportCadJsonArgs = z.object({
428
+ cad: CadJsonInput,
429
+ storey: z.number().int().optional(),
430
+ })
431
+ export type PluginImportCadJsonArgs = z.infer<typeof PluginImportCadJsonArgs>
432
+
433
+ /**
434
+ * Arguments for {@link PluginCoreIoImportApi.model}.
435
+ *
436
+ * | Property | Type | Description |
437
+ * |---|---|---|
438
+ * | `source` | `string` | Model `https://` URL or `data:` URL |
439
+ * | `storey` | `number`? | Target storey (default: active) |
440
+ * | `format` | {@link ModelFormat}? | File format (inferred when omitted) |
441
+ * | `position` | {@link Vec3Handle}? | Placement (default: storey origin) |
442
+ * | `label` | `string`? | Name for the component |
443
+ */
444
+ export const PluginImportModelArgs = z.object({
445
+ source: ImportSource,
446
+ storey: z.number().int().optional(),
447
+ format: ModelFormat.optional(),
448
+ position: Vec3Handle.optional(),
449
+ label: z.string().optional(),
450
+ })
451
+ export type PluginImportModelArgs = z.infer<typeof PluginImportModelArgs>
452
+
453
+ /**
454
+ * Arguments for {@link PluginCoreIoImportApi.terrain}.
455
+ *
456
+ * | Property | Type | Description |
457
+ * |---|---|---|
458
+ * | `lat` | `number` | Latitude (degrees), `-90`..`90` |
459
+ * | `lng` | `number` | Longitude (degrees), `-180`..`180` |
460
+ * | `width` | `number` | East–west extent, metres |
461
+ * | `length` | `number` | North–south extent, metres |
462
+ * | `elevation` | `boolean`? | Include DEM elevation (default `true`) |
463
+ * | `satellite` | `boolean`? | Drape satellite imagery (default `true`) |
464
+ * | `neighborhood` | `boolean`? | Include context buildings (default `false`) |
465
+ * | `parcels` | `boolean`? | Include parcel boundaries (default `false`) |
466
+ */
467
+ export const PluginImportTerrainArgs = z.object({
468
+ lat: z.number().min(-90).max(90),
469
+ lng: z.number().min(-180).max(180),
470
+ width: z.number().positive().finite(),
471
+ length: z.number().positive().finite(),
472
+ elevation: z.boolean().optional(),
473
+ satellite: z.boolean().optional(),
474
+ neighborhood: z.boolean().optional(),
475
+ parcels: z.boolean().optional(),
476
+ })
477
+ export type PluginImportTerrainArgs = z.infer<typeof PluginImportTerrainArgs>
478
+
479
+ /**
480
+ * Arguments for {@link PluginCoreIoImportApi.epw}.
481
+ *
482
+ * | Property | Type | Description |
483
+ * |---|---|---|
484
+ * | `source` | `string` | EPW `https://` URL or `data:` URL |
485
+ */
486
+ export const PluginImportEpwArgs = z.object({
487
+ source: ImportSource,
488
+ })
489
+ export type PluginImportEpwArgs = z.infer<typeof PluginImportEpwArgs>
490
+
491
+ /**
492
+ * Result of {@link PluginCoreIoImportApi.epw} — the imported weather file's
493
+ * catalog entry.
494
+ *
495
+ * | Property | Type | Description |
496
+ * |---|---|---|
497
+ * | `weatherFileId` | `string` | Catalog id (same key as `analysis.weather`'s `weatherFileId`) |
498
+ * | `label` | `string` | Human-readable name (station / file name) |
499
+ */
500
+ export const PluginImportEpwResult = z.object({
501
+ weatherFileId: z.string(),
502
+ label: z.string(),
503
+ })
504
+ export type PluginImportEpwResult = z.infer<typeof PluginImportEpwResult>