@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
package/CHANGELOG.md CHANGED
@@ -1,402 +1,408 @@
1
- # @snaptrude/plugin-core
2
-
3
- ## 0.9.16
4
-
5
- ### Patch Changes
6
-
7
- - e8d4ec3: Add camera pose and navigation APIs to `core.camera` (`getPose`, `setPose`, `move`, `rotate`, `getNavigationSettings`, `updateNavigationSettings`) and a new `presentation.walkthroughs` namespace for camera paths: create from keyframes, floor points or camera-move presets; edit walkthroughs and their keyframes; `sample`, `listIssues` and `capture` them; play them with `start`/`stop`/`getState`; and `export` them as a video onto the Present canvas as a job (`getJob`, `cancelJob`, `listJobs`).
8
-
9
- ## 0.9.15
10
-
11
- ### Patch Changes
12
-
13
- - 4bfe118: `core.layers`: the layers in the Layers panel in the left sidebar. `list()`, `get(layerId)` and `getActive()` read the active design option's layers (default first, with `objectCount`); `create(name)` adds a layer to the active option (undoable, not activated); `rename(layerId, name)` renames (not undoable, default layer refused); `update(layerId, { locked?, hidden? })` locks and hides per-user (undoable; other option keys are rejected); `setActive(layerId)` sets where new objects land; `delete(layerId, { deleteObjects: true } | { targetLayerId })` deletes with an explicit disposition (server-validated; refused for the default, last, locked or shared layer; treat as permanent); `assign(componentIds, layerId)` moves objects onto a layer (undoable, locked objects skipped). Layer ids are the ids `core.proposals` records list as `bucketIds` / `defaultBucketId` (the proposals docs now point there).
14
-
15
- MOVED (compatible at runtime): the former `core.layers` (each storey's sketch surfaces and imported underlays, ids `ly_*`) is now `core.storeys.layers`, with the same methods (`list`, `get`, `getActive`, `setActive`, `update`) and signatures. Plugins that still call the old shape on `core.layers` (`list(storeyId)`, `getActive(storeyId)`, `get` / `setActive` / `update(layerId, hidden)` with a `ly_*` id) keep working: the host routes those calls to `core.storeys.layers`. They are deprecated; new code should call `snaptrude.core.storeys.layers.*` directly. Only the TypeScript names change: `PluginCoreLayersApi` -> `PluginCoreStoreysLayersApi`, `PluginCoreLayer` -> `PluginCoreStoreyLayer`, `PluginCoreLayerType` -> `PluginCoreStoreyLayerType`, and `PluginCoreLayers*Args` / `PluginCoreLayers*Result` -> `PluginCoreStoreysLayers*Args` / `PluginCoreStoreysLayers*Result`; the names `PluginCoreLayersApi`, `PluginCoreLayer` and `PluginCoreLayers*` now describe the Layers panel.
16
-
17
- - 80c1a2b: `presentation.camera`: the Present-mode canvas camera. `capture()` screenshots what the Present canvas currently shows (the visible area at the current pan and zoom, as a base64 data URL with its pixel size); `get()` reads pan, zoom, the visible page rectangle and the sheet on screen; `set({ x?, y?, zoom? })` pans and zooms; `zoomToFit()` frames every sheet; `zoomTo(shapeId)` frames one sheet or placed shape. View-state only (not write-gated, not undoable); requires Present mode to be open.
18
- - 0e3df96: Raked and gabled walls: `design.create.walls` takes a trailing `topProfiles` argument and `design.create.wallRuns` items a `topProfiles` field — one optional `PluginWallTopProfile` per centerline curve (`[{ along, height }, …]`, `along` 0→1, at least two points). The wall is built as a real Wall with the given top edge (two points = single slope, three = gable), mitred at run corners, in the same single undo entry; it is non-parametric (the parametric wall graph does not rebuild it), like a wall whose top edge was dragged by hand.
19
- - d84ea44: `design.update.setObjectType(components, objectType)`: convert masses into real walls, roofs, slabs or floors (`objectType` uses the `design.query` `type` vocabulary: `"wall" | "roof" | "slab" | "floor"`) — the properties panel's Object Type dropdown, headless, keeping the geometry exactly as built (no flattening to a default thickness) and keeping the component's handle. Instance families convert together. One undoable operation.
20
-
21
- ## 0.9.14
22
-
23
- ### Patch Changes
24
-
25
- - 22c919b: Curved-geometry contracts — spline curves, primitive solids, curved-surface reads, and the shared mass-parameter record.
26
- - **`spline` handle kind** — `SplineHandle` joins `CurveHandle` (`line | arc | spline`), so every `core.geom.query.curve` member and `profileFromCurves` take a spline, and joins `ArenaKind`, so `core.handles.release`, `releaseAll("spline")` and `endScope`'s `retain` list accept one. Closed spline loops never exist as a single handle: a closed run is authored directly as a profile.
27
- - **`core.geom.create.splineFromPoints`** — an open NURBS curve passing exactly THROUGH 3..256 ordered points (interpolated, not control points).
28
- - **`core.geom.create.profileFromSplinePoints`** — the closed counterpart: a smooth closed profile through the points, ready for `contourFromProfile` → extrude/loft/sweep. No mixed straight/spline input in v1 — compose those with `profileFromCurves`.
29
- - **Primitive solids** — `brepFromSphere`, `brepFromCylinder`, `brepFromCone` (optional `topRadius`, default 0 = apex) and `brepFromTorus`. Positional plain `{x, y, z}` args matching the `brepFromRevolution` family; `base` is the bottom-cap centre for cylinder/cone, `centre` for sphere/torus. Each result carries an exact analytic surface and a creation recipe.
30
- - **`core.geom.create.brepFromSweep`** — `options.transition` gains `"round"` beside `"miter"` and `{ bevel }`: corners carried around a tangent circular arc, so the corner patch is a real curved surface.
31
- - **`core.geom.create.brepFromRevolution`** — JSDoc rewritten to the implemented gate: segments may run at any angle to the axis (an incline revolves into a cone, an arc into a sphere or torus section). Two rules, checked in this order: the axis must lie in the profile's plane or run parallel to it (a parallel axis offset from the plane is judged by its projection into the plane, as 0.9.13 built it; a skew axis is `PRECONDITION_FAILED`), then the whole profile must stay on one side of the axis — touching it is legal (dome poles, cylinder inner edges), crossing it is not (`VALIDATION`).
32
- - **`core.geom.query.brep.getEdgeCurve`** — a fourth `"spline"` variant (`degree`, `controlPoints`, expanded `knots`, endpoints, `length`), so reading back a spline edge no longer throws.
33
- - **`core.geom.query.brep.getVolume`** — exact enclosed volume integrated from the surfaces, free of tessellation error.
34
- - **`core.geom.query.face.getSurfaceKind`** — `"plane" | "cylinder" | "cone" | "sphere" | "torus" | "spline" | null`: the one read that tells a real curved face from a faceted approximation of one.
35
- - **`core.geom.query.curve`** — class JSDoc gains the per-member spline support table; unsupported members refuse with `PRECONDITION_FAILED` rather than answering wrongly.
36
- - **`design.query.getParameters`** — read a mass's creation-parameter record (`kind` + values, world coordinates), or `null` for geometry with no honest recipe.
37
- - **`massParameters`** (new module, exported from the package root) — `MassParametersRecord` / `ContourRecord` / `ProfileRecord` / `CurveRecord` / `XYZ` types and Zod schemas, shared verbatim by plugin-core, the host and the agent runtime. Pure JSON, one `{x, y, z}` point shape, array bounds as contract (through-points ≤ 256, spline poles ≤ 512, sweep path ≤ 512, loft sections ≤ 32, profile curves ≤ 256, contour holes ≤ 64).
38
-
39
- Editing what was made, and rounding it:
40
- - **`design.update.parameters(component, patch)`** — the write pair of `design.query.getParameters`: patch a parametric mass's creation recipe and the solid rebuilds in place, keeping its id, storey, materials, position and linked copies. `patch` is a partial of the record's `values` (keys you omit are unchanged; each key you send replaces that value whole — a scalar, a point, a list or a record (`contour`, `profile`, `sections`, `path`, `options`, `operations`) alike — so send the complete value you read) or the whole record read back and edited; `kind` and `version` are immutable; the reply is the complete post-edit record in world coordinates. `PRECONDITION_FAILED` when the component is not a mass (a family instance: use `design.family.setParameters`), when the mass carries no live recipe, when it was scaled (undo the scale and call again), when it or a linked copy is locked (unlock them — the recipe is intact, and `getParameters` cannot see the lock), when a recorded operation's selectors no longer resolve, or when the edit is the first twist or taper, or the straightening, of an extrusion that carries a shell, fillet or chamfer (the faces renumber: send the twist keys with `operations: []` first, then re-apply), `VALIDATION` for argument errors, `OPERATION_FAILED` for a kernel refusal that leaves the model unchanged.
41
- - **`core.geom.create.splineFromControlPoints(controlPoints, { degree? })`** — an open NURBS curve DEFINED BY 3..512 control points (pulled toward, not through, the interior ones), degree 2 or 3, clamped uniform knots computed host-side. It is the write pair of a `splinePoles` curve record, so a curve read back off a mass can have its poles moved and be rebuilt.
42
- - **`brepFromFillet` / `brepFromChamfer` / `brepFromShell` / `brepFromOffset` accept CURVED solids.** The straight-edges-only and no-shared-vertex rules are gone: edges may be straight, circular or spline, and adjacent edges (corner blends) are allowed. The JSDoc carries the rim-selection recipe — `brep.listEdges` → `edge.listFaces` → `face.getSurfaceKind` on both faces (`listEdgesBetween` takes two VERTEX indices and is not a face-pair lookup) — and the new refusals: `VALIDATION` where there is no crease (a seam, or two patches of one smooth surface), and — for `brepFromFillet` alone, the one operation with a per-edge kernel verdict — `PRECONDITION_FAILED` naming the edge the kernel could not blend. An over-size chamfer, shell or offset is `OPERATION_FAILED` with a hint asking for a smaller distance/thickness and no edge or face named. Selection caps are RECORD caps, not call caps: the wire schemas accept any selection size, exactly as 0.9.x did — a fillet/chamfer of more than 64 edges or a shell opening more than 32 faces still builds, but the result carries no parameter record (the recipe-less fallback).
43
- - **`MassParametersRecord` gains an optional `operations` list** (`version` stays 1) — a fillet, chamfer, shell or offset of a recorded solid keeps the record, with `kind` still the base constructor and the operation appended. `OperationRecord`, `EdgeSelector`, `FaceSelector`, `TopologyCounts`, `SurfaceKind`, `CurveKind`, `ParameterPatchValue` and `MassParametersPatch` join the module, with caps `MAX_OPERATIONS` 8, `MAX_OPERATION_EDGES` 64, `MAX_OPERATION_FACES` 32. Selectors are TOPOLOGICAL (edge index + its face pair + curve kind; face index + surface kind + edge count) so they survive a scalar edit of the base and trip on a reorder.
44
- - **`design.query.geometry.getBrep`** — additive `options.frame` (`"local"` default, unchanged 0.9.x behaviour; `"world"` places the solid where it sits in the scene, for booleans between scene masses and for fillet/shell/offset/chamfer results committed back beside their source).
45
-
46
- Freeform surfaces, sweeps, patches and variable blends:
47
- - **`core.geom.create.brepFromSweepAlongCurve(profile, path, options?)`** — sweeps a cross-section along a SMOOTH path of 1..64 curves (splines, arcs and lines chained end to end), where `brepFromSweep` takes a polyline. `options.orientation` picks how the profile turns: `"upright"` (default) keeps its up aligned with world up, `"frenet"` rolls it with the curvature, `"fixed"` carries the authored frame along unchanged — which is a pure translation, and so is also how a footprint is bent along an arc. `options.rail`, a second guide curve, carries the profile along a second path; a closed rail, or a rail together with `orientation`, is `VALIDATION`, and a railed sweep records nothing (`getParameters` answers `null`). Otherwise it records as the one new kind `sweepAlongCurve` (`{ profile, path: CurveRecord[], options? }`, `version` still 1), so the orientation stays editable through `design.update.parameters`; the shipped `sweep` kind and its `XYZ[]` path are untouched.
48
- - **`core.geom.create.brepFromPatch(boundary, thickness, options?)`** — stretches a surface across a closed loop of 3..16 boundary items and thickens it into a SOLID in one call: a roof over four curves, a canopy between two rails, a blend across a gap. A boundary item is a curve or an `OwnedEdge` (`{ edge, brep, face? }` — an edge handle cannot name its own solid, and one patch may meet two of them). `options.continuity: "tangent"` leaves the named face smoothly and then REQUIRES `face` on every item, because a solid edge lies between two faces and only the caller knows which one to continue; `interiorPoints` (≤ 64) pull the surface through them, and `side` picks which way the solid grows. There is no sheet handle and no surface object: only solids reach the canvas. The result carries no recipe — `design.query.getParameters` is `null` and the mass label is its provenance.
49
- - **`core.geom.create.brepsFromSplitByFace(brep, toolFace, toolBrep)`** — the curved twin of `brepsFromSplit`, which cuts with a plane: cuts a solid with a FACE of another solid and returns the pieces largest first. Both breps must be in the same frame — read scene masses with `getBrep(x, { frame: "world" })`. A cut that does not pass all the way through is a `VALIDATION`, not a silent single piece.
50
- - **`core.geom.create.curvesFromProjection(curve, face, faceBrep)`** — drops a curve onto a face along its normal and returns the runs that land on it, one curve per connected run, or `[]` when the curve misses the face; `VALIDATION` when the curve's midpoint lies on the face's surface (no drop direction).
51
- - **`core.geom.query.face.getArea(face, brep)` / `.getCentroid(face, brep)`** — exact area (internal units squared) and centre of area of one face, integrated off its surface rather than its facets. These are the two reads that tell freeform faces apart, where every face answers `"spline"` and a kind census returns nothing. Both take the parent brep, owner last, exactly like `listVertexPositions(face, brep)`.
52
- - **`brepFromLoft`** gains `options.smoothing` — `"ruled"` (default) is today's straight-sided loft byte for byte; `"smooth"` lofts every section in ONE pass, so the sides curve continuously through the intermediates. `LoftOptions` carries it into the record.
53
- - **`brepFromExtrusion`** gains a trailing `options?: { twistInDegrees?, topScale?, levels? }`, and the shipped `extrusion` record gains the same three optional values (`version` still 1) — a twisted or tapered tower stays the SAME kind, so "twist this tower" is an edit rather than a delete-and-rebuild. `twistInDegrees` is an angle and `topScale` a ratio: neither is ever converted by a unit factor. `levels` is a count (each step may turn at most 22.5°), resolved at create time and written into the record, so an existing mass regenerates from the record and never from a heuristic that may change under it. All three absent is the straight extrusion, byte for byte.
54
- - **`brepFromFillet`** gains a trailing `options?: { endRadius?, radii? }` — a VARIABLE blend. `endRadius` interpolates linearly from `radius` along the whole tangent chain the one edge you pass belongs to (an open chain: on a closed rim a law whose two ends differ is refused, for `endRadius` and `radii` alike). `radii` is the sampled form instead: 2..32 rows of `{ position, radius }` with `position` the normalised ARC LENGTH along that contour from the passed edge's start — a ratio, never converted — strictly increasing. Either form takes exactly ONE edge, they are mutually exclusive, and both round-trip on the fillet `OperationRecord`.
55
- - **Face and edge selectors gain `at?`** — where the face or edge sits inside its solid, its centroid (an edge's midpoint) over the solid's bounding-box extent, a ratio per axis. It is written only when index, kind and counts cannot tell two of them apart, which on a freeform solid is the common case, and it is verified — never searched — on replay, so a record written without it resolves byte-identically.
56
- - **`ParameterPatchValue`** widens with `SweepAlongCurveRecordOptions` and `CurveRecord[]` (the `sweepAlongCurve` path). It only ever widens; every value it took before it still takes.
57
- - **`massParameters.ops` — a second record module.** `EdgeSelector`, `FaceSelector`, `TopologyCounts`, `OperationRecord`, `ParameterPatchValue` and the operation caps (plus the new `MAX_FILLET_LAW_SAMPLES` 32) move into `src/massParameters.ops.ts` and are re-exported from `massParameters` and from the package root — no import path changes. The split keeps each module small enough for an agent to read whole. New caps beside the shipped ones: `MAX_PATH_CURVES` 64, `MAX_TWIST_LEVELS` 64, and — on the creator, because a patch has no record — `MAX_PATCH_BOUNDARY_CURVES` 16 and `MAX_PATCH_INTERIOR_POINTS` 64.
58
- - **`design.family.getParameters`** gains an optional `parts: PluginFamilyPart[]` — each placed part's `name`, the `engine` that built it (`"kernel"` for a curved sketch built exactly, `"mesh"` otherwise) and, on kernel parts, a read-only part-local `brep` for the face-kind census. `design.query.geometry.getBrep` keeps returning `null` for a family.
59
-
60
- ## 0.9.13
61
-
62
- ### Patch Changes
63
-
64
- - 6c85137: design.family: add `delete({ id, instances? })` — archives the library row; `instances: true` also deletes this project's placed instances undoably. `create` result gains `placed` and `hint`.
65
-
66
- ## 0.9.12
67
-
68
- ### Patch Changes
69
-
70
- - 6e4a055: added param comps
71
- - 8e37e1f: `design.family`: `prepare` and `flex` warnings gain the `proportion` code — parts thinner than 0.5 mm, parts or a family larger than 50 m, a family smaller than 10 mm, each naming the parts — and `flex` now returns `warnings`.
72
- - 2a06dd9: `design.family`: `prepare`, `flex` and `getParameters` accept a `snapshot` option and return a `snapshot` block — an isolated offscreen picture of the family (iso, front, right, top tiles with a dimension line) as a PNG data URL, so an agent can look at what it built before reporting.
73
-
74
- ## 0.9.11
75
-
76
- ### Patch Changes
77
-
78
- - 7fd374e: Add `hinge` to `design.create.door` / `window` (in `options`), `doors[]` / `windows[]` items and `opening`: a world point near the jamb the leaf is hinged on; the placed item is reflected along the wall when the catalog item's authored hinge side would land on the other jamb — one undo entry, no extra command.
79
-
80
- **BEHAVIOUR FIX — `facing`.** The host placed the swing on the side OPPOSITE the `facing` point (the interactive tool's cursor convention) while the contract said "the room it opens into". The host now matches the contract: the leaf swings open toward `facing`. Callers that passed a point on the room side get the documented result; nothing in the contract changed shape.
81
-
82
- `design.doors.getSwingDirection` is now derived from the placed mesh's orientation (hand as seen standing on the swing side facing the door); it previously read a scaling sign that placement never sets and reported `"left"` for every door. `mirror` axis docs now say what each axis does to a door (`'x'` swing side, `'z'` hinged jamb, `'y'` upside down); behaviour unchanged.
83
-
84
- - e325bee: Add the structural-grids API surface: queries, creators, a sparse patch updater, structural line edits, and generic-family acceptance.
85
- - **Query** — `design.query.listGrids(filter?)` (standard `PluginEntityFilter`; `"grid"` joins `PluginEntityType`, so `listByType("grid")` works) and the field-projected `design.query.grids.get(gridId, properties[])`: `angle` (degrees, CCW about +Y), `props`, `storeyVisibility`, `wings` (per-wing spacing/tagStart/span and per-line world-space `CurveHandle`, tag, visibility, effective + override props), `bounds`. Unknown ids throw `HANDLE_INVALID`.
86
- - **Create** — `design.create.grid(linesX, linesY, spacingX, spacingY, options?)` (free 2-wing grid; optional `angle` in degrees and `center`, which defaults to the native viewport-centre behavior), `design.create.gridFromCurve(curve, options?)` (one wing/one line — line or arc), `design.create.gridsFromMasses(masses, spacingX, spacingY, options?)` (fitted, one group per footprint cluster, lenient skipping, ONE undo unit), and `design.create.columnsFromGrids(grids, width, depth)` (columns at every line intersection, ONE undo unit).
87
- - **Update** — `design.update.grid(grid, patch)`: GROUP-LEVEL sparse patch — `props` (default styling), absolute `angle` in degrees, and `storeyVisibility`; each changed field commits its own undoable command through the panel's setters.
88
- - **Grids family** — `design.grids.updateWings(grid, wings[])` (per-wing `spacing`/`count`/`spanMin`/`spanMax`; a `count` change re-indexes lines — re-query) and `design.grids.updateLines(grid, lines[])` (per-line `tag`/`distanceToPrevious`|`distanceToNext` (mutually exclusive)/`props`/`visible: false` (hide-only, product parity — show the GROUP to restore all lines); hiding every remaining visible line hides the GROUP instead — native last-line rule); plus `design.grids.addLine(grid, wing, position)` and `design.grids.removeLines(grid, lines, tagPolicy?)` (`"rewrite"` default | `"preserve"`; removing the group's last line throws `OPERATION_FAILED` — delete the group instead). Every update routes through the exact setters the native panel uses — one undoable command per changed field, identical to panel edits.
89
- - **Generic families** — grid groups are accepted by `design.transform.move`/`rotate`, `design.visibility.hide`/`show`, `design.lock`/`unlock`, `design.delete.entities`, and `design.selection`; `transform.align` and `transform.mirror` throw `OPERATION_FAILED` for grids.
90
-
91
- Grid lines have no stable identity: every line-level operation addresses them positionally as `{ wing, line }` index pairs into the `wings` arrays returned by `grids.get`; structural edits re-index lines, so re-query after mutating.
92
-
93
- ## 0.9.10
94
-
95
- ### Patch Changes
96
-
97
- - `core.mode.set("program")` now opens the Program tab (the same path as the top bar's Program button) instead of refusing with `PRECONDITION_FAILED`; it resolves once the tab has connected, and waits for the user to confirm the editor's pop-up modal when the browser blocks the window.
98
- - `workspace.openProgramMode()` / `closeProgramMode()` — the Program-tab counterpart of `openPresentMode` / `closePresentMode`: open (awaiting the tab's connection) and close the paired Program window.
99
-
100
- ## 0.9.9
101
-
102
- ### Patch Changes
103
-
104
- - Program-mode additions requested by the healthcare programming pilot (all additive; no signature changes):
105
- - `program.metadata` — the Program Data sheet's custom columns and per-space values: `listHeaders`, `createHeader` (reuse-by-name), `get`, `list`, `update`, `updateMany`. Writes are undoable and work with the Program tab closed.
106
- - `program.labels` — the program rows (space types) with targets: `list`, `get`, `create` (optionally `createSpaces`), `setTargetArea`, `setTargetCount`. Covers "target area of non-departments": a space inherits its label's target; departments keep `program.departments.setTargetArea`.
107
- - `program.spreadsheet` — custom program sheets with a group-by hierarchy: `createCustomSheet`, `updateCustomSheet`, `getCustomSheet`, `listCustomSheets`, `listCustomSheetProperties`.
108
- - `core.tags.listForComponents(componentIds?)` — every space's tags + colors in one round-trip.
109
- - `program.departments` — `targetCount` on the department record and `setTargetCount(departmentId, targetCount)`, the count twin of `setTargetArea` (the engine stores both on the department, like on labels).
110
-
111
- ## 0.9.8
112
-
113
- ### Patch Changes
114
-
115
- - 7fd374e: Add `hinge` to `design.create.door` / `window` (in `options`), `doors[]` / `windows[]` items and `opening`: a world point near the jamb the leaf is hinged on; the placed item is reflected along the wall when the catalog item's authored hinge side would land on the other jamb — one undo entry, no extra command.
116
-
117
- **BEHAVIOUR FIX — `facing`.** The host placed the swing on the side OPPOSITE the `facing` point (the interactive tool's cursor convention) while the contract said "the room it opens into". The host now matches the contract: the leaf swings open toward `facing`. Callers that passed a point on the room side get the documented result; nothing in the contract changed shape.
118
-
119
- `design.doors.getSwingDirection` is now derived from the placed mesh's orientation (hand as seen standing on the swing side facing the door); it previously read a scaling sign that placement never sets and reported `"left"` for every door. `mirror` axis docs now say what each axis does to a door (`'x'` swing side, `'z'` hinged jamb, `'y'` upside down); behaviour unchanged.
120
-
121
- ## 0.9.7
122
-
123
- ### Patch Changes
124
-
125
- - 6e60c61: Add bulk `design.create` creators — `wallRuns`, `floors`, `doors`, `windows`, `furnitureItems`: one host round-trip and one undo entry per batch.
126
-
127
- Singular creators, fixes shipped alongside (behaviour changes for existing callers):
128
- - `floor`, `slab`, `roof`, `ceiling`, `column`, `beam`: contour holes are now cut (holed contours previously failed or lost their holes).
129
- - `floor`: the `position` offset is applied before the creation snapshot, so it survives redo and reload and drives storey assignment.
130
- - `furniture`: `options.label` is now also the readable label (`design.query.getLabel`); `createNewSourceMesh` can no longer skip recording a source this call brought in (the host owns source persistence, recorded exactly once across concurrent calls); refuses with `PRECONDITION_FAILED` (`engineCode: "TOOL_ACTIVE"`) while the interactive furniture tool is active. The same refusal applies to `core.io.import.model` furniture placement.
131
- - `door`, `window`: published as one save document instead of five; undo granularity unchanged.
132
-
133
- - 2f10e6b: Add `design.dimensions` — dimension lines (Measuring Tape): `create`, `list`, `get`, `delete`, `hide`, `show`, plus the new `DimensionHandle`.
134
-
135
- ## 0.9.6
136
-
137
- ### Patch Changes
138
-
139
- - Add five APIs the plugin agent repeatedly reached for but that did not exist.
140
- - `core.project.getInfo()` — project id, display name, unit type, storey count (scoped to the active building), active storey, and site location in one call. Closes a genuine capability gap: nothing previously returned the current project's identity.
141
- - `core.storeys.getActive()` — `setActive` had no getter, unlike `core.buildings`, `core.layers` and `core.proposals`.
142
- - `core.camera.getMode()` — `setMode` had no getter, while `getProjection`/`setProjection` on the same class were paired.
143
- - `design.visibility.show(components)` — `hide` previously had only `showAll` as its inverse, so specific components could not be revealed.
144
- - `design.furniture.listCatalogGroups()` — matches `design.doors` and `design.windows`, so all three catalog surfaces are named alike. `design.furniture.listCategories()` is deprecated but still supported.
145
-
146
- ## 0.9.4
147
-
148
- ### Patch Changes
149
-
150
- - 0008010: `brepFromLoft` auto-matches section edge counts (deterministic vertex insertion) and auto-aligns seams for straight-edge sections; `brepFromSweep` accepts closed (ring) paths and gains an optional taper scale law (`startScale`/`endScale` or per-point `scales`).
151
- - f54ea17: `brepFromLoft` gains `options` (`compatibility: "strict" | "auto"`, `seamAlignment: "authored" | "auto"` — strict+authored restores pre-0.9.4 lofting); `brepFromSweep` options gain `transition` (`"miter"` default, `"round"` on open paths, `{ bevel }` chamfered corners on open and closed paths); new pure read `core.geom.query.brep.getEdgeCurve` returns exact line/arc/circle edge curve data.
152
-
153
- ## 0.9.3
154
-
155
- ### Patch Changes
156
-
157
- - 3584bde: Expose configured BIM creation and Site Analysis sheet APIs: `design.create.opening` (catalog door/window with width/height/sillHeight), staircase `dimensions` overrides (strict-validated, quarter-turn rotation), and `program.site.getSiteAnalysis`/`updateSiteAnalysis` with merge/append semantics. `PluginSiteAnalysisUpdateOptions` accepts `null` as "no options".
158
-
159
- ## 0.9.2
160
-
161
- ### Patch Changes
162
-
163
- - 5b91c7b: OCC geometry batch: new `core.geom.create` brep constructors — `brepFromFillet`, `brepFromChamfer`, `brepFromOffset`, `brepFromShell`, `brepsFromSplit`, `brepFromSweep`, `brepFromRevolution`; `brepFromLoft` widened with optional `intermediateContours` (multi-section lofts); new `core.geom.query.brep.getDistance` (minimum distance between two solids with witness points).
164
-
165
- ## 0.9.1
166
-
167
- ### Patch Changes
168
-
169
- - Plugin API 0.9.1 — strictly additive: the terrain mesh lane and the Present-mode styling/canvas lane. No signatures changed, no behavior of existing methods changed; deployed plugins are unaffected (`versionGate` compares major.minor only).
170
- - **`core.io.terrain.replaceMesh(positions, indices, units?, options?)`** — replace the site terrain surface with a caller-supplied triangle mesh. World-space flat arrays (the `getTriangulatedMeshes` frame, so read-modify-write round-trips); `units: "meters"` converts on the way in. Validated before any engine call (finiteness, index bounds, degenerate topology, scene-bound extents, **500k vertex/triangle caps** — the surface's first explicit payload ceiling); a predominantly down-facing mesh is auto-flipped with a warning. Datum, geolocation, opacity, lock state and the elevation/satellite toggles survive; the map texture is re-projected best-effort; cut/fill history resets by contract. One undo step restores the prior surface exactly (buffers, UVs, submesh layout, cut/fill data, volumes). The replacement **persists with the project**: the mesh uploads to bucket storage and reload applies it instead of the heightmap build. The optional `options` mirrors the requester's contract: `coordinateSpace`, `expectedTerrain` (session optimistic check), `expectedModelRevision` (session terrain-mesh revision, returned as `modelRevision` in the result), `baseline: "make-replacement-original"`, `preserve` opt-outs (`datum`/`satellite`/`material` have real opt-out behavior; `geolocation: false` rejects — always preserved), and `clientMutationId` (session-scoped idempotent replay). For projects without terrain, compose `core.io.import.terrain` + `replaceMesh` (a separate `createFromMesh` was deliberately not added).
171
- - **`presentation.placedViews` — per-instance styling** (`getStyles` / `updateStyles` / `resetStyles` / `updateFontStyles`): the Present sidebar's full per-category styling surface — 7 line dash styles, line color, dash-length multiplier, **cut vs projection line weights in mm** (plus tree/parking weights), fills (`department`/`texture`/`monochrome`/`tag:<tagCategoryId>`), per-category opacity, category hide (cascades to labels), view-style restyles (block/rounded/bubble with radius + inset), Topography map/building sub-controls, and Text-tab typography per label type (font family incl. Google Fonts, weight, italic, px size, visible label types). Writes are host-validated (typed VALIDATION with the supported-setting list), land as **one undo step per call**, re-render immediately, and fan out across an auto-diagram sheet's set. `getStyles` returns fully-resolved values plus each category's supported setting ids.
172
- - **`presentation.placedViews` — labels** (`listLabels` / `moveLabel` / `setLabelHidden`): a placed view's generated labels (space names, departments, areas, dimensions) as addressable shapes — read text/category/position, reposition them, hide/show individually (independent of category-level hides).
173
- - **`presentation.placedViews` — generic canvas shapes** (`listShapes` / `getShape` / `moveShape` / `resizeShape` / `deleteShapes` / `setOpacity` / `setMask`): every top-level Present-canvas shape — AI-generated outputs, placed diagram images, plugin shapes, annotations — addressable by id with an `origin` classification. Previously `aiInspiration.generate` and `diagrams.place` returned shape ids nothing could act on. `deleteShapes` is atomic (any rejected id deletes nothing), protects sheets (use `sheets.delete`) and user-drawn annotations, and closes the placed-view no-delete gap. `setOpacity` is the whole-shape slider; `setMask` toggles context-building masking on views.
174
- - **`presentation.sheets.setMargin(sheetId, margin)`** — set a sheet's print margin to one of the canonical unit-agnostic presets `0 | 0.25 | 0.5 | 0.75 | 1` (displayed None / 1/4″ / 1/2″ / 3/4″ / 1″ on imperial projects, None / 5mm / 10mm / 15mm / 20mm on metric) — the sheet header's margin dropdown; one undo step. Sheet reads (`list`/`get`) now report `margin` (`null` when absent/unrecognized).
175
- - **`presentation.placedViews.setRotation(shapeId, rotation)`** — set a placed view's **absolute** rotation in radians (tldraw convention) about its bounds center (the UI rotation handle's pivot); one undo step. Angles are canonicalized into `[0, 2π)`, so the round-trip with the record's `rotation` read is exact only in that range (`setRotation(-0.1)` reads back `2π − 0.1`). **3D placed views only** — 2D/site-plan views cannot rotate (the canvas reverts any rotation applied to them) and are refused with a typed `PRECONDITION_FAILED`. Joins the absolute-setter family (`setScale`/`setCrop`/`setOpacity`/`setMask`).
176
- - **`presentation.placedViews.rotateShape(shapeId, rotation)`** — the same absolute center-pivot rotation for any top-level Present-canvas shape (AI outputs, diagram images, plugin shapes, annotations — placed views too); targets and refusals mirror `moveShape`. Same `[0, 2π)` canonicalization (exact round-trip only in that range), and a placed view passed here falls under the same 3D-only rule — 2D/site-plan views refuse with a typed `PRECONDITION_FAILED`.
177
- - **`presentation.tables.place(sheetId, { rows }, options?)`** — new `tables` namespace: place plain string rows as a table shape on a sheet (the same shape the Present canvas creates when a Program-mode / Google Sheets / Excel table is pasted). Rows may be ragged, capped at 10000 cells total; `options.position` is sheet-local like `annotate.*` (default: the paste flow's viewport-center auto-placement); returns the created shape as a canvas-shape record; one undo step.
178
- - **`presentation.slideshow`** (`start` / `stop` / `getState`) — new namespace: run the Present-mode slideshow programmatically. The slideshow plays **all** sheets in order; `start` options only pick the opening slide (`startIndex` wins over a single-entry `sheetIds`, clamped into range). Browsers grant fullscreen only on a user gesture, so a plugin-triggered `start` typically runs **non-fullscreen** (the rejection is caught). `start`/`stop` are write-gated; `getState` is a never-throw read (`running` + current `index`, `null` when not running). No `next`/`prev`/`goTo` — slide navigation stays with the user.
179
- - **`workspace.openPresentMode()` / `closePresentMode()`** — open/close the Present-mode documentation editor. `openPresentMode` resolves only once the Present canvas has mounted (up to ~15s, then a typed `PRECONDITION_FAILED`), so follow-up `presentation.*` calls find Present mode open; both are idempotent and **write-gated** (a mode switch under the user's feet is approval-controlled by design). Overlaps `core.mode.set("present")`/`set("design")` but adds the mount-await + idempotency contract.
180
- - **`presentation.diagrams.generateSite(options?)`** — run the site-diagram auto-generation (location / streetview / climate sheets from the map terrain) and return the created `sheetIds`. `options.types` selects a subset (default all three); generation is tracked per-proposal — already-generated types are **skipped, not duplicated** (mount-time-flow semantics; existing site-diagram sheets are never replaced), so `sheetIds` can be empty. Requires Present mode and a map terrain.
181
- - **`presentation.annotate.text` typography** — the existing text args gain optional `fontFamily` (system family or any Google Font, loaded automatically — the Present text panel's loader), `fontWeight` (numeric string like `"400"`/`"700"`, `-italic` suffix allowed; unavailable weights fall back to regular), and `fontSizePx` (exact pixel size overriding the `size` preset — `size` alone keeps working). Existing calls are unaffected.
182
- - **`program.site.listEdges()`** — read each site parcel's boundary edges with their **front / side / rear** classification: the exact resolution the buildable-envelope setback pills show on canvas (user per-edge overrides win over the tangent heuristic; `roleSource` says which applied), so a plugin picks up the user's edge tagging without asking them to set it up again. Per edge: canonical `edgeIndex` (the envelope `edgeOverrides` index space), endpoints and length in **world meters** (`lengthUnit: "m"` — unlike `listPolygons`' Snaptrude-unit footprints), and the **effective base setback** (per-edge exception, else the role value; `null` for parcels with no zoning envelope — those still classify by heuristic). Entries also report the linked `buildableEnvelopeId` and whether stale index-misaligned overrides were dropped. Never throws; `"street"` resolves to `front`.
183
-
184
- ## 0.9.0
185
-
186
- ### Minor Changes
187
-
188
- - Plugin API 0.9.0 — namespace reorganization (deprecate-in-place, nothing breaks), the analysis wave, and the spreadsheet data lane.
189
- - **Namespace map** — `entity.*` retired via aliases (storeys → `core.storeys`, reference lines / buildable envelopes → `design.*`), `core.zoom` → `core.camera`, `core.tags.getTagsForComponent` → `core.tags.listForComponent`, new `workspace.*` (projects/teams) and `core.mode` top-levels. Every deprecated path still works and delegates to its canonical implementation.
190
- - **Analysis wave** — `analysis.weather.getSeries` (hourly EPW rows + station metadata + quality flags), `analysis.solar.sampleGrid` (per-point GPU-raytraced visibility/shade/SVF/irradiance), `analysis.daylight.compute` (LM-83 numeric ASE + sDA with full provenance — see the sDA entry below), `program.site.getWeather`/`getTimezone`, `.epw` import + project weather override via `core.io.import`.
191
- - **sDA (LM-83 Spatial Daylight Autonomy)** — `analysis.daylight` now computes real sDA300/50% with dynamic blind operation (LM-83 2% rule, 5% diffuse closed-blind default) via Radiance. Runs including `"sDA"` are asynchronous: `compute` returns `{ status: "running", runId }` and the new `analysis.daylight.poll({ runId, cursor? })` fetches results; ASE-only runs stay synchronous and unchanged. New optional `optics` arg overrides surface optics per material (keyed by Snaptrude material name) or per category, over LM-83/convention defaults (wall 0.5 / ceiling 0.7 / floor 0.2 / furniture 0.5 / glazing Tvis 0.65 / context 0.2); glazing takes visible transmittance (Tvis), converted to Radiance transmissivity backend-side. Sensor records gain `da300Percent`/`sdaPass`, aggregates gain `sdaPercent`, results gain `runId`, and the ASE fields (`directSunHoursAtOrAbove1000Lux`/`aseFail`/`asePercent`) are now optional — present when ASE is requested.
192
- - **Heatmaps** — named overlays (up to 16, `analysis.heatmaps.overlays.*`), `renderField` arbitrary cell geometry, `renderSurfaceGrid` façade grids, discrete/threshold/categorical scales, hover tooltips.
193
- - **Geometry reads** — `design.query.geometry.getTriangulatedMeshes` (world-space triangles, optional per-triangle `materialIds` and B-rep `faceIds`), `design.query.spaces.getEnclosure` (geometric v1).
194
- - **Spreadsheet data lane** — `program.spreadsheet.datasets.{set, list}` (plugin datasets, upsert + auto-refresh of bound ranges), binding `source` widened to `{ dataset: "takeoff" } | { dataset: "plugin", name }` (takeoff unchanged), `addImage` (data-URI floating pictures, stable-name replace-on-rerun), `addChart` accepts the ratified `"column"`/`"bar"` enum on the wire.
195
- - **Presentation** — `presentation.shapes` keyed shape lifecycle on Present sheets (`upsert`/`remove`/`removeAll`/`list`).
196
- - **Constructive geometry (brep kernel)** — the full `core.geom.create.brepFrom*` constructor family minting `BrepHandle`s at authored coordinates: `brepFromFaces` (plain face-point loops; host validates welding, planarity, edge coherence and fixes global orientation), `brepFromExtrusion` (prisms along any direction, holes + arc profiles), `brepFromLoft` (tapered solids between two contours; lofts producing non-planar side faces are rejected), `brepFromMesh` (indexed vertex + face-index input), and the OpenCascade booleans `brepFromUnion` / `brepFromSubtraction` (`a` minus `b`) / `brepFromIntersection` (scene-derived `getBrep` handles accepted as read-only inputs; empty/disjoint results throw typed VALIDATION).
197
- - **`design.create.massFromBrep(brep, label?)`** — commit a mass from any `core.geom.create` brep constructor's handle (scene-derived breps stay read-only): generic mass at authored coordinates, single undo step, collab-synced.
198
- - **`presentation.placedViews`** — layout control over placed views (`list`/`get`/`move`/`scale`/`setScale`/`setCrop`): sheet-local position, size, architectural scale, rotation, crop and link state; uniform corner-drag-equivalent `scale`, Scale-dropdown `setScale` (2D only), native crop fractions with page-anchored visible region — crop survives `sheets.updatePlacedView` refreshes.
199
-
200
- ## 0.8.0
201
-
202
- ### Minor Changes
203
-
204
- - More additions to the APIs, infra changes.
205
- - Plugin API 0.8.0 — the 0.8 API wave, handle lifecycle, events, and geometry/data-integrity fixes.
206
- - **The 0.8 API wave** — model/presentation **export** (`core.io.export`, `presentation.export`), **element editing** widenings on `design.update`/`design.edit`, **`core.camera.*`**, **storey management**, **`design.visibility.*`**, **dimensions**, **locale/user** reads (`core.user`), `design.types.list`, presentation annotate, plus completeness widenings across existing families. The discovery manifest grows from 423 to 595 method paths (including the new `core.handles.*` lifecycle below).
207
- - **Events** — `model:changed` subscription contract: debounced model-change notifications delivered to the plugin worker for user and plugin edits alike (client API: `PluginWorker.subscribe`). `selection:changed`, `tool:activated`, `project:saved`, `view:changed` are reserved names, not yet emitted.
208
- - **BREAKING — `program.site` footprints are world Snaptrude units.** Previously the only plan-geometry read scaled to scene metres (×0.254), so site parcels could not be overlaid on `planPoints`. Now the same world XZ plan space as every other plan read; areas and the geographic reads are unchanged.
209
- - **BREAKING — real beam/column geometry.** `design.create.beam` with a horizontal direction (the documented canonical call) used to build a zero-height flat sheet; it now builds a true beam with tool-parity semantics (section X extent = width, Z extent = depth, body hangs below the authoring plane by its depth). `design.create.column` builds exactly the requested height (the silent slab-thickness deduction is gone). Degenerate extrusions (zero/diagonal/in-plane directions, zero height) throw VALIDATION errors instead of silently building corrupt solids; directions are normalized.
210
- - **Data integrity** — `design.query.list*` no longer returns engine-internal shells (originals parked by instance copies, disposed phantoms), so one copy no longer doubles `listSpaces`/schedules; `design.delete.entities` orders instance copies before their source meshes so mixed batches cannot fail midway; `design.update.wall` documents that rebuilds replace the wall — re-bind from `affected[0]`.
211
- - **Handle lifecycle (`core.handles.*`) + GC-backed `Handle` objects** — `Handle` is now a class wrapping the wire id (public type names unchanged): `handle.id`, `toString()` stays the bare id, `equals()` compares by id, and `toJSON()` emits the `{__h: id}` wire tag — `JSON.stringify` output containing handles changes shape. New namespace: `core.handles.release(handles[])` (batched, arena-agnostic, silent no-op on unknown/released ids), `core.handles.releaseAll(kind?)`, mint-recording scopes `beginScope()` / `endScope(scope, retain?)` (token-addressed, retain promotes to the enclosing scope), and `core.handles.stats()`. The client runs a FinalizationRegistry-backed auto-release queue, so unreferenced handles are reclaimed host-side without explicit code. `core.geom.delete.profile/contour` are retained as deprecated aliases of `core.handles.release`. Entity-style kinds (component/material/underlay/terrain/importJob) remain branded strings.
212
-
213
- ## 0.7.1
214
-
215
- ### Patch Changes
216
-
217
- - 8f22cb7: Expand `presentation.aiInspiration` to the full AI Inspiration surface (18 methods) and make it a required member of `PluginPresentationApi` now that the host implementation lands.
218
- - New methods: `getModelCapabilities`, `getSelection`, `getPresetCatalog`, `getJob`, `cancelJob`, `listJobs`, `getWorkflowForShape`, `getSelectedBranchRerunPlan`, `rerunSelectedBranch`, `extractRecipeFromShape`, `listRecipes`, `saveRecipe`, `deleteRecipe`, `runRecipe` (plus job/workflow/recipe Zod schemas).
219
- - `generate`/`refine` (and `rerunSelectedBranch`/`runRecipe`) accept `runMode: "blocking" | "job"`; `PluginAIInspirationRunResult` is accordingly loosened to `{ jobId?, output?, outputs?, metadata? }` — job-mode calls resolve with a `jobId` instead of outputs.
220
-
221
- ## 0.7.0
222
-
223
- ### Minor Changes
224
-
225
- - e1915e4: Plugin API 0.7.0 — new families and methods (supersedes the cancelled `design.update.setLabel` 0.6.1 patch, now folded in).
226
- - **`core.proposals.*`** — design options as infrastructure: `list` / `get` / `getActive` / `listForComponent` / `isActive` (reads) plus `create` / `rename` / `setActive` / `delete` (non-undoable writes).
227
- - **`program.layout.*`** — the space-solver promoted to a real async-job family: `arrange` / `pack` now start a backend job and return immediately, with `getState` (poll) and `cancel`.
228
- - **Door & window catalogs + placement** — `design.doors.*` / `design.windows.*` gain `listCatalogGroups` / `listCatalog` / `getCatalogItem` / `exists` (shared catalog DTOs), and `design.create.door` / `design.create.window` place a catalog item into a host wall.
229
- - **`design.create.smartLayout`** — clone an in-scene template cluster into one or more target spaces in a single undo batch.
230
- - **Furniture categories** — `design.furniture.listCategories` plus a `category` filter on `listCatalog`, and `thumbnailUrl` on catalog items.
231
- - **`design.transform.align`** — snap components' bounding-box edges (left/right/center/top/bottom/middle), optionally to a reference.
232
- - **Per-face materials** — `design.materials.applyToFaces` / `resetFaces` / `getByFace` / `listByFace` (BREP faces by durable index, visual-only).
233
- - **Staircase params** — `design.query.getStaircaseParams` (read) and `design.update.staircase` (sparse parametric write).
234
- - **Footprint-area lock** — `design.lockArea` / `unlockArea` / `isAreaLocked` / `listAreaLocked` for Room/Department spaces.
235
- - **`entity.story.duplicate`** — duplicate a storey (or a subset) up/down, auto-creating the target storey.
236
- - **`presentation.import.svg`** — import a guaranteed-vector SVG onto the Present sheet.
237
- - **View display settings** — `presentation.views.getSettings` / `updateSettings` (background, color mode, axis, edges, labels).
238
- - **`design.update.setLabel`** — set any component's panel Label (write pair of `design.query.getLabel`); undoable, lock-aware, validated.
239
- - **`program.areas`** — area members now report their `areaClass` (`NET` / `GROSS` / `EXCLUDED`), with net/gross documented as the plain-English synonyms for carpet/built-up.
240
- - **Typed plugin API errors** — every host failure now crosses the wire as a structured envelope instead of a flattened `Execution error: <msg>` string: a stable `code` from the shared plugin-core error registry, the offending handle, an authored `hint`, and `retryAfterMs` / `didYouMean` enrichment where applicable. Zod validation failures forward the first 10 issues, each projected to `{path, code, message}` (full count in `details.issueCount`); internal faults are sanitized and host-logged under an `errorId`. Every handler across the API surface now throws typed codes, each doc page gained an Errors section, and a drift test pins thrown code literals to the registry.
241
-
242
- ## 0.6.0
243
-
244
- ### Minor Changes
245
-
246
- - 934405e: New `analysis.heatmaps` family — render plugin-computed scalar data as a
247
- heatmap: `renderSpaces` (flat colour per space top face), `renderGrid`
248
- (coloured grid mesh from world-coordinate samples), `reset`, `isActive`.
249
- Single active heatmap (a render replaces the previous one); values outside
250
- `[min, max]` clamp to the end colours; ephemeral (auto-cleared on
251
- scene-mutating edits, never persisted). The react host has landed, so all
252
- members are required `abstract` and in the discovery manifest.
253
- - a68e866: New top-level `analysis` namespace:
254
- `analysis.sunpath` (enable/disable/isActive), `analysis.shadows`
255
- (enable/disable/isEnabled/setDateTime/getDateTime — ISO date-time strings),
256
- and the async heatmap job families `analysis.sunlightHours` and
257
- `analysis.illuminance` (compute/get/cancel/reset; illuminance is Pro-gated).
258
- Also extends `program.site` with `getLocation()` and `getNorthAngle()` geo
259
- reads. The react host has landed, so all new members are required `abstract`
260
- and in the discovery manifest; the plugin-client mounts the `analysis` RPC
261
- namespace.
262
- - New `core.io` namespace — file import, import jobs, and underlay/terrain
263
- management:
264
- - `core.io.import` — bring external files onto a storey: `image`, `pdf`
265
- (page-selectable), `dwg`, `cadJson` (parsed CAD geometry as a sketch),
266
- `model` (3D formats), and `terrain` (map snapshot at a lat/lng with
267
- optional elevation). Slow formats return an `ImportJobHandle` instead of
268
- blocking.
269
- - `core.io.job` — poll those imports: `getStatus`, `isComplete`, `getResult`,
270
- `getError`.
271
- - `core.io.query` — pre-import inspection: `getPdfPageCount`,
272
- `listCadLayers`.
273
- - `core.io.underlay` — manage imported image/PDF/CAD underlays: `list`,
274
- `getBounds`, `getScale`/`setScale`/`resetScale`, `getOpacity`/`setOpacity`,
275
- `delete`.
276
- - `core.io.terrain` — manage the imported map terrain: `exists`, `get`,
277
- `getDatum` (current vertical position, world Y) / `setDatum` (relative
278
- datum shift, not an absolute elevation), `delete`, `getReport`, elevation and satellite-imagery toggles
279
- (`isElevationEnabled`/`enableElevation`/`disableElevation`,
280
- `isSatelliteEnabled`/`enableSatellite`/`disableSatellite`), and
281
- `getOpacity`/`setOpacity`.
282
-
283
- - New `design.selection.setByFilter(filter)` — the programmatic equivalent of
284
- the editor's **Story selection** and **Filter selection** menus. Replaces the
285
- selection with every visible entity of the active building matching
286
- `{ storeys?: number[], types?: PluginSelectionEntityType[] }` (fields AND,
287
- values within a field OR; at least one field required — use `clear()` to
288
- deselect). The new `PluginSelectionEntityType` vocabulary covers all 24 menu
289
- kinds, including selection-only kinds the query surface can't reach (sites,
290
- terrain, CAD/PDF/image imports, dimension lines, 3D models, neighborhood
291
- buildings, program blocks). One-shot bulk select, not a persistent mode; not
292
- undoable, like the other selection mutators.
293
-
294
- ## 0.5.0
295
-
296
- ### Minor Changes
297
-
298
- - 7750aa3: Exposed more snaptrude APIs
299
-
300
- ## 0.4.0
301
-
302
- The Plugin API expose initiative. Pre-1.0, breaking changes ship as minor
303
- version bumps.
304
-
305
- ### Minor Changes
306
-
307
- - **Breaking — positional arguments.** Every API method now takes positional
308
- parameters, options-last (required first, then one optional `options`
309
- object), replacing the single-args-object transport. Migrate by unwrapping
310
- the object into positional args — `foo({ a, b, t })` → `foo(a, b, t)`. E.g.
311
- `vec3.lerp(a, b, 0.5)`, `departments.assign(departmentId, spaceIds)`,
312
- `design.query.spaces.get(space, ["area", "position"])`.
313
- - **Breaking — handle-based boundary.** Only JS primitives and enum strings
314
- cross the plugin ↔ host boundary by value. Every structured type (vector,
315
- curve, profile, BREP topology, scene entity) is now an opaque, kind-tagged
316
- string handle; scene-entity references use `ComponentHandle`. Migrate by
317
- minting a handle instead of passing a raw object (`vec3.new(x, y, z)`, then
318
- pass the handle) and reading values back with a query call
319
- (`vec3.components(h)` → `{ x, y, z }`) — reads return primitive records,
320
- never handles.
321
- - **New namespaces.** Reorganized the surface into `core`, `design`, `entity`,
322
- `program`, and `presentation`:
323
- - `core`: `math` (`vec3`/`quat`), the `geom` geometry kernel, `history`
324
- (undo/redo), `units`, `zoom`, `project`, `comment`, and the
325
- `groups`/`buildings`/`layers`/`tags` organization families.
326
- - `design`: `create`, `query` (+ `geom` query), `selection`, `doors`,
327
- `windows`, `furniture`, `materials`, `boolean`, `erase`, `delete`,
328
- `transform`, `edit`, `update`, plus top-level `lock`/`unlock`/`isLocked`/
329
- `listLocked` verbs.
330
- - `entity`: `story`, `referenceLine`, `buildableEnvelope`.
331
- - `program`: `areas`, `departments`, `metrics`, `site`, `cores`,
332
- `classification`, `adjacency`, and the `spreadsheet` reporting family.
333
- - `presentation`: `views`, `sheets`, `diagrams`, `import`.
334
- - Removed the legacy `entity.space.*` methods from the contract and
335
- consolidated departments under `program.departments`. The selective space
336
- property read is now `design.query.spaces.get`, and space adjacency is read
337
- as an entity property via `design.query.getProperties().adjacency`. (The
338
- host keeps a deprecated `entity.space.get` shim, but it is no longer part of
339
- the published contract surface.)
340
- - Added the `overlaps` entity filter to `design.query` list methods — matches
341
- entities whose plan footprint has non-zero intersection area with a given
342
- space's bottom-face footprint (fully-inside, boundary-nested, and overhanging
343
- all match; edge-touching does not; holes respected). Hidden entities are
344
- excluded unless `isHidden` is passed explicitly.
345
- - Removed the never-shipped `computeAdjacencyMatrix`; renamed
346
- `buildings.duplicate` → `buildings.copy`; folded `tags.createAndAssign` into
347
- `tags.assign` (resolve-or-create, one atomic undo).
348
-
349
- ## 0.2.8
350
-
351
- ### Patch Changes
352
-
353
- - 445e0b8: Add Present-mode AI Inspiration plugin APIs under `snaptrude.documentation.aiInspiration`.
354
-
355
- ## 0.2.7
356
-
357
- ### Patch Changes
358
-
359
- - c5afd9a: added adjacecny
360
-
361
- ## 0.2.6
362
-
363
- ### Patch Changes
364
-
365
- - c17d287: Add bulk space APIs: `space.bulkCreate`, `space.bulkUpdate`, and `space.bulkDelete`. Each batches its work into a single undo/redo step. All items are validated up front and the call throws if any item is invalid (nothing is applied), consistent with the single-item space methods. `bulkCreate` supports `spaceType`/`massType`/`departmentId` per item and returns `{ spaceIds }`; `bulkUpdate` takes geometry (`profile`+`extrudeHeight`) and/or nested `properties` per item and returns `{ spaces }` (one echoed result per item, like `update`); `bulkDelete` returns `{ deletedSpaceIds }`.
366
- - 0683194: bulk apis
367
-
368
- ## 0.2.5
369
-
370
- ### Patch Changes
371
-
372
- - Restore declaration files in published plugin packages.
373
-
374
- ## 0.2.4
375
-
376
- ### Patch Changes
377
-
378
- - Add inner profile hole support to space creation from profiles.
379
-
380
- ## 0.2.3
381
-
382
- ### Patch Changes
383
-
384
- - 647466b: fixed merge issues
385
-
386
- ## 0.2.2
387
-
388
- ### Patch Changes
389
-
390
- - 064777a: buildable envelope apis
391
-
392
- ## 0.2.1
393
-
394
- ### Patch Changes
395
-
396
- - 3b37f4a: added buildable envelop api
397
-
398
- ## 0.2.0
399
-
400
- ### Minor Changes
401
-
402
- - 4d959a4: Exposed new offset and copy APIs for tools
1
+ # @snaptrude/plugin-core
2
+
3
+ ## 0.9.17
4
+
5
+ ### Patch Changes
6
+
7
+ - d63e597: Add a `presentation.sections` namespace for section lines: `list`, `get`, `create` (from two plan points), `update`, `delete`, open or close a section view with `getActive` / `setActive`, and `getCut`, which reports every element a section's cutting plane passes through, with the cut's area, outline length and extent on the plane. Unknown-id reads return `null`; unknown-id writes and `getCut` throw `HANDLE_INVALID`.
8
+
9
+ ## 0.9.16
10
+
11
+ ### Patch Changes
12
+
13
+ - e8d4ec3: Add camera pose and navigation APIs to `core.camera` (`getPose`, `setPose`, `move`, `rotate`, `getNavigationSettings`, `updateNavigationSettings`) and a new `presentation.walkthroughs` namespace for camera paths: create from keyframes, floor points or camera-move presets; edit walkthroughs and their keyframes; `sample`, `listIssues` and `capture` them; play them with `start`/`stop`/`getState`; and `export` them as a video onto the Present canvas as a job (`getJob`, `cancelJob`, `listJobs`).
14
+
15
+ ## 0.9.15
16
+
17
+ ### Patch Changes
18
+
19
+ - 4bfe118: `core.layers`: the layers in the Layers panel in the left sidebar. `list()`, `get(layerId)` and `getActive()` read the active design option's layers (default first, with `objectCount`); `create(name)` adds a layer to the active option (undoable, not activated); `rename(layerId, name)` renames (not undoable, default layer refused); `update(layerId, { locked?, hidden? })` locks and hides per-user (undoable; other option keys are rejected); `setActive(layerId)` sets where new objects land; `delete(layerId, { deleteObjects: true } | { targetLayerId })` deletes with an explicit disposition (server-validated; refused for the default, last, locked or shared layer; treat as permanent); `assign(componentIds, layerId)` moves objects onto a layer (undoable, locked objects skipped). Layer ids are the ids `core.proposals` records list as `bucketIds` / `defaultBucketId` (the proposals docs now point there).
20
+
21
+ MOVED (compatible at runtime): the former `core.layers` (each storey's sketch surfaces and imported underlays, ids `ly_*`) is now `core.storeys.layers`, with the same methods (`list`, `get`, `getActive`, `setActive`, `update`) and signatures. Plugins that still call the old shape on `core.layers` (`list(storeyId)`, `getActive(storeyId)`, `get` / `setActive` / `update(layerId, hidden)` with a `ly_*` id) keep working: the host routes those calls to `core.storeys.layers`. They are deprecated; new code should call `snaptrude.core.storeys.layers.*` directly. Only the TypeScript names change: `PluginCoreLayersApi` -> `PluginCoreStoreysLayersApi`, `PluginCoreLayer` -> `PluginCoreStoreyLayer`, `PluginCoreLayerType` -> `PluginCoreStoreyLayerType`, and `PluginCoreLayers*Args` / `PluginCoreLayers*Result` -> `PluginCoreStoreysLayers*Args` / `PluginCoreStoreysLayers*Result`; the names `PluginCoreLayersApi`, `PluginCoreLayer` and `PluginCoreLayers*` now describe the Layers panel.
22
+
23
+ - 80c1a2b: `presentation.camera`: the Present-mode canvas camera. `capture()` screenshots what the Present canvas currently shows (the visible area at the current pan and zoom, as a base64 data URL with its pixel size); `get()` reads pan, zoom, the visible page rectangle and the sheet on screen; `set({ x?, y?, zoom? })` pans and zooms; `zoomToFit()` frames every sheet; `zoomTo(shapeId)` frames one sheet or placed shape. View-state only (not write-gated, not undoable); requires Present mode to be open.
24
+ - 0e3df96: Raked and gabled walls: `design.create.walls` takes a trailing `topProfiles` argument and `design.create.wallRuns` items a `topProfiles` field — one optional `PluginWallTopProfile` per centerline curve (`[{ along, height }, …]`, `along` 0→1, at least two points). The wall is built as a real Wall with the given top edge (two points = single slope, three = gable), mitred at run corners, in the same single undo entry; it is non-parametric (the parametric wall graph does not rebuild it), like a wall whose top edge was dragged by hand.
25
+ - d84ea44: `design.update.setObjectType(components, objectType)`: convert masses into real walls, roofs, slabs or floors (`objectType` uses the `design.query` `type` vocabulary: `"wall" | "roof" | "slab" | "floor"`) — the properties panel's Object Type dropdown, headless, keeping the geometry exactly as built (no flattening to a default thickness) and keeping the component's handle. Instance families convert together. One undoable operation.
26
+
27
+ ## 0.9.14
28
+
29
+ ### Patch Changes
30
+
31
+ - 22c919b: Curved-geometry contracts — spline curves, primitive solids, curved-surface reads, and the shared mass-parameter record.
32
+ - **`spline` handle kind** — `SplineHandle` joins `CurveHandle` (`line | arc | spline`), so every `core.geom.query.curve` member and `profileFromCurves` take a spline, and joins `ArenaKind`, so `core.handles.release`, `releaseAll("spline")` and `endScope`'s `retain` list accept one. Closed spline loops never exist as a single handle: a closed run is authored directly as a profile.
33
+ - **`core.geom.create.splineFromPoints`** — an open NURBS curve passing exactly THROUGH 3..256 ordered points (interpolated, not control points).
34
+ - **`core.geom.create.profileFromSplinePoints`** — the closed counterpart: a smooth closed profile through the points, ready for `contourFromProfile` → extrude/loft/sweep. No mixed straight/spline input in v1 — compose those with `profileFromCurves`.
35
+ - **Primitive solids** — `brepFromSphere`, `brepFromCylinder`, `brepFromCone` (optional `topRadius`, default 0 = apex) and `brepFromTorus`. Positional plain `{x, y, z}` args matching the `brepFromRevolution` family; `base` is the bottom-cap centre for cylinder/cone, `centre` for sphere/torus. Each result carries an exact analytic surface and a creation recipe.
36
+ - **`core.geom.create.brepFromSweep`** — `options.transition` gains `"round"` beside `"miter"` and `{ bevel }`: corners carried around a tangent circular arc, so the corner patch is a real curved surface.
37
+ - **`core.geom.create.brepFromRevolution`** — JSDoc rewritten to the implemented gate: segments may run at any angle to the axis (an incline revolves into a cone, an arc into a sphere or torus section). Two rules, checked in this order: the axis must lie in the profile's plane or run parallel to it (a parallel axis offset from the plane is judged by its projection into the plane, as 0.9.13 built it; a skew axis is `PRECONDITION_FAILED`), then the whole profile must stay on one side of the axis — touching it is legal (dome poles, cylinder inner edges), crossing it is not (`VALIDATION`).
38
+ - **`core.geom.query.brep.getEdgeCurve`** — a fourth `"spline"` variant (`degree`, `controlPoints`, expanded `knots`, endpoints, `length`), so reading back a spline edge no longer throws.
39
+ - **`core.geom.query.brep.getVolume`** — exact enclosed volume integrated from the surfaces, free of tessellation error.
40
+ - **`core.geom.query.face.getSurfaceKind`** — `"plane" | "cylinder" | "cone" | "sphere" | "torus" | "spline" | null`: the one read that tells a real curved face from a faceted approximation of one.
41
+ - **`core.geom.query.curve`** — class JSDoc gains the per-member spline support table; unsupported members refuse with `PRECONDITION_FAILED` rather than answering wrongly.
42
+ - **`design.query.getParameters`** — read a mass's creation-parameter record (`kind` + values, world coordinates), or `null` for geometry with no honest recipe.
43
+ - **`massParameters`** (new module, exported from the package root) — `MassParametersRecord` / `ContourRecord` / `ProfileRecord` / `CurveRecord` / `XYZ` types and Zod schemas, shared verbatim by plugin-core, the host and the agent runtime. Pure JSON, one `{x, y, z}` point shape, array bounds as contract (through-points ≤ 256, spline poles ≤ 512, sweep path ≤ 512, loft sections ≤ 32, profile curves ≤ 256, contour holes ≤ 64).
44
+
45
+ Editing what was made, and rounding it:
46
+ - **`design.update.parameters(component, patch)`** — the write pair of `design.query.getParameters`: patch a parametric mass's creation recipe and the solid rebuilds in place, keeping its id, storey, materials, position and linked copies. `patch` is a partial of the record's `values` (keys you omit are unchanged; each key you send replaces that value whole — a scalar, a point, a list or a record (`contour`, `profile`, `sections`, `path`, `options`, `operations`) alike — so send the complete value you read) or the whole record read back and edited; `kind` and `version` are immutable; the reply is the complete post-edit record in world coordinates. `PRECONDITION_FAILED` when the component is not a mass (a family instance: use `design.family.setParameters`), when the mass carries no live recipe, when it was scaled (undo the scale and call again), when it or a linked copy is locked (unlock them — the recipe is intact, and `getParameters` cannot see the lock), when a recorded operation's selectors no longer resolve, or when the edit is the first twist or taper, or the straightening, of an extrusion that carries a shell, fillet or chamfer (the faces renumber: send the twist keys with `operations: []` first, then re-apply), `VALIDATION` for argument errors, `OPERATION_FAILED` for a kernel refusal that leaves the model unchanged.
47
+ - **`core.geom.create.splineFromControlPoints(controlPoints, { degree? })`** — an open NURBS curve DEFINED BY 3..512 control points (pulled toward, not through, the interior ones), degree 2 or 3, clamped uniform knots computed host-side. It is the write pair of a `splinePoles` curve record, so a curve read back off a mass can have its poles moved and be rebuilt.
48
+ - **`brepFromFillet` / `brepFromChamfer` / `brepFromShell` / `brepFromOffset` accept CURVED solids.** The straight-edges-only and no-shared-vertex rules are gone: edges may be straight, circular or spline, and adjacent edges (corner blends) are allowed. The JSDoc carries the rim-selection recipe — `brep.listEdges` → `edge.listFaces` → `face.getSurfaceKind` on both faces (`listEdgesBetween` takes two VERTEX indices and is not a face-pair lookup) — and the new refusals: `VALIDATION` where there is no crease (a seam, or two patches of one smooth surface), and — for `brepFromFillet` alone, the one operation with a per-edge kernel verdict — `PRECONDITION_FAILED` naming the edge the kernel could not blend. An over-size chamfer, shell or offset is `OPERATION_FAILED` with a hint asking for a smaller distance/thickness and no edge or face named. Selection caps are RECORD caps, not call caps: the wire schemas accept any selection size, exactly as 0.9.x did — a fillet/chamfer of more than 64 edges or a shell opening more than 32 faces still builds, but the result carries no parameter record (the recipe-less fallback).
49
+ - **`MassParametersRecord` gains an optional `operations` list** (`version` stays 1) — a fillet, chamfer, shell or offset of a recorded solid keeps the record, with `kind` still the base constructor and the operation appended. `OperationRecord`, `EdgeSelector`, `FaceSelector`, `TopologyCounts`, `SurfaceKind`, `CurveKind`, `ParameterPatchValue` and `MassParametersPatch` join the module, with caps `MAX_OPERATIONS` 8, `MAX_OPERATION_EDGES` 64, `MAX_OPERATION_FACES` 32. Selectors are TOPOLOGICAL (edge index + its face pair + curve kind; face index + surface kind + edge count) so they survive a scalar edit of the base and trip on a reorder.
50
+ - **`design.query.geometry.getBrep`** — additive `options.frame` (`"local"` default, unchanged 0.9.x behaviour; `"world"` places the solid where it sits in the scene, for booleans between scene masses and for fillet/shell/offset/chamfer results committed back beside their source).
51
+
52
+ Freeform surfaces, sweeps, patches and variable blends:
53
+ - **`core.geom.create.brepFromSweepAlongCurve(profile, path, options?)`** — sweeps a cross-section along a SMOOTH path of 1..64 curves (splines, arcs and lines chained end to end), where `brepFromSweep` takes a polyline. `options.orientation` picks how the profile turns: `"upright"` (default) keeps its up aligned with world up, `"frenet"` rolls it with the curvature, `"fixed"` carries the authored frame along unchanged — which is a pure translation, and so is also how a footprint is bent along an arc. `options.rail`, a second guide curve, carries the profile along a second path; a closed rail, or a rail together with `orientation`, is `VALIDATION`, and a railed sweep records nothing (`getParameters` answers `null`). Otherwise it records as the one new kind `sweepAlongCurve` (`{ profile, path: CurveRecord[], options? }`, `version` still 1), so the orientation stays editable through `design.update.parameters`; the shipped `sweep` kind and its `XYZ[]` path are untouched.
54
+ - **`core.geom.create.brepFromPatch(boundary, thickness, options?)`** — stretches a surface across a closed loop of 3..16 boundary items and thickens it into a SOLID in one call: a roof over four curves, a canopy between two rails, a blend across a gap. A boundary item is a curve or an `OwnedEdge` (`{ edge, brep, face? }` — an edge handle cannot name its own solid, and one patch may meet two of them). `options.continuity: "tangent"` leaves the named face smoothly and then REQUIRES `face` on every item, because a solid edge lies between two faces and only the caller knows which one to continue; `interiorPoints` (≤ 64) pull the surface through them, and `side` picks which way the solid grows. There is no sheet handle and no surface object: only solids reach the canvas. The result carries no recipe — `design.query.getParameters` is `null` and the mass label is its provenance.
55
+ - **`core.geom.create.brepsFromSplitByFace(brep, toolFace, toolBrep)`** — the curved twin of `brepsFromSplit`, which cuts with a plane: cuts a solid with a FACE of another solid and returns the pieces largest first. Both breps must be in the same frame — read scene masses with `getBrep(x, { frame: "world" })`. A cut that does not pass all the way through is a `VALIDATION`, not a silent single piece.
56
+ - **`core.geom.create.curvesFromProjection(curve, face, faceBrep)`** — drops a curve onto a face along its normal and returns the runs that land on it, one curve per connected run, or `[]` when the curve misses the face; `VALIDATION` when the curve's midpoint lies on the face's surface (no drop direction).
57
+ - **`core.geom.query.face.getArea(face, brep)` / `.getCentroid(face, brep)`** — exact area (internal units squared) and centre of area of one face, integrated off its surface rather than its facets. These are the two reads that tell freeform faces apart, where every face answers `"spline"` and a kind census returns nothing. Both take the parent brep, owner last, exactly like `listVertexPositions(face, brep)`.
58
+ - **`brepFromLoft`** gains `options.smoothing` — `"ruled"` (default) is today's straight-sided loft byte for byte; `"smooth"` lofts every section in ONE pass, so the sides curve continuously through the intermediates. `LoftOptions` carries it into the record.
59
+ - **`brepFromExtrusion`** gains a trailing `options?: { twistInDegrees?, topScale?, levels? }`, and the shipped `extrusion` record gains the same three optional values (`version` still 1) — a twisted or tapered tower stays the SAME kind, so "twist this tower" is an edit rather than a delete-and-rebuild. `twistInDegrees` is an angle and `topScale` a ratio: neither is ever converted by a unit factor. `levels` is a count (each step may turn at most 22.5°), resolved at create time and written into the record, so an existing mass regenerates from the record and never from a heuristic that may change under it. All three absent is the straight extrusion, byte for byte.
60
+ - **`brepFromFillet`** gains a trailing `options?: { endRadius?, radii? }` — a VARIABLE blend. `endRadius` interpolates linearly from `radius` along the whole tangent chain the one edge you pass belongs to (an open chain: on a closed rim a law whose two ends differ is refused, for `endRadius` and `radii` alike). `radii` is the sampled form instead: 2..32 rows of `{ position, radius }` with `position` the normalised ARC LENGTH along that contour from the passed edge's start — a ratio, never converted — strictly increasing. Either form takes exactly ONE edge, they are mutually exclusive, and both round-trip on the fillet `OperationRecord`.
61
+ - **Face and edge selectors gain `at?`** — where the face or edge sits inside its solid, its centroid (an edge's midpoint) over the solid's bounding-box extent, a ratio per axis. It is written only when index, kind and counts cannot tell two of them apart, which on a freeform solid is the common case, and it is verified — never searched — on replay, so a record written without it resolves byte-identically.
62
+ - **`ParameterPatchValue`** widens with `SweepAlongCurveRecordOptions` and `CurveRecord[]` (the `sweepAlongCurve` path). It only ever widens; every value it took before it still takes.
63
+ - **`massParameters.ops` — a second record module.** `EdgeSelector`, `FaceSelector`, `TopologyCounts`, `OperationRecord`, `ParameterPatchValue` and the operation caps (plus the new `MAX_FILLET_LAW_SAMPLES` 32) move into `src/massParameters.ops.ts` and are re-exported from `massParameters` and from the package root — no import path changes. The split keeps each module small enough for an agent to read whole. New caps beside the shipped ones: `MAX_PATH_CURVES` 64, `MAX_TWIST_LEVELS` 64, and — on the creator, because a patch has no record — `MAX_PATCH_BOUNDARY_CURVES` 16 and `MAX_PATCH_INTERIOR_POINTS` 64.
64
+ - **`design.family.getParameters`** gains an optional `parts: PluginFamilyPart[]` — each placed part's `name`, the `engine` that built it (`"kernel"` for a curved sketch built exactly, `"mesh"` otherwise) and, on kernel parts, a read-only part-local `brep` for the face-kind census. `design.query.geometry.getBrep` keeps returning `null` for a family.
65
+
66
+ ## 0.9.13
67
+
68
+ ### Patch Changes
69
+
70
+ - 6c85137: design.family: add `delete({ id, instances? })` — archives the library row; `instances: true` also deletes this project's placed instances undoably. `create` result gains `placed` and `hint`.
71
+
72
+ ## 0.9.12
73
+
74
+ ### Patch Changes
75
+
76
+ - 6e4a055: added param comps
77
+ - 8e37e1f: `design.family`: `prepare` and `flex` warnings gain the `proportion` code — parts thinner than 0.5 mm, parts or a family larger than 50 m, a family smaller than 10 mm, each naming the parts — and `flex` now returns `warnings`.
78
+ - 2a06dd9: `design.family`: `prepare`, `flex` and `getParameters` accept a `snapshot` option and return a `snapshot` block — an isolated offscreen picture of the family (iso, front, right, top tiles with a dimension line) as a PNG data URL, so an agent can look at what it built before reporting.
79
+
80
+ ## 0.9.11
81
+
82
+ ### Patch Changes
83
+
84
+ - 7fd374e: Add `hinge` to `design.create.door` / `window` (in `options`), `doors[]` / `windows[]` items and `opening`: a world point near the jamb the leaf is hinged on; the placed item is reflected along the wall when the catalog item's authored hinge side would land on the other jamb — one undo entry, no extra command.
85
+
86
+ **BEHAVIOUR FIX — `facing`.** The host placed the swing on the side OPPOSITE the `facing` point (the interactive tool's cursor convention) while the contract said "the room it opens into". The host now matches the contract: the leaf swings open toward `facing`. Callers that passed a point on the room side get the documented result; nothing in the contract changed shape.
87
+
88
+ `design.doors.getSwingDirection` is now derived from the placed mesh's orientation (hand as seen standing on the swing side facing the door); it previously read a scaling sign that placement never sets and reported `"left"` for every door. `mirror` axis docs now say what each axis does to a door (`'x'` swing side, `'z'` hinged jamb, `'y'` upside down); behaviour unchanged.
89
+
90
+ - e325bee: Add the structural-grids API surface: queries, creators, a sparse patch updater, structural line edits, and generic-family acceptance.
91
+ - **Query** — `design.query.listGrids(filter?)` (standard `PluginEntityFilter`; `"grid"` joins `PluginEntityType`, so `listByType("grid")` works) and the field-projected `design.query.grids.get(gridId, properties[])`: `angle` (degrees, CCW about +Y), `props`, `storeyVisibility`, `wings` (per-wing spacing/tagStart/span and per-line world-space `CurveHandle`, tag, visibility, effective + override props), `bounds`. Unknown ids throw `HANDLE_INVALID`.
92
+ - **Create** — `design.create.grid(linesX, linesY, spacingX, spacingY, options?)` (free 2-wing grid; optional `angle` in degrees and `center`, which defaults to the native viewport-centre behavior), `design.create.gridFromCurve(curve, options?)` (one wing/one line — line or arc), `design.create.gridsFromMasses(masses, spacingX, spacingY, options?)` (fitted, one group per footprint cluster, lenient skipping, ONE undo unit), and `design.create.columnsFromGrids(grids, width, depth)` (columns at every line intersection, ONE undo unit).
93
+ - **Update** — `design.update.grid(grid, patch)`: GROUP-LEVEL sparse patch — `props` (default styling), absolute `angle` in degrees, and `storeyVisibility`; each changed field commits its own undoable command through the panel's setters.
94
+ - **Grids family** — `design.grids.updateWings(grid, wings[])` (per-wing `spacing`/`count`/`spanMin`/`spanMax`; a `count` change re-indexes lines — re-query) and `design.grids.updateLines(grid, lines[])` (per-line `tag`/`distanceToPrevious`|`distanceToNext` (mutually exclusive)/`props`/`visible: false` (hide-only, product parity — show the GROUP to restore all lines); hiding every remaining visible line hides the GROUP instead — native last-line rule); plus `design.grids.addLine(grid, wing, position)` and `design.grids.removeLines(grid, lines, tagPolicy?)` (`"rewrite"` default | `"preserve"`; removing the group's last line throws `OPERATION_FAILED` — delete the group instead). Every update routes through the exact setters the native panel uses — one undoable command per changed field, identical to panel edits.
95
+ - **Generic families** — grid groups are accepted by `design.transform.move`/`rotate`, `design.visibility.hide`/`show`, `design.lock`/`unlock`, `design.delete.entities`, and `design.selection`; `transform.align` and `transform.mirror` throw `OPERATION_FAILED` for grids.
96
+
97
+ Grid lines have no stable identity: every line-level operation addresses them positionally as `{ wing, line }` index pairs into the `wings` arrays returned by `grids.get`; structural edits re-index lines, so re-query after mutating.
98
+
99
+ ## 0.9.10
100
+
101
+ ### Patch Changes
102
+
103
+ - `core.mode.set("program")` now opens the Program tab (the same path as the top bar's Program button) instead of refusing with `PRECONDITION_FAILED`; it resolves once the tab has connected, and waits for the user to confirm the editor's pop-up modal when the browser blocks the window.
104
+ - `workspace.openProgramMode()` / `closeProgramMode()` — the Program-tab counterpart of `openPresentMode` / `closePresentMode`: open (awaiting the tab's connection) and close the paired Program window.
105
+
106
+ ## 0.9.9
107
+
108
+ ### Patch Changes
109
+
110
+ - Program-mode additions requested by the healthcare programming pilot (all additive; no signature changes):
111
+ - `program.metadata` — the Program Data sheet's custom columns and per-space values: `listHeaders`, `createHeader` (reuse-by-name), `get`, `list`, `update`, `updateMany`. Writes are undoable and work with the Program tab closed.
112
+ - `program.labels` — the program rows (space types) with targets: `list`, `get`, `create` (optionally `createSpaces`), `setTargetArea`, `setTargetCount`. Covers "target area of non-departments": a space inherits its label's target; departments keep `program.departments.setTargetArea`.
113
+ - `program.spreadsheet` — custom program sheets with a group-by hierarchy: `createCustomSheet`, `updateCustomSheet`, `getCustomSheet`, `listCustomSheets`, `listCustomSheetProperties`.
114
+ - `core.tags.listForComponents(componentIds?)` — every space's tags + colors in one round-trip.
115
+ - `program.departments` — `targetCount` on the department record and `setTargetCount(departmentId, targetCount)`, the count twin of `setTargetArea` (the engine stores both on the department, like on labels).
116
+
117
+ ## 0.9.8
118
+
119
+ ### Patch Changes
120
+
121
+ - 7fd374e: Add `hinge` to `design.create.door` / `window` (in `options`), `doors[]` / `windows[]` items and `opening`: a world point near the jamb the leaf is hinged on; the placed item is reflected along the wall when the catalog item's authored hinge side would land on the other jamb — one undo entry, no extra command.
122
+
123
+ **BEHAVIOUR FIX — `facing`.** The host placed the swing on the side OPPOSITE the `facing` point (the interactive tool's cursor convention) while the contract said "the room it opens into". The host now matches the contract: the leaf swings open toward `facing`. Callers that passed a point on the room side get the documented result; nothing in the contract changed shape.
124
+
125
+ `design.doors.getSwingDirection` is now derived from the placed mesh's orientation (hand as seen standing on the swing side facing the door); it previously read a scaling sign that placement never sets and reported `"left"` for every door. `mirror` axis docs now say what each axis does to a door (`'x'` swing side, `'z'` hinged jamb, `'y'` upside down); behaviour unchanged.
126
+
127
+ ## 0.9.7
128
+
129
+ ### Patch Changes
130
+
131
+ - 6e60c61: Add bulk `design.create` creators — `wallRuns`, `floors`, `doors`, `windows`, `furnitureItems`: one host round-trip and one undo entry per batch.
132
+
133
+ Singular creators, fixes shipped alongside (behaviour changes for existing callers):
134
+ - `floor`, `slab`, `roof`, `ceiling`, `column`, `beam`: contour holes are now cut (holed contours previously failed or lost their holes).
135
+ - `floor`: the `position` offset is applied before the creation snapshot, so it survives redo and reload and drives storey assignment.
136
+ - `furniture`: `options.label` is now also the readable label (`design.query.getLabel`); `createNewSourceMesh` can no longer skip recording a source this call brought in (the host owns source persistence, recorded exactly once across concurrent calls); refuses with `PRECONDITION_FAILED` (`engineCode: "TOOL_ACTIVE"`) while the interactive furniture tool is active. The same refusal applies to `core.io.import.model` furniture placement.
137
+ - `door`, `window`: published as one save document instead of five; undo granularity unchanged.
138
+
139
+ - 2f10e6b: Add `design.dimensions` — dimension lines (Measuring Tape): `create`, `list`, `get`, `delete`, `hide`, `show`, plus the new `DimensionHandle`.
140
+
141
+ ## 0.9.6
142
+
143
+ ### Patch Changes
144
+
145
+ - Add five APIs the plugin agent repeatedly reached for but that did not exist.
146
+ - `core.project.getInfo()` — project id, display name, unit type, storey count (scoped to the active building), active storey, and site location in one call. Closes a genuine capability gap: nothing previously returned the current project's identity.
147
+ - `core.storeys.getActive()` — `setActive` had no getter, unlike `core.buildings`, `core.layers` and `core.proposals`.
148
+ - `core.camera.getMode()` — `setMode` had no getter, while `getProjection`/`setProjection` on the same class were paired.
149
+ - `design.visibility.show(components)` — `hide` previously had only `showAll` as its inverse, so specific components could not be revealed.
150
+ - `design.furniture.listCatalogGroups()` — matches `design.doors` and `design.windows`, so all three catalog surfaces are named alike. `design.furniture.listCategories()` is deprecated but still supported.
151
+
152
+ ## 0.9.4
153
+
154
+ ### Patch Changes
155
+
156
+ - 0008010: `brepFromLoft` auto-matches section edge counts (deterministic vertex insertion) and auto-aligns seams for straight-edge sections; `brepFromSweep` accepts closed (ring) paths and gains an optional taper scale law (`startScale`/`endScale` or per-point `scales`).
157
+ - f54ea17: `brepFromLoft` gains `options` (`compatibility: "strict" | "auto"`, `seamAlignment: "authored" | "auto"` — strict+authored restores pre-0.9.4 lofting); `brepFromSweep` options gain `transition` (`"miter"` default, `"round"` on open paths, `{ bevel }` chamfered corners on open and closed paths); new pure read `core.geom.query.brep.getEdgeCurve` returns exact line/arc/circle edge curve data.
158
+
159
+ ## 0.9.3
160
+
161
+ ### Patch Changes
162
+
163
+ - 3584bde: Expose configured BIM creation and Site Analysis sheet APIs: `design.create.opening` (catalog door/window with width/height/sillHeight), staircase `dimensions` overrides (strict-validated, quarter-turn rotation), and `program.site.getSiteAnalysis`/`updateSiteAnalysis` with merge/append semantics. `PluginSiteAnalysisUpdateOptions` accepts `null` as "no options".
164
+
165
+ ## 0.9.2
166
+
167
+ ### Patch Changes
168
+
169
+ - 5b91c7b: OCC geometry batch: new `core.geom.create` brep constructors — `brepFromFillet`, `brepFromChamfer`, `brepFromOffset`, `brepFromShell`, `brepsFromSplit`, `brepFromSweep`, `brepFromRevolution`; `brepFromLoft` widened with optional `intermediateContours` (multi-section lofts); new `core.geom.query.brep.getDistance` (minimum distance between two solids with witness points).
170
+
171
+ ## 0.9.1
172
+
173
+ ### Patch Changes
174
+
175
+ - Plugin API 0.9.1 — strictly additive: the terrain mesh lane and the Present-mode styling/canvas lane. No signatures changed, no behavior of existing methods changed; deployed plugins are unaffected (`versionGate` compares major.minor only).
176
+ - **`core.io.terrain.replaceMesh(positions, indices, units?, options?)`** — replace the site terrain surface with a caller-supplied triangle mesh. World-space flat arrays (the `getTriangulatedMeshes` frame, so read-modify-write round-trips); `units: "meters"` converts on the way in. Validated before any engine call (finiteness, index bounds, degenerate topology, scene-bound extents, **500k vertex/triangle caps** — the surface's first explicit payload ceiling); a predominantly down-facing mesh is auto-flipped with a warning. Datum, geolocation, opacity, lock state and the elevation/satellite toggles survive; the map texture is re-projected best-effort; cut/fill history resets by contract. One undo step restores the prior surface exactly (buffers, UVs, submesh layout, cut/fill data, volumes). The replacement **persists with the project**: the mesh uploads to bucket storage and reload applies it instead of the heightmap build. The optional `options` mirrors the requester's contract: `coordinateSpace`, `expectedTerrain` (session optimistic check), `expectedModelRevision` (session terrain-mesh revision, returned as `modelRevision` in the result), `baseline: "make-replacement-original"`, `preserve` opt-outs (`datum`/`satellite`/`material` have real opt-out behavior; `geolocation: false` rejects — always preserved), and `clientMutationId` (session-scoped idempotent replay). For projects without terrain, compose `core.io.import.terrain` + `replaceMesh` (a separate `createFromMesh` was deliberately not added).
177
+ - **`presentation.placedViews` — per-instance styling** (`getStyles` / `updateStyles` / `resetStyles` / `updateFontStyles`): the Present sidebar's full per-category styling surface — 7 line dash styles, line color, dash-length multiplier, **cut vs projection line weights in mm** (plus tree/parking weights), fills (`department`/`texture`/`monochrome`/`tag:<tagCategoryId>`), per-category opacity, category hide (cascades to labels), view-style restyles (block/rounded/bubble with radius + inset), Topography map/building sub-controls, and Text-tab typography per label type (font family incl. Google Fonts, weight, italic, px size, visible label types). Writes are host-validated (typed VALIDATION with the supported-setting list), land as **one undo step per call**, re-render immediately, and fan out across an auto-diagram sheet's set. `getStyles` returns fully-resolved values plus each category's supported setting ids.
178
+ - **`presentation.placedViews` — labels** (`listLabels` / `moveLabel` / `setLabelHidden`): a placed view's generated labels (space names, departments, areas, dimensions) as addressable shapes — read text/category/position, reposition them, hide/show individually (independent of category-level hides).
179
+ - **`presentation.placedViews` — generic canvas shapes** (`listShapes` / `getShape` / `moveShape` / `resizeShape` / `deleteShapes` / `setOpacity` / `setMask`): every top-level Present-canvas shape — AI-generated outputs, placed diagram images, plugin shapes, annotations — addressable by id with an `origin` classification. Previously `aiInspiration.generate` and `diagrams.place` returned shape ids nothing could act on. `deleteShapes` is atomic (any rejected id deletes nothing), protects sheets (use `sheets.delete`) and user-drawn annotations, and closes the placed-view no-delete gap. `setOpacity` is the whole-shape slider; `setMask` toggles context-building masking on views.
180
+ - **`presentation.sheets.setMargin(sheetId, margin)`** — set a sheet's print margin to one of the canonical unit-agnostic presets `0 | 0.25 | 0.5 | 0.75 | 1` (displayed None / 1/4″ / 1/2″ / 3/4″ / 1″ on imperial projects, None / 5mm / 10mm / 15mm / 20mm on metric) — the sheet header's margin dropdown; one undo step. Sheet reads (`list`/`get`) now report `margin` (`null` when absent/unrecognized).
181
+ - **`presentation.placedViews.setRotation(shapeId, rotation)`** — set a placed view's **absolute** rotation in radians (tldraw convention) about its bounds center (the UI rotation handle's pivot); one undo step. Angles are canonicalized into `[0, 2π)`, so the round-trip with the record's `rotation` read is exact only in that range (`setRotation(-0.1)` reads back `2π − 0.1`). **3D placed views only** — 2D/site-plan views cannot rotate (the canvas reverts any rotation applied to them) and are refused with a typed `PRECONDITION_FAILED`. Joins the absolute-setter family (`setScale`/`setCrop`/`setOpacity`/`setMask`).
182
+ - **`presentation.placedViews.rotateShape(shapeId, rotation)`** — the same absolute center-pivot rotation for any top-level Present-canvas shape (AI outputs, diagram images, plugin shapes, annotations — placed views too); targets and refusals mirror `moveShape`. Same `[0, 2π)` canonicalization (exact round-trip only in that range), and a placed view passed here falls under the same 3D-only rule — 2D/site-plan views refuse with a typed `PRECONDITION_FAILED`.
183
+ - **`presentation.tables.place(sheetId, { rows }, options?)`** — new `tables` namespace: place plain string rows as a table shape on a sheet (the same shape the Present canvas creates when a Program-mode / Google Sheets / Excel table is pasted). Rows may be ragged, capped at 10000 cells total; `options.position` is sheet-local like `annotate.*` (default: the paste flow's viewport-center auto-placement); returns the created shape as a canvas-shape record; one undo step.
184
+ - **`presentation.slideshow`** (`start` / `stop` / `getState`) — new namespace: run the Present-mode slideshow programmatically. The slideshow plays **all** sheets in order; `start` options only pick the opening slide (`startIndex` wins over a single-entry `sheetIds`, clamped into range). Browsers grant fullscreen only on a user gesture, so a plugin-triggered `start` typically runs **non-fullscreen** (the rejection is caught). `start`/`stop` are write-gated; `getState` is a never-throw read (`running` + current `index`, `null` when not running). No `next`/`prev`/`goTo` — slide navigation stays with the user.
185
+ - **`workspace.openPresentMode()` / `closePresentMode()`** — open/close the Present-mode documentation editor. `openPresentMode` resolves only once the Present canvas has mounted (up to ~15s, then a typed `PRECONDITION_FAILED`), so follow-up `presentation.*` calls find Present mode open; both are idempotent and **write-gated** (a mode switch under the user's feet is approval-controlled by design). Overlaps `core.mode.set("present")`/`set("design")` but adds the mount-await + idempotency contract.
186
+ - **`presentation.diagrams.generateSite(options?)`** — run the site-diagram auto-generation (location / streetview / climate sheets from the map terrain) and return the created `sheetIds`. `options.types` selects a subset (default all three); generation is tracked per-proposal — already-generated types are **skipped, not duplicated** (mount-time-flow semantics; existing site-diagram sheets are never replaced), so `sheetIds` can be empty. Requires Present mode and a map terrain.
187
+ - **`presentation.annotate.text` typography** — the existing text args gain optional `fontFamily` (system family or any Google Font, loaded automatically — the Present text panel's loader), `fontWeight` (numeric string like `"400"`/`"700"`, `-italic` suffix allowed; unavailable weights fall back to regular), and `fontSizePx` (exact pixel size overriding the `size` preset — `size` alone keeps working). Existing calls are unaffected.
188
+ - **`program.site.listEdges()`** — read each site parcel's boundary edges with their **front / side / rear** classification: the exact resolution the buildable-envelope setback pills show on canvas (user per-edge overrides win over the tangent heuristic; `roleSource` says which applied), so a plugin picks up the user's edge tagging without asking them to set it up again. Per edge: canonical `edgeIndex` (the envelope `edgeOverrides` index space), endpoints and length in **world meters** (`lengthUnit: "m"` — unlike `listPolygons`' Snaptrude-unit footprints), and the **effective base setback** (per-edge exception, else the role value; `null` for parcels with no zoning envelope — those still classify by heuristic). Entries also report the linked `buildableEnvelopeId` and whether stale index-misaligned overrides were dropped. Never throws; `"street"` resolves to `front`.
189
+
190
+ ## 0.9.0
191
+
192
+ ### Minor Changes
193
+
194
+ - Plugin API 0.9.0 — namespace reorganization (deprecate-in-place, nothing breaks), the analysis wave, and the spreadsheet data lane.
195
+ - **Namespace map** — `entity.*` retired via aliases (storeys → `core.storeys`, reference lines / buildable envelopes → `design.*`), `core.zoom` → `core.camera`, `core.tags.getTagsForComponent` → `core.tags.listForComponent`, new `workspace.*` (projects/teams) and `core.mode` top-levels. Every deprecated path still works and delegates to its canonical implementation.
196
+ - **Analysis wave** — `analysis.weather.getSeries` (hourly EPW rows + station metadata + quality flags), `analysis.solar.sampleGrid` (per-point GPU-raytraced visibility/shade/SVF/irradiance), `analysis.daylight.compute` (LM-83 numeric ASE + sDA with full provenance — see the sDA entry below), `program.site.getWeather`/`getTimezone`, `.epw` import + project weather override via `core.io.import`.
197
+ - **sDA (LM-83 Spatial Daylight Autonomy)** — `analysis.daylight` now computes real sDA300/50% with dynamic blind operation (LM-83 2% rule, 5% diffuse closed-blind default) via Radiance. Runs including `"sDA"` are asynchronous: `compute` returns `{ status: "running", runId }` and the new `analysis.daylight.poll({ runId, cursor? })` fetches results; ASE-only runs stay synchronous and unchanged. New optional `optics` arg overrides surface optics per material (keyed by Snaptrude material name) or per category, over LM-83/convention defaults (wall 0.5 / ceiling 0.7 / floor 0.2 / furniture 0.5 / glazing Tvis 0.65 / context 0.2); glazing takes visible transmittance (Tvis), converted to Radiance transmissivity backend-side. Sensor records gain `da300Percent`/`sdaPass`, aggregates gain `sdaPercent`, results gain `runId`, and the ASE fields (`directSunHoursAtOrAbove1000Lux`/`aseFail`/`asePercent`) are now optional — present when ASE is requested.
198
+ - **Heatmaps** — named overlays (up to 16, `analysis.heatmaps.overlays.*`), `renderField` arbitrary cell geometry, `renderSurfaceGrid` façade grids, discrete/threshold/categorical scales, hover tooltips.
199
+ - **Geometry reads** — `design.query.geometry.getTriangulatedMeshes` (world-space triangles, optional per-triangle `materialIds` and B-rep `faceIds`), `design.query.spaces.getEnclosure` (geometric v1).
200
+ - **Spreadsheet data lane** — `program.spreadsheet.datasets.{set, list}` (plugin datasets, upsert + auto-refresh of bound ranges), binding `source` widened to `{ dataset: "takeoff" } | { dataset: "plugin", name }` (takeoff unchanged), `addImage` (data-URI floating pictures, stable-name replace-on-rerun), `addChart` accepts the ratified `"column"`/`"bar"` enum on the wire.
201
+ - **Presentation** — `presentation.shapes` keyed shape lifecycle on Present sheets (`upsert`/`remove`/`removeAll`/`list`).
202
+ - **Constructive geometry (brep kernel)** — the full `core.geom.create.brepFrom*` constructor family minting `BrepHandle`s at authored coordinates: `brepFromFaces` (plain face-point loops; host validates welding, planarity, edge coherence and fixes global orientation), `brepFromExtrusion` (prisms along any direction, holes + arc profiles), `brepFromLoft` (tapered solids between two contours; lofts producing non-planar side faces are rejected), `brepFromMesh` (indexed vertex + face-index input), and the OpenCascade booleans `brepFromUnion` / `brepFromSubtraction` (`a` minus `b`) / `brepFromIntersection` (scene-derived `getBrep` handles accepted as read-only inputs; empty/disjoint results throw typed VALIDATION).
203
+ - **`design.create.massFromBrep(brep, label?)`** — commit a mass from any `core.geom.create` brep constructor's handle (scene-derived breps stay read-only): generic mass at authored coordinates, single undo step, collab-synced.
204
+ - **`presentation.placedViews`** — layout control over placed views (`list`/`get`/`move`/`scale`/`setScale`/`setCrop`): sheet-local position, size, architectural scale, rotation, crop and link state; uniform corner-drag-equivalent `scale`, Scale-dropdown `setScale` (2D only), native crop fractions with page-anchored visible region — crop survives `sheets.updatePlacedView` refreshes.
205
+
206
+ ## 0.8.0
207
+
208
+ ### Minor Changes
209
+
210
+ - More additions to the APIs, infra changes.
211
+ - Plugin API 0.8.0 — the 0.8 API wave, handle lifecycle, events, and geometry/data-integrity fixes.
212
+ - **The 0.8 API wave** — model/presentation **export** (`core.io.export`, `presentation.export`), **element editing** widenings on `design.update`/`design.edit`, **`core.camera.*`**, **storey management**, **`design.visibility.*`**, **dimensions**, **locale/user** reads (`core.user`), `design.types.list`, presentation annotate, plus completeness widenings across existing families. The discovery manifest grows from 423 to 595 method paths (including the new `core.handles.*` lifecycle below).
213
+ - **Events** — `model:changed` subscription contract: debounced model-change notifications delivered to the plugin worker for user and plugin edits alike (client API: `PluginWorker.subscribe`). `selection:changed`, `tool:activated`, `project:saved`, `view:changed` are reserved names, not yet emitted.
214
+ - **BREAKING — `program.site` footprints are world Snaptrude units.** Previously the only plan-geometry read scaled to scene metres (×0.254), so site parcels could not be overlaid on `planPoints`. Now the same world XZ plan space as every other plan read; areas and the geographic reads are unchanged.
215
+ - **BREAKING — real beam/column geometry.** `design.create.beam` with a horizontal direction (the documented canonical call) used to build a zero-height flat sheet; it now builds a true beam with tool-parity semantics (section X extent = width, Z extent = depth, body hangs below the authoring plane by its depth). `design.create.column` builds exactly the requested height (the silent slab-thickness deduction is gone). Degenerate extrusions (zero/diagonal/in-plane directions, zero height) throw VALIDATION errors instead of silently building corrupt solids; directions are normalized.
216
+ - **Data integrity** — `design.query.list*` no longer returns engine-internal shells (originals parked by instance copies, disposed phantoms), so one copy no longer doubles `listSpaces`/schedules; `design.delete.entities` orders instance copies before their source meshes so mixed batches cannot fail midway; `design.update.wall` documents that rebuilds replace the wall — re-bind from `affected[0]`.
217
+ - **Handle lifecycle (`core.handles.*`) + GC-backed `Handle` objects** — `Handle` is now a class wrapping the wire id (public type names unchanged): `handle.id`, `toString()` stays the bare id, `equals()` compares by id, and `toJSON()` emits the `{__h: id}` wire tag — `JSON.stringify` output containing handles changes shape. New namespace: `core.handles.release(handles[])` (batched, arena-agnostic, silent no-op on unknown/released ids), `core.handles.releaseAll(kind?)`, mint-recording scopes `beginScope()` / `endScope(scope, retain?)` (token-addressed, retain promotes to the enclosing scope), and `core.handles.stats()`. The client runs a FinalizationRegistry-backed auto-release queue, so unreferenced handles are reclaimed host-side without explicit code. `core.geom.delete.profile/contour` are retained as deprecated aliases of `core.handles.release`. Entity-style kinds (component/material/underlay/terrain/importJob) remain branded strings.
218
+
219
+ ## 0.7.1
220
+
221
+ ### Patch Changes
222
+
223
+ - 8f22cb7: Expand `presentation.aiInspiration` to the full AI Inspiration surface (18 methods) and make it a required member of `PluginPresentationApi` now that the host implementation lands.
224
+ - New methods: `getModelCapabilities`, `getSelection`, `getPresetCatalog`, `getJob`, `cancelJob`, `listJobs`, `getWorkflowForShape`, `getSelectedBranchRerunPlan`, `rerunSelectedBranch`, `extractRecipeFromShape`, `listRecipes`, `saveRecipe`, `deleteRecipe`, `runRecipe` (plus job/workflow/recipe Zod schemas).
225
+ - `generate`/`refine` (and `rerunSelectedBranch`/`runRecipe`) accept `runMode: "blocking" | "job"`; `PluginAIInspirationRunResult` is accordingly loosened to `{ jobId?, output?, outputs?, metadata? }` — job-mode calls resolve with a `jobId` instead of outputs.
226
+
227
+ ## 0.7.0
228
+
229
+ ### Minor Changes
230
+
231
+ - e1915e4: Plugin API 0.7.0 — new families and methods (supersedes the cancelled `design.update.setLabel` 0.6.1 patch, now folded in).
232
+ - **`core.proposals.*`** — design options as infrastructure: `list` / `get` / `getActive` / `listForComponent` / `isActive` (reads) plus `create` / `rename` / `setActive` / `delete` (non-undoable writes).
233
+ - **`program.layout.*`** — the space-solver promoted to a real async-job family: `arrange` / `pack` now start a backend job and return immediately, with `getState` (poll) and `cancel`.
234
+ - **Door & window catalogs + placement** — `design.doors.*` / `design.windows.*` gain `listCatalogGroups` / `listCatalog` / `getCatalogItem` / `exists` (shared catalog DTOs), and `design.create.door` / `design.create.window` place a catalog item into a host wall.
235
+ - **`design.create.smartLayout`** — clone an in-scene template cluster into one or more target spaces in a single undo batch.
236
+ - **Furniture categories** — `design.furniture.listCategories` plus a `category` filter on `listCatalog`, and `thumbnailUrl` on catalog items.
237
+ - **`design.transform.align`** — snap components' bounding-box edges (left/right/center/top/bottom/middle), optionally to a reference.
238
+ - **Per-face materials** — `design.materials.applyToFaces` / `resetFaces` / `getByFace` / `listByFace` (BREP faces by durable index, visual-only).
239
+ - **Staircase params** — `design.query.getStaircaseParams` (read) and `design.update.staircase` (sparse parametric write).
240
+ - **Footprint-area lock** — `design.lockArea` / `unlockArea` / `isAreaLocked` / `listAreaLocked` for Room/Department spaces.
241
+ - **`entity.story.duplicate`** — duplicate a storey (or a subset) up/down, auto-creating the target storey.
242
+ - **`presentation.import.svg`** — import a guaranteed-vector SVG onto the Present sheet.
243
+ - **View display settings** — `presentation.views.getSettings` / `updateSettings` (background, color mode, axis, edges, labels).
244
+ - **`design.update.setLabel`** — set any component's panel Label (write pair of `design.query.getLabel`); undoable, lock-aware, validated.
245
+ - **`program.areas`** — area members now report their `areaClass` (`NET` / `GROSS` / `EXCLUDED`), with net/gross documented as the plain-English synonyms for carpet/built-up.
246
+ - **Typed plugin API errors** — every host failure now crosses the wire as a structured envelope instead of a flattened `Execution error: <msg>` string: a stable `code` from the shared plugin-core error registry, the offending handle, an authored `hint`, and `retryAfterMs` / `didYouMean` enrichment where applicable. Zod validation failures forward the first 10 issues, each projected to `{path, code, message}` (full count in `details.issueCount`); internal faults are sanitized and host-logged under an `errorId`. Every handler across the API surface now throws typed codes, each doc page gained an Errors section, and a drift test pins thrown code literals to the registry.
247
+
248
+ ## 0.6.0
249
+
250
+ ### Minor Changes
251
+
252
+ - 934405e: New `analysis.heatmaps` family — render plugin-computed scalar data as a
253
+ heatmap: `renderSpaces` (flat colour per space top face), `renderGrid`
254
+ (coloured grid mesh from world-coordinate samples), `reset`, `isActive`.
255
+ Single active heatmap (a render replaces the previous one); values outside
256
+ `[min, max]` clamp to the end colours; ephemeral (auto-cleared on
257
+ scene-mutating edits, never persisted). The react host has landed, so all
258
+ members are required `abstract` and in the discovery manifest.
259
+ - a68e866: New top-level `analysis` namespace:
260
+ `analysis.sunpath` (enable/disable/isActive), `analysis.shadows`
261
+ (enable/disable/isEnabled/setDateTime/getDateTime — ISO date-time strings),
262
+ and the async heatmap job families `analysis.sunlightHours` and
263
+ `analysis.illuminance` (compute/get/cancel/reset; illuminance is Pro-gated).
264
+ Also extends `program.site` with `getLocation()` and `getNorthAngle()` geo
265
+ reads. The react host has landed, so all new members are required `abstract`
266
+ and in the discovery manifest; the plugin-client mounts the `analysis` RPC
267
+ namespace.
268
+ - New `core.io` namespace — file import, import jobs, and underlay/terrain
269
+ management:
270
+ - `core.io.import` — bring external files onto a storey: `image`, `pdf`
271
+ (page-selectable), `dwg`, `cadJson` (parsed CAD geometry as a sketch),
272
+ `model` (3D formats), and `terrain` (map snapshot at a lat/lng with
273
+ optional elevation). Slow formats return an `ImportJobHandle` instead of
274
+ blocking.
275
+ - `core.io.job` — poll those imports: `getStatus`, `isComplete`, `getResult`,
276
+ `getError`.
277
+ - `core.io.query` — pre-import inspection: `getPdfPageCount`,
278
+ `listCadLayers`.
279
+ - `core.io.underlay` — manage imported image/PDF/CAD underlays: `list`,
280
+ `getBounds`, `getScale`/`setScale`/`resetScale`, `getOpacity`/`setOpacity`,
281
+ `delete`.
282
+ - `core.io.terrain` — manage the imported map terrain: `exists`, `get`,
283
+ `getDatum` (current vertical position, world Y) / `setDatum` (relative
284
+ datum shift, not an absolute elevation), `delete`, `getReport`, elevation and satellite-imagery toggles
285
+ (`isElevationEnabled`/`enableElevation`/`disableElevation`,
286
+ `isSatelliteEnabled`/`enableSatellite`/`disableSatellite`), and
287
+ `getOpacity`/`setOpacity`.
288
+
289
+ - New `design.selection.setByFilter(filter)` — the programmatic equivalent of
290
+ the editor's **Story selection** and **Filter selection** menus. Replaces the
291
+ selection with every visible entity of the active building matching
292
+ `{ storeys?: number[], types?: PluginSelectionEntityType[] }` (fields AND,
293
+ values within a field OR; at least one field required — use `clear()` to
294
+ deselect). The new `PluginSelectionEntityType` vocabulary covers all 24 menu
295
+ kinds, including selection-only kinds the query surface can't reach (sites,
296
+ terrain, CAD/PDF/image imports, dimension lines, 3D models, neighborhood
297
+ buildings, program blocks). One-shot bulk select, not a persistent mode; not
298
+ undoable, like the other selection mutators.
299
+
300
+ ## 0.5.0
301
+
302
+ ### Minor Changes
303
+
304
+ - 7750aa3: Exposed more snaptrude APIs
305
+
306
+ ## 0.4.0
307
+
308
+ The Plugin API expose initiative. Pre-1.0, breaking changes ship as minor
309
+ version bumps.
310
+
311
+ ### Minor Changes
312
+
313
+ - **Breaking — positional arguments.** Every API method now takes positional
314
+ parameters, options-last (required first, then one optional `options`
315
+ object), replacing the single-args-object transport. Migrate by unwrapping
316
+ the object into positional args — `foo({ a, b, t })` → `foo(a, b, t)`. E.g.
317
+ `vec3.lerp(a, b, 0.5)`, `departments.assign(departmentId, spaceIds)`,
318
+ `design.query.spaces.get(space, ["area", "position"])`.
319
+ - **Breaking — handle-based boundary.** Only JS primitives and enum strings
320
+ cross the plugin ↔ host boundary by value. Every structured type (vector,
321
+ curve, profile, BREP topology, scene entity) is now an opaque, kind-tagged
322
+ string handle; scene-entity references use `ComponentHandle`. Migrate by
323
+ minting a handle instead of passing a raw object (`vec3.new(x, y, z)`, then
324
+ pass the handle) and reading values back with a query call
325
+ (`vec3.components(h)` → `{ x, y, z }`) — reads return primitive records,
326
+ never handles.
327
+ - **New namespaces.** Reorganized the surface into `core`, `design`, `entity`,
328
+ `program`, and `presentation`:
329
+ - `core`: `math` (`vec3`/`quat`), the `geom` geometry kernel, `history`
330
+ (undo/redo), `units`, `zoom`, `project`, `comment`, and the
331
+ `groups`/`buildings`/`layers`/`tags` organization families.
332
+ - `design`: `create`, `query` (+ `geom` query), `selection`, `doors`,
333
+ `windows`, `furniture`, `materials`, `boolean`, `erase`, `delete`,
334
+ `transform`, `edit`, `update`, plus top-level `lock`/`unlock`/`isLocked`/
335
+ `listLocked` verbs.
336
+ - `entity`: `story`, `referenceLine`, `buildableEnvelope`.
337
+ - `program`: `areas`, `departments`, `metrics`, `site`, `cores`,
338
+ `classification`, `adjacency`, and the `spreadsheet` reporting family.
339
+ - `presentation`: `views`, `sheets`, `diagrams`, `import`.
340
+ - Removed the legacy `entity.space.*` methods from the contract and
341
+ consolidated departments under `program.departments`. The selective space
342
+ property read is now `design.query.spaces.get`, and space adjacency is read
343
+ as an entity property via `design.query.getProperties().adjacency`. (The
344
+ host keeps a deprecated `entity.space.get` shim, but it is no longer part of
345
+ the published contract surface.)
346
+ - Added the `overlaps` entity filter to `design.query` list methods — matches
347
+ entities whose plan footprint has non-zero intersection area with a given
348
+ space's bottom-face footprint (fully-inside, boundary-nested, and overhanging
349
+ all match; edge-touching does not; holes respected). Hidden entities are
350
+ excluded unless `isHidden` is passed explicitly.
351
+ - Removed the never-shipped `computeAdjacencyMatrix`; renamed
352
+ `buildings.duplicate` → `buildings.copy`; folded `tags.createAndAssign` into
353
+ `tags.assign` (resolve-or-create, one atomic undo).
354
+
355
+ ## 0.2.8
356
+
357
+ ### Patch Changes
358
+
359
+ - 445e0b8: Add Present-mode AI Inspiration plugin APIs under `snaptrude.documentation.aiInspiration`.
360
+
361
+ ## 0.2.7
362
+
363
+ ### Patch Changes
364
+
365
+ - c5afd9a: added adjacecny
366
+
367
+ ## 0.2.6
368
+
369
+ ### Patch Changes
370
+
371
+ - c17d287: Add bulk space APIs: `space.bulkCreate`, `space.bulkUpdate`, and `space.bulkDelete`. Each batches its work into a single undo/redo step. All items are validated up front and the call throws if any item is invalid (nothing is applied), consistent with the single-item space methods. `bulkCreate` supports `spaceType`/`massType`/`departmentId` per item and returns `{ spaceIds }`; `bulkUpdate` takes geometry (`profile`+`extrudeHeight`) and/or nested `properties` per item and returns `{ spaces }` (one echoed result per item, like `update`); `bulkDelete` returns `{ deletedSpaceIds }`.
372
+ - 0683194: bulk apis
373
+
374
+ ## 0.2.5
375
+
376
+ ### Patch Changes
377
+
378
+ - Restore declaration files in published plugin packages.
379
+
380
+ ## 0.2.4
381
+
382
+ ### Patch Changes
383
+
384
+ - Add inner profile hole support to space creation from profiles.
385
+
386
+ ## 0.2.3
387
+
388
+ ### Patch Changes
389
+
390
+ - 647466b: fixed merge issues
391
+
392
+ ## 0.2.2
393
+
394
+ ### Patch Changes
395
+
396
+ - 064777a: buildable envelope apis
397
+
398
+ ## 0.2.1
399
+
400
+ ### Patch Changes
401
+
402
+ - 3b37f4a: added buildable envelop api
403
+
404
+ ## 0.2.0
405
+
406
+ ### Minor Changes
407
+
408
+ - 4d959a4: Exposed new offset and copy APIs for tools