@snaptrude/plugin-core 0.0.0-bimsvg-20260928124803
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +231 -0
- package/CHANGELOG.md +341 -0
- package/CLAUDE.md +11 -0
- package/api-manifest.json +9120 -0
- package/dist/api/analysis/daylight.d.ts +603 -0
- package/dist/api/analysis/daylight.d.ts.map +1 -0
- package/dist/api/analysis/heatmaps.d.ts +658 -0
- package/dist/api/analysis/heatmaps.d.ts.map +1 -0
- package/dist/api/analysis/illuminance.d.ts +145 -0
- package/dist/api/analysis/illuminance.d.ts.map +1 -0
- package/dist/api/analysis/index.d.ts +59 -0
- package/dist/api/analysis/index.d.ts.map +1 -0
- package/dist/api/analysis/shadows.d.ts +165 -0
- package/dist/api/analysis/shadows.d.ts.map +1 -0
- package/dist/api/analysis/solar.d.ts +249 -0
- package/dist/api/analysis/solar.d.ts.map +1 -0
- package/dist/api/analysis/sunlightHours.d.ts +208 -0
- package/dist/api/analysis/sunlightHours.d.ts.map +1 -0
- package/dist/api/analysis/sunpath.d.ts +80 -0
- package/dist/api/analysis/sunpath.d.ts.map +1 -0
- package/dist/api/analysis/weather.d.ts +193 -0
- package/dist/api/analysis/weather.d.ts.map +1 -0
- package/dist/api/core/buildings.d.ts +427 -0
- package/dist/api/core/buildings.d.ts.map +1 -0
- package/dist/api/core/camera/index.d.ts +261 -0
- package/dist/api/core/camera/index.d.ts.map +1 -0
- package/dist/api/core/comment/index.d.ts +333 -0
- package/dist/api/core/comment/index.d.ts.map +1 -0
- package/dist/api/core/geom/create/index.d.ts +1073 -0
- package/dist/api/core/geom/create/index.d.ts.map +1 -0
- package/dist/api/core/geom/delete/index.d.ts +78 -0
- package/dist/api/core/geom/delete/index.d.ts.map +1 -0
- package/dist/api/core/geom/index.d.ts +33 -0
- package/dist/api/core/geom/index.d.ts.map +1 -0
- package/dist/api/core/geom/query/arc.d.ts +169 -0
- package/dist/api/core/geom/query/arc.d.ts.map +1 -0
- package/dist/api/core/geom/query/brep.d.ts +514 -0
- package/dist/api/core/geom/query/brep.d.ts.map +1 -0
- package/dist/api/core/geom/query/circle.d.ts +378 -0
- package/dist/api/core/geom/query/circle.d.ts.map +1 -0
- package/dist/api/core/geom/query/contour.d.ts +450 -0
- package/dist/api/core/geom/query/contour.d.ts.map +1 -0
- package/dist/api/core/geom/query/curve.d.ts +918 -0
- package/dist/api/core/geom/query/curve.d.ts.map +1 -0
- package/dist/api/core/geom/query/edge.d.ts +128 -0
- package/dist/api/core/geom/query/edge.d.ts.map +1 -0
- package/dist/api/core/geom/query/face.d.ts +363 -0
- package/dist/api/core/geom/query/face.d.ts.map +1 -0
- package/dist/api/core/geom/query/halfedge.d.ts +206 -0
- package/dist/api/core/geom/query/halfedge.d.ts.map +1 -0
- package/dist/api/core/geom/query/index.d.ts +61 -0
- package/dist/api/core/geom/query/index.d.ts.map +1 -0
- package/dist/api/core/geom/query/profile.d.ts +410 -0
- package/dist/api/core/geom/query/profile.d.ts.map +1 -0
- package/dist/api/core/geom/query/vertex.d.ts +187 -0
- package/dist/api/core/geom/query/vertex.d.ts.map +1 -0
- package/dist/api/core/geom/update/contour.d.ts +272 -0
- package/dist/api/core/geom/update/contour.d.ts.map +1 -0
- package/dist/api/core/geom/update/curve.d.ts +180 -0
- package/dist/api/core/geom/update/curve.d.ts.map +1 -0
- package/dist/api/core/geom/update/index.d.ts +27 -0
- package/dist/api/core/geom/update/index.d.ts.map +1 -0
- package/dist/api/core/geom/update/profile.d.ts +329 -0
- package/dist/api/core/geom/update/profile.d.ts.map +1 -0
- package/dist/api/core/groups.d.ts +485 -0
- package/dist/api/core/groups.d.ts.map +1 -0
- package/dist/api/core/handles/index.d.ts +210 -0
- package/dist/api/core/handles/index.d.ts.map +1 -0
- package/dist/api/core/history/index.d.ts +78 -0
- package/dist/api/core/history/index.d.ts.map +1 -0
- package/dist/api/core/index.d.ts +97 -0
- package/dist/api/core/index.d.ts.map +1 -0
- package/dist/api/core/io/export/index.d.ts +134 -0
- package/dist/api/core/io/export/index.d.ts.map +1 -0
- package/dist/api/core/io/import/index.d.ts +455 -0
- package/dist/api/core/io/import/index.d.ts.map +1 -0
- package/dist/api/core/io/index.d.ts +40 -0
- package/dist/api/core/io/index.d.ts.map +1 -0
- package/dist/api/core/io/job/index.d.ts +139 -0
- package/dist/api/core/io/job/index.d.ts.map +1 -0
- package/dist/api/core/io/query/index.d.ts +74 -0
- package/dist/api/core/io/query/index.d.ts.map +1 -0
- package/dist/api/core/io/terrain/index.d.ts +341 -0
- package/dist/api/core/io/terrain/index.d.ts.map +1 -0
- package/dist/api/core/io/underlay/index.d.ts +805 -0
- package/dist/api/core/io/underlay/index.d.ts.map +1 -0
- package/dist/api/core/layers.d.ts +402 -0
- package/dist/api/core/layers.d.ts.map +1 -0
- package/dist/api/core/math/index.d.ts +18 -0
- package/dist/api/core/math/index.d.ts.map +1 -0
- package/dist/api/core/math/quat.d.ts +170 -0
- package/dist/api/core/math/quat.d.ts.map +1 -0
- package/dist/api/core/math/vec3.d.ts +174 -0
- package/dist/api/core/math/vec3.d.ts.map +1 -0
- package/dist/api/core/mode/index.d.ts +111 -0
- package/dist/api/core/mode/index.d.ts.map +1 -0
- package/dist/api/core/project/index.d.ts +466 -0
- package/dist/api/core/project/index.d.ts.map +1 -0
- package/dist/api/core/proposals/index.d.ts +531 -0
- package/dist/api/core/proposals/index.d.ts.map +1 -0
- package/dist/api/core/storeys/index.d.ts +265 -0
- package/dist/api/core/storeys/index.d.ts.map +1 -0
- package/dist/api/core/tags.d.ts +860 -0
- package/dist/api/core/tags.d.ts.map +1 -0
- package/dist/api/core/units/index.d.ts +175 -0
- package/dist/api/core/units/index.d.ts.map +1 -0
- package/dist/api/core/user.d.ts +44 -0
- package/dist/api/core/user.d.ts.map +1 -0
- package/dist/api/core/zoom/index.d.ts +49 -0
- package/dist/api/core/zoom/index.d.ts.map +1 -0
- package/dist/api/design/boolean/index.d.ts +168 -0
- package/dist/api/design/boolean/index.d.ts.map +1 -0
- package/dist/api/design/create/bulk-items.d.ts +185 -0
- package/dist/api/design/create/bulk-items.d.ts.map +1 -0
- package/dist/api/design/create/grids.d.ts +132 -0
- package/dist/api/design/create/grids.d.ts.map +1 -0
- package/dist/api/design/create/index.d.ts +1747 -0
- package/dist/api/design/create/index.d.ts.map +1 -0
- package/dist/api/design/create/opening-fields.d.ts +37 -0
- package/dist/api/design/create/opening-fields.d.ts.map +1 -0
- package/dist/api/design/delete/index.d.ts +70 -0
- package/dist/api/design/delete/index.d.ts.map +1 -0
- package/dist/api/design/dimensions.d.ts +427 -0
- package/dist/api/design/dimensions.d.ts.map +1 -0
- package/dist/api/design/doors/index.d.ts +470 -0
- package/dist/api/design/doors/index.d.ts.map +1 -0
- package/dist/api/design/edit/index.d.ts +49 -0
- package/dist/api/design/edit/index.d.ts.map +1 -0
- package/dist/api/design/erase/index.d.ts +160 -0
- package/dist/api/design/erase/index.d.ts.map +1 -0
- package/dist/api/design/family.d.ts +611 -0
- package/dist/api/design/family.d.ts.map +1 -0
- package/dist/api/design/furniture/index.d.ts +296 -0
- package/dist/api/design/furniture/index.d.ts.map +1 -0
- package/dist/api/design/grids.d.ts +237 -0
- package/dist/api/design/grids.d.ts.map +1 -0
- package/dist/api/design/index.d.ts +260 -0
- package/dist/api/design/index.d.ts.map +1 -0
- package/dist/api/design/lock.d.ts +67 -0
- package/dist/api/design/lock.d.ts.map +1 -0
- package/dist/api/design/materials/index.d.ts +551 -0
- package/dist/api/design/materials/index.d.ts.map +1 -0
- package/dist/api/design/query/geometry/index.d.ts +252 -0
- package/dist/api/design/query/geometry/index.d.ts.map +1 -0
- package/dist/api/design/query/grids.d.ts +287 -0
- package/dist/api/design/query/grids.d.ts.map +1 -0
- package/dist/api/design/query/index.d.ts +1318 -0
- package/dist/api/design/query/index.d.ts.map +1 -0
- package/dist/api/design/query/referenceLines.d.ts +45 -0
- package/dist/api/design/query/referenceLines.d.ts.map +1 -0
- package/dist/api/design/query/spaces.d.ts +394 -0
- package/dist/api/design/query/spaces.d.ts.map +1 -0
- package/dist/api/design/selection/index.d.ts +282 -0
- package/dist/api/design/selection/index.d.ts.map +1 -0
- package/dist/api/design/shared.d.ts +38 -0
- package/dist/api/design/shared.d.ts.map +1 -0
- package/dist/api/design/transform/index.d.ts +249 -0
- package/dist/api/design/transform/index.d.ts.map +1 -0
- package/dist/api/design/types/index.d.ts +181 -0
- package/dist/api/design/types/index.d.ts.map +1 -0
- package/dist/api/design/update/index.d.ts +803 -0
- package/dist/api/design/update/index.d.ts.map +1 -0
- package/dist/api/design/visibility.d.ts +126 -0
- package/dist/api/design/visibility.d.ts.map +1 -0
- package/dist/api/design/windows/index.d.ts +300 -0
- package/dist/api/design/windows/index.d.ts.map +1 -0
- package/dist/api/entity/buildableEnvelope.d.ts +265 -0
- package/dist/api/entity/buildableEnvelope.d.ts.map +1 -0
- package/dist/api/entity/department.d.ts +1 -0
- package/dist/api/entity/department.d.ts.map +1 -0
- package/dist/api/entity/index.d.ts +28 -0
- package/dist/api/entity/index.d.ts.map +1 -0
- package/dist/api/entity/referenceLine.d.ts +210 -0
- package/dist/api/entity/referenceLine.d.ts.map +1 -0
- package/dist/api/entity/space.d.ts +846 -0
- package/dist/api/entity/space.d.ts.map +1 -0
- package/dist/api/entity/story.d.ts +476 -0
- package/dist/api/entity/story.d.ts.map +1 -0
- package/dist/api/index.d.ts +45 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/presentation/aiInspiration.d.ts +2400 -0
- package/dist/api/presentation/aiInspiration.d.ts.map +1 -0
- package/dist/api/presentation/annotate.d.ts +483 -0
- package/dist/api/presentation/annotate.d.ts.map +1 -0
- package/dist/api/presentation/diagrams.d.ts +184 -0
- package/dist/api/presentation/diagrams.d.ts.map +1 -0
- package/dist/api/presentation/drawings.d.ts +2900 -0
- package/dist/api/presentation/drawings.d.ts.map +1 -0
- package/dist/api/presentation/export.d.ts +126 -0
- package/dist/api/presentation/export.d.ts.map +1 -0
- package/dist/api/presentation/import.d.ts +164 -0
- package/dist/api/presentation/import.d.ts.map +1 -0
- package/dist/api/presentation/index.d.ts +114 -0
- package/dist/api/presentation/index.d.ts.map +1 -0
- package/dist/api/presentation/placedViews.d.ts +1232 -0
- package/dist/api/presentation/placedViews.d.ts.map +1 -0
- package/dist/api/presentation/shapes.d.ts +481 -0
- package/dist/api/presentation/shapes.d.ts.map +1 -0
- package/dist/api/presentation/sheets.d.ts +643 -0
- package/dist/api/presentation/sheets.d.ts.map +1 -0
- package/dist/api/presentation/slideshow.d.ts +125 -0
- package/dist/api/presentation/slideshow.d.ts.map +1 -0
- package/dist/api/presentation/tables.d.ts +81 -0
- package/dist/api/presentation/tables.d.ts.map +1 -0
- package/dist/api/presentation/viewPresets.d.ts +70 -0
- package/dist/api/presentation/viewPresets.d.ts.map +1 -0
- package/dist/api/presentation/views.d.ts +741 -0
- package/dist/api/presentation/views.d.ts.map +1 -0
- package/dist/api/program/adjacency.d.ts +155 -0
- package/dist/api/program/adjacency.d.ts.map +1 -0
- package/dist/api/program/areas.d.ts +450 -0
- package/dist/api/program/areas.d.ts.map +1 -0
- package/dist/api/program/classification.d.ts +135 -0
- package/dist/api/program/classification.d.ts.map +1 -0
- package/dist/api/program/cores.d.ts +78 -0
- package/dist/api/program/cores.d.ts.map +1 -0
- package/dist/api/program/departments.d.ts +487 -0
- package/dist/api/program/departments.d.ts.map +1 -0
- package/dist/api/program/index.d.ts +71 -0
- package/dist/api/program/index.d.ts.map +1 -0
- package/dist/api/program/labels.d.ts +355 -0
- package/dist/api/program/labels.d.ts.map +1 -0
- package/dist/api/program/layout.d.ts +398 -0
- package/dist/api/program/layout.d.ts.map +1 -0
- package/dist/api/program/metadata.d.ts +328 -0
- package/dist/api/program/metadata.d.ts.map +1 -0
- package/dist/api/program/metrics.d.ts +108 -0
- package/dist/api/program/metrics.d.ts.map +1 -0
- package/dist/api/program/site.d.ts +823 -0
- package/dist/api/program/site.d.ts.map +1 -0
- package/dist/api/program/spreadsheet.d.ts +2628 -0
- package/dist/api/program/spreadsheet.d.ts.map +1 -0
- package/dist/api/workspace/index.d.ts +552 -0
- package/dist/api/workspace/index.d.ts.map +1 -0
- package/dist/errors/codes.d.ts +34 -0
- package/dist/errors/codes.d.ts.map +1 -0
- package/dist/errors/envelope.d.ts +56 -0
- package/dist/errors/envelope.d.ts.map +1 -0
- package/dist/errors/index.d.ts +6 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/plugin-error.d.ts +69 -0
- package/dist/errors/plugin-error.d.ts.map +1 -0
- package/dist/handles.d.ts +229 -0
- package/dist/handles.d.ts.map +1 -0
- package/dist/host-utils.d.ts +47 -0
- package/dist/host-utils.d.ts.map +1 -0
- package/dist/index.cjs +8836 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7568 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +41 -0
- package/scripts/generate-manifest.mjs +238 -0
- package/scripts/generate-manifest.test.mjs +292 -0
- package/src/api/analysis/daylight.ts +470 -0
- package/src/api/analysis/heatmaps.ts +683 -0
- package/src/api/analysis/illuminance.ts +155 -0
- package/src/api/analysis/index.ts +61 -0
- package/src/api/analysis/shadows.ts +183 -0
- package/src/api/analysis/solar.ts +237 -0
- package/src/api/analysis/sunlightHours.ts +211 -0
- package/src/api/analysis/sunpath.ts +83 -0
- package/src/api/analysis/weather.ts +179 -0
- package/src/api/core/buildings.ts +485 -0
- package/src/api/core/camera/index.ts +268 -0
- package/src/api/core/comment/index.ts +376 -0
- package/src/api/core/geom/create/index.ts +1204 -0
- package/src/api/core/geom/delete/index.ts +84 -0
- package/src/api/core/geom/index.ts +35 -0
- package/src/api/core/geom/query/arc.ts +169 -0
- package/src/api/core/geom/query/brep.ts +563 -0
- package/src/api/core/geom/query/circle.ts +404 -0
- package/src/api/core/geom/query/contour.ts +540 -0
- package/src/api/core/geom/query/curve.ts +1078 -0
- package/src/api/core/geom/query/edge.ts +130 -0
- package/src/api/core/geom/query/face.ts +403 -0
- package/src/api/core/geom/query/halfedge.ts +210 -0
- package/src/api/core/geom/query/index.ts +63 -0
- package/src/api/core/geom/query/profile.ts +473 -0
- package/src/api/core/geom/query/vertex.ts +205 -0
- package/src/api/core/geom/update/contour.ts +315 -0
- package/src/api/core/geom/update/curve.ts +209 -0
- package/src/api/core/geom/update/index.ts +29 -0
- package/src/api/core/geom/update/profile.ts +377 -0
- package/src/api/core/groups.ts +521 -0
- package/src/api/core/handles/index.ts +233 -0
- package/src/api/core/history/index.ts +84 -0
- package/src/api/core/index.ts +99 -0
- package/src/api/core/io/export/index.ts +126 -0
- package/src/api/core/io/import/index.ts +504 -0
- package/src/api/core/io/index.ts +42 -0
- package/src/api/core/io/job/index.ts +140 -0
- package/src/api/core/io/query/index.ts +71 -0
- package/src/api/core/io/terrain/index.ts +360 -0
- package/src/api/core/io/underlay/index.ts +705 -0
- package/src/api/core/layers.ts +333 -0
- package/src/api/core/math/index.ts +20 -0
- package/src/api/core/math/quat.ts +172 -0
- package/src/api/core/math/vec3.ts +179 -0
- package/src/api/core/mode/index.ts +108 -0
- package/src/api/core/project/index.ts +479 -0
- package/src/api/core/proposals/index.ts +569 -0
- package/src/api/core/storeys/index.ts +294 -0
- package/src/api/core/tags.ts +911 -0
- package/src/api/core/units/index.ts +164 -0
- package/src/api/core/user.ts +46 -0
- package/src/api/core/zoom/index.ts +51 -0
- package/src/api/design/boolean/index.ts +179 -0
- package/src/api/design/create/bulk-items.ts +186 -0
- package/src/api/design/create/grids.ts +137 -0
- package/src/api/design/create/index.ts +1954 -0
- package/src/api/design/create/opening-fields.ts +37 -0
- package/src/api/design/delete/index.ts +74 -0
- package/src/api/design/dimensions.ts +453 -0
- package/src/api/design/doors/index.ts +500 -0
- package/src/api/design/edit/index.ts +54 -0
- package/src/api/design/erase/index.ts +151 -0
- package/src/api/design/family.ts +646 -0
- package/src/api/design/furniture/index.ts +319 -0
- package/src/api/design/grids.ts +237 -0
- package/src/api/design/index.ts +274 -0
- package/src/api/design/lock.ts +69 -0
- package/src/api/design/materials/index.ts +608 -0
- package/src/api/design/query/geometry/index.ts +288 -0
- package/src/api/design/query/grids.ts +197 -0
- package/src/api/design/query/index.ts +1176 -0
- package/src/api/design/query/referenceLines.ts +52 -0
- package/src/api/design/query/spaces.ts +275 -0
- package/src/api/design/selection/index.ts +273 -0
- package/src/api/design/shared.ts +40 -0
- package/src/api/design/transform/index.ts +254 -0
- package/src/api/design/types/index.ts +156 -0
- package/src/api/design/update/index.ts +798 -0
- package/src/api/design/visibility.ts +143 -0
- package/src/api/design/windows/index.ts +333 -0
- package/src/api/entity/buildableEnvelope.ts +322 -0
- package/src/api/entity/department.ts +0 -0
- package/src/api/entity/index.ts +30 -0
- package/src/api/entity/referenceLine.ts +235 -0
- package/src/api/entity/space.ts +612 -0
- package/src/api/entity/story.ts +517 -0
- package/src/api/index.ts +47 -0
- package/src/api/presentation/aiInspiration.ts +1116 -0
- package/src/api/presentation/annotate.ts +417 -0
- package/src/api/presentation/diagrams.ts +194 -0
- package/src/api/presentation/drawings.ts +529 -0
- package/src/api/presentation/export.ts +133 -0
- package/src/api/presentation/import.ts +159 -0
- package/src/api/presentation/index.ts +129 -0
- package/src/api/presentation/placedViews.ts +1203 -0
- package/src/api/presentation/shapes.ts +274 -0
- package/src/api/presentation/sheets.ts +616 -0
- package/src/api/presentation/slideshow.ts +134 -0
- package/src/api/presentation/tables.ts +84 -0
- package/src/api/presentation/viewPresets.ts +83 -0
- package/src/api/presentation/views.ts +698 -0
- package/src/api/program/adjacency.ts +164 -0
- package/src/api/program/areas.ts +408 -0
- package/src/api/program/classification.ts +138 -0
- package/src/api/program/cores.ts +83 -0
- package/src/api/program/departments.ts +517 -0
- package/src/api/program/index.ts +73 -0
- package/src/api/program/labels.ts +332 -0
- package/src/api/program/layout.ts +422 -0
- package/src/api/program/metadata.ts +364 -0
- package/src/api/program/metrics.ts +120 -0
- package/src/api/program/site.ts +748 -0
- package/src/api/program/spreadsheet.ts +2698 -0
- package/src/api/workspace/index.ts +612 -0
- package/src/errors/codes.ts +136 -0
- package/src/errors/envelope.ts +75 -0
- package/src/errors/index.ts +21 -0
- package/src/errors/plugin-error.ts +134 -0
- package/src/handles.ts +282 -0
- package/src/host-utils.ts +68 -0
- package/src/index.ts +5 -0
- package/src/types.ts +16 -0
- package/test/errors.test.mjs +184 -0
- package/tsconfig.json +22 -0
- package/tsup.config.ts +12 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api/core/math/vec3.ts","../src/api/core/math/quat.ts","../src/api/core/math/index.ts","../src/api/core/geom/create/index.ts","../src/handles.ts","../src/api/core/geom/query/curve.ts","../src/api/core/geom/query/arc.ts","../src/api/core/geom/query/circle.ts","../src/api/core/geom/query/profile.ts","../src/api/core/geom/query/contour.ts","../src/api/core/geom/query/brep.ts","../src/api/core/geom/query/face.ts","../src/api/core/geom/query/edge.ts","../src/api/core/geom/query/halfedge.ts","../src/api/core/geom/query/vertex.ts","../src/api/core/geom/query/index.ts","../src/api/core/geom/update/curve.ts","../src/api/core/geom/update/profile.ts","../src/api/core/geom/update/contour.ts","../src/api/core/geom/update/index.ts","../src/api/core/geom/delete/index.ts","../src/api/core/geom/index.ts","../src/api/core/history/index.ts","../src/api/core/units/index.ts","../src/api/core/zoom/index.ts","../src/api/core/camera/index.ts","../src/api/core/user.ts","../src/api/core/project/index.ts","../src/api/core/comment/index.ts","../src/api/core/groups.ts","../src/api/core/buildings.ts","../src/api/core/storeys/index.ts","../src/api/entity/story.ts","../src/api/core/mode/index.ts","../src/api/core/layers.ts","../src/api/core/tags.ts","../src/api/core/io/import/index.ts","../src/api/core/io/job/index.ts","../src/api/core/io/underlay/index.ts","../src/api/core/io/terrain/index.ts","../src/api/core/io/query/index.ts","../src/api/core/io/export/index.ts","../src/api/core/io/index.ts","../src/api/core/handles/index.ts","../src/api/core/proposals/index.ts","../src/api/core/index.ts","../src/api/design/create/index.ts","../src/api/entity/space.ts","../src/api/design/create/opening-fields.ts","../src/api/design/create/bulk-items.ts","../src/api/design/create/grids.ts","../src/api/design/query/index.ts","../src/api/design/query/geometry/index.ts","../src/api/design/query/spaces.ts","../src/api/design/query/referenceLines.ts","../src/api/design/query/grids.ts","../src/api/design/selection/index.ts","../src/api/design/doors/index.ts","../src/api/design/windows/index.ts","../src/api/design/furniture/index.ts","../src/api/design/materials/index.ts","../src/api/design/lock.ts","../src/api/design/erase/index.ts","../src/api/design/delete/index.ts","../src/api/design/shared.ts","../src/api/design/boolean/index.ts","../src/api/design/transform/index.ts","../src/api/design/edit/index.ts","../src/api/design/update/index.ts","../src/api/design/visibility.ts","../src/api/design/dimensions.ts","../src/api/design/types/index.ts","../src/api/design/family.ts","../src/api/design/grids.ts","../src/api/design/index.ts","../src/api/entity/buildableEnvelope.ts","../src/api/entity/referenceLine.ts","../src/api/entity/index.ts","../src/api/program/departments.ts","../src/api/program/metrics.ts","../src/api/program/adjacency.ts","../src/api/program/layout.ts","../src/api/program/spreadsheet.ts","../src/api/program/site.ts","../src/api/program/cores.ts","../src/api/program/classification.ts","../src/api/program/areas.ts","../src/api/program/metadata.ts","../src/api/program/labels.ts","../src/api/program/index.ts","../src/api/presentation/views.ts","../src/api/presentation/sheets.ts","../src/api/presentation/placedViews.ts","../src/api/presentation/diagrams.ts","../src/api/presentation/annotate.ts","../src/api/presentation/shapes.ts","../src/api/presentation/tables.ts","../src/api/presentation/slideshow.ts","../src/api/presentation/import.ts","../src/api/presentation/aiInspiration.ts","../src/api/presentation/export.ts","../src/api/presentation/drawings.ts","../src/api/presentation/viewPresets.ts","../src/api/presentation/index.ts","../src/api/analysis/sunpath.ts","../src/api/analysis/shadows.ts","../src/api/analysis/sunlightHours.ts","../src/api/analysis/illuminance.ts","../src/api/analysis/heatmaps.ts","../src/api/analysis/weather.ts","../src/api/analysis/solar.ts","../src/api/analysis/daylight.ts","../src/api/analysis/index.ts","../src/api/workspace/index.ts","../src/api/index.ts","../src/errors/codes.ts","../src/errors/envelope.ts","../src/errors/plugin-error.ts"],"sourcesContent":["import { PluginApiReturn } from \"../../../types\"\nimport { Vec3Handle, Vec3Components } from \"../../../handles\"\n\n/**\n * 3D vector operations (all-handle model, §11).\n *\n * Vectors are opaque {@linkcode Vec3Handle}s; every operation is a host call\n * returning a new handle (vectors are immutable). Read primitive components back\n * with {@linkcode PluginVec3Api.components}. Accessed via `snaptrude.core.math.vec3`.\n */\nexport abstract class PluginVec3Api {\n constructor() {}\n\n /**\n * Create a vector from primitive components.\n *\n * # Example\n * ```ts\n * const v = await snaptrude.core.math.vec3.new(10, 0, 5)\n * const { x, y, z } = await snaptrude.core.math.vec3.components(v)\n * ```\n */\n abstract new(x: number, y: number, z: number): PluginApiReturn<Vec3Handle>\n /**\n * Add two vectors component-wise (`a + b`).\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(1, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const sum = await snaptrude.core.math.vec3.add(a, b)\n * ```\n */\n abstract add(a: Vec3Handle, b: Vec3Handle): PluginApiReturn<Vec3Handle>\n /**\n * Subtract vector `b` from `a` component-wise (`a - b`).\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(3, 2, 1)\n * const b = await snaptrude.core.math.vec3.new(1, 1, 1)\n * const diff = await snaptrude.core.math.vec3.subtract(a, b)\n * ```\n */\n abstract subtract(a: Vec3Handle, b: Vec3Handle): PluginApiReturn<Vec3Handle>\n /**\n * Scale `v` by a scalar.\n *\n * # Example\n * ```ts\n * const v = await snaptrude.core.math.vec3.new(1, 2, 3)\n * const doubled = await snaptrude.core.math.vec3.scale(v, 2)\n * ```\n */\n abstract scale(v: Vec3Handle, scalar: number): PluginApiReturn<Vec3Handle>\n /**\n * Compute the dot product of two vectors.\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(1, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(0.5, 0.5, 0)\n * const d = await snaptrude.core.math.vec3.dot(a, b) // 0.5\n * ```\n */\n abstract dot(a: Vec3Handle, b: Vec3Handle): PluginApiReturn<number>\n /**\n * Compute the cross product `a × b` — the vector perpendicular to both.\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(1, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const perpendicular = await snaptrude.core.math.vec3.cross(a, b)\n * ```\n */\n abstract cross(a: Vec3Handle, b: Vec3Handle): PluginApiReturn<Vec3Handle>\n /**\n * Compute the Euclidean length (magnitude) of a vector.\n *\n * # Example\n * ```ts\n * const v = await snaptrude.core.math.vec3.new(3, 4, 0)\n * const len = await snaptrude.core.math.vec3.length(v) // 5\n * ```\n */\n abstract length(v: Vec3Handle): PluginApiReturn<number>\n /**\n * Compute the squared Euclidean length of a vector (avoids the sqrt).\n *\n * # Example\n * ```ts\n * const v = await snaptrude.core.math.vec3.new(3, 4, 0)\n * const sq = await snaptrude.core.math.vec3.lengthSquared(v) // 25\n * ```\n */\n abstract lengthSquared(v: Vec3Handle): PluginApiReturn<number>\n /**\n * Return the unit vector in the same direction (the zero vector if length is 0).\n *\n * # Example\n * ```ts\n * const v = await snaptrude.core.math.vec3.new(0, 0, 10)\n * const unit = await snaptrude.core.math.vec3.normalize(v) // (0, 0, 1)\n * ```\n */\n abstract normalize(v: Vec3Handle): PluginApiReturn<Vec3Handle>\n /**\n * Measure the straight-line (Euclidean) distance between two points.\n *\n * @examplePrompt What is the distance between these two points?\n * @examplePrompt Measure how far apart these two points are\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(3, 4, 0)\n * const dist = await snaptrude.core.math.vec3.distance(a, b) // 5\n * ```\n */\n abstract distance(a: Vec3Handle, b: Vec3Handle): PluginApiReturn<number>\n /**\n * Linearly interpolate between two vectors: `a + (b - a) * t`.\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(10, 0, 0)\n * const midpoint = await snaptrude.core.math.vec3.lerp(a, b, 0.5)\n * ```\n */\n abstract lerp(a: Vec3Handle, b: Vec3Handle, t: number): PluginApiReturn<Vec3Handle>\n /**\n * Negate a vector, reversing its direction.\n *\n * # Example\n * ```ts\n * const v = await snaptrude.core.math.vec3.new(1, -2, 3)\n * const reversed = await snaptrude.core.math.vec3.negate(v) // (-1, 2, -3)\n * ```\n */\n abstract negate(v: Vec3Handle): PluginApiReturn<Vec3Handle>\n /**\n * Test whether two vectors have exactly equal components.\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(1, 2, 3)\n * const b = await snaptrude.core.math.vec3.new(1, 2, 3)\n * const same = await snaptrude.core.math.vec3.equals(a, b) // true\n * ```\n */\n abstract equals(a: Vec3Handle, b: Vec3Handle): PluginApiReturn<boolean>\n /**\n * Test whether two vectors are equal within a tolerance `epsilon` (default `1e-6`).\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(1, 2, 3)\n * const b = await snaptrude.core.math.vec3.new(1.0004, 2, 3)\n * const close = await snaptrude.core.math.vec3.equalsApprox(a, b, 1e-3) // true\n * ```\n */\n abstract equalsApprox(\n a: Vec3Handle,\n b: Vec3Handle,\n epsilon?: number,\n ): PluginApiReturn<boolean>\n /**\n * Read the primitive `{ x, y, z }` components of a vector handle.\n *\n * # Example\n * ```ts\n * const v = await snaptrude.core.math.vec3.new(1, 2, 3)\n * const { x, y, z } = await snaptrude.core.math.vec3.components(v)\n * ```\n */\n abstract components(v: Vec3Handle): PluginApiReturn<Vec3Components>\n}\n","import { PluginApiReturn } from \"../../../types\"\nimport { Vec3Handle, QuatHandle, QuatComponents } from \"../../../handles\"\n\n/**\n * Quaternion operations (all-handle model, §11).\n *\n * Quaternions are opaque {@linkcode QuatHandle}s; every operation is a host call\n * returning a new handle. Read primitive components back with\n * {@linkcode PluginQuatApi.components}. Accessed via `snaptrude.core.math.quat`.\n */\nexport abstract class PluginQuatApi {\n constructor() {}\n\n /**\n * Create a quaternion from primitive components.\n *\n * # Example\n * ```ts\n * const q = await snaptrude.core.math.quat.new(0, 0, 0, 1)\n * const { x, y, z, w } = await snaptrude.core.math.quat.components(q)\n * ```\n */\n abstract new(x: number, y: number, z: number, w: number): PluginApiReturn<QuatHandle>\n /**\n * Return the identity quaternion (no rotation).\n *\n * # Example\n * ```ts\n * const noRotation = await snaptrude.core.math.quat.identity()\n * ```\n */\n abstract identity(): PluginApiReturn<QuatHandle>\n /**\n * Build a quaternion from a rotation axis and an angle in radians.\n *\n * # Example\n * ```ts\n * const axis = await snaptrude.core.math.vec3.new(0, 0, 1)\n * const quarterTurn = await snaptrude.core.math.quat.fromAxisAngle(axis, Math.PI / 2)\n * ```\n */\n abstract fromAxisAngle(axis: Vec3Handle, angle: number): PluginApiReturn<QuatHandle>\n /**\n * Build a quaternion from Euler angles in radians.\n *\n * # Example\n * ```ts\n * const q = await snaptrude.core.math.quat.fromEuler(0, Math.PI / 2, 0)\n * ```\n */\n abstract fromEuler(x: number, y: number, z: number): PluginApiReturn<QuatHandle>\n /**\n * Compose two rotations with the Hamilton product `a * b` (apply `b` then `a`).\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.quat.fromEuler(0, Math.PI / 2, 0)\n * const b = await snaptrude.core.math.quat.fromEuler(Math.PI / 4, 0, 0)\n * const combined = await snaptrude.core.math.quat.multiply(a, b) // b first, then a\n * ```\n */\n abstract multiply(a: QuatHandle, b: QuatHandle): PluginApiReturn<QuatHandle>\n /**\n * Compute the conjugate of a quaternion (negated vector part).\n *\n * # Example\n * ```ts\n * const q = await snaptrude.core.math.quat.fromEuler(0, Math.PI / 4, 0)\n * const conj = await snaptrude.core.math.quat.conjugate(q)\n * ```\n */\n abstract conjugate(q: QuatHandle): PluginApiReturn<QuatHandle>\n /**\n * Compute the magnitude (norm) of a quaternion.\n *\n * # Example\n * ```ts\n * const q = await snaptrude.core.math.quat.new(0, 0, 0, 1)\n * const len = await snaptrude.core.math.quat.length(q) // 1\n * ```\n */\n abstract length(q: QuatHandle): PluginApiReturn<number>\n /**\n * Normalize a quaternion to unit length.\n *\n * # Example\n * ```ts\n * const q = await snaptrude.core.math.quat.new(0, 0, 1, 1)\n * const unit = await snaptrude.core.math.quat.normalize(q)\n * ```\n */\n abstract normalize(q: QuatHandle): PluginApiReturn<QuatHandle>\n /**\n * Compute the inverse quaternion — the rotation that undoes `q`.\n *\n * # Example\n * ```ts\n * const q = await snaptrude.core.math.quat.fromEuler(0, Math.PI / 2, 0)\n * const undo = await snaptrude.core.math.quat.inverse(q)\n * ```\n */\n abstract inverse(q: QuatHandle): PluginApiReturn<QuatHandle>\n /**\n * Decompose a quaternion into its rotation axis and angle (radians).\n *\n * # Example\n * ```ts\n * const q = await snaptrude.core.math.quat.fromEuler(0, Math.PI / 3, 0)\n * const { axis, angle } = await snaptrude.core.math.quat.toAxisAngle(q)\n * const { x, y, z } = await snaptrude.core.math.vec3.components(axis)\n * ```\n */\n abstract toAxisAngle(q: QuatHandle): PluginApiReturn<{ axis: Vec3Handle; angle: number }>\n /**\n * Spherically interpolate (slerp) between two rotations by factor `t`.\n *\n * # Example\n * ```ts\n * const start = await snaptrude.core.math.quat.identity()\n * const end = await snaptrude.core.math.quat.fromEuler(0, Math.PI, 0)\n * const halfway = await snaptrude.core.math.quat.slerp(start, end, 0.5)\n * ```\n */\n abstract slerp(a: QuatHandle, b: QuatHandle, t: number): PluginApiReturn<QuatHandle>\n /**\n * Compute the dot product of two quaternions.\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.quat.identity()\n * const b = await snaptrude.core.math.quat.fromEuler(0, Math.PI / 2, 0)\n * const d = await snaptrude.core.math.quat.dot(a, b)\n * ```\n */\n abstract dot(a: QuatHandle, b: QuatHandle): PluginApiReturn<number>\n /**\n * Test whether two quaternions have exactly equal components.\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.quat.new(0, 0, 0, 1)\n * const b = await snaptrude.core.math.quat.identity()\n * const same = await snaptrude.core.math.quat.equals(a, b) // true\n * ```\n */\n abstract equals(a: QuatHandle, b: QuatHandle): PluginApiReturn<boolean>\n /**\n * Test whether two quaternions are equal within a tolerance `epsilon` (default `1e-6`).\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.quat.fromEuler(0, Math.PI / 2, 0)\n * const b = await snaptrude.core.math.quat.fromEuler(0, 1.5711, 0)\n * const close = await snaptrude.core.math.quat.equalsApprox(a, b, 1e-3) // true\n * ```\n */\n abstract equalsApprox(\n a: QuatHandle,\n b: QuatHandle,\n epsilon?: number,\n ): PluginApiReturn<boolean>\n /**\n * Read the primitive `{ x, y, z, w }` components of a quaternion handle.\n *\n * # Example\n * ```ts\n * const q = await snaptrude.core.math.quat.fromEuler(0, Math.PI / 2, 0)\n * const { x, y, z, w } = await snaptrude.core.math.quat.components(q)\n * ```\n */\n abstract components(q: QuatHandle): PluginApiReturn<QuatComponents>\n}\n","import { PluginVec3Api } from \"./vec3\"\nimport { PluginQuatApi } from \"./quat\"\n\n/**\n * Math primitives for 3D vector and quaternion operations.\n *\n * - {@linkcode PluginMathApi.vec3} — 3D vector creation and arithmetic\n * - {@linkcode PluginMathApi.quat} — Quaternion creation and rotation operations\n */\nexport abstract class PluginMathApi {\n /** 3D vector operations. See {@linkcode PluginVec3Api}. */\n public abstract vec3: PluginVec3Api\n /** Quaternion operations. See {@linkcode PluginQuatApi}. */\n public abstract quat: PluginQuatApi\n\n constructor() {}\n}\n\nexport * from \"./vec3\"\nexport * from \"./quat\"\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport { Vec3Handle, LineHandle, ArcHandle, CircleHandle, CurveHandle, ProfileHandle, ContourHandle, BrepHandle, FaceHandle, EdgeHandle, Vec3Components } from \"../../../../handles\"\n\n/**\n * Curve creation — construct new geometric curves from point handles (all-handle\n * model, §11).\n *\n * Each method takes point handles ({@linkcode Vec3Handle}) and returns an opaque\n * curve handle ({@linkcode LineHandle} or {@linkcode ArcHandle}) that you pass to\n * query, update, or entity-creation methods. Read geometry back as plain values\n * via the `snaptrude.core.geom.query.*` methods.\n *\n * Accessed via `snaptrude.core.geom.create`.\n */\nexport abstract class PluginGeomCreateApi {\n constructor() {}\n\n /**\n * Create a straight line segment between two point handles. Host API call —\n * returns a handle.\n *\n * @param startPoint Start point of the line segment\n * @param endPoint End point of the line segment\n * @returns The new line as a {@linkcode LineHandle}\n *\n * @examplePrompt Draw a line from point A to point B\n * @examplePrompt Draw a straight edge between these two points\n *\n * @example\n * ```ts\n * const line = snaptrude.core.geom.create.line(startPoint, endPoint)\n * ```\n */\n public abstract line(startPoint: Vec3Handle, endPoint: Vec3Handle): PluginApiReturn<LineHandle>\n\n /**\n * Create a circular arc from its start, end, centre, and axis point handles.\n * Host API call — returns a handle.\n *\n * @param startPoint Start point of the arc\n * @param endPoint End point of the arc\n * @param centrePoint Centre point of the arc\n * @param axis Axis direction of the arc\n * @returns The new arc as an {@linkcode ArcHandle}\n *\n * @examplePrompt Draw an arc between these two points around a centre\n * @examplePrompt Add a curved segment from A to B\n *\n * @example\n * ```ts\n * const arc = snaptrude.core.geom.create.arc(startPoint, endPoint, centrePoint, axis)\n * ```\n */\n public abstract arc(\n startPoint: Vec3Handle,\n endPoint: Vec3Handle,\n centrePoint: Vec3Handle,\n axis: Vec3Handle,\n ): PluginApiReturn<ArcHandle>\n\n /**\n * Create a circle from a centre point, an axis (plane normal), and a radius.\n * Host API call — returns a handle. A circle is a closed curve; read it back via\n * `snaptrude.core.geom.query.circle.*`.\n *\n * @param centrePoint Centre point of the circle\n * @param axis Axis direction (plane normal) of the circle\n * @param radius Radius of the circle\n * @returns The new circle as a {@linkcode CircleHandle}\n *\n * @examplePrompt Make a circle of radius 2 at this point\n * @examplePrompt Draw a circular outline 5m across\n *\n * @example\n * ```ts\n * const circle = snaptrude.core.geom.create.circle(centrePoint, axis, 2)\n * ```\n */\n public abstract circle(\n centrePoint: Vec3Handle,\n axis: Vec3Handle,\n radius: number,\n ): PluginApiReturn<CircleHandle>\n\n /**\n * Create a closed profile from an ordered list of point handles connected by\n * line segments (last auto-connected to first). Host API call — returns a handle.\n *\n * @param points Ordered vertex handles of the profile\n * @returns The new profile as a {@linkcode ProfileHandle}\n *\n * @examplePrompt Draw a closed polygon through these corner points\n * @examplePrompt Create an L-shaped outline from a list of points\n *\n * @example\n * ```ts\n * const profile = snaptrude.core.geom.create.profileFromLinePoints(points)\n * ```\n */\n public abstract profileFromLinePoints(points: Vec3Handle[]): PluginApiReturn<ProfileHandle>\n\n /**\n * Create a profile from an ordered list of curve handles forming a closed loop.\n * Host API call — returns a handle.\n *\n * @param curves Ordered curve handles forming a closed loop\n * @returns The new profile as a {@linkcode ProfileHandle}\n *\n * @examplePrompt Join these lines and arcs into one closed shape\n *\n * @example\n * ```ts\n * const profile = snaptrude.core.geom.create.profileFromCurves([line, arc])\n * ```\n */\n public abstract profileFromCurves(curves: CurveHandle[]): PluginApiReturn<ProfileHandle>\n\n /**\n * Create an axis-aligned **rectangle** profile (XZ plane) of `width` × `depth`,\n * centred at `center` (defaults to the origin). Host API call — returns a handle.\n *\n * Pair with `design.create.space` / `design.create.spaces` to author a rectangular\n * (box) space — there is no dedicated rectangular-space creator; build the profile\n * here, then extrude it.\n *\n * @param width Extent along X\n * @param depth Extent along Z\n * @param center Optional centre point (default origin)\n * @returns The new rectangle profile as a {@linkcode ProfileHandle}\n *\n * @examplePrompt Draw a 4m by 3m rectangle\n * @examplePrompt Make a rectangular outline for a room footprint\n *\n * @example\n * ```ts\n * const rect = snaptrude.core.geom.create.profileRect(4, 3)\n * const contour = snaptrude.core.geom.create.contourFromProfile(rect)\n * const space = snaptrude.design.create.space(contour, 3)\n * ```\n */\n public abstract profileRect(\n width: number,\n depth: number,\n center?: Vec3Handle,\n ): PluginApiReturn<ProfileHandle>\n\n /**\n * Create a contour (an outer profile loop, no holes). Host API call — returns a handle.\n *\n * @param outer The outer boundary loop\n * @returns The new contour as a {@linkcode ContourHandle}\n * @example\n * ```ts\n * const contour = snaptrude.core.geom.create.contourFromProfile(outer)\n * ```\n */\n public abstract contourFromProfile(outer: ProfileHandle): PluginApiReturn<ContourHandle>\n\n /**\n * Create a contour from an outer profile plus zero or more inner profiles (holes).\n * Host API call — returns a handle.\n *\n * @param outer The outer boundary loop\n * @param holes Optional inner hole loops\n * @returns The new contour as a {@linkcode ContourHandle}\n *\n * @examplePrompt Make a shape with a hole in the middle\n * @examplePrompt Cut a courtyard opening out of this floor outline\n *\n * @example\n * ```ts\n * const contour = snaptrude.core.geom.create.contourFromProfiles(outer, [hole])\n * ```\n */\n public abstract contourFromProfiles(\n outer: ProfileHandle,\n holes?: ProfileHandle[],\n ): PluginApiReturn<ContourHandle>\n\n /**\n * Create a closed solid **B-rep** from explicit face loops. Host API call —\n * returns a {@linkcode BrepHandle}. Faces are plain arrays of `{x, y, z}`\n * point components, NOT point handles (bulk-data precedent:\n * `design.query.geometry.getTriangulatedMeshes`), in raw Babylon units.\n *\n * Each face is one planar loop of at least 3 points; at least 4 faces are\n * required. Loops may be authored in any consistent winding — the host\n * validates edge coherence (every edge shared by exactly two faces, in\n * opposite directions) and fixes the global orientation so faces point\n * outward. Faces must be planar and the solid must be closed; holes in faces\n * are not supported (v1).\n *\n * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene\n * with `design.create.massFromBrep`.\n *\n * @param faces Face loops — each an ordered array of `{x, y, z}` points (≥3 points per face, ≥4 faces)\n * @returns The new solid as a {@linkcode BrepHandle}\n * @throws if a face is degenerate or non-planar, an edge is not shared by\n * exactly two faces (open shell, non-manifold, or inconsistent winding), or\n * the faces do not form a valid closed solid\n *\n * @examplePrompt Create a brep from faces\n * @examplePrompt Build a custom solid from a set of faces\n * @examplePrompt Make a pyramid from triangular faces\n *\n * # Example\n * ```ts\n * // A pyramid: square base + 4 triangular sides\n * const apex = { x: 0, y: 4, z: 0 }\n * const a = { x: -2, y: 0, z: -2 }\n * const b = { x: 2, y: 0, z: -2 }\n * const c = { x: 2, y: 0, z: 2 }\n * const d = { x: -2, y: 0, z: 2 }\n * const brep = await snaptrude.core.geom.create.brepFromFaces([\n * [a, d, c, b], // base\n * [a, b, apex],\n * [b, c, apex],\n * [c, d, apex],\n * [d, a, apex],\n * ])\n * const faceCount = await snaptrude.core.geom.query.brep.getFaceCount(brep) // 5\n * ```\n */\n public abstract brepFromFaces(faces: Vec3Components[][]): PluginApiReturn<BrepHandle>\n\n /**\n * Create a closed solid **B-rep** by extruding a contour along a direction.\n * Host API call — returns a {@linkcode BrepHandle}. The direction is\n * normalised by the host, so `amount` is the extrusion distance in raw\n * Babylon units (negative extrudes the opposite way). The contour is copied —\n * the input handle is never mutated. Holes and arc/circle profiles extrude\n * natively (a circle profile yields a cylinder).\n *\n * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene\n * with `design.create.massFromBrep`.\n *\n * @param contour The cross-section to extrude (outer profile + optional holes)\n * @param direction Extrusion direction as plain `{x, y, z}` components (non-zero, normalised by the host)\n * @param amount Extrusion distance (non-zero; negative extrudes opposite to `direction`)\n * @returns The new solid as a {@linkcode BrepHandle}\n * @throws if the extrusion is degenerate (zero amount, zero direction, or a\n * direction lying in the contour plane) or the contour cannot be extruded\n * into a valid solid\n *\n * @examplePrompt Extrude this profile 3 metres up\n * @examplePrompt Make a solid by extruding this outline\n * @examplePrompt Create a cylinder from a circle profile\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const brep = await snaptrude.core.geom.create.brepFromExtrusion(contour, { x: 0, y: 1, z: 0 }, 3)\n * const faceCount = await snaptrude.core.geom.query.brep.getFaceCount(brep) // 6\n * ```\n */\n public abstract brepFromExtrusion(\n contour: ContourHandle,\n direction: Vec3Components,\n amount: number,\n ): PluginApiReturn<BrepHandle>\n\n /**\n * Create a closed solid **B-rep** by lofting between a bottom and a top\n * contour, optionally through intermediate cross-sections. Host API call —\n * returns a {@linkcode BrepHandle}. Sections with different edge counts are\n * auto-matched: vertices are deterministically inserted on the smaller\n * sections at the larger section's perimeter positions, so the same input\n * always yields the same solid. For straight-edge (all-line) sections the\n * seam is auto-aligned too — winding and start-vertex correspondence are\n * chosen to avoid a twist, so outlines authored from any starting corner\n * loft cleanly; arc-bearing sections keep the authored correspondence\n * unchanged. A pair whose side faces would be non-planar under every\n * correspondence still builds for hole-free straight-edged pairs — planar\n * side quads stay quads and warped ones split into planar triangles (the\n * triangulated lane); arc-bearing or holed twisted pairs are rejected, as is\n * auto-matching that would pair an arc with a straight edge. Matching hole\n * counts are allowed in the two-section form. With intermediates the loft\n * is a chain of ruled segments folded into one solid: sections must be\n * planar and hole-free, ordered bottom → intermediates → top. The contours\n * are copied — the input handles are never mutated.\n *\n * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene\n * with `design.create.massFromBrep`.\n *\n * @param bottomContour The bottom cross-section\n * @param topContour The top cross-section (edge count may differ from the\n * bottom — sections are auto-matched)\n * @param intermediateContours Optional in-between cross-sections, ordered\n * bottom to top (hole-free; edge counts may differ — every section is\n * normalized to the largest)\n * @param options Optional behavior switches. `compatibility` — `\"auto\"`\n * (default) inserts vertices to match differing edge counts; `\"strict\"`\n * skips normalization and throws VALIDATION when outer edge counts (or,\n * in the two-section form, hole-pair counts) differ. `seamAlignment` —\n * `\"auto\"` (default) searches seam rotations on straight-edge sections to\n * avoid a twist; `\"authored\"` keeps the authored start-vertex\n * correspondence unchanged (a twisted correspondence then builds through\n * the triangulated lane for hole-free straight-edged pairs).\n * `{ compatibility: \"strict\", seamAlignment: \"authored\" }` reproduces\n * pre-0.9.4 lofting exactly.\n * @returns The new solid as a {@linkcode BrepHandle}\n * @throws VALIDATION if the contours are coincident, their hole counts\n * differ, any section has holes when intermediates are present\n * (multi-section lofts take hole-free sections), a section is non-planar,\n * sections are too dissimilar to auto-match, auto-matching would pair an\n * arc with a straight edge, or — for arc-bearing or holed pairs only — no vertex correspondence avoids a twist (a\n * side face between corresponding edges would be non-planar — the error\n * names the offending sections); with `compatibility: \"strict\"`, if\n * section edge counts (or hole-pair counts) differ — the error names the\n * sections and counts and points at `compatibility: \"auto\"`\n * @throws OPERATION_FAILED if the lofted segments cannot be joined into a\n * valid solid\n *\n * @examplePrompt Make a tapered tower from these two outlines\n * @examplePrompt Loft between a large base and a smaller top\n * @examplePrompt Create a frustum from two squares\n * @examplePrompt Loft the tower through these three floor outlines\n * @examplePrompt Loft a four-sided base to a five-sided top\n * @examplePrompt Loft these outlines even though their corners don't line up\n * @examplePrompt Loft these outlines exactly as authored, corner to corner\n * @examplePrompt Fail the loft if my sections don't have matching edge counts\n *\n * # Example\n * ```ts\n * // A square frustum: 4m base lofted to a 2m top, 3m up\n * const base = await snaptrude.core.geom.create.profileRect(4, 4)\n * const top = await snaptrude.core.geom.create.profileRect(2, 2, await snaptrude.core.math.vec3.new(0, 3, 0))\n * const brep = await snaptrude.core.geom.create.brepFromLoft(\n * await snaptrude.core.geom.create.contourFromProfile(base),\n * await snaptrude.core.geom.create.contourFromProfile(top),\n * )\n * const faceCount = await snaptrude.core.geom.query.brep.getFaceCount(brep) // 6\n * ```\n */\n public abstract brepFromLoft(\n bottomContour: ContourHandle,\n topContour: ContourHandle,\n intermediateContours?: ContourHandle[],\n options?: { compatibility?: \"strict\" | \"auto\"; seamAlignment?: \"authored\" | \"auto\" },\n ): PluginApiReturn<BrepHandle>\n\n /**\n * Create a closed solid **B-rep** from indexed mesh data: a vertex position\n * array plus face loops of indices into it. Host API call — returns a\n * {@linkcode BrepHandle}. The indexed form of\n * {@linkcode PluginGeomCreateApi.brepFromFaces} — same validation (planar\n * faces, closed manifold solid, every edge shared by exactly two faces) after\n * the indices are expanded to point loops.\n *\n * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene\n * with `design.create.massFromBrep`.\n *\n * @param positions Vertex positions as plain `{x, y, z}` components (≥4)\n * @param faces Face loops — each an ordered array of indices into `positions` (≥3 indices per face, ≥4 faces)\n * @returns The new solid as a {@linkcode BrepHandle}\n * @throws if an index is not an integer within `positions` bounds, a face is\n * degenerate or non-planar, or the faces do not form a valid closed solid\n *\n * @examplePrompt Create a brep from mesh vertices and indices\n * @examplePrompt Turn this indexed mesh into a solid\n * @examplePrompt Build a solid from vertex positions and face indices\n *\n * # Example\n * ```ts\n * // A tetrahedron from 4 vertices and 4 triangular faces\n * const positions = [\n * { x: 0, y: 0, z: 0 },\n * { x: 4, y: 0, z: 0 },\n * { x: 2, y: 0, z: 4 },\n * { x: 2, y: 3, z: 1.5 },\n * ]\n * const brep = await snaptrude.core.geom.create.brepFromMesh(positions, [\n * [0, 2, 1], // base\n * [0, 1, 3],\n * [1, 2, 3],\n * [2, 0, 3],\n * ])\n * const vertexCount = await snaptrude.core.geom.query.brep.getVertexCount(brep) // 4\n * ```\n */\n public abstract brepFromMesh(\n positions: Vec3Components[],\n faces: number[][],\n ): PluginApiReturn<BrepHandle>\n\n /**\n * Create a closed solid **B-rep** as the boolean **union** of two breps\n * (`a ∪ b`). Host API call — returns a new {@linkcode BrepHandle}. Inputs are\n * read-only and may be authored breps (any `core.geom.create` brep\n * constructor) or scene-derived breps from `design.query.geometry.getBrep`;\n * coordinates are combined as-is, so both inputs must share a frame. The\n * result must be a single solid — disjoint inputs are rejected.\n *\n * The first boolean call loads the OpenCascade geometry kernel (a wasm of\n * tens of MB — expect a pause of seconds); subsequent calls are fast.\n *\n * @param a First solid\n * @param b Second solid\n * @returns The union as a new {@linkcode BrepHandle}\n * @throws if the inputs do not overlap or touch (the union would be disjoint\n * solids) or the result cannot be built as a valid single solid\n *\n * @examplePrompt Union these two solids\n * @examplePrompt Merge these breps into one mass\n * @examplePrompt Combine the tower and the podium into a single solid\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.geom.create.brepFromExtrusion(baseContour, { x: 0, y: 1, z: 0 }, 3)\n * const b = await snaptrude.core.geom.create.brepFromExtrusion(overlappingContour, { x: 0, y: 1, z: 0 }, 5)\n * const merged = await snaptrude.core.geom.create.brepFromUnion(a, b)\n * ```\n */\n public abstract brepFromUnion(a: BrepHandle, b: BrepHandle): PluginApiReturn<BrepHandle>\n\n /**\n * Create a closed solid **B-rep** as the boolean **subtraction** of two\n * breps: **`a` minus `b`** — `b` is cut away from `a`. Host API call —\n * returns a new {@linkcode BrepHandle}. Argument order matters:\n * `brepFromSubtraction(a, b)` removes `b`'s volume from `a`. Inputs are\n * read-only and may be authored or scene-derived breps (see\n * {@linkcode PluginGeomCreateApi.brepFromUnion}); coordinates are combined\n * as-is.\n *\n * The first boolean call loads the OpenCascade geometry kernel (a wasm of\n * tens of MB — expect a pause of seconds); subsequent calls are fast.\n *\n * @param a The solid to subtract from\n * @param b The solid to remove from `a`\n * @returns `a` minus `b` as a new {@linkcode BrepHandle}\n * @throws if `b` consumes `a` entirely (empty result), the result splits into\n * disjoint solids, or it cannot be built as a valid single solid\n *\n * @examplePrompt Subtract the courtyard from the building mass\n * @examplePrompt Cut this solid out of that one\n * @examplePrompt Punch a hole through the slab with this box\n *\n * # Example\n * ```ts\n * // buildingBrep minus courtyardBrep — order matters\n * const carved = await snaptrude.core.geom.create.brepFromSubtraction(buildingBrep, courtyardBrep)\n * ```\n */\n public abstract brepFromSubtraction(a: BrepHandle, b: BrepHandle): PluginApiReturn<BrepHandle>\n\n /**\n * Create a closed solid **B-rep** as the boolean **intersection** of two\n * breps (`a ∩ b` — the shared volume only). Host API call — returns a new\n * {@linkcode BrepHandle}. Inputs are read-only and may be authored or\n * scene-derived breps (see {@linkcode PluginGeomCreateApi.brepFromUnion});\n * coordinates are combined as-is.\n *\n * The first boolean call loads the OpenCascade geometry kernel (a wasm of\n * tens of MB — expect a pause of seconds); subsequent calls are fast.\n *\n * @param a First solid\n * @param b Second solid\n * @returns The shared volume as a new {@linkcode BrepHandle}\n * @throws if the inputs do not overlap (empty intersection), the result\n * splits into disjoint solids, or it cannot be built as a valid single\n * solid\n *\n * @examplePrompt Keep only the overlap of these two solids\n * @examplePrompt Intersect these two masses\n * @examplePrompt Clip the tower to the zoning envelope\n *\n * # Example\n * ```ts\n * const overlap = await snaptrude.core.geom.create.brepFromIntersection(towerBrep, envelopeBrep)\n * ```\n */\n public abstract brepFromIntersection(a: BrepHandle, b: BrepHandle): PluginApiReturn<BrepHandle>\n\n /**\n * Rounds the given straight edges of a solid with a constant radius. Host\n * API call — returns a new {@linkcode BrepHandle}; the input brep is\n * read-only.\n *\n * v1 fillets straight edges only, and no two filleted edges may share a\n * vertex — corner blends produce spherical patches Snaptrude cannot\n * represent. Runs on the OpenCascade kernel (the first kernel call loads a\n * wasm of tens of MB — expect a pause of seconds).\n *\n * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene\n * with `design.create.massFromBrep`.\n *\n * @param brep The solid whose edges to round\n * @param edges The straight edges to fillet (≥1, all on `brep`, no two sharing a vertex)\n * @param radius Fillet radius (positive, finite)\n * @returns The filleted solid as a new {@linkcode BrepHandle}\n * @throws VALIDATION if `edges` is empty, `radius` is not a positive finite\n * number, an edge is not on `brep`, an edge is an arc (v1 fillets straight\n * edges only), or two edges share a vertex (fillet non-adjacent edges)\n * @throws OPERATION_FAILED if the kernel cannot build the fillet (the radius\n * likely exceeds the adjacent face size — reduce it) or the result\n * contains curved surfaces Snaptrude cannot represent\n *\n * @examplePrompt Round the edges of this mass\n * @examplePrompt Fillet the corners of the podium with a 0.5m radius\n * @examplePrompt Soften the vertical edges of this tower\n *\n * # Example\n * ```ts\n * const brep = await snaptrude.core.geom.create.brepFromExtrusion(contour, { x: 0, y: 1, z: 0 }, 3)\n * const edges = await snaptrude.core.geom.query.brep.listEdges(brep)\n * const rounded = await snaptrude.core.geom.create.brepFromFillet(brep, [edges[0]], 0.3)\n * ```\n */\n public abstract brepFromFillet(\n brep: BrepHandle,\n edges: EdgeHandle[],\n radius: number,\n ): PluginApiReturn<BrepHandle>\n\n /**\n * Grows or shrinks a solid by offsetting every face — positive distance\n * moves faces outward, negative moves them inward. Host API call — returns\n * a new {@linkcode BrepHandle}; the input brep is read-only.\n *\n * Runs on the OpenCascade kernel (the first kernel call loads a wasm of\n * tens of MB — expect a pause of seconds).\n *\n * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene\n * with `design.create.massFromBrep`.\n *\n * @param brep The solid to offset\n * @param distance Offset distance (finite, non-zero; positive grows, negative shrinks)\n * @returns The offset solid as a new {@linkcode BrepHandle}\n * @throws VALIDATION if `distance` is not finite, is too small to offset\n * anything, or an inward distance consumes the solid entirely\n * @throws OPERATION_FAILED if the kernel cannot build the offset or the\n * result contains curved surfaces Snaptrude cannot represent\n *\n * @examplePrompt Grow this solid by 0.5m in every direction\n * @examplePrompt Shrink this mass by 200mm\n * @examplePrompt Offset the building envelope outward by 1m\n *\n * # Example\n * ```ts\n * const brep = await snaptrude.core.geom.create.brepFromExtrusion(contour, { x: 0, y: 1, z: 0 }, 3)\n * const grown = await snaptrude.core.geom.create.brepFromOffset(brep, 0.5)\n * ```\n */\n public abstract brepFromOffset(brep: BrepHandle, distance: number): PluginApiReturn<BrepHandle>\n\n /**\n * Hollows a solid into constant-thickness walls, removing the given faces\n * as openings. Host API call — returns a new {@linkcode BrepHandle}; the\n * input brep is read-only. The outer surface is kept and the walls grow\n * inward.\n *\n * Runs on the OpenCascade kernel (the first kernel call loads a wasm of\n * tens of MB — expect a pause of seconds).\n *\n * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene\n * with `design.create.massFromBrep`.\n *\n * @param brep The solid to hollow\n * @param openFaces The faces to remove as openings (≥1, all on `brep`)\n * @param thickness Wall thickness (positive, finite, smaller than half the solid's smallest span)\n * @returns The hollowed solid as a new {@linkcode BrepHandle}\n * @throws VALIDATION if `openFaces` is empty, a face is not on `brep`,\n * `thickness` is not a positive finite number, or the thickness is too\n * large (it must be smaller than half the solid's smallest span)\n * @throws OPERATION_FAILED if the kernel cannot build the shell or the\n * result contains curved surfaces Snaptrude cannot represent\n *\n * @examplePrompt Hollow this mass into 200mm walls\n * @examplePrompt Shell this form with the top face open\n * @examplePrompt Turn this solid tower into a tube open at both ends\n *\n * # Example\n * ```ts\n * const brep = await snaptrude.core.geom.create.brepFromExtrusion(contour, { x: 0, y: 1, z: 0 }, 3)\n * const faces = await snaptrude.core.geom.query.brep.listFaces(brep)\n * const hollow = await snaptrude.core.geom.create.brepFromShell(brep, [faces[0]], 0.2)\n * ```\n */\n public abstract brepFromShell(\n brep: BrepHandle,\n openFaces: FaceHandle[],\n thickness: number,\n ): PluginApiReturn<BrepHandle>\n\n /**\n * Cuts a solid by an infinite plane and returns one brep per resulting\n * piece — a single piece if the plane misses the solid. Host API call —\n * returns new {@linkcode BrepHandle}s; the input brep is read-only. The\n * plane is defined by a point on it and its normal direction.\n *\n * Runs on the OpenCascade kernel (the first kernel call loads a wasm of\n * tens of MB — expect a pause of seconds).\n *\n * Inspect each result via `core.geom.query.brep.*`, or commit them to the\n * scene with `design.create.massFromBrep`.\n *\n * @param brep The solid to split\n * @param planeOrigin A point on the cutting plane as plain `{x, y, z}` components\n * @param planeNormal The plane normal as plain `{x, y, z}` components (non-zero)\n * @returns The resulting pieces as {@linkcode BrepHandle}`[]` (one per solid)\n * @throws VALIDATION if `planeNormal` is zero-length or the origin/normal\n * components are not finite\n * @throws OPERATION_FAILED if the kernel cannot split the solid\n *\n * @examplePrompt Cut this building at 12m height\n * @examplePrompt Split the tower from the podium with a horizontal plane\n * @examplePrompt Slice this mass along a vertical plane\n *\n * # Example\n * ```ts\n * const pieces = await snaptrude.core.geom.create.brepsFromSplit(\n * towerBrep,\n * { x: 0, y: 12, z: 0 },\n * { x: 0, y: 1, z: 0 },\n * )\n * console.log(\"pieces:\", pieces.length)\n * ```\n */\n public abstract brepsFromSplit(\n brep: BrepHandle,\n planeOrigin: Vec3Components,\n planeNormal: Vec3Components,\n ): PluginApiReturn<BrepHandle[]>\n\n /**\n * Bevels the given straight edges of a solid with a symmetric planar cut.\n * Host API call — returns a new {@linkcode BrepHandle}; the input brep is\n * read-only.\n *\n * v1 chamfers straight edges only, and no two chamfered edges may share a\n * vertex. Runs on the OpenCascade kernel (the first kernel call loads a\n * wasm of tens of MB — expect a pause of seconds).\n *\n * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene\n * with `design.create.massFromBrep`.\n *\n * @param brep The solid whose edges to bevel\n * @param edges The straight edges to chamfer (≥1, all on `brep`, no two sharing a vertex)\n * @param distance Chamfer distance from the edge on each adjacent face (positive, finite)\n * @returns The chamfered solid as a new {@linkcode BrepHandle}\n * @throws VALIDATION if `edges` is empty, `distance` is not a positive\n * finite number, an edge is not on `brep`, an edge is an arc (v1 chamfers\n * straight edges only), or two edges share a vertex (chamfer non-adjacent\n * edges)\n * @throws OPERATION_FAILED if the kernel cannot build the chamfer (the\n * distance likely exceeds the adjacent face size — reduce it)\n *\n * @examplePrompt Bevel these edges by 100mm\n * @examplePrompt Chamfer the top edges of the plinth\n * @examplePrompt Cut a 45-degree flat along the corners of this mass\n *\n * # Example\n * ```ts\n * const brep = await snaptrude.core.geom.create.brepFromExtrusion(contour, { x: 0, y: 1, z: 0 }, 3)\n * const edges = await snaptrude.core.geom.query.brep.listEdges(brep)\n * const beveled = await snaptrude.core.geom.create.brepFromChamfer(brep, [edges[0]], 0.1)\n * ```\n */\n public abstract brepFromChamfer(\n brep: BrepHandle,\n edges: EdgeHandle[],\n distance: number,\n ): PluginApiReturn<BrepHandle>\n\n /**\n * Sweeps a planar profile along a polyline path, with mitred corners at\n * each bend. Host API call — returns a new {@linkcode BrepHandle}; the\n * input contour is read-only. The profile must be hole-free and must not\n * lie in a plane containing the first path segment's direction. Repeating\n * the first point as the last closes the path into a ring solid (a picture\n * frame): closed paths must be flat and straight-segmented and take a\n * straight-edged (arc-free) profile — the ring's cross-section is the\n * authored profile projected along the first leg onto the first mitre\n * plane, so a profile not perpendicular to the first leg appears sheared.\n * An optional scale law tapers the sweep: `startScale`/`endScale`\n * interpolate linearly by arc length, or `scales` gives one factor per path\n * point (scaling is about the profile centroid). A uniform scale (all\n * factors equal) works on any path today's sweep accepts; a varying scale\n * takes straight (collinear) open paths with straight-edged profiles —\n * tapering across a bend or along an arc edge would create faces Snaptrude\n * cannot represent and throws (sweep each straight run separately and union\n * the results instead). An optional `transition` picks the corner\n * treatment: `\"miter\"` (default) is today's sharp mitred bend;\n * `{ bevel: b }` chamfers each turning corner by cutting `b` back along\n * both adjacent legs (works on open and closed paths — each leg must be\n * long enough for its cuts). On a corner-less path `transition` has no effect, and the\n * varying-scale lane takes straight paths, so it never sees a corner.\n *\n * Open paths run on the OpenCascade kernel (the first kernel call loads a\n * wasm of tens of MB — expect a pause of seconds); closed paths are\n * computed host-side and never load the kernel.\n *\n * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene\n * with `design.create.massFromBrep`.\n *\n * @param profile The cross-section to sweep (hole-free contour)\n * @param path The polyline path as plain `{x, y, z}` points (≥2; repeat the\n * first point as the last to close the path into a ring)\n * @param options Optional scale law and corner transition. Scale law:\n * `startScale`/`endScale` (linear taper by arc length) or `scales` (one\n * factor per path point; mutually exclusive with\n * `startScale`/`endScale`); every factor must be finite and within\n * 1e-3..1e3. `transition`: `\"miter\"` (default, sharp corners) or\n * `{ bevel: b }` (chamfered corners, `b` finite within 1e-3..1e3; open\n * and closed paths)\n * @returns The swept solid as a new {@linkcode BrepHandle}\n * @throws VALIDATION if the profile has holes, the path has fewer than 2\n * points, consecutive path points coincide, a coordinate is not finite,\n * or the profile plane contains the first path segment's direction\n * (degenerate sweep); for closed paths, if the ring has fewer than 3\n * distinct corners, the path is non-planar, a corner doubles back, the\n * profile has an arc edge, or the profile is too large for a mitred\n * corner; for scale options, if a factor is outside 1e-3..1e3, `scales`\n * is combined with `startScale`/`endScale`, `scales` does not match the\n * path length, or a varying scale is used on a bent or closed path or\n * with an arc-edged profile; for `{ bevel }`, if a leg is too short for\n * the cuts its beveled corners take from it or a corner nearly doubles\n * back so its chamfer collapses (the errors name the corner)\n * @throws OPERATION_FAILED if the kernel cannot sweep the profile into a\n * valid solid or a transition patch is a curved surface Snaptrude cannot\n * represent\n *\n * @examplePrompt Sweep this profile along the corridor path\n * @examplePrompt Extrude the railing section along this route\n * @examplePrompt Run a duct profile through these points\n * @examplePrompt Sweep a frame profile around a closed rectangular loop\n * @examplePrompt Taper this duct from 1x1 to 2x2 along its run\n * @examplePrompt Sweep a column section that narrows toward the top\n * @examplePrompt Chamfer the corners of this swept frame by 200mm\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(0.4, 0.4)\n * const profile = await snaptrude.core.geom.create.contourFromProfile(rect)\n * // profileRect lies in the XZ plane, so the path must START out of that\n * // plane (here: straight up), then it can run horizontally.\n * const duct = await snaptrude.core.geom.create.brepFromSweep(profile, [\n * { x: 0, y: 0, z: 0 },\n * { x: 0, y: 2.8, z: 0 },\n * { x: 10, y: 2.8, z: 0 },\n * ])\n * // A closed ring (picture frame): repeat the first point as the last\n * const frame = await snaptrude.core.geom.create.brepFromSweep(profile, [\n * { x: 0, y: 0, z: 0 },\n * { x: 0, y: 3, z: 0 },\n * { x: 4, y: 3, z: 0 },\n * { x: 4, y: 0, z: 0 },\n * { x: 0, y: 0, z: 0 },\n * ])\n * // A tapered straight run: 0.4x0.4 at the start, 0.8x0.8 at the end\n * const flue = await snaptrude.core.geom.create.brepFromSweep(\n * profile,\n * [\n * { x: 0, y: 0, z: 0 },\n * { x: 0, y: 10, z: 0 },\n * ],\n * { startScale: 1, endScale: 2 },\n * )\n * ```\n */\n public abstract brepFromSweep(\n profile: ContourHandle,\n path: Vec3Components[],\n options?: {\n startScale?: number\n endScale?: number\n scales?: number[]\n transition?: \"miter\" | { bevel: number }\n },\n ): PluginApiReturn<BrepHandle>\n\n /**\n * Revolves a planar profile about an axis to make a solid of revolution — a\n * full turn by default. Host API call — returns a new\n * {@linkcode BrepHandle}; the input contour is read-only. Holes in the\n * profile are allowed. Profile segments must stay parallel or perpendicular\n * to the axis — inclined or arc segments would revolve into surfaces\n * Snaptrude cannot represent.\n *\n * Runs on the OpenCascade kernel (the first kernel call loads a wasm of\n * tens of MB — expect a pause of seconds).\n *\n * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene\n * with `design.create.massFromBrep`.\n *\n * @param profile The cross-section to revolve (planar contour, holes allowed)\n * @param axisOrigin A point on the revolution axis as plain `{x, y, z}` components\n * @param axisDirection The axis direction as plain `{x, y, z}` components (non-zero)\n * @param angleInDegrees Optional revolution angle in degrees (0 < angle ≤ 360; default 360)\n * @returns The revolved solid as a new {@linkcode BrepHandle}\n * @throws VALIDATION if `axisDirection` is zero-length, `angleInDegrees` is\n * not in (0, 360], the axis passes through the profile interior, or a\n * coordinate is not finite\n * @throws OPERATION_FAILED if the kernel cannot revolve the profile or the\n * result contains curved surfaces Snaptrude cannot represent (keep\n * profile segments parallel or perpendicular to the axis)\n *\n * @examplePrompt Create a dome from this section\n * @examplePrompt Revolve this profile 360 degrees around the vertical axis\n * @examplePrompt Build a rotunda by revolving this wall section\n *\n * # Example\n * ```ts\n * // A cylinder: revolve a 2m-wide, 3m-tall rectangle about the Y axis at its edge\n * const rect = await snaptrude.core.geom.create.profileFromLinePoints([\n * await snaptrude.core.math.vec3.new(0, 0, 0),\n * await snaptrude.core.math.vec3.new(2, 0, 0),\n * await snaptrude.core.math.vec3.new(2, 3, 0),\n * await snaptrude.core.math.vec3.new(0, 3, 0),\n * ])\n * const profile = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const cylinder = await snaptrude.core.geom.create.brepFromRevolution(\n * profile,\n * { x: 0, y: 0, z: 0 },\n * { x: 0, y: 1, z: 0 },\n * )\n * ```\n */\n public abstract brepFromRevolution(\n profile: ContourHandle,\n axisOrigin: Vec3Components,\n axisDirection: Vec3Components,\n angleInDegrees?: number,\n ): PluginApiReturn<BrepHandle>\n}\n\n/**\n * Arguments for {@linkcode PluginGeomCreateApi.line}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `startPoint` | {@linkcode Vec3Handle} | Start point of the line segment |\n * | `endPoint` | {@linkcode Vec3Handle} | End point of the line segment |\n */\nexport const PluginGeomCreateLineArgs = z.object({\n startPoint: Vec3Handle,\n endPoint: Vec3Handle,\n})\n\nexport type PluginGeomCreateLineArgs = z.infer<typeof PluginGeomCreateLineArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomCreateApi.arc}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `startPoint` | {@linkcode Vec3Handle} | Start point of the arc |\n * | `endPoint` | {@linkcode Vec3Handle} | End point of the arc |\n * | `centrePoint` | {@linkcode Vec3Handle} | Centre point of the arc |\n * | `axis` | {@linkcode Vec3Handle} | Axis direction of the arc |\n */\nexport const PluginGeomCreateArcArgs = z.object({\n startPoint: Vec3Handle,\n endPoint: Vec3Handle,\n centrePoint: Vec3Handle,\n axis: Vec3Handle,\n})\n\nexport type PluginGeomCreateArcArgs = z.infer<typeof PluginGeomCreateArcArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomCreateApi.circle}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `centrePoint` | {@linkcode Vec3Handle} | Centre point of the circle |\n * | `axis` | {@linkcode Vec3Handle} | Axis direction (plane normal) of the circle |\n * | `radius` | `number` | Radius of the circle |\n */\nexport const PluginGeomCreateCircleArgs = z.object({\n centrePoint: Vec3Handle,\n axis: Vec3Handle,\n radius: z.number(),\n})\n\nexport type PluginGeomCreateCircleArgs = z.infer<typeof PluginGeomCreateCircleArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomCreateApi.profileFromLinePoints}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `points` | {@linkcode Vec3Handle}`[]` | Ordered vertex handles of the profile |\n */\nexport const PluginGeomCreateProfileFromLinePointsArgs = z.object({\n points: z.array(Vec3Handle),\n})\n\nexport type PluginGeomCreateProfileFromLinePointsArgs = z.infer<\n typeof PluginGeomCreateProfileFromLinePointsArgs\n>\n\n/**\n * Arguments for {@linkcode PluginGeomCreateApi.profileFromCurves}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curves` | {@linkcode CurveHandle}`[]` | Ordered curve handles forming a closed loop |\n */\nexport const PluginGeomCreateProfileFromCurvesArgs = z.object({\n curves: z.array(CurveHandle),\n})\n\nexport type PluginGeomCreateProfileFromCurvesArgs = z.infer<\n typeof PluginGeomCreateProfileFromCurvesArgs\n>\n\n/**\n * Arguments for {@linkcode PluginGeomCreateApi.contourFromProfile}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `outer` | {@linkcode ProfileHandle} | The outer boundary loop |\n */\nexport const PluginGeomCreateContourFromProfileArgs = z.object({\n outer: ProfileHandle,\n})\n\nexport type PluginGeomCreateContourFromProfileArgs = z.infer<\n typeof PluginGeomCreateContourFromProfileArgs\n>\n\n/**\n * Arguments for {@linkcode PluginGeomCreateApi.contourFromProfiles}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `outer` | {@linkcode ProfileHandle} | The outer boundary loop |\n * | `holes` | {@linkcode ProfileHandle}`[]` | Optional inner hole loops |\n */\nexport const PluginGeomCreateContourFromProfilesArgs = z.object({\n outer: ProfileHandle,\n holes: z.array(ProfileHandle).optional(),\n})\n\nexport type PluginGeomCreateContourFromProfilesArgs = z.infer<\n typeof PluginGeomCreateContourFromProfilesArgs\n>\n\n/**\n * Arguments for {@linkcode PluginGeomCreateApi.brepFromFaces}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `faces` | {@linkcode Vec3Components}`[][]` | Face loops — each an ordered array of `{x, y, z}` points (≥3 points per face, ≥4 faces) |\n */\nexport const PluginGeomCreateBrepFromFacesArgs = z.object({\n faces: z.array(z.array(Vec3Components).min(3)).min(4),\n})\n\nexport type PluginGeomCreateBrepFromFacesArgs = z.infer<\n typeof PluginGeomCreateBrepFromFacesArgs\n>\n\n/**\n * Arguments for {@linkcode PluginGeomCreateApi.brepFromExtrusion}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The cross-section to extrude |\n * | `direction` | {@linkcode Vec3Components} | Extrusion direction (non-zero, finite components) |\n * | `amount` | `number` | Extrusion distance (finite, non-zero) |\n */\nexport const PluginGeomCreateBrepFromExtrusionArgs = z.object({\n contour: ContourHandle,\n direction: z.object({\n x: z.number().finite(),\n y: z.number().finite(),\n z: z.number().finite(),\n }),\n amount: z\n .number()\n .finite()\n .refine((value) => value !== 0, { message: \"amount must be non-zero\" }),\n})\n\nexport type PluginGeomCreateBrepFromExtrusionArgs = z.infer<\n typeof PluginGeomCreateBrepFromExtrusionArgs\n>\n\n/**\n * Arguments for {@linkcode PluginGeomCreateApi.brepFromLoft}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `bottomContour` | {@linkcode ContourHandle} | The bottom cross-section |\n * | `topContour` | {@linkcode ContourHandle} | The top cross-section |\n * | `intermediateContours` | {@linkcode ContourHandle}`[]`? | Optional in-between cross-sections, ordered bottom to top |\n * | `options` | `object`? | Optional behavior switches: `compatibility` (`\"strict\"` throws on mismatched edge counts instead of auto-matching; default `\"auto\"`) and `seamAlignment` (`\"authored\"` keeps the authored correspondence instead of searching seam rotations; default `\"auto\"`) |\n */\nexport const PluginGeomCreateBrepFromLoftArgs = z.object({\n bottomContour: ContourHandle,\n topContour: ContourHandle,\n intermediateContours: z.array(ContourHandle).optional(),\n options: z\n .object({\n compatibility: z.enum([\"strict\", \"auto\"]).optional(),\n seamAlignment: z.enum([\"authored\", \"auto\"]).optional(),\n })\n .optional(),\n})\n\nexport type PluginGeomCreateBrepFromLoftArgs = z.infer<typeof PluginGeomCreateBrepFromLoftArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomCreateApi.brepFromMesh}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `positions` | {@linkcode Vec3Components}`[]` | Vertex positions (≥4) |\n * | `faces` | `number[][]` | Face loops of indices into `positions` (≥3 indices per face, ≥4 faces) |\n */\nexport const PluginGeomCreateBrepFromMeshArgs = z.object({\n positions: z.array(Vec3Components).min(4),\n faces: z.array(z.array(z.number()).min(3)).min(4),\n})\n\nexport type PluginGeomCreateBrepFromMeshArgs = z.infer<typeof PluginGeomCreateBrepFromMeshArgs>\n\n/**\n * Arguments for the brep boolean constructors —\n * {@linkcode PluginGeomCreateApi.brepFromUnion},\n * {@linkcode PluginGeomCreateApi.brepFromSubtraction} (`a` minus `b`), and\n * {@linkcode PluginGeomCreateApi.brepFromIntersection}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `a` | {@linkcode BrepHandle} | First solid (the subtraction target) |\n * | `b` | {@linkcode BrepHandle} | Second solid (the subtraction tool) |\n */\nexport const PluginGeomCreateBrepBooleanArgs = z.object({\n a: BrepHandle,\n b: BrepHandle,\n})\n\nexport type PluginGeomCreateBrepBooleanArgs = z.infer<typeof PluginGeomCreateBrepBooleanArgs>\n\n// Plain {x, y, z} record with finite components (brepFromExtrusion `direction` pattern).\nconst FiniteVec3Components = z.object({\n x: z.number().finite(),\n y: z.number().finite(),\n z: z.number().finite(),\n})\n\n/**\n * Arguments for {@linkcode PluginGeomCreateApi.brepFromFillet}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `brep` | {@linkcode BrepHandle} | The solid whose edges to round |\n * | `edges` | {@linkcode EdgeHandle}`[]` | The straight edges to fillet (≥1) |\n * | `radius` | `number` | Fillet radius (positive, finite) |\n */\nexport const PluginGeomCreateBrepFromFilletArgs = z.object({\n brep: BrepHandle,\n edges: z.array(EdgeHandle).min(1),\n radius: z.number().finite().positive(),\n})\n\nexport type PluginGeomCreateBrepFromFilletArgs = z.infer<typeof PluginGeomCreateBrepFromFilletArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomCreateApi.brepFromOffset}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `brep` | {@linkcode BrepHandle} | The solid to offset |\n * | `distance` | `number` | Offset distance (finite, non-zero; positive grows, negative shrinks) |\n */\nexport const PluginGeomCreateBrepFromOffsetArgs = z.object({\n brep: BrepHandle,\n distance: z\n .number()\n .finite()\n .refine((value) => value !== 0, { message: \"distance must be non-zero\" }),\n})\n\nexport type PluginGeomCreateBrepFromOffsetArgs = z.infer<typeof PluginGeomCreateBrepFromOffsetArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomCreateApi.brepFromShell}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `brep` | {@linkcode BrepHandle} | The solid to hollow |\n * | `openFaces` | {@linkcode FaceHandle}`[]` | The faces to remove as openings (≥1) |\n * | `thickness` | `number` | Wall thickness (positive, finite) |\n */\nexport const PluginGeomCreateBrepFromShellArgs = z.object({\n brep: BrepHandle,\n openFaces: z.array(FaceHandle).min(1),\n thickness: z.number().finite().positive(),\n})\n\nexport type PluginGeomCreateBrepFromShellArgs = z.infer<typeof PluginGeomCreateBrepFromShellArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomCreateApi.brepsFromSplit}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `brep` | {@linkcode BrepHandle} | The solid to split |\n * | `planeOrigin` | {@linkcode Vec3Components} | A point on the cutting plane (finite components) |\n * | `planeNormal` | {@linkcode Vec3Components} | The plane normal (non-zero, finite components) |\n */\nexport const PluginGeomCreateBrepsFromSplitArgs = z.object({\n brep: BrepHandle,\n planeOrigin: FiniteVec3Components,\n planeNormal: FiniteVec3Components,\n})\n\nexport type PluginGeomCreateBrepsFromSplitArgs = z.infer<typeof PluginGeomCreateBrepsFromSplitArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomCreateApi.brepFromChamfer}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `brep` | {@linkcode BrepHandle} | The solid whose edges to bevel |\n * | `edges` | {@linkcode EdgeHandle}`[]` | The straight edges to chamfer (≥1) |\n * | `distance` | `number` | Chamfer distance from the edge on each adjacent face (positive, finite) |\n */\nexport const PluginGeomCreateBrepFromChamferArgs = z.object({\n brep: BrepHandle,\n edges: z.array(EdgeHandle).min(1),\n distance: z.number().finite().positive(),\n})\n\nexport type PluginGeomCreateBrepFromChamferArgs = z.infer<\n typeof PluginGeomCreateBrepFromChamferArgs\n>\n\n// Taper scale factor — bounded so a near-zero scale cannot pinch a section\n// into a degenerate sliver the downstream planarity gates would miss.\nconst SweepScaleFactor = z.number().finite().min(1e-3).max(1e3)\n\n/**\n * Arguments for {@linkcode PluginGeomCreateApi.brepFromSweep}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ContourHandle} | The cross-section to sweep (hole-free) |\n * | `path` | {@linkcode Vec3Components}`[]` | The polyline path (≥2 points, finite components; repeating the first point as the last closes it into a ring — closed paths must be flat and straight-segmented and take a straight-edged profile) |\n * | `options` | `object`? | Optional scale law: `startScale`/`endScale` (linear taper by arc length) or per-point `scales` (mutually exclusive; factors finite, 1e-3..1e3; `scales.length` must equal `path.length`). Optional corner `transition`: `\"miter\"` (default) or `{ bevel }` (chamfered corners; finite, 1e-3..1e3) |\n */\nexport const PluginGeomCreateBrepFromSweepArgs = z\n .object({\n profile: ContourHandle,\n path: z.array(FiniteVec3Components).min(2),\n options: z\n .object({\n startScale: SweepScaleFactor.optional(),\n endScale: SweepScaleFactor.optional(),\n scales: z.array(SweepScaleFactor).optional(),\n transition: z\n .union([\n z.literal(\"miter\"),\n z.object({ bevel: z.number().finite().min(1e-3).max(1e3) }),\n ])\n .optional(),\n })\n .optional(),\n })\n .superRefine((a, ctx) => {\n const options = a.options\n if (!options || options.scales === undefined) return\n if (options.startScale !== undefined || options.endScale !== undefined) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: \"scales is mutually exclusive with startScale/endScale\",\n })\n }\n if (options.scales.length !== a.path.length) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: \"scales must have exactly one entry per path point\",\n })\n }\n })\n\nexport type PluginGeomCreateBrepFromSweepArgs = z.infer<typeof PluginGeomCreateBrepFromSweepArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomCreateApi.brepFromRevolution}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ContourHandle} | The cross-section to revolve (holes allowed) |\n * | `axisOrigin` | {@linkcode Vec3Components} | A point on the revolution axis (finite components) |\n * | `axisDirection` | {@linkcode Vec3Components} | The axis direction (non-zero, finite components) |\n * | `angleInDegrees` | `number`? | Revolution angle in degrees (0 < angle ≤ 360; default 360) |\n */\nexport const PluginGeomCreateBrepFromRevolutionArgs = z.object({\n profile: ContourHandle,\n axisOrigin: FiniteVec3Components,\n axisDirection: FiniteVec3Components,\n angleInDegrees: z.number().finite().positive().max(360).optional(),\n})\n\nexport type PluginGeomCreateBrepFromRevolutionArgs = z.infer<\n typeof PluginGeomCreateBrepFromRevolutionArgs\n>\n","import * as z from \"zod\"\nimport type { ArenaKind } from \"./api/core/handles\"\n\n// Runtime polyfill: engines without explicit-resource-management would otherwise\n// evaluate the class's computed [Symbol.asyncDispose] key as undefined and\n// silently create a property literally named \"undefined\".\n;(Symbol as { asyncDispose?: symbol }).asyncDispose ??= Symbol.for(\"Symbol.asyncDispose\")\n\n/**\n * An opaque, kind-tagged reference to a host-side object that crosses the RPC\n * boundary by IDENTITY rather than by value. See `PLUGIN_API_EXPOSURE_HLD.md` §9.\n *\n * A handle is a small CLASS instance wrapping the wire id. The phantom\n * `__handle` brand makes a `Handle<\"profile\">` incompatible with a\n * `Handle<\"vec3\">` at compile time. On the wire the id string is all that\n * travels: the client SDK unwraps `Handle` arguments to `.id` before the RPC\n * call, and the host tags returned handles as `{ __h: \"<id>\" }` (via\n * {@linkcode Handle.toJSON}) for the client to re-wrap.\n *\n * Plugin-side lifecycle: the client SDK interns one live instance per id and\n * registers it with a FinalizationRegistry — dropping every reference to a\n * handle eventually releases its host registry entry. Deterministic release is\n * still preferred: `core.handles.release/releaseAll` or scopes.\n *\n * Crossing non-RPC boundaries (UI postMessage, persistence, logging):\n * send {@linkcode Handle.id} — structured clone strips the class prototype.\n */\nexport class Handle<K extends string> {\n /** Phantom brand — keeps `Handle<\"vec3\">` ≠ `Handle<\"mass\">` at compile time. */\n declare readonly __handle: K\n\n constructor(readonly id: string) {}\n\n /** Wire tag — the host's result serialization (JSON round-trip) calls this. */\n toJSON(): { __h: string } {\n return { __h: this.id }\n }\n\n /** Template-string / logging form stays the bare id. */\n toString(): string {\n return this.id\n }\n\n /** Identity comparison by id — safe across re-wraps. */\n equals(other: Handle<string>): boolean {\n return this.id === other.id\n }\n\n /**\n * Explicit-resource-management hook (`await using`). A no-op here; the\n * plugin-client runtime installs the real enqueue-release behavior on the\n * interned instance.\n */\n async [Symbol.asyncDispose](): Promise<void> {}\n}\n\n/** Union of all releasable (arena-backed) handle kinds. */\nexport type AnyArenaHandle = Handle<ArenaKind>\n\n/**\n * A scene-entity handle. Backed by the host `Component.id` and resolved live via\n * the host scene index (StructureCollection) — never stored in the resource\n * registry. Stable across undo/redo and persistable across sessions (§9.2).\n * Entity handles remain raw branded STRINGS (not {@linkcode Handle} instances):\n * they have no arena entry, no lifecycle, and are equality-matched against\n * plain ids embedded in result records.\n */\nexport type EntityId<K extends string = string> = string & { readonly __handle: K }\n\n/** Resource handle: a host-resident, session-ephemeral geometry object (§9.2). */\nexport type ProfileHandle = Handle<\"profile\">\n\n/** Resource handle: a host-resident contour (outer profile + hole profiles). */\nexport type ContourHandle = Handle<\"contour\">\n\n/** Entity-handle example: a Mass / space scene object (Component.id). */\nexport type MassHandle = EntityId<\"mass\">\n\n/**\n * A scene BIM entity (wall, space, slab, floor, …). Backed by `Component.id` and\n * resolved live via the host scene index (StructureCollection) — never stored in\n * the resource registry. Stable across undo/redo and persistable (§9.2). This is\n * the canonical return handle for every `design.create.*` creator. The token is\n * the raw `Component.id` — NO `\"component_\"` prefix, so it round-trips with the\n * ids the host already hands out.\n */\nexport type ComponentHandle = EntityId<\"component\">\n\n/**\n * A material handle. Like {@linkcode ComponentHandle}/`CommentHandle` this is an\n * arena-free **entity-style** handle: the token IS the material's stable engine\n * name (`Material.name`/`.id`), resolved live via `scene.getMaterialByName`\n * host-side — no registry arena, no quota, no dispose, stable across undo/redo.\n * Project-scoped, not a scene entity.\n */\nexport type MaterialHandle = EntityId<\"material\">\n\n/**\n * A placed **scene underlay** — an imported reference plane the user traces over:\n * an image `FloorPlan`, a `PDF` overlay, or a `CAD` sketch. Entity-style: the token\n * IS the raw `Component.id` (`fp_…` / a pdf id / `cad_…`). Resolved live host-side\n * via the import-order layer walk (`collectStoreyImportItems`) — NOT via\n * `ComponentUtility.FindComponentById`, which does not index underlays. No arena, no\n * quota, stable across undo/redo. Returned by `core.io.import.{image,pdf,cadJson}`\n * and consumed by every `core.io.underlay.*` method.\n */\nexport type UnderlayHandle = EntityId<\"underlay\">\n\n/**\n * The project's **site terrain** — a Mapbox-derived topography mesh. Entity-style\n * and a project **singleton** (one terrain per project): the token is the terrain\n * `Component.id` (`terrain-…`), resolved live via `TerrainOperations.getTerrain()`\n * (absent from the level component collections, so NOT `FindComponentById`).\n * Returned by `core.io.import.terrain`.\n */\nexport type TerrainHandle = EntityId<\"terrain\">\n\n/**\n * A **dimension line** — one measurement annotation left behind by the Measuring\n * Tape tool (NOT a width/height/depth property; see `design.dimensions`).\n * Entity-style: the token IS the raw engine dimension id (`dim_…`), resolved live\n * host-side from the dimension-line registry (`getDimensionLineMap()`) — not via\n * `ComponentUtility.FindComponentById`, which does not index dimension lines (a\n * dimension line is not a Component). No arena, no quota, stable across\n * undo/redo and across reloads (the record persists with the project). Returned\n * by `design.dimensions.create` and consumed by every other\n * `design.dimensions.*` method.\n */\nexport type DimensionHandle = EntityId<\"dimension\">\n\n/**\n * A handle to an **asynchronous import job** (today: a DWG → Forge conversion, which\n * can take minutes). Returned immediately by `core.io.import.dwg`; poll it via\n * `core.io.job.*` (`getStatus` / `isComplete` / `getResult` / `getError`). The token\n * is the engine's import request id; job state lives in a host-side registry.\n */\nexport type ImportJobHandle = EntityId<\"importJob\">\n\n// ── Value-kind handles (all-handle model, §11) ──────────────────────────────\n// Under the all-handle decision every structured type is a handle. The only\n// non-handles crossing the boundary are JS primitives (number/string/boolean)\n// and enums — the irreducible construction/read leaves. Value handles are\n// immutable; math/geom operate on them via host RPC (no in-worker compute).\nexport type Vec3Handle = Handle<\"vec3\">\nexport type QuatHandle = Handle<\"quat\">\nexport type LineHandle = Handle<\"line\">\nexport type ArcHandle = Handle<\"arc\">\n/** A curve is a line or an arc — its handle is one of those kinds. */\nexport type CurveHandle = LineHandle | ArcHandle\n/**\n * A circle value handle. A circle is a CLOSED curve — it deliberately does NOT\n * join {@linkcode CurveHandle} (it has no stable start/end and would break the\n * shared curve read contract), so it carries its own kind and its own\n * `core.geom.query.circle` namespace (mirroring `query.arc`).\n */\nexport type CircleHandle = Handle<\"circle\">\n\n// ── BREP topology handles (geometry-kernel resource family, §2A.2.1) ─────────\n// Session-ephemeral, identity-deduped RESOURCE handles for the boundary-\n// representation topology graph. Read-only via `core.geom.query.{brep,face,edge,\n// halfedge,vertex}`; they go stale on undo/redo/re-topologize. Obtain a brep via\n// `design.query.geometry.getBrep`; every other topology handle is minted by\n// walking from a brep/face/edge/vertex handle already resolved off that brep.\nexport type BrepHandle = Handle<\"brep\">\nexport type FaceHandle = Handle<\"face\">\nexport type EdgeHandle = Handle<\"edge\">\n/** Half-edge kind token is all-lowercase `\"halfedge\"` per §6.1 (no camelCase namespaces). */\nexport type HalfEdgeHandle = Handle<\"halfedge\">\nexport type VertexHandle = Handle<\"vertex\">\n\n/**\n * Bare primitive records — how value-handle reads return their components\n * (§11.4). Curves have **no** by-value record: a curve crosses the boundary as a\n * {@linkcode CurveHandle} (line/arc), and its coordinates are read via the\n * `core.geom.query.curve` API. There is no `CurveData` under the all-handle model.\n */\nexport type Vec3Components = { x: number; y: number; z: number }\nexport type QuatComponents = { x: number; y: number; z: number; w: number }\n/** Axis-aligned bounding box, read as two primitive corner records. */\nexport type BBoxComponents = { min: Vec3Components; max: Vec3Components }\n\n/**\n * Build a Zod schema validating a handle of the given `kind` at the trust\n * boundary and emitting a {@linkcode Handle} instance for impl code. Inbound\n * forms accepted:\n * - the bare id string (the wire form — the client unwraps args to ids)\n * - a live `Handle` instance (host-internal re-parse, direct host callers)\n * - `{ __h: string }` (the tagged result form, echoed back as an arg)\n * - `{ id: string }` (a Handle stripped by structured clone at the UI boundary)\n * Shape checking stays prefix-only; existence, kind, and ownership are enforced\n * host-side by the HandleRegistry (§9.7) — no existence oracle.\n */\nexport const handleSchema = <K extends string>(kind: K) =>\n z\n .preprocess(\n (v) => {\n if (typeof v === \"string\") return v\n if (v instanceof Handle) return v.id\n if (v && typeof v === \"object\") {\n const o = v as Record<string, unknown>\n if (typeof o.__h === \"string\") return o.__h\n if (typeof o.id === \"string\") return o.id\n }\n return v\n },\n z.string().regex(new RegExp(`^${kind}_[A-Za-z0-9_-]+$`), `expected a \"${kind}\" handle`)\n )\n .transform((s) => new Handle<K>(s))\n\nexport const ProfileHandle = handleSchema(\"profile\")\nexport const ContourHandle = handleSchema(\"contour\")\n\n/**\n * Entity handles are raw `Component.id` strings (no `\"<kind>_\"` prefix) — validated\n * only as a non-empty string, resolved live via the scene index host-side.\n */\nexport const ComponentHandle = z\n .string()\n .min(1)\n .transform((s) => s as ComponentHandle)\n\n/**\n * Material handles are the raw `Material.name` string (entity-style, no `\"<kind>_\"`\n * prefix) — validated only as non-empty, resolved live via `scene.getMaterialByName`.\n */\nexport const MaterialHandle = z\n .string()\n .min(1)\n .transform((s) => s as MaterialHandle)\n\n/**\n * {@linkcode UnderlayHandle} / {@linkcode TerrainHandle} / {@linkcode ImportJobHandle}\n * are entity-style handles — raw host ids, validated only as a non-empty string\n * (no `\"<kind>_\"` prefix), resolved live host-side (existence enforced there).\n */\nexport const UnderlayHandle = z\n .string()\n .min(1)\n .transform((s) => s as UnderlayHandle)\nexport const TerrainHandle = z\n .string()\n .min(1)\n .transform((s) => s as TerrainHandle)\nexport const ImportJobHandle = z\n .string()\n .min(1)\n .transform((s) => s as ImportJobHandle)\n\n/**\n * {@linkcode DimensionHandle} is an entity-style handle — the raw `dim_…` engine\n * id, validated only as a non-empty string (no `\"<kind>_\"` prefix enforcement),\n * resolved live host-side against the dimension-line registry (existence\n * enforced there, as `HANDLE_INVALID`).\n */\nexport const DimensionHandle = z\n .string()\n .min(1)\n .transform((s) => s as DimensionHandle)\n\n// Value-kind handle schemas (all-handle model, §11).\nexport const Vec3Handle = handleSchema(\"vec3\")\nexport const QuatHandle = handleSchema(\"quat\")\nexport const LineHandle = handleSchema(\"line\")\nexport const ArcHandle = handleSchema(\"arc\")\nexport const CurveHandle = z.union([LineHandle, ArcHandle])\nexport const CircleHandle = handleSchema(\"circle\")\n\n// BREP topology handle schemas (§2A.2.1). Prefix-shape validation only; existence,\n// kind, and ownership are enforced host-side by the HandleRegistry topology arena.\nexport const BrepHandle = handleSchema(\"brep\")\nexport const FaceHandle = handleSchema(\"face\")\nexport const EdgeHandle = handleSchema(\"edge\")\nexport const HalfEdgeHandle = handleSchema(\"halfedge\")\nexport const VertexHandle = handleSchema(\"vertex\")\nexport const Vec3Components = z.object({ x: z.number(), y: z.number(), z: z.number() })\nexport const QuatComponents = z.object({\n x: z.number(),\n y: z.number(),\n z: z.number(),\n w: z.number(),\n})\nexport const BBoxComponents = z.object({ min: Vec3Components, max: Vec3Components })\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport { Vec3Handle, CurveHandle, LineHandle, Vec3Components } from \"../../../../handles\"\n\n/**\n * Curve queries — read-only geometric questions about curves (lines and arcs)\n * and the relationships between two curves (all-handle model, §11).\n *\n * Every method takes opaque curve handles ({@linkcode CurveHandle}/{@linkcode LineHandle})\n * and point handles ({@linkcode Vec3Handle}) and returns plain values — points as\n * {@linkcode Vec3Components}, lengths/distances/parameters as `number`, predicates as\n * `boolean`. Relationship methods that produce a new curve return a\n * {@linkcode CurveHandle} (or `null` when there is no result). No query mutates its\n * inputs.\n *\n * Accessed via `snaptrude.core.geom.query.curve`.\n */\nexport abstract class PluginGeomQueryCurveApi {\n constructor() {}\n\n /**\n * Get the start point of a curve.\n * @param curve The curve to query\n * @returns The start point as {@linkcode Vec3Components}\n *\n * @examplePrompt Where does this wall edge start?\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const centerline = await snaptrude.design.query.geometry.getCenterline(wall)\n * if (centerline) {\n * const start = await snaptrude.core.geom.query.curve.getStartPoint(centerline)\n * console.log(start.x, start.y, start.z)\n * }\n * ```\n */\n public abstract getStartPoint(curve: CurveHandle): PluginApiReturn<Vec3Components>\n\n /**\n * Get the end point of a curve.\n * @param curve The curve to query\n * @returns The end point as {@linkcode Vec3Components}\n *\n * @examplePrompt Get the coordinates of the far end of this edge\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const line = await snaptrude.core.geom.create.line(a, b)\n * const end = await snaptrude.core.geom.query.curve.getEndPoint(line) // { x: 5, y: 0, z: 0 }\n * ```\n */\n public abstract getEndPoint(curve: CurveHandle): PluginApiReturn<Vec3Components>\n\n /**\n * Get the mid point of a curve.\n * @param curve The curve to query\n * @returns The mid point as {@linkcode Vec3Components}\n *\n * @examplePrompt Find the midpoint of this wall edge\n * @examplePrompt Where is the centre of this boundary line?\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const centerline = await snaptrude.design.query.geometry.getCenterline(wall)\n * if (centerline) {\n * const mid = await snaptrude.core.geom.query.curve.getMidPoint(centerline)\n * console.log(mid.x, mid.y, mid.z)\n * }\n * ```\n */\n public abstract getMidPoint(curve: CurveHandle): PluginApiReturn<Vec3Components>\n\n /**\n * Get the arc-length of a curve.\n * @param curve The curve to query\n * @returns The curve length as a `number`\n *\n * @examplePrompt How long is this wall edge?\n * @examplePrompt Measure the length of this curved boundary\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const centerline = await snaptrude.design.query.geometry.getCenterline(wall)\n * if (centerline) {\n * const length = await snaptrude.core.geom.query.curve.getLength(centerline)\n * }\n * ```\n */\n public abstract getLength(curve: CurveHandle): PluginApiReturn<number>\n\n /**\n * Get the straight-line chord length between a curve's endpoints.\n * @param curve The curve to query\n * @returns The chord length as a `number`\n *\n * @examplePrompt What is the straight-line distance between the two ends of this arc?\n *\n * # Example\n * ```ts\n * const start = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const end = await snaptrude.core.math.vec3.new(-5, 0, 0)\n * const centre = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const axis = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const arc = await snaptrude.core.geom.create.arc(start, end, centre, axis)\n * const chord = await snaptrude.core.geom.query.curve.getChordLength(arc) // 10 (getLength gives the arc length)\n * ```\n */\n public abstract getChordLength(curve: CurveHandle): PluginApiReturn<number>\n\n /**\n * Get the unit tangent vector of a curve at a point on it.\n * @param curve The curve to query\n * @param point Point on the curve\n * @returns The tangent vector as {@linkcode Vec3Components}\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(3, 0, 4)\n * const line = await snaptrude.core.geom.create.line(a, b)\n * const tangent = await snaptrude.core.geom.query.curve.getTangent(line, a) // { x: 0.6, y: 0, z: 0.8 }\n * ```\n */\n public abstract getTangent(\n curve: CurveHandle,\n point: Vec3Handle,\n ): PluginApiReturn<Vec3Components>\n\n /**\n * Get the unit normal vector of a curve at a point on it.\n * @param curve The curve to query\n * @param point Point on the curve\n * @returns The normal vector as {@linkcode Vec3Components}\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(10, 0, 0)\n * const line = await snaptrude.core.geom.create.line(a, b)\n * const normal = await snaptrude.core.geom.query.curve.getNormal(line, a)\n * ```\n */\n public abstract getNormal(\n curve: CurveHandle,\n point: Vec3Handle,\n ): PluginApiReturn<Vec3Components>\n\n /**\n * Get the point on a curve nearest to an arbitrary point.\n * @param curve The curve to query\n * @param point The reference point\n * @returns The nearest point on the curve as {@linkcode Vec3Components}\n *\n * @examplePrompt Find the point on this edge closest to the column\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const centerline = await snaptrude.design.query.geometry.getCenterline(wall)\n * const reference = await snaptrude.core.math.vec3.new(2, 0, 3)\n * if (centerline) {\n * const nearest = await snaptrude.core.geom.query.curve.getNearestPoint(centerline, reference)\n * }\n * ```\n */\n public abstract getNearestPoint(\n curve: CurveHandle,\n point: Vec3Handle,\n ): PluginApiReturn<Vec3Components>\n\n /**\n * Project a point onto a curve.\n * @param curve The curve to project onto\n * @param point The point to project\n * @returns The projected point as {@linkcode Vec3Components}\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(10, 0, 0)\n * const line = await snaptrude.core.geom.create.line(a, b)\n * const point = await snaptrude.core.math.vec3.new(3, 0, 4)\n * const projected = await snaptrude.core.geom.query.curve.getProjection(line, point) // { x: 3, y: 0, z: 0 }\n * ```\n */\n public abstract getProjection(\n curve: CurveHandle,\n point: Vec3Handle,\n ): PluginApiReturn<Vec3Components>\n\n /**\n * Get the point at a given distance along a curve from a reference point.\n * @param curve The curve to query\n * @param referencePoint Point on the curve to measure from\n * @param distance Distance along the curve from the reference point\n * @param forward Optional direction along the curve (defaults to `true`)\n * @returns The resulting point as {@linkcode Vec3Components}\n *\n * @examplePrompt Find the point 2 metres along this edge from the corner\n * @examplePrompt Mark a spot 900mm from the start of the wall edge\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(10, 0, 0)\n * const line = await snaptrude.core.geom.create.line(a, b)\n * const p = await snaptrude.core.geom.query.curve.getPointAtDistance(line, a, 2) // { x: 2, y: 0, z: 0 }\n * const q = await snaptrude.core.geom.query.curve.getPointAtDistance(line, b, 2, false) // measured backwards from the end\n * ```\n */\n public abstract getPointAtDistance(\n curve: CurveHandle,\n referencePoint: Vec3Handle,\n distance: number,\n forward?: boolean,\n ): PluginApiReturn<Vec3Components>\n\n /**\n * Get the curve parameter corresponding to a point on the curve.\n * @param curve The curve to query\n * @param point Point on the curve\n * @returns The parameter as a `number`\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(8, 0, 0)\n * const line = await snaptrude.core.geom.create.line(a, b)\n * const t = await snaptrude.core.geom.query.curve.getParameterAtPoint(line, b)\n * ```\n */\n public abstract getParameterAtPoint(\n curve: CurveHandle,\n point: Vec3Handle,\n ): PluginApiReturn<number>\n\n /**\n * Get the curvature of a curve at a point on it.\n * @param curve The curve to query\n * @param point Point on the curve\n * @returns The curvature as a `number`\n *\n * # Example\n * ```ts\n * const start = await snaptrude.core.math.vec3.new(2, 0, 0)\n * const end = await snaptrude.core.math.vec3.new(-2, 0, 0)\n * const centre = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const axis = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const arc = await snaptrude.core.geom.create.arc(start, end, centre, axis)\n * const k = await snaptrude.core.geom.query.curve.getCurvature(arc, start) // 0.5 (1 / radius)\n * ```\n */\n public abstract getCurvature(\n curve: CurveHandle,\n point: Vec3Handle,\n ): PluginApiReturn<number>\n\n /**\n * Get the shortest distance from an arbitrary point to a curve.\n * @param curve The curve to query\n * @param point The point to measure from\n * @returns The distance as a `number`\n *\n * @examplePrompt How far is this column from the wall edge?\n * @examplePrompt What is the shortest distance from this point to the boundary line?\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const centerline = await snaptrude.design.query.geometry.getCenterline(wall)\n * const point = await snaptrude.core.math.vec3.new(2, 0, 3)\n * if (centerline) {\n * const distance = await snaptrude.core.geom.query.curve.getDistanceToPoint(centerline, point)\n * }\n * ```\n */\n public abstract getDistanceToPoint(\n curve: CurveHandle,\n point: Vec3Handle,\n ): PluginApiReturn<number>\n\n /**\n * Get the arc-length distance along a curve between two points on it.\n * @param curve The curve to query\n * @param pointA First point on the curve\n * @param pointB Second point on the curve\n * @returns The distance along the curve as a `number`\n *\n * @examplePrompt How far apart are these two points measured along the curved wall?\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(10, 0, 0)\n * const line = await snaptrude.core.geom.create.line(a, b)\n * const mid = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const d = await snaptrude.core.geom.query.curve.getDistanceAlong(line, a, mid) // 5\n * ```\n */\n public abstract getDistanceAlong(\n curve: CurveHandle,\n pointA: Vec3Handle,\n pointB: Vec3Handle,\n ): PluginApiReturn<number>\n\n /**\n * List sampled points along a curve.\n * @param curve The curve to sample\n * @param resolution Optional number of samples for arcs (defaults to `20`; ignored for lines)\n * @returns The sampled points as {@linkcode Vec3Components}`[]`\n *\n * # Example\n * ```ts\n * const start = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const end = await snaptrude.core.math.vec3.new(0, 0, 5)\n * const centre = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const axis = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const arc = await snaptrude.core.geom.create.arc(start, end, centre, axis)\n * const points = await snaptrude.core.geom.query.curve.listPoints(arc, 40)\n * for (const p of points) console.log(p.x, p.y, p.z)\n * ```\n */\n public abstract listPoints(\n curve: CurveHandle,\n resolution?: number,\n ): PluginApiReturn<Vec3Components[]>\n\n /**\n * List the points produced by subdividing a curve into equal segments.\n * @param curve The curve to subdivide\n * @param segments Number of equal segments to divide the curve into\n * @returns The subdivision points as {@linkcode Vec3Components}`[]`\n *\n * @examplePrompt Divide this edge into 5 equal segments\n * @examplePrompt Split the wall line into equal parts for placing columns\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const centerline = await snaptrude.design.query.geometry.getCenterline(wall)\n * if (centerline) {\n * const points = await snaptrude.core.geom.query.curve.listSubdivisions(centerline, 5)\n * }\n * ```\n */\n public abstract listSubdivisions(\n curve: CurveHandle,\n segments: number,\n ): PluginApiReturn<Vec3Components[]>\n\n /**\n * Test whether a curve is a straight line.\n * @param curve The curve to query\n * @returns `true` if the curve is linear, otherwise `false`\n *\n * @examplePrompt Is this edge straight or curved?\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const centerline = await snaptrude.design.query.geometry.getCenterline(wall)\n * if (centerline) {\n * const straight = await snaptrude.core.geom.query.curve.isLinear(centerline)\n * }\n * ```\n */\n public abstract isLinear(curve: CurveHandle): PluginApiReturn<boolean>\n\n /**\n * Test whether a point lies on a curve within an optional threshold.\n * @param curve The curve to query\n * @param point The point to test\n * @param threshold Optional distance tolerance (defaults to the engine default)\n * @returns `true` if the point is on the curve, otherwise `false`\n *\n * @examplePrompt Check whether this point lies on the wall edge\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(10, 0, 0)\n * const line = await snaptrude.core.geom.create.line(a, b)\n * const point = await snaptrude.core.math.vec3.new(5, 0, 0.005)\n * const on = await snaptrude.core.geom.query.curve.isOnCurve(line, point, 0.01) // true within tolerance\n * ```\n */\n public abstract isOnCurve(\n curve: CurveHandle,\n point: Vec3Handle,\n threshold?: number,\n ): PluginApiReturn<boolean>\n\n /**\n * Test whether a curve contains a point, optionally including its endpoints.\n * @param curve The curve to query\n * @param point The point to test\n * @param includeEndPoints Optional flag to count endpoints as contained (defaults to `true`)\n * @returns `true` if the curve contains the point, otherwise `false`\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(10, 0, 0)\n * const line = await snaptrude.core.geom.create.line(a, b)\n * const atEnd = await snaptrude.core.geom.query.curve.hasPoint(line, b) // true (endpoints count by default)\n * const interiorOnly = await snaptrude.core.geom.query.curve.hasPoint(line, b, false) // false\n * ```\n */\n public abstract hasPoint(\n curve: CurveHandle,\n point: Vec3Handle,\n includeEndPoints?: boolean,\n ): PluginApiReturn<boolean>\n\n /**\n * Test whether two curves are geometrically equal.\n * @param curveA First curve\n * @param curveB Second curve\n * @returns `true` if the curves are equal, otherwise `false`\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(10, 0, 0)\n * const lineA = await snaptrude.core.geom.create.line(a, b)\n * const lineB = await snaptrude.core.geom.create.line(a, b)\n * const same = await snaptrude.core.geom.query.curve.isEqual(lineA, lineB) // true\n * ```\n */\n public abstract isEqual(\n curveA: CurveHandle,\n curveB: CurveHandle,\n ): PluginApiReturn<boolean>\n\n /**\n * Test whether two curves overlap (share a collinear/concentric span), optionally including their endpoints.\n * @param curveA First curve\n * @param curveB Second curve\n * @param includeEndPoints Optional flag to count endpoint contact as overlap (defaults to `false`)\n * @returns `true` if the curves overlap, otherwise `false`\n *\n * @examplePrompt Do these two wall edges overlap?\n *\n * # Example\n * ```ts\n * const p0 = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const p5 = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const p10 = await snaptrude.core.math.vec3.new(10, 0, 0)\n * const p15 = await snaptrude.core.math.vec3.new(15, 0, 0)\n * const lineA = await snaptrude.core.geom.create.line(p0, p10)\n * const lineB = await snaptrude.core.geom.create.line(p5, p15)\n * const overlaps = await snaptrude.core.geom.query.curve.isOverlapping(lineA, lineB) // true (share the 5..10 span)\n * ```\n */\n public abstract isOverlapping(\n curveA: CurveHandle,\n curveB: CurveHandle,\n includeEndPoints?: boolean,\n ): PluginApiReturn<boolean>\n\n /**\n * Get the common (overlapping) part of two curves.\n * @param curveA First curve\n * @param curveB Second curve\n * @returns The common part as a {@linkcode CurveHandle}, or `null` if there is none\n *\n * @examplePrompt Get the shared segment where these two edges overlap\n *\n * # Example\n * ```ts\n * const p0 = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const p5 = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const p10 = await snaptrude.core.math.vec3.new(10, 0, 0)\n * const p15 = await snaptrude.core.math.vec3.new(15, 0, 0)\n * const lineA = await snaptrude.core.geom.create.line(p0, p10)\n * const lineB = await snaptrude.core.geom.create.line(p5, p15)\n * const common = await snaptrude.core.geom.query.curve.getCommonPart(lineA, lineB)\n * if (common) {\n * const length = await snaptrude.core.geom.query.curve.getLength(common) // 5\n * }\n * ```\n */\n public abstract getCommonPart(\n curveA: CurveHandle,\n curveB: CurveHandle,\n ): PluginApiReturn<CurveHandle | null>\n\n /**\n * List the intersection points between two curves.\n * @param curveA First curve\n * @param curveB Second curve\n * @param boundedA Optional flag to bound intersections to `curveA`'s extent (defaults to `true`)\n * @param boundedB Optional flag to bound intersections to `curveB`'s extent (defaults to `true`)\n * @returns The intersection points as {@linkcode Vec3Components}`[]`\n *\n * @examplePrompt Where do these two lines cross?\n * @examplePrompt Find the intersection points of these two gridlines\n *\n * # Example\n * ```ts\n * const a1 = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const a2 = await snaptrude.core.math.vec3.new(10, 0, 0)\n * const b1 = await snaptrude.core.math.vec3.new(5, 0, -5)\n * const b2 = await snaptrude.core.math.vec3.new(5, 0, 5)\n * const lineA = await snaptrude.core.geom.create.line(a1, a2)\n * const lineB = await snaptrude.core.geom.create.line(b1, b2)\n * const points = await snaptrude.core.geom.query.curve.listIntersections(lineA, lineB) // [{ x: 5, y: 0, z: 0 }]\n * const unbounded = await snaptrude.core.geom.query.curve.listIntersections(lineA, lineB, false, false) // treat both as infinite\n * ```\n */\n public abstract listIntersections(\n curveA: CurveHandle,\n curveB: CurveHandle,\n boundedA?: boolean,\n boundedB?: boolean,\n ): PluginApiReturn<Vec3Components[]>\n\n /**\n * Merge two curves into a single curve when they are mergeable.\n * @param curveA First curve\n * @param curveB Second curve\n * @returns The merged curve as a {@linkcode CurveHandle}, or `null` if they cannot be merged\n *\n * @examplePrompt Merge these two line segments into a single line\n *\n * # Example\n * ```ts\n * const p0 = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const p5 = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const p10 = await snaptrude.core.math.vec3.new(10, 0, 0)\n * const lineA = await snaptrude.core.geom.create.line(p0, p5)\n * const lineB = await snaptrude.core.geom.create.line(p5, p10)\n * const merged = await snaptrude.core.geom.query.curve.getMergedCurve(lineA, lineB)\n * if (merged) {\n * const length = await snaptrude.core.geom.query.curve.getLength(merged) // 10\n * }\n * ```\n */\n public abstract getMergedCurve(\n curveA: CurveHandle,\n curveB: CurveHandle,\n ): PluginApiReturn<CurveHandle | null>\n\n /**\n * Test whether two curves are continuous (share an endpoint without a gap).\n * @param curveA First curve\n * @param curveB Second curve\n * @returns `true` if the curves are continuous, otherwise `false`\n *\n * @examplePrompt Do these two edges connect end to end without a gap?\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const c = await snaptrude.core.math.vec3.new(5, 0, 5)\n * const lineA = await snaptrude.core.geom.create.line(a, b)\n * const lineB = await snaptrude.core.geom.create.line(b, c)\n * const connected = await snaptrude.core.geom.query.curve.isContinuous(lineA, lineB) // true\n * ```\n */\n public abstract isContinuous(\n curveA: CurveHandle,\n curveB: CurveHandle,\n ): PluginApiReturn<boolean>\n\n /**\n * Test whether two lines are parallel within an optional tolerance.\n * @param lineA First line\n * @param lineB Second line\n * @param tolerance Optional angular tolerance (defaults to the engine default)\n * @returns `true` if the lines are parallel, otherwise `false`\n *\n * @examplePrompt Are these two walls parallel?\n * @examplePrompt Check if these gridlines run parallel to each other\n *\n * # Example\n * ```ts\n * const a1 = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const a2 = await snaptrude.core.math.vec3.new(10, 0, 0)\n * const b1 = await snaptrude.core.math.vec3.new(0, 0, 3)\n * const b2 = await snaptrude.core.math.vec3.new(10, 0, 3)\n * const lineA = await snaptrude.core.geom.create.line(a1, a2)\n * const lineB = await snaptrude.core.geom.create.line(b1, b2)\n * const parallel = await snaptrude.core.geom.query.curve.isParallel(lineA, lineB) // true\n * ```\n */\n public abstract isParallel(\n lineA: LineHandle,\n lineB: LineHandle,\n tolerance?: number,\n ): PluginApiReturn<boolean>\n\n /**\n * Get the shortest gap between two curves, optionally including their endpoints.\n * @param curveA First curve\n * @param curveB Second curve\n * @param includeEndPoints Optional flag to include endpoints when measuring (defaults to `false`)\n * @returns The shortest gap as a `number`\n *\n * @examplePrompt What is the gap between these two wall edges?\n * @examplePrompt How close do these two curves get to each other?\n *\n * # Example\n * ```ts\n * const a1 = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const a2 = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const b1 = await snaptrude.core.math.vec3.new(0, 0, 2)\n * const b2 = await snaptrude.core.math.vec3.new(5, 0, 2)\n * const curveA = await snaptrude.core.geom.create.line(a1, a2)\n * const curveB = await snaptrude.core.geom.create.line(b1, b2)\n * const gap = await snaptrude.core.geom.query.curve.getShortestGap(curveA, curveB) // 2\n * ```\n */\n public abstract getShortestGap(\n curveA: CurveHandle,\n curveB: CurveHandle,\n includeEndPoints?: boolean,\n ): PluginApiReturn<number>\n\n /**\n * Get the perpendicular distance between two parallel lines.\n * @param lineA First line\n * @param lineB Second line\n * @returns The distance between the lines as a `number`\n *\n * @examplePrompt How far apart are these two parallel walls?\n * @examplePrompt Measure the spacing between these two gridlines\n *\n * # Example\n * ```ts\n * const a1 = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const a2 = await snaptrude.core.math.vec3.new(10, 0, 0)\n * const b1 = await snaptrude.core.math.vec3.new(0, 0, 4)\n * const b2 = await snaptrude.core.math.vec3.new(10, 0, 4)\n * const lineA = await snaptrude.core.geom.create.line(a1, a2)\n * const lineB = await snaptrude.core.geom.create.line(b1, b2)\n * const spacing = await snaptrude.core.geom.query.curve.getDistanceBetween(lineA, lineB) // 4\n * ```\n */\n public abstract getDistanceBetween(\n lineA: LineHandle,\n lineB: LineHandle,\n ): PluginApiReturn<number>\n}\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.getStartPoint}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to query |\n */\nexport const PluginGeomQueryCurveGetStartPointArgs = z.object({\n curve: CurveHandle,\n})\n\nexport type PluginGeomQueryCurveGetStartPointArgs = z.infer<typeof PluginGeomQueryCurveGetStartPointArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.getEndPoint}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to query |\n */\nexport const PluginGeomQueryCurveGetEndPointArgs = z.object({\n curve: CurveHandle,\n})\n\nexport type PluginGeomQueryCurveGetEndPointArgs = z.infer<typeof PluginGeomQueryCurveGetEndPointArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.getMidPoint}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to query |\n */\nexport const PluginGeomQueryCurveGetMidPointArgs = z.object({\n curve: CurveHandle,\n})\n\nexport type PluginGeomQueryCurveGetMidPointArgs = z.infer<typeof PluginGeomQueryCurveGetMidPointArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.getLength}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to query |\n */\nexport const PluginGeomQueryCurveGetLengthArgs = z.object({\n curve: CurveHandle,\n})\n\nexport type PluginGeomQueryCurveGetLengthArgs = z.infer<typeof PluginGeomQueryCurveGetLengthArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.getChordLength}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to query |\n */\nexport const PluginGeomQueryCurveGetChordLengthArgs = z.object({\n curve: CurveHandle,\n})\n\nexport type PluginGeomQueryCurveGetChordLengthArgs = z.infer<typeof PluginGeomQueryCurveGetChordLengthArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.getTangent}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to query |\n * | `point` | {@linkcode Vec3Handle} | Point on the curve |\n */\nexport const PluginGeomQueryCurveGetTangentArgs = z.object({\n curve: CurveHandle,\n point: Vec3Handle,\n})\n\nexport type PluginGeomQueryCurveGetTangentArgs = z.infer<typeof PluginGeomQueryCurveGetTangentArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.getNormal}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to query |\n * | `point` | {@linkcode Vec3Handle} | Point on the curve |\n */\nexport const PluginGeomQueryCurveGetNormalArgs = z.object({\n curve: CurveHandle,\n point: Vec3Handle,\n})\n\nexport type PluginGeomQueryCurveGetNormalArgs = z.infer<typeof PluginGeomQueryCurveGetNormalArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.getNearestPoint}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to query |\n * | `point` | {@linkcode Vec3Handle} | The reference point |\n */\nexport const PluginGeomQueryCurveGetNearestPointArgs = z.object({\n curve: CurveHandle,\n point: Vec3Handle,\n})\n\nexport type PluginGeomQueryCurveGetNearestPointArgs = z.infer<typeof PluginGeomQueryCurveGetNearestPointArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.getProjection}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to project onto |\n * | `point` | {@linkcode Vec3Handle} | The point to project |\n */\nexport const PluginGeomQueryCurveGetProjectionArgs = z.object({\n curve: CurveHandle,\n point: Vec3Handle,\n})\n\nexport type PluginGeomQueryCurveGetProjectionArgs = z.infer<typeof PluginGeomQueryCurveGetProjectionArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.getPointAtDistance}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to query |\n * | `referencePoint` | {@linkcode Vec3Handle} | Point on the curve to measure from |\n * | `distance` | `number` | Distance along the curve from the reference point |\n * | `forward` | `boolean` | Optional direction along the curve (defaults to `true`) |\n */\nexport const PluginGeomQueryCurveGetPointAtDistanceArgs = z.object({\n curve: CurveHandle,\n referencePoint: Vec3Handle,\n distance: z.number(),\n forward: z.boolean().optional(),\n})\n\nexport type PluginGeomQueryCurveGetPointAtDistanceArgs = z.infer<typeof PluginGeomQueryCurveGetPointAtDistanceArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.getParameterAtPoint}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to query |\n * | `point` | {@linkcode Vec3Handle} | Point on the curve |\n */\nexport const PluginGeomQueryCurveGetParameterAtPointArgs = z.object({\n curve: CurveHandle,\n point: Vec3Handle,\n})\n\nexport type PluginGeomQueryCurveGetParameterAtPointArgs = z.infer<typeof PluginGeomQueryCurveGetParameterAtPointArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.getCurvature}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to query |\n * | `point` | {@linkcode Vec3Handle} | Point on the curve |\n */\nexport const PluginGeomQueryCurveGetCurvatureArgs = z.object({\n curve: CurveHandle,\n point: Vec3Handle,\n})\n\nexport type PluginGeomQueryCurveGetCurvatureArgs = z.infer<typeof PluginGeomQueryCurveGetCurvatureArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.getDistanceToPoint}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to query |\n * | `point` | {@linkcode Vec3Handle} | The point to measure from |\n */\nexport const PluginGeomQueryCurveGetDistanceToPointArgs = z.object({\n curve: CurveHandle,\n point: Vec3Handle,\n})\n\nexport type PluginGeomQueryCurveGetDistanceToPointArgs = z.infer<typeof PluginGeomQueryCurveGetDistanceToPointArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.getDistanceAlong}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to query |\n * | `pointA` | {@linkcode Vec3Handle} | First point on the curve |\n * | `pointB` | {@linkcode Vec3Handle} | Second point on the curve |\n */\nexport const PluginGeomQueryCurveGetDistanceAlongArgs = z.object({\n curve: CurveHandle,\n pointA: Vec3Handle,\n pointB: Vec3Handle,\n})\n\nexport type PluginGeomQueryCurveGetDistanceAlongArgs = z.infer<typeof PluginGeomQueryCurveGetDistanceAlongArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.listPoints}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to sample |\n * | `resolution` | `number` | Optional number of samples for arcs (defaults to `20`; ignored for lines) |\n */\nexport const PluginGeomQueryCurveListPointsArgs = z.object({\n curve: CurveHandle,\n resolution: z.number().optional(),\n})\n\nexport type PluginGeomQueryCurveListPointsArgs = z.infer<typeof PluginGeomQueryCurveListPointsArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.listSubdivisions}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to subdivide |\n * | `segments` | `number` | Number of equal segments to divide the curve into |\n */\nexport const PluginGeomQueryCurveListSubdivisionsArgs = z.object({\n curve: CurveHandle,\n segments: z.number(),\n})\n\nexport type PluginGeomQueryCurveListSubdivisionsArgs = z.infer<typeof PluginGeomQueryCurveListSubdivisionsArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.isLinear}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to query |\n */\nexport const PluginGeomQueryCurveIsLinearArgs = z.object({\n curve: CurveHandle,\n})\n\nexport type PluginGeomQueryCurveIsLinearArgs = z.infer<typeof PluginGeomQueryCurveIsLinearArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.isOnCurve}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to query |\n * | `point` | {@linkcode Vec3Handle} | The point to test |\n * | `threshold` | `number` | Optional distance tolerance (defaults to the engine default) |\n */\nexport const PluginGeomQueryCurveIsOnCurveArgs = z.object({\n curve: CurveHandle,\n point: Vec3Handle,\n threshold: z.number().optional(),\n})\n\nexport type PluginGeomQueryCurveIsOnCurveArgs = z.infer<typeof PluginGeomQueryCurveIsOnCurveArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.hasPoint}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to query |\n * | `point` | {@linkcode Vec3Handle} | The point to test |\n * | `includeEndPoints` | `boolean` | Optional flag to count endpoints as contained (defaults to `true`) |\n */\nexport const PluginGeomQueryCurveHasPointArgs = z.object({\n curve: CurveHandle,\n point: Vec3Handle,\n includeEndPoints: z.boolean().optional(),\n})\n\nexport type PluginGeomQueryCurveHasPointArgs = z.infer<typeof PluginGeomQueryCurveHasPointArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.isEqual}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curveA` | {@linkcode CurveHandle} | First curve |\n * | `curveB` | {@linkcode CurveHandle} | Second curve |\n */\nexport const PluginGeomQueryCurveIsEqualArgs = z.object({\n curveA: CurveHandle,\n curveB: CurveHandle,\n})\n\nexport type PluginGeomQueryCurveIsEqualArgs = z.infer<typeof PluginGeomQueryCurveIsEqualArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.isOverlapping}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curveA` | {@linkcode CurveHandle} | First curve |\n * | `curveB` | {@linkcode CurveHandle} | Second curve |\n * | `includeEndPoints` | `boolean` | Optional flag to count endpoint contact as overlap (defaults to `false`) |\n */\nexport const PluginGeomQueryCurveIsOverlappingArgs = z.object({\n curveA: CurveHandle,\n curveB: CurveHandle,\n includeEndPoints: z.boolean().optional(),\n})\n\nexport type PluginGeomQueryCurveIsOverlappingArgs = z.infer<typeof PluginGeomQueryCurveIsOverlappingArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.getCommonPart}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curveA` | {@linkcode CurveHandle} | First curve |\n * | `curveB` | {@linkcode CurveHandle} | Second curve |\n */\nexport const PluginGeomQueryCurveGetCommonPartArgs = z.object({\n curveA: CurveHandle,\n curveB: CurveHandle,\n})\n\nexport type PluginGeomQueryCurveGetCommonPartArgs = z.infer<typeof PluginGeomQueryCurveGetCommonPartArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.listIntersections}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curveA` | {@linkcode CurveHandle} | First curve |\n * | `curveB` | {@linkcode CurveHandle} | Second curve |\n * | `boundedA` | `boolean` | Optional flag to bound intersections to `curveA`'s extent (defaults to `true`) |\n * | `boundedB` | `boolean` | Optional flag to bound intersections to `curveB`'s extent (defaults to `true`) |\n */\nexport const PluginGeomQueryCurveListIntersectionsArgs = z.object({\n curveA: CurveHandle,\n curveB: CurveHandle,\n boundedA: z.boolean().optional(),\n boundedB: z.boolean().optional(),\n})\n\nexport type PluginGeomQueryCurveListIntersectionsArgs = z.infer<typeof PluginGeomQueryCurveListIntersectionsArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.getMergedCurve}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curveA` | {@linkcode CurveHandle} | First curve |\n * | `curveB` | {@linkcode CurveHandle} | Second curve |\n */\nexport const PluginGeomQueryCurveGetMergedCurveArgs = z.object({\n curveA: CurveHandle,\n curveB: CurveHandle,\n})\n\nexport type PluginGeomQueryCurveGetMergedCurveArgs = z.infer<typeof PluginGeomQueryCurveGetMergedCurveArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.isContinuous}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curveA` | {@linkcode CurveHandle} | First curve |\n * | `curveB` | {@linkcode CurveHandle} | Second curve |\n */\nexport const PluginGeomQueryCurveIsContinuousArgs = z.object({\n curveA: CurveHandle,\n curveB: CurveHandle,\n})\n\nexport type PluginGeomQueryCurveIsContinuousArgs = z.infer<typeof PluginGeomQueryCurveIsContinuousArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.isParallel}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `lineA` | {@linkcode LineHandle} | First line |\n * | `lineB` | {@linkcode LineHandle} | Second line |\n * | `tolerance` | `number` | Optional angular tolerance (defaults to the engine default) |\n */\nexport const PluginGeomQueryCurveIsParallelArgs = z.object({\n lineA: LineHandle,\n lineB: LineHandle,\n tolerance: z.number().optional(),\n})\n\nexport type PluginGeomQueryCurveIsParallelArgs = z.infer<typeof PluginGeomQueryCurveIsParallelArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.getShortestGap}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curveA` | {@linkcode CurveHandle} | First curve |\n * | `curveB` | {@linkcode CurveHandle} | Second curve |\n * | `includeEndPoints` | `boolean` | Optional flag to include endpoints when measuring (defaults to `false`) |\n */\nexport const PluginGeomQueryCurveGetShortestGapArgs = z.object({\n curveA: CurveHandle,\n curveB: CurveHandle,\n includeEndPoints: z.boolean().optional(),\n})\n\nexport type PluginGeomQueryCurveGetShortestGapArgs = z.infer<typeof PluginGeomQueryCurveGetShortestGapArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCurveApi.getDistanceBetween}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `lineA` | {@linkcode LineHandle} | First line |\n * | `lineB` | {@linkcode LineHandle} | Second line |\n */\nexport const PluginGeomQueryCurveGetDistanceBetweenArgs = z.object({\n lineA: LineHandle,\n lineB: LineHandle,\n})\n\nexport type PluginGeomQueryCurveGetDistanceBetweenArgs = z.infer<typeof PluginGeomQueryCurveGetDistanceBetweenArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport { ArcHandle, Vec3Components } from \"../../../../handles\"\n\n/**\n * Arc-specific geometry queries — read circular-arc properties as plain values.\n *\n * Every method takes an opaque {@linkcode ArcHandle} and returns plain values\n * (numbers or {@linkcode Vec3Components}), never a handle. Use these to inspect\n * the radius, centre, axis, sweep angle, and validity of an arc handle obtained\n * from {@linkcode PluginGeomCreateApi.arc} or another query.\n *\n * Accessed via `snaptrude.core.geom.query.arc`.\n */\nexport abstract class PluginGeomQueryArcApi {\n constructor() {}\n\n /**\n * Read the radius of an arc.\n *\n * @param arc The arc to query\n * @returns The arc radius as a `number`\n *\n * @examplePrompt What is the radius of this curved wall segment?\n * @examplePrompt Measure the radius of the selected arc\n *\n * # Example\n * ```ts\n * const start = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const end = await snaptrude.core.math.vec3.new(0, 0, 5)\n * const origin = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const up = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const arc = await snaptrude.core.geom.create.arc(start, end, origin, up)\n * const radius = await snaptrude.core.geom.query.arc.getRadius(arc)\n * ```\n */\n public abstract getRadius(arc: ArcHandle): PluginApiReturn<number>\n\n /**\n * Read the centre point of an arc.\n *\n * @param arc The arc to query\n * @returns The arc centre as {@linkcode Vec3Components}\n *\n * @examplePrompt Where is the centre of this arc?\n * @examplePrompt Find the center point of the curved wall\n *\n * # Example\n * ```ts\n * const start = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const end = await snaptrude.core.math.vec3.new(0, 0, 5)\n * const origin = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const up = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const arc = await snaptrude.core.geom.create.arc(start, end, origin, up)\n * const centre = await snaptrude.core.geom.query.arc.getCentre(arc)\n * console.log(centre.x, centre.y, centre.z)\n * ```\n */\n public abstract getCentre(arc: ArcHandle): PluginApiReturn<Vec3Components>\n\n /**\n * Read the rotation axis of an arc.\n *\n * @param arc The arc to query\n * @returns The arc axis as {@linkcode Vec3Components}\n *\n * # Example\n * ```ts\n * const start = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const end = await snaptrude.core.math.vec3.new(0, 0, 5)\n * const origin = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const up = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const arc = await snaptrude.core.geom.create.arc(start, end, origin, up)\n * const axis = await snaptrude.core.geom.query.arc.getAxis(arc)\n * console.log(axis.x, axis.y, axis.z)\n * ```\n */\n public abstract getAxis(arc: ArcHandle): PluginApiReturn<Vec3Components>\n\n /**\n * Read the sweep angle of an arc (the angle subtended at its centre).\n *\n * @param arc The arc to query\n * @returns The sweep angle in radians as a `number`\n *\n * @examplePrompt What angle does this arc sweep through?\n * @examplePrompt How many degrees does this curved wall turn?\n *\n * # Example\n * ```ts\n * const start = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const end = await snaptrude.core.math.vec3.new(0, 0, 5)\n * const origin = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const up = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const arc = await snaptrude.core.geom.create.arc(start, end, origin, up)\n * const sweep = await snaptrude.core.geom.query.arc.getSweepAngle(arc)\n * console.log(\"degrees:\", (sweep * 180) / Math.PI)\n * ```\n */\n public abstract getSweepAngle(arc: ArcHandle): PluginApiReturn<number>\n\n /**\n * Check whether an arc is geometrically valid.\n *\n * @param arc The arc to query\n * @returns `true` if the arc is valid as a `boolean`\n *\n * # Example\n * ```ts\n * const start = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const end = await snaptrude.core.math.vec3.new(0, 0, 5)\n * const origin = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const up = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const arc = await snaptrude.core.geom.create.arc(start, end, origin, up)\n * const ok = await snaptrude.core.geom.query.arc.isValid(arc)\n * ```\n */\n public abstract isValid(arc: ArcHandle): PluginApiReturn<boolean>\n}\n\n/**\n * Arguments for {@linkcode PluginGeomQueryArcApi.getRadius}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `arc` | {@linkcode ArcHandle} | The arc to query |\n */\nexport const PluginGeomQueryArcGetRadiusArgs = z.object({ arc: ArcHandle })\nexport type PluginGeomQueryArcGetRadiusArgs = z.infer<typeof PluginGeomQueryArcGetRadiusArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryArcApi.getCentre}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `arc` | {@linkcode ArcHandle} | The arc to query |\n */\nexport const PluginGeomQueryArcGetCentreArgs = z.object({ arc: ArcHandle })\nexport type PluginGeomQueryArcGetCentreArgs = z.infer<typeof PluginGeomQueryArcGetCentreArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryArcApi.getAxis}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `arc` | {@linkcode ArcHandle} | The arc to query |\n */\nexport const PluginGeomQueryArcGetAxisArgs = z.object({ arc: ArcHandle })\nexport type PluginGeomQueryArcGetAxisArgs = z.infer<typeof PluginGeomQueryArcGetAxisArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryArcApi.getSweepAngle}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `arc` | {@linkcode ArcHandle} | The arc to query |\n */\nexport const PluginGeomQueryArcGetSweepAngleArgs = z.object({ arc: ArcHandle })\nexport type PluginGeomQueryArcGetSweepAngleArgs = z.infer<typeof PluginGeomQueryArcGetSweepAngleArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryArcApi.isValid}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `arc` | {@linkcode ArcHandle} | The arc to query |\n */\nexport const PluginGeomQueryArcIsValidArgs = z.object({ arc: ArcHandle })\nexport type PluginGeomQueryArcIsValidArgs = z.infer<typeof PluginGeomQueryArcIsValidArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport { CircleHandle, Vec3Handle, Vec3Components } from \"../../../../handles\"\n\n/**\n * Circle-specific geometry queries — read closed-circle properties as plain values\n * (all-handle model, §11). A circle is a closed curve with its own namespace\n * (mirroring {@linkcode PluginGeomQueryArcApi}); it is NOT part of the shared\n * {@linkcode CurveHandle} line/arc surface.\n *\n * Every method takes an opaque {@linkcode CircleHandle} (plus a point handle where\n * relevant) and returns plain values — numbers, {@linkcode Vec3Components},\n * predicates as `boolean`. No query mutates its inputs.\n *\n * Accessed via `snaptrude.core.geom.query.circle`.\n */\nexport abstract class PluginGeomQueryCircleApi {\n constructor() {}\n\n /**\n * Read the radius of a circle.\n * @param circle The circle to query\n * @returns The radius as a `number`\n *\n * @examplePrompt What is the radius of this circle?\n * @examplePrompt Measure the radius of the circular column grid\n *\n * # Example\n * ```ts\n * const origin = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const up = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const circle = await snaptrude.core.geom.create.circle(origin, up, 5)\n * const radius = await snaptrude.core.geom.query.circle.getRadius(circle)\n * ```\n */\n public abstract getRadius(circle: CircleHandle): PluginApiReturn<number>\n\n /**\n * Read the centre point of a circle.\n * @param circle The circle to query\n * @returns The centre as {@linkcode Vec3Components}\n *\n * @examplePrompt Where is the centre of this circle?\n * @examplePrompt Find the center point of the circular courtyard outline\n *\n * # Example\n * ```ts\n * const origin = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const up = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const circle = await snaptrude.core.geom.create.circle(origin, up, 5)\n * const centre = await snaptrude.core.geom.query.circle.getCentre(circle)\n * console.log(centre.x, centre.y, centre.z)\n * ```\n */\n public abstract getCentre(circle: CircleHandle): PluginApiReturn<Vec3Components>\n\n /**\n * Read the axis (plane normal) of a circle.\n * @param circle The circle to query\n * @returns The axis as {@linkcode Vec3Components}\n *\n * # Example\n * ```ts\n * const origin = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const up = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const circle = await snaptrude.core.geom.create.circle(origin, up, 5)\n * const axis = await snaptrude.core.geom.query.circle.getAxis(circle)\n * console.log(axis.x, axis.y, axis.z)\n * ```\n */\n public abstract getAxis(circle: CircleHandle): PluginApiReturn<Vec3Components>\n\n /**\n * Read the length (circumference, `2πr`) of a circle.\n * @param circle The circle to query\n * @returns The circumference as a `number`\n *\n * @examplePrompt What is the circumference of this circle?\n * @examplePrompt How long is the circular path around the courtyard?\n *\n * # Example\n * ```ts\n * const origin = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const up = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const circle = await snaptrude.core.geom.create.circle(origin, up, 5)\n * const circumference = await snaptrude.core.geom.query.circle.getLength(circle)\n * ```\n */\n public abstract getLength(circle: CircleHandle): PluginApiReturn<number>\n\n /**\n * Check whether a circle is geometrically valid (non-degenerate axis + positive radius).\n * @param circle The circle to query\n * @returns `true` if the circle is valid, otherwise `false`\n *\n * # Example\n * ```ts\n * const origin = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const up = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const circle = await snaptrude.core.geom.create.circle(origin, up, 5)\n * const ok = await snaptrude.core.geom.query.circle.isValid(circle)\n * ```\n */\n public abstract isValid(circle: CircleHandle): PluginApiReturn<boolean>\n\n /**\n * Get the point on a circle nearest to an arbitrary point.\n * @param circle The circle to query\n * @param point The reference point\n * @returns The nearest point as {@linkcode Vec3Components}\n *\n * @examplePrompt Find the point on the circle closest to this column\n *\n * # Example\n * ```ts\n * const origin = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const up = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const circle = await snaptrude.core.geom.create.circle(origin, up, 5)\n * const target = await snaptrude.core.math.vec3.new(8, 0, 2)\n * const nearest = await snaptrude.core.geom.query.circle.getNearestPoint(circle, target)\n * console.log(nearest.x, nearest.y, nearest.z)\n * ```\n */\n public abstract getNearestPoint(\n circle: CircleHandle,\n point: Vec3Handle\n ): PluginApiReturn<Vec3Components>\n\n /**\n * Test whether a point lies on a circle.\n * @param circle The circle to query\n * @param point The point to test\n * @returns `true` if the point is on the circle, otherwise `false`\n *\n * @examplePrompt Check whether this point sits on the circle\n *\n * # Example\n * ```ts\n * const origin = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const up = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const circle = await snaptrude.core.geom.create.circle(origin, up, 5)\n * const pt = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const onCircle = await snaptrude.core.geom.query.circle.hasPoint(circle, pt)\n * ```\n */\n public abstract hasPoint(circle: CircleHandle, point: Vec3Handle): PluginApiReturn<boolean>\n\n /**\n * Get the tangent direction of a circle at a point on it.\n * @param circle The circle to query\n * @param point A point on the circle\n * @returns The tangent as {@linkcode Vec3Components}\n *\n * # Example\n * ```ts\n * const origin = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const up = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const circle = await snaptrude.core.geom.create.circle(origin, up, 5)\n * const pt = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const tangent = await snaptrude.core.geom.query.circle.getTangent(circle, pt)\n * console.log(tangent.x, tangent.y, tangent.z)\n * ```\n */\n public abstract getTangent(circle: CircleHandle, point: Vec3Handle): PluginApiReturn<Vec3Components>\n\n /**\n * Get the (in-plane, radial) normal direction of a circle at a point on it.\n * @param circle The circle to query\n * @param point A point on the circle\n * @returns The radial normal as {@linkcode Vec3Components}\n *\n * # Example\n * ```ts\n * const origin = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const up = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const circle = await snaptrude.core.geom.create.circle(origin, up, 5)\n * const pt = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const normal = await snaptrude.core.geom.query.circle.getNormal(circle, pt)\n * console.log(normal.x, normal.y, normal.z)\n * ```\n */\n public abstract getNormal(circle: CircleHandle, point: Vec3Handle): PluginApiReturn<Vec3Components>\n\n /**\n * Get the point reached by travelling a signed arc-distance along the circle from\n * a reference point that lies on it.\n * @param circle The circle to query\n * @param referencePoint A point on the circle to travel from\n * @param distance Arc distance to travel\n * @param forward Travel direction (optional; default forward)\n * @returns The resulting point as {@linkcode Vec3Components}\n *\n * @examplePrompt Mark a point 2 metres along the circle from this point\n *\n * # Example\n * ```ts\n * const origin = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const up = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const circle = await snaptrude.core.geom.create.circle(origin, up, 5)\n * const ref = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const ahead = await snaptrude.core.geom.query.circle.getPointAtDistance(circle, ref, 2)\n * const behind = await snaptrude.core.geom.query.circle.getPointAtDistance(circle, ref, 2, false)\n * ```\n */\n public abstract getPointAtDistance(\n circle: CircleHandle,\n referencePoint: Vec3Handle,\n distance: number,\n forward?: boolean\n ): PluginApiReturn<Vec3Components>\n\n /**\n * Test whether two circles are geometrically equal.\n * @param circleA First circle\n * @param circleB Second circle\n * @returns `true` if the circles are equal, otherwise `false`\n *\n * # Example\n * ```ts\n * const origin = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const up = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const a = await snaptrude.core.geom.create.circle(origin, up, 5)\n * const b = await snaptrude.core.geom.create.circle(origin, up, 3)\n * const same = await snaptrude.core.geom.query.circle.isEqual(a, b)\n * ```\n */\n public abstract isEqual(circleA: CircleHandle, circleB: CircleHandle): PluginApiReturn<boolean>\n\n /**\n * Sample a circle into an ordered list of points.\n * @param circle The circle to query\n * @param resolution Number of segments to sample (optional)\n * @returns The sampled points as {@linkcode Vec3Components}`[]`\n *\n * @examplePrompt Divide this circle into 12 equal points\n * @examplePrompt Get evenly spaced points around the circle to place columns\n *\n * # Example\n * ```ts\n * const origin = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const up = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const circle = await snaptrude.core.geom.create.circle(origin, up, 5)\n * const points = await snaptrude.core.geom.query.circle.listPoints(circle, 12)\n * for (const p of points) console.log(p.x, p.y, p.z)\n * ```\n */\n public abstract listPoints(circle: CircleHandle, resolution?: number): PluginApiReturn<Vec3Components[]>\n}\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCircleApi.getRadius}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `circle` | {@linkcode CircleHandle} | The circle to query |\n */\nexport const PluginGeomQueryCircleGetRadiusArgs = z.object({ circle: CircleHandle })\nexport type PluginGeomQueryCircleGetRadiusArgs = z.infer<typeof PluginGeomQueryCircleGetRadiusArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCircleApi.getCentre}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `circle` | {@linkcode CircleHandle} | The circle to query |\n */\nexport const PluginGeomQueryCircleGetCentreArgs = z.object({ circle: CircleHandle })\nexport type PluginGeomQueryCircleGetCentreArgs = z.infer<typeof PluginGeomQueryCircleGetCentreArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCircleApi.getAxis}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `circle` | {@linkcode CircleHandle} | The circle to query |\n */\nexport const PluginGeomQueryCircleGetAxisArgs = z.object({ circle: CircleHandle })\nexport type PluginGeomQueryCircleGetAxisArgs = z.infer<typeof PluginGeomQueryCircleGetAxisArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCircleApi.getLength}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `circle` | {@linkcode CircleHandle} | The circle to query |\n */\nexport const PluginGeomQueryCircleGetLengthArgs = z.object({ circle: CircleHandle })\nexport type PluginGeomQueryCircleGetLengthArgs = z.infer<typeof PluginGeomQueryCircleGetLengthArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCircleApi.isValid}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `circle` | {@linkcode CircleHandle} | The circle to query |\n */\nexport const PluginGeomQueryCircleIsValidArgs = z.object({ circle: CircleHandle })\nexport type PluginGeomQueryCircleIsValidArgs = z.infer<typeof PluginGeomQueryCircleIsValidArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCircleApi.getNearestPoint}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `circle` | {@linkcode CircleHandle} | The circle to query |\n * | `point` | {@linkcode Vec3Handle} | The reference point |\n */\nexport const PluginGeomQueryCircleGetNearestPointArgs = z.object({\n circle: CircleHandle,\n point: Vec3Handle,\n})\nexport type PluginGeomQueryCircleGetNearestPointArgs = z.infer<\n typeof PluginGeomQueryCircleGetNearestPointArgs\n>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCircleApi.hasPoint}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `circle` | {@linkcode CircleHandle} | The circle to query |\n * | `point` | {@linkcode Vec3Handle} | The point to test |\n */\nexport const PluginGeomQueryCircleHasPointArgs = z.object({\n circle: CircleHandle,\n point: Vec3Handle,\n})\nexport type PluginGeomQueryCircleHasPointArgs = z.infer<typeof PluginGeomQueryCircleHasPointArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCircleApi.getTangent}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `circle` | {@linkcode CircleHandle} | The circle to query |\n * | `point` | {@linkcode Vec3Handle} | A point on the circle |\n */\nexport const PluginGeomQueryCircleGetTangentArgs = z.object({\n circle: CircleHandle,\n point: Vec3Handle,\n})\nexport type PluginGeomQueryCircleGetTangentArgs = z.infer<typeof PluginGeomQueryCircleGetTangentArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCircleApi.getNormal}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `circle` | {@linkcode CircleHandle} | The circle to query |\n * | `point` | {@linkcode Vec3Handle} | A point on the circle |\n */\nexport const PluginGeomQueryCircleGetNormalArgs = z.object({\n circle: CircleHandle,\n point: Vec3Handle,\n})\nexport type PluginGeomQueryCircleGetNormalArgs = z.infer<typeof PluginGeomQueryCircleGetNormalArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCircleApi.getPointAtDistance}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `circle` | {@linkcode CircleHandle} | The circle to query |\n * | `referencePoint` | {@linkcode Vec3Handle} | A point on the circle to travel from |\n * | `distance` | `number` | Arc distance to travel |\n * | `forward` | `boolean` | Travel direction (optional; default forward) |\n */\nexport const PluginGeomQueryCircleGetPointAtDistanceArgs = z.object({\n circle: CircleHandle,\n referencePoint: Vec3Handle,\n distance: z.number(),\n forward: z.boolean().optional(),\n})\nexport type PluginGeomQueryCircleGetPointAtDistanceArgs = z.infer<\n typeof PluginGeomQueryCircleGetPointAtDistanceArgs\n>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCircleApi.isEqual}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `circleA` | {@linkcode CircleHandle} | First circle |\n * | `circleB` | {@linkcode CircleHandle} | Second circle |\n */\nexport const PluginGeomQueryCircleIsEqualArgs = z.object({\n circleA: CircleHandle,\n circleB: CircleHandle,\n})\nexport type PluginGeomQueryCircleIsEqualArgs = z.infer<typeof PluginGeomQueryCircleIsEqualArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryCircleApi.listPoints}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `circle` | {@linkcode CircleHandle} | The circle to query |\n * | `resolution` | `number` | Number of segments to sample (optional) |\n */\nexport const PluginGeomQueryCircleListPointsArgs = z.object({\n circle: CircleHandle,\n resolution: z.number().optional(),\n})\nexport type PluginGeomQueryCircleListPointsArgs = z.infer<typeof PluginGeomQueryCircleListPointsArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport {\n ProfileHandle,\n Vec3Handle,\n CurveHandle,\n Vec3Components,\n BBoxComponents,\n} from \"../../../../handles\"\n\n/**\n * Profile queries — read-only geometric questions about a profile (an ordered\n * loop or chain of curves) and the relationships between two profiles\n * (all-handle model, §11).\n *\n * Every method takes an opaque {@linkcode ProfileHandle} (plus point handles\n * {@linkcode Vec3Handle} where relevant) and returns plain values — points as\n * {@linkcode Vec3Components}, areas/perimeters/distances as `number`, predicates\n * as `boolean`, the bounding box as {@linkcode BBoxComponents}. Methods that\n * enumerate the profile's child curves return fresh {@linkcode CurveHandle}`[]`.\n * No query mutates its inputs.\n *\n * Accessed via `snaptrude.core.geom.query.profile`.\n */\nexport abstract class PluginGeomQueryProfileApi {\n constructor() {}\n\n /**\n * List the tessellated points along a profile.\n * @param profile The profile to query\n * @returns The profile points as {@linkcode Vec3Components}`[]`\n *\n * @examplePrompt Give me the corner coordinates of this shape\n * @examplePrompt List the vertex points along this outline\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const points = await snaptrude.core.geom.query.profile.listPoints(rect)\n * for (const p of points) console.log(p.x, p.y, p.z)\n * ```\n */\n public abstract listPoints(profile: ProfileHandle): PluginApiReturn<Vec3Components[]>\n\n /**\n * List the curves that make up a profile. Host API call — mints fresh handles.\n * @param profile The profile to query\n * @returns The profile's curves as {@linkcode CurveHandle}`[]`\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const curves = await snaptrude.core.geom.query.profile.listCurves(rect)\n * for (const c of curves) console.log(await snaptrude.core.geom.query.curve.getLength(c))\n * ```\n */\n public abstract listCurves(profile: ProfileHandle): PluginApiReturn<CurveHandle[]>\n\n /**\n * Get the (approximate) enclosed area of a profile.\n * @param profile The profile to query\n * @returns The area as a `number`\n *\n * @examplePrompt What is the area enclosed by this outline?\n * @examplePrompt Measure the area of this shape\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const area = await snaptrude.core.geom.query.profile.getArea(rect) // 12\n * ```\n */\n public abstract getArea(profile: ProfileHandle): PluginApiReturn<number>\n\n /**\n * Get the perimeter (total curve length) of a profile.\n * @param profile The profile to query\n * @returns The perimeter as a `number`\n *\n * @examplePrompt How long is the boundary of this shape in total?\n * @examplePrompt Measure the perimeter of this outline\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const perimeter = await snaptrude.core.geom.query.profile.getPerimeter(rect) // 14\n * ```\n */\n public abstract getPerimeter(profile: ProfileHandle): PluginApiReturn<number>\n\n /**\n * Get the axis-aligned bounding box of a profile.\n * @param profile The profile to query\n * @returns The bounding box as {@linkcode BBoxComponents}\n *\n * @examplePrompt How wide and long is this outline overall?\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const { min, max } = await snaptrude.core.geom.query.profile.getBoundingBox(rect)\n * console.log(max.x - min.x, max.z - min.z) // 4 3\n * ```\n */\n public abstract getBoundingBox(profile: ProfileHandle): PluginApiReturn<BBoxComponents>\n\n /**\n * Get the unit normal vector of a profile's plane.\n * @param profile The profile to query\n * @returns The normal as {@linkcode Vec3Components}, or `null` if the profile has no well-defined normal\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const normal = await snaptrude.core.geom.query.profile.getNormal(rect)\n * if (normal) console.log(normal.x, normal.y, normal.z)\n * ```\n */\n public abstract getNormal(profile: ProfileHandle): PluginApiReturn<Vec3Components | null>\n\n /**\n * Get the point on a profile nearest to an arbitrary point.\n * @param profile The profile to query\n * @param point The reference point\n * @returns The nearest point on the profile as {@linkcode Vec3Components}\n *\n * @examplePrompt Find the closest point on this outline to a given point\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const point = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const nearest = await snaptrude.core.geom.query.profile.getNearestPoint(rect, point)\n * console.log(nearest.x, nearest.y, nearest.z) // 2 0 0\n * ```\n */\n public abstract getNearestPoint(\n profile: ProfileHandle,\n point: Vec3Handle,\n ): PluginApiReturn<Vec3Components>\n\n /**\n * Test whether a point lies inside a profile.\n * @param profile The profile to query\n * @param point The point to test\n * @returns `true` if the point is inside the profile, otherwise `false`\n *\n * @examplePrompt Does this point fall inside the outline?\n * @examplePrompt Check if the column location lies within this room boundary\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const point = await snaptrude.core.math.vec3.new(1, 0, 1)\n * const inside = await snaptrude.core.geom.query.profile.hasPoint(rect, point) // true\n * ```\n */\n public abstract hasPoint(\n profile: ProfileHandle,\n point: Vec3Handle,\n ): PluginApiReturn<boolean>\n\n /**\n * Test whether a point lies on a profile's boundary.\n * @param profile The profile to query\n * @param point The point to test\n * @returns `true` if the point is on the boundary, otherwise `false`\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const point = await snaptrude.core.math.vec3.new(2, 0, 0)\n * const onEdge = await snaptrude.core.geom.query.profile.isOnBoundary(rect, point) // true\n * ```\n */\n public abstract isOnBoundary(\n profile: ProfileHandle,\n point: Vec3Handle,\n ): PluginApiReturn<boolean>\n\n /**\n * Test whether a profile forms a closed loop.\n * @param profile The profile to query\n * @returns `true` if the profile is closed, otherwise `false`\n *\n * @examplePrompt Is my sketch outline closed or still open?\n * @examplePrompt Check whether this boundary forms a complete loop\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const closed = await snaptrude.core.geom.query.profile.isClosed(rect) // true\n * ```\n */\n public abstract isClosed(profile: ProfileHandle): PluginApiReturn<boolean>\n\n /**\n * Test whether a profile is planar.\n * @param profile The profile to query\n * @returns `true` if the profile is planar, otherwise `false`\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const planar = await snaptrude.core.geom.query.profile.isPlanar(rect) // true\n * ```\n */\n public abstract isPlanar(profile: ProfileHandle): PluginApiReturn<boolean>\n\n /**\n * Test whether a profile is wound clockwise on the XZ plane.\n * @param profile The profile to query\n * @returns `true` if the profile is clockwise, otherwise `false`\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const clockwise = await snaptrude.core.geom.query.profile.isClockwise(rect)\n * ```\n */\n public abstract isClockwise(profile: ProfileHandle): PluginApiReturn<boolean>\n\n /**\n * Test whether a profile intersects itself.\n * @param profile The profile to query\n * @returns `true` if the profile is self-intersecting, otherwise `false`\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(2, 0, 2)\n * const c = await snaptrude.core.math.vec3.new(2, 0, 0)\n * const d = await snaptrude.core.math.vec3.new(0, 0, 2)\n * const bowtie = await snaptrude.core.geom.create.profileFromLinePoints([a, b, c, d])\n * const crossed = await snaptrude.core.geom.query.profile.isSelfIntersecting(bowtie) // true\n * ```\n */\n public abstract isSelfIntersecting(profile: ProfileHandle): PluginApiReturn<boolean>\n\n /**\n * Test whether a profile contains no curves.\n * @param profile The profile to query\n * @returns `true` if the profile is empty, otherwise `false`\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const empty = await snaptrude.core.geom.query.profile.isEmpty(rect) // false\n * ```\n */\n public abstract isEmpty(profile: ProfileHandle): PluginApiReturn<boolean>\n\n /**\n * Test whether two profiles are geometrically equal.\n * @param profileA First profile\n * @param profileB Second profile\n * @returns `true` if the profiles are equal, otherwise `false`\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.geom.create.profileRect(4, 3)\n * const b = await snaptrude.core.geom.create.profileRect(4, 3)\n * const same = await snaptrude.core.geom.query.profile.isEqual(a, b) // true\n * ```\n */\n public abstract isEqual(\n profileA: ProfileHandle,\n profileB: ProfileHandle,\n ): PluginApiReturn<boolean>\n}\n\n/**\n * Arguments for {@linkcode PluginGeomQueryProfileApi.listPoints}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to query |\n */\nexport const PluginGeomQueryProfileListPointsArgs = z.object({\n profile: ProfileHandle,\n})\n\nexport type PluginGeomQueryProfileListPointsArgs = z.infer<typeof PluginGeomQueryProfileListPointsArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryProfileApi.listCurves}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to query |\n */\nexport const PluginGeomQueryProfileListCurvesArgs = z.object({\n profile: ProfileHandle,\n})\n\nexport type PluginGeomQueryProfileListCurvesArgs = z.infer<typeof PluginGeomQueryProfileListCurvesArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryProfileApi.getArea}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to query |\n */\nexport const PluginGeomQueryProfileGetAreaArgs = z.object({\n profile: ProfileHandle,\n})\n\nexport type PluginGeomQueryProfileGetAreaArgs = z.infer<typeof PluginGeomQueryProfileGetAreaArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryProfileApi.getPerimeter}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to query |\n */\nexport const PluginGeomQueryProfileGetPerimeterArgs = z.object({\n profile: ProfileHandle,\n})\n\nexport type PluginGeomQueryProfileGetPerimeterArgs = z.infer<typeof PluginGeomQueryProfileGetPerimeterArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryProfileApi.getBoundingBox}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to query |\n */\nexport const PluginGeomQueryProfileGetBoundingBoxArgs = z.object({\n profile: ProfileHandle,\n})\n\nexport type PluginGeomQueryProfileGetBoundingBoxArgs = z.infer<typeof PluginGeomQueryProfileGetBoundingBoxArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryProfileApi.getNormal}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to query |\n */\nexport const PluginGeomQueryProfileGetNormalArgs = z.object({\n profile: ProfileHandle,\n})\n\nexport type PluginGeomQueryProfileGetNormalArgs = z.infer<typeof PluginGeomQueryProfileGetNormalArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryProfileApi.getNearestPoint}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to query |\n * | `point` | {@linkcode Vec3Handle} | The reference point |\n */\nexport const PluginGeomQueryProfileGetNearestPointArgs = z.object({\n profile: ProfileHandle,\n point: Vec3Handle,\n})\n\nexport type PluginGeomQueryProfileGetNearestPointArgs = z.infer<typeof PluginGeomQueryProfileGetNearestPointArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryProfileApi.hasPoint}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to query |\n * | `point` | {@linkcode Vec3Handle} | The point to test |\n */\nexport const PluginGeomQueryProfileHasPointArgs = z.object({\n profile: ProfileHandle,\n point: Vec3Handle,\n})\n\nexport type PluginGeomQueryProfileHasPointArgs = z.infer<typeof PluginGeomQueryProfileHasPointArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryProfileApi.isOnBoundary}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to query |\n * | `point` | {@linkcode Vec3Handle} | The point to test |\n */\nexport const PluginGeomQueryProfileIsOnBoundaryArgs = z.object({\n profile: ProfileHandle,\n point: Vec3Handle,\n})\n\nexport type PluginGeomQueryProfileIsOnBoundaryArgs = z.infer<typeof PluginGeomQueryProfileIsOnBoundaryArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryProfileApi.isClosed}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to query |\n */\nexport const PluginGeomQueryProfileIsClosedArgs = z.object({\n profile: ProfileHandle,\n})\n\nexport type PluginGeomQueryProfileIsClosedArgs = z.infer<typeof PluginGeomQueryProfileIsClosedArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryProfileApi.isPlanar}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to query |\n */\nexport const PluginGeomQueryProfileIsPlanarArgs = z.object({\n profile: ProfileHandle,\n})\n\nexport type PluginGeomQueryProfileIsPlanarArgs = z.infer<typeof PluginGeomQueryProfileIsPlanarArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryProfileApi.isClockwise}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to query |\n */\nexport const PluginGeomQueryProfileIsClockwiseArgs = z.object({\n profile: ProfileHandle,\n})\n\nexport type PluginGeomQueryProfileIsClockwiseArgs = z.infer<typeof PluginGeomQueryProfileIsClockwiseArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryProfileApi.isSelfIntersecting}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to query |\n */\nexport const PluginGeomQueryProfileIsSelfIntersectingArgs = z.object({\n profile: ProfileHandle,\n})\n\nexport type PluginGeomQueryProfileIsSelfIntersectingArgs = z.infer<typeof PluginGeomQueryProfileIsSelfIntersectingArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryProfileApi.isEmpty}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to query |\n */\nexport const PluginGeomQueryProfileIsEmptyArgs = z.object({\n profile: ProfileHandle,\n})\n\nexport type PluginGeomQueryProfileIsEmptyArgs = z.infer<typeof PluginGeomQueryProfileIsEmptyArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryProfileApi.isEqual}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profileA` | {@linkcode ProfileHandle} | First profile |\n * | `profileB` | {@linkcode ProfileHandle} | Second profile |\n */\nexport const PluginGeomQueryProfileIsEqualArgs = z.object({\n profileA: ProfileHandle,\n profileB: ProfileHandle,\n})\n\nexport type PluginGeomQueryProfileIsEqualArgs = z.infer<typeof PluginGeomQueryProfileIsEqualArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport {\n ContourHandle,\n ProfileHandle,\n Vec3Handle,\n Vec3Components,\n BBoxComponents,\n} from \"../../../../handles\"\n\n/**\n * Contour queries — read-only geometric questions about a contour (an outer\n * {@linkcode ProfileHandle} plus zero or more inner profiles that act as holes)\n * and the relationships between two contours (all-handle model, §11).\n *\n * Every method takes an opaque {@linkcode ContourHandle} (plus point handles\n * {@linkcode Vec3Handle} where relevant) and returns plain values — points as\n * {@linkcode Vec3Components}, areas/perimeters/counts as `number`, predicates as\n * `boolean`, the bounding box as {@linkcode BBoxComponents}. Methods that\n * enumerate the contour's child profiles return fresh {@linkcode ProfileHandle}s.\n * No query mutates its inputs.\n *\n * Accessed via `snaptrude.core.geom.query.contour`.\n */\nexport abstract class PluginGeomQueryContourApi {\n constructor() {}\n\n /**\n * Get the outer (boundary) profile of a contour. Host API call — mints a fresh handle.\n * @param contour The contour to query\n * @returns The outer profile as a {@linkcode ProfileHandle}, or `null` if the contour has no outer profile\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const outer = await snaptrude.core.geom.query.contour.getOuterProfile(contour)\n * if (outer) console.log(await snaptrude.core.geom.query.profile.getPerimeter(outer))\n * ```\n */\n public abstract getOuterProfile(\n contour: ContourHandle\n ): PluginApiReturn<ProfileHandle | null>\n\n /**\n * List the inner profiles (holes) of a contour. Host API call — mints fresh handles.\n * @param contour The contour to query\n * @returns The contour's holes as {@linkcode ProfileHandle}`[]`\n *\n * # Example\n * ```ts\n * const outer = await snaptrude.core.geom.create.profileRect(6, 4)\n * const hole = await snaptrude.core.geom.create.profileRect(2, 1)\n * const contour = await snaptrude.core.geom.create.contourFromProfiles(outer, [hole])\n * const holes = await snaptrude.core.geom.query.contour.listHoles(contour)\n * for (const h of holes) console.log(await snaptrude.core.geom.query.profile.getArea(h))\n * ```\n */\n public abstract listHoles(\n contour: ContourHandle\n ): PluginApiReturn<ProfileHandle[]>\n\n /**\n * Get the number of inner profiles (holes) in a contour.\n * @param contour The contour to query\n * @returns The hole count as a `number`\n *\n * @examplePrompt How many holes does this shape have?\n * @examplePrompt Count the openings in this slab outline\n *\n * # Example\n * ```ts\n * const outer = await snaptrude.core.geom.create.profileRect(6, 4)\n * const hole = await snaptrude.core.geom.create.profileRect(2, 1)\n * const contour = await snaptrude.core.geom.create.contourFromProfiles(outer, [hole])\n * const count = await snaptrude.core.geom.query.contour.getHoleCount(contour) // 1\n * ```\n */\n public abstract getHoleCount(\n contour: ContourHandle\n ): PluginApiReturn<number>\n\n /**\n * Get the (approximate) net enclosed area of a contour — the outer profile area\n * minus the area of every hole.\n * @param contour The contour to query\n * @returns The area as a `number`\n *\n * @examplePrompt What is the area of this shape?\n * @examplePrompt Calculate the usable area of this outline after subtracting its holes\n *\n * # Example\n * ```ts\n * const outer = await snaptrude.core.geom.create.profileRect(6, 4)\n * const hole = await snaptrude.core.geom.create.profileRect(2, 1)\n * const contour = await snaptrude.core.geom.create.contourFromProfiles(outer, [hole])\n * const area = await snaptrude.core.geom.query.contour.getArea(contour) // 24 - 2 = 22\n * ```\n */\n public abstract getArea(\n contour: ContourHandle\n ): PluginApiReturn<number>\n\n /**\n * Get the perimeter (total length of every curve) of a contour, including holes.\n * @param contour The contour to query\n * @returns The perimeter as a `number`\n *\n * @examplePrompt What is the perimeter of this shape?\n * @examplePrompt How long is the boundary of this outline, including its holes?\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const perimeter = await snaptrude.core.geom.query.contour.getPerimeter(contour) // 14\n * ```\n */\n public abstract getPerimeter(\n contour: ContourHandle\n ): PluginApiReturn<number>\n\n /**\n * Get the axis-aligned bounding box of a contour.\n * @param contour The contour to query\n * @returns The bounding box as {@linkcode BBoxComponents}\n *\n * @examplePrompt What are the overall extents of this shape?\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const { min, max } = await snaptrude.core.geom.query.contour.getBoundingBox(contour)\n * console.log(max.x - min.x, max.z - min.z) // 4 3\n * ```\n */\n public abstract getBoundingBox(\n contour: ContourHandle\n ): PluginApiReturn<BBoxComponents>\n\n /**\n * Get the unit normal vector of a contour's plane.\n * @param contour The contour to query\n * @returns The normal as {@linkcode Vec3Components}, or `null` if the contour has no well-defined normal\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const normal = await snaptrude.core.geom.query.contour.getNormal(contour)\n * if (normal) console.log(normal.x, normal.y, normal.z)\n * ```\n */\n public abstract getNormal(\n contour: ContourHandle\n ): PluginApiReturn<Vec3Components | null>\n\n /**\n * Get the point on a contour nearest to an arbitrary point.\n * @param contour The contour to query\n * @param point The reference point\n * @returns The nearest point on the contour as {@linkcode Vec3Components}\n *\n * @examplePrompt Find the point on this boundary closest to a given location\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const point = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const nearest = await snaptrude.core.geom.query.contour.getNearestPoint(contour, point)\n * console.log(nearest.x, nearest.y, nearest.z) // 2 0 0\n * ```\n */\n public abstract getNearestPoint(\n contour: ContourHandle,\n point: Vec3Handle\n ): PluginApiReturn<Vec3Components>\n\n /**\n * Test whether a point lies inside a contour (inside the outer profile and\n * outside every hole).\n * @param contour The contour to query\n * @param point The point to test\n * @returns `true` if the point is inside the contour, otherwise `false`\n *\n * @examplePrompt Is this point inside the shape?\n * @examplePrompt Check whether a location falls within this boundary but not in a hole\n *\n * # Example\n * ```ts\n * const outer = await snaptrude.core.geom.create.profileRect(6, 4)\n * const hole = await snaptrude.core.geom.create.profileRect(2, 1)\n * const contour = await snaptrude.core.geom.create.contourFromProfiles(outer, [hole])\n * const point = await snaptrude.core.math.vec3.new(2.5, 0, 1.5)\n * const inside = await snaptrude.core.geom.query.contour.hasPoint(contour, point) // true — outside the hole\n * ```\n */\n public abstract hasPoint(\n contour: ContourHandle,\n point: Vec3Handle\n ): PluginApiReturn<boolean>\n\n /**\n * Test whether a point lies on a contour's boundary (any of its curves).\n * @param contour The contour to query\n * @param point The point to test\n * @returns `true` if the point is on the boundary, otherwise `false`\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const point = await snaptrude.core.math.vec3.new(2, 0, 0)\n * const onEdge = await snaptrude.core.geom.query.contour.isOnBoundary(contour, point) // true\n * ```\n */\n public abstract isOnBoundary(\n contour: ContourHandle,\n point: Vec3Handle\n ): PluginApiReturn<boolean>\n\n /**\n * Test whether a contour's outer profile is wound clockwise on the XZ plane.\n * @param contour The contour to query\n * @returns `true` if the contour is clockwise, otherwise `false`\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const clockwise = await snaptrude.core.geom.query.contour.isClockwise(contour)\n * ```\n */\n public abstract isClockwise(\n contour: ContourHandle\n ): PluginApiReturn<boolean>\n\n /**\n * Test whether a contour is planar.\n * @param contour The contour to query\n * @returns `true` if the contour is planar, otherwise `false`\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const planar = await snaptrude.core.geom.query.contour.isPlanar(contour) // true\n * ```\n */\n public abstract isPlanar(\n contour: ContourHandle\n ): PluginApiReturn<boolean>\n\n /**\n * Test whether a contour is empty (its outer profile contains no curves).\n * @param contour The contour to query\n * @returns `true` if the contour is empty, otherwise `false`\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const empty = await snaptrude.core.geom.query.contour.isEmpty(contour) // false\n * ```\n */\n public abstract isEmpty(\n contour: ContourHandle\n ): PluginApiReturn<boolean>\n\n /**\n * Test whether a contour intersects itself.\n * @param contour The contour to query\n * @returns `true` if the contour is self-intersecting, otherwise `false`\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const crossed = await snaptrude.core.geom.query.contour.isSelfIntersecting(contour) // false\n * ```\n */\n public abstract isSelfIntersecting(\n contour: ContourHandle\n ): PluginApiReturn<boolean>\n\n /**\n * Test whether a contour's orientation is valid (outer profile and holes are\n * wound consistently).\n * @param contour The contour to query\n * @returns `true` if the orientation is valid, otherwise `false`\n *\n * # Example\n * ```ts\n * const outer = await snaptrude.core.geom.create.profileRect(6, 4)\n * const hole = await snaptrude.core.geom.create.profileRect(2, 1)\n * const contour = await snaptrude.core.geom.create.contourFromProfiles(outer, [hole])\n * const valid = await snaptrude.core.geom.query.contour.isOrientationValid(contour)\n * ```\n */\n public abstract isOrientationValid(\n contour: ContourHandle\n ): PluginApiReturn<boolean>\n\n /**\n * Test whether two contours are geometrically equal.\n * @param contourA First contour\n * @param contourB Second contour\n * @returns `true` if the contours are equal, otherwise `false`\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const a = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const b = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const same = await snaptrude.core.geom.query.contour.isEqual(a, b) // true\n * ```\n */\n public abstract isEqual(\n contourA: ContourHandle,\n contourB: ContourHandle\n ): PluginApiReturn<boolean>\n}\n\n/**\n * Arguments for {@linkcode PluginGeomQueryContourApi.getOuterProfile}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to query |\n */\nexport const PluginGeomQueryContourGetOuterProfileArgs = z.object({\n contour: ContourHandle,\n})\n\nexport type PluginGeomQueryContourGetOuterProfileArgs = z.infer<typeof PluginGeomQueryContourGetOuterProfileArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryContourApi.listHoles}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to query |\n */\nexport const PluginGeomQueryContourListHolesArgs = z.object({\n contour: ContourHandle,\n})\n\nexport type PluginGeomQueryContourListHolesArgs = z.infer<typeof PluginGeomQueryContourListHolesArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryContourApi.getHoleCount}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to query |\n */\nexport const PluginGeomQueryContourGetHoleCountArgs = z.object({\n contour: ContourHandle,\n})\n\nexport type PluginGeomQueryContourGetHoleCountArgs = z.infer<typeof PluginGeomQueryContourGetHoleCountArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryContourApi.getArea}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to query |\n */\nexport const PluginGeomQueryContourGetAreaArgs = z.object({\n contour: ContourHandle,\n})\n\nexport type PluginGeomQueryContourGetAreaArgs = z.infer<typeof PluginGeomQueryContourGetAreaArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryContourApi.getPerimeter}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to query |\n */\nexport const PluginGeomQueryContourGetPerimeterArgs = z.object({\n contour: ContourHandle,\n})\n\nexport type PluginGeomQueryContourGetPerimeterArgs = z.infer<typeof PluginGeomQueryContourGetPerimeterArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryContourApi.getBoundingBox}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to query |\n */\nexport const PluginGeomQueryContourGetBoundingBoxArgs = z.object({\n contour: ContourHandle,\n})\n\nexport type PluginGeomQueryContourGetBoundingBoxArgs = z.infer<typeof PluginGeomQueryContourGetBoundingBoxArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryContourApi.getNormal}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to query |\n */\nexport const PluginGeomQueryContourGetNormalArgs = z.object({\n contour: ContourHandle,\n})\n\nexport type PluginGeomQueryContourGetNormalArgs = z.infer<typeof PluginGeomQueryContourGetNormalArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryContourApi.getNearestPoint}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to query |\n * | `point` | {@linkcode Vec3Handle} | The reference point |\n */\nexport const PluginGeomQueryContourGetNearestPointArgs = z.object({\n contour: ContourHandle,\n point: Vec3Handle,\n})\n\nexport type PluginGeomQueryContourGetNearestPointArgs = z.infer<typeof PluginGeomQueryContourGetNearestPointArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryContourApi.hasPoint}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to query |\n * | `point` | {@linkcode Vec3Handle} | The point to test |\n */\nexport const PluginGeomQueryContourHasPointArgs = z.object({\n contour: ContourHandle,\n point: Vec3Handle,\n})\n\nexport type PluginGeomQueryContourHasPointArgs = z.infer<typeof PluginGeomQueryContourHasPointArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryContourApi.isOnBoundary}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to query |\n * | `point` | {@linkcode Vec3Handle} | The point to test |\n */\nexport const PluginGeomQueryContourIsOnBoundaryArgs = z.object({\n contour: ContourHandle,\n point: Vec3Handle,\n})\n\nexport type PluginGeomQueryContourIsOnBoundaryArgs = z.infer<typeof PluginGeomQueryContourIsOnBoundaryArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryContourApi.isClockwise}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to query |\n */\nexport const PluginGeomQueryContourIsClockwiseArgs = z.object({\n contour: ContourHandle,\n})\n\nexport type PluginGeomQueryContourIsClockwiseArgs = z.infer<typeof PluginGeomQueryContourIsClockwiseArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryContourApi.isPlanar}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to query |\n */\nexport const PluginGeomQueryContourIsPlanarArgs = z.object({\n contour: ContourHandle,\n})\n\nexport type PluginGeomQueryContourIsPlanarArgs = z.infer<typeof PluginGeomQueryContourIsPlanarArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryContourApi.isEmpty}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to query |\n */\nexport const PluginGeomQueryContourIsEmptyArgs = z.object({\n contour: ContourHandle,\n})\n\nexport type PluginGeomQueryContourIsEmptyArgs = z.infer<typeof PluginGeomQueryContourIsEmptyArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryContourApi.isSelfIntersecting}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to query |\n */\nexport const PluginGeomQueryContourIsSelfIntersectingArgs = z.object({\n contour: ContourHandle,\n})\n\nexport type PluginGeomQueryContourIsSelfIntersectingArgs = z.infer<typeof PluginGeomQueryContourIsSelfIntersectingArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryContourApi.isOrientationValid}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to query |\n */\nexport const PluginGeomQueryContourIsOrientationValidArgs = z.object({\n contour: ContourHandle,\n})\n\nexport type PluginGeomQueryContourIsOrientationValidArgs = z.infer<typeof PluginGeomQueryContourIsOrientationValidArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryContourApi.isEqual}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contourA` | {@linkcode ContourHandle} | First contour |\n * | `contourB` | {@linkcode ContourHandle} | Second contour |\n */\nexport const PluginGeomQueryContourIsEqualArgs = z.object({\n contourA: ContourHandle,\n contourB: ContourHandle,\n})\n\nexport type PluginGeomQueryContourIsEqualArgs = z.infer<typeof PluginGeomQueryContourIsEqualArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport {\n BrepHandle,\n FaceHandle,\n EdgeHandle,\n VertexHandle,\n HalfEdgeHandle,\n Vec3Components,\n BBoxComponents,\n} from \"../../../../handles\"\n\n/**\n * BREP queries — read the topology and geometry of a boundary-representation mesh\n * as plain values / child handles (all-handle model, §11). A {@linkcode BrepHandle}\n * is a session-ephemeral resource handle obtained from `design.query.geometry.getBrep`;\n * it goes stale on undo/redo/re-topologize.\n *\n * Enumerations mint fresh child topology handles ({@linkcode FaceHandle},\n * {@linkcode EdgeHandle}, {@linkcode VertexHandle}, {@linkcode HalfEdgeHandle}); the\n * registry dedupes by identity, so re-enumeration returns the same handles. Position\n * reads return {@linkcode Vec3Components}. No query mutates its inputs.\n *\n * Accessed via `snaptrude.core.geom.query.brep`.\n */\nexport abstract class PluginGeomQueryBrepApi {\n constructor() {}\n\n /**\n * List the faces of a brep.\n * @param brep The brep to query\n * @returns The faces as {@linkcode FaceHandle}`[]`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const faces = await snaptrude.core.geom.query.brep.listFaces(brep)\n * }\n * ```\n */\n public abstract listFaces(brep: BrepHandle): PluginApiReturn<FaceHandle[]>\n\n /**\n * List the edges of a brep.\n * @param brep The brep to query\n * @returns The edges as {@linkcode EdgeHandle}`[]`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const edges = await snaptrude.core.geom.query.brep.listEdges(brep)\n * }\n * ```\n */\n public abstract listEdges(brep: BrepHandle): PluginApiReturn<EdgeHandle[]>\n\n /**\n * List the vertices of a brep.\n * @param brep The brep to query\n * @returns The vertices as {@linkcode VertexHandle}`[]`\n *\n * # Example\n * ```ts\n * const [mass] = await snaptrude.design.query.listMasses()\n * const brep = await snaptrude.design.query.geometry.getBrep(mass)\n * if (brep) {\n * const vertices = await snaptrude.core.geom.query.brep.listVertices(brep)\n * }\n * ```\n */\n public abstract listVertices(brep: BrepHandle): PluginApiReturn<VertexHandle[]>\n\n /**\n * List the half-edges of a brep.\n * @param brep The brep to query\n * @returns The half-edges as {@linkcode HalfEdgeHandle}`[]`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const halfEdges = await snaptrude.core.geom.query.brep.listHalfEdges(brep)\n * }\n * ```\n */\n public abstract listHalfEdges(brep: BrepHandle): PluginApiReturn<HalfEdgeHandle[]>\n\n /**\n * Get the edge connecting two vertices (by their brep vertex indices).\n * @param brep The brep to query\n * @param vertexIndexA First vertex index\n * @param vertexIndexB Second vertex index\n * @returns The edge as an {@linkcode EdgeHandle}, or `null` if no such edge exists\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [va, vb] = await snaptrude.core.geom.query.brep.listVertices(brep)\n * const ia = await snaptrude.core.geom.query.vertex.getIndex(va)\n * const ib = await snaptrude.core.geom.query.vertex.getIndex(vb)\n * const edge = await snaptrude.core.geom.query.brep.getEdge(brep, ia, ib)\n * if (edge) console.log(\"those two vertices are connected\")\n * }\n * ```\n */\n public abstract getEdge(\n brep: BrepHandle,\n vertexIndexA: number,\n vertexIndexB: number\n ): PluginApiReturn<EdgeHandle | null>\n\n /**\n * Test whether an edge connects two vertices (by their brep vertex indices).\n * @param brep The brep to query\n * @param vertexIndexA First vertex index\n * @param vertexIndexB Second vertex index\n * @returns `true` if such an edge exists, otherwise `false`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * // brep vertex indices (get one from core.geom.query.vertex.getIndex)\n * const connected = await snaptrude.core.geom.query.brep.hasEdge(brep, 0, 1)\n * }\n * ```\n */\n public abstract hasEdge(\n brep: BrepHandle,\n vertexIndexA: number,\n vertexIndexB: number\n ): PluginApiReturn<boolean>\n\n /**\n * List every edge connecting two vertices (by their brep vertex indices).\n * @param brep The brep to query\n * @param vertexIndexA First vertex index\n * @param vertexIndexB Second vertex index\n * @returns The edges as {@linkcode EdgeHandle}`[]`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * // brep vertex indices (get one from core.geom.query.vertex.getIndex)\n * const edges = await snaptrude.core.geom.query.brep.listEdgesBetween(brep, 0, 1)\n * console.log(\"edges between vertices 0 and 1:\", edges.length)\n * }\n * ```\n */\n public abstract listEdgesBetween(\n brep: BrepHandle,\n vertexIndexA: number,\n vertexIndexB: number\n ): PluginApiReturn<EdgeHandle[]>\n\n /**\n * Read the exact curve of a brep edge as plain values — a discriminated\n * union on `type`. Pure read: no geometry kernel is loaded. Points and\n * directions are {@linkcode Vec3Components}; lengths and radii are raw\n * Babylon units.\n *\n * | `type` | Fields |\n * |---|---|\n * | `\"line\"` | `startPoint`, `endPoint`, `length` |\n * | `\"arc\"` | `startPoint`, `endPoint`, `centre`, `axis`, `radius`, `length` |\n * | `\"circle\"` | `centre`, `axis`, `radius`, `length` — a full-circle edge (e.g. a cylinder cap rim) |\n *\n * @param brep The brep the edge belongs to\n * @param edge The edge whose curve to read\n * @returns The curve data as a discriminated union on `type` (see table above)\n * @throws VALIDATION if the edge is not on `brep`\n *\n * @examplePrompt Read the radius of this curved edge\n * @examplePrompt Is this edge straight or an arc?\n * @examplePrompt Get the exact start and end points of this edge\n * @examplePrompt Measure the length of each edge of this mass\n *\n * # Example\n * ```ts\n * const [mass] = await snaptrude.design.query.listMasses()\n * const brep = await snaptrude.design.query.geometry.getBrep(mass)\n * if (brep) {\n * const edges = await snaptrude.core.geom.query.brep.listEdges(brep)\n * for (const edge of edges) {\n * const curve = await snaptrude.core.geom.query.brep.getEdgeCurve(brep, edge)\n * if (curve.type === \"arc\") console.log(\"arc radius:\", curve.radius)\n * }\n * }\n * ```\n */\n public abstract getEdgeCurve(\n brep: BrepHandle,\n edge: EdgeHandle\n ): PluginApiReturn<\n | { type: \"line\"; startPoint: Vec3Components; endPoint: Vec3Components; length: number }\n | {\n type: \"arc\"\n startPoint: Vec3Components\n endPoint: Vec3Components\n centre: Vec3Components\n axis: Vec3Components\n radius: number\n length: number\n }\n | {\n type: \"circle\"\n centre: Vec3Components\n axis: Vec3Components\n radius: number\n length: number\n }\n >\n\n /**\n * Get the position of a vertex within a brep.\n * @param brep The brep to query\n * @param vertex The vertex whose position to read\n * @returns The position as {@linkcode Vec3Components}\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [vertex] = await snaptrude.core.geom.query.brep.listVertices(brep)\n * const { x, y, z } = await snaptrude.core.geom.query.brep.getVertexPosition(brep, vertex)\n * }\n * ```\n */\n public abstract getVertexPosition(\n brep: BrepHandle,\n vertex: VertexHandle\n ): PluginApiReturn<Vec3Components>\n\n /**\n * List the positions of all vertices in a brep (ordered as {@linkcode PluginGeomQueryBrepApi.listVertices}).\n * @param brep The brep to query\n * @returns The positions as {@linkcode Vec3Components}`[]`\n *\n * @examplePrompt Get the coordinates of every corner of this shape\n * @examplePrompt Give me all the corner points of the selected mass as XYZ coordinates\n *\n * # Example\n * ```ts\n * const [mass] = await snaptrude.design.query.listMasses()\n * const brep = await snaptrude.design.query.geometry.getBrep(mass)\n * if (brep) {\n * const positions = await snaptrude.core.geom.query.brep.listVertexPositions(brep)\n * for (const p of positions) console.log(p.x, p.y, p.z)\n * }\n * ```\n */\n public abstract listVertexPositions(brep: BrepHandle): PluginApiReturn<Vec3Components[]>\n\n /**\n * Get the centroid (mean of all vertex positions) of a brep.\n * @param brep The brep to query\n * @returns The centroid as {@linkcode Vec3Components}\n *\n * @examplePrompt Find the centre point of this shape\n * @examplePrompt What is the centroid of the selected object?\n *\n * # Example\n * ```ts\n * const [mass] = await snaptrude.design.query.listMasses()\n * const brep = await snaptrude.design.query.geometry.getBrep(mass)\n * if (brep) {\n * const centroid = await snaptrude.core.geom.query.brep.getCentroid(brep)\n * console.log(centroid.x, centroid.y, centroid.z)\n * }\n * ```\n */\n public abstract getCentroid(brep: BrepHandle): PluginApiReturn<Vec3Components>\n\n /**\n * Get the number of faces in a brep.\n * @param brep The brep to query\n * @returns The face count as a `number`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const faceCount = await snaptrude.core.geom.query.brep.getFaceCount(brep)\n * }\n * ```\n */\n public abstract getFaceCount(brep: BrepHandle): PluginApiReturn<number>\n\n /**\n * Get the number of edges in a brep.\n * @param brep The brep to query\n * @returns The edge count as a `number`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const edgeCount = await snaptrude.core.geom.query.brep.getEdgeCount(brep)\n * }\n * ```\n */\n public abstract getEdgeCount(brep: BrepHandle): PluginApiReturn<number>\n\n /**\n * Get the number of vertices in a brep.\n * @param brep The brep to query\n * @returns The vertex count as a `number`\n *\n * # Example\n * ```ts\n * const [mass] = await snaptrude.design.query.listMasses()\n * const brep = await snaptrude.design.query.geometry.getBrep(mass)\n * if (brep) {\n * const vertexCount = await snaptrude.core.geom.query.brep.getVertexCount(brep)\n * }\n * ```\n */\n public abstract getVertexCount(brep: BrepHandle): PluginApiReturn<number>\n\n /**\n * Get the number of half-edges in a brep.\n * @param brep The brep to query\n * @returns The half-edge count as a `number`\n *\n * # Example\n * ```ts\n * const [mass] = await snaptrude.design.query.listMasses()\n * const brep = await snaptrude.design.query.geometry.getBrep(mass)\n * if (brep) {\n * const halfEdgeCount = await snaptrude.core.geom.query.brep.getHalfEdgeCount(brep)\n * }\n * ```\n */\n public abstract getHalfEdgeCount(brep: BrepHandle): PluginApiReturn<number>\n\n /**\n * Get the axis-aligned bounding box of a brep.\n * @param brep The brep to query\n * @returns The bounding box as {@linkcode BBoxComponents}\n *\n * @examplePrompt What is the bounding box of this shape?\n * @examplePrompt How big is this object overall — give me its min and max extents\n *\n * # Example\n * ```ts\n * const [mass] = await snaptrude.design.query.listMasses()\n * const brep = await snaptrude.design.query.geometry.getBrep(mass)\n * if (brep) {\n * const { min, max } = await snaptrude.core.geom.query.brep.getBoundingBox(brep)\n * console.log(\"size:\", max.x - min.x, max.y - min.y, max.z - min.z)\n * }\n * ```\n */\n public abstract getBoundingBox(brep: BrepHandle): PluginApiReturn<BBoxComponents>\n\n /**\n * Test whether two breps are geometrically equal.\n * @param brepA First brep\n * @param brepB Second brep\n * @returns `true` if the breps are equal, otherwise `false`\n *\n * @examplePrompt Are these two shapes geometrically identical?\n * @examplePrompt Check whether these two objects have the same geometry\n *\n * # Example\n * ```ts\n * const [a, b] = await snaptrude.design.query.listMasses()\n * const brepA = await snaptrude.design.query.geometry.getBrep(a)\n * const brepB = await snaptrude.design.query.geometry.getBrep(b)\n * if (brepA && brepB) {\n * const same = await snaptrude.core.geom.query.brep.isEqual(brepA, brepB)\n * }\n * ```\n */\n public abstract isEqual(brepA: BrepHandle, brepB: BrepHandle): PluginApiReturn<boolean>\n\n /**\n * Measures the minimum distance between two solids, with the closest witness\n * point on each. Touching or overlapping solids report a distance of 0 with\n * contact points.\n *\n * @param brepA First solid\n * @param brepB Second solid\n * @returns `{ distance, pointA, pointB }` — the minimum distance and the\n * closest points on `brepA` and `brepB` as {@linkcode Vec3Components}\n * @throws OPERATION_FAILED if the kernel cannot compute the distance\n *\n * @examplePrompt What is the clearance between these two masses?\n * @examplePrompt How far apart are the tower and the neighbouring building?\n * @examplePrompt Find the closest points between these two solids\n *\n * # Example\n * ```ts\n * const [a, b] = await snaptrude.design.query.listMasses()\n * const brepA = await snaptrude.design.query.geometry.getBrep(a)\n * const brepB = await snaptrude.design.query.geometry.getBrep(b)\n * if (brepA && brepB) {\n * const { distance, pointA, pointB } = await snaptrude.core.geom.query.brep.getDistance(brepA, brepB)\n * console.log(\"clearance:\", distance)\n * }\n * ```\n */\n public abstract getDistance(\n brepA: BrepHandle,\n brepB: BrepHandle\n ): PluginApiReturn<{ distance: number; pointA: Vec3Components; pointB: Vec3Components }>\n}\n\nconst brepArg = z.object({ brep: BrepHandle })\nconst brepEdgeArg = z.object({\n brep: BrepHandle,\n vertexIndexA: z.number(),\n vertexIndexB: z.number(),\n})\n\n/** Arguments for {@linkcode PluginGeomQueryBrepApi.listFaces}. `{ brep: BrepHandle }` */\nexport const PluginGeomQueryBrepListFacesArgs = brepArg\nexport type PluginGeomQueryBrepListFacesArgs = z.infer<typeof PluginGeomQueryBrepListFacesArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryBrepApi.listEdges}. `{ brep: BrepHandle }` */\nexport const PluginGeomQueryBrepListEdgesArgs = brepArg\nexport type PluginGeomQueryBrepListEdgesArgs = z.infer<typeof PluginGeomQueryBrepListEdgesArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryBrepApi.listVertices}. `{ brep: BrepHandle }` */\nexport const PluginGeomQueryBrepListVerticesArgs = brepArg\nexport type PluginGeomQueryBrepListVerticesArgs = z.infer<typeof PluginGeomQueryBrepListVerticesArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryBrepApi.listHalfEdges}. `{ brep: BrepHandle }` */\nexport const PluginGeomQueryBrepListHalfEdgesArgs = brepArg\nexport type PluginGeomQueryBrepListHalfEdgesArgs = z.infer<\n typeof PluginGeomQueryBrepListHalfEdgesArgs\n>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryBrepApi.getEdge}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `brep` | {@linkcode BrepHandle} | The brep to query |\n * | `vertexIndexA` | `number` | First vertex index |\n * | `vertexIndexB` | `number` | Second vertex index |\n */\nexport const PluginGeomQueryBrepGetEdgeArgs = brepEdgeArg\nexport type PluginGeomQueryBrepGetEdgeArgs = z.infer<typeof PluginGeomQueryBrepGetEdgeArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryBrepApi.hasEdge}. `{ brep, vertexIndexA, vertexIndexB }` */\nexport const PluginGeomQueryBrepHasEdgeArgs = brepEdgeArg\nexport type PluginGeomQueryBrepHasEdgeArgs = z.infer<typeof PluginGeomQueryBrepHasEdgeArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryBrepApi.listEdgesBetween}. `{ brep, vertexIndexA, vertexIndexB }` */\nexport const PluginGeomQueryBrepListEdgesBetweenArgs = brepEdgeArg\nexport type PluginGeomQueryBrepListEdgesBetweenArgs = z.infer<\n typeof PluginGeomQueryBrepListEdgesBetweenArgs\n>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryBrepApi.getEdgeCurve}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `brep` | {@linkcode BrepHandle} | The brep the edge belongs to |\n * | `edge` | {@linkcode EdgeHandle} | The edge whose curve to read |\n */\nexport const PluginGeomQueryBrepGetEdgeCurveArgs = z.object({\n brep: BrepHandle,\n edge: EdgeHandle,\n})\nexport type PluginGeomQueryBrepGetEdgeCurveArgs = z.infer<\n typeof PluginGeomQueryBrepGetEdgeCurveArgs\n>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryBrepApi.getVertexPosition}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `brep` | {@linkcode BrepHandle} | The brep to query |\n * | `vertex` | {@linkcode VertexHandle} | The vertex whose position to read |\n */\nexport const PluginGeomQueryBrepGetVertexPositionArgs = z.object({\n brep: BrepHandle,\n vertex: VertexHandle,\n})\nexport type PluginGeomQueryBrepGetVertexPositionArgs = z.infer<\n typeof PluginGeomQueryBrepGetVertexPositionArgs\n>\n\n/** Arguments for {@linkcode PluginGeomQueryBrepApi.listVertexPositions}. `{ brep: BrepHandle }` */\nexport const PluginGeomQueryBrepListVertexPositionsArgs = brepArg\nexport type PluginGeomQueryBrepListVertexPositionsArgs = z.infer<\n typeof PluginGeomQueryBrepListVertexPositionsArgs\n>\n\n/** Arguments for {@linkcode PluginGeomQueryBrepApi.getCentroid}. `{ brep: BrepHandle }` */\nexport const PluginGeomQueryBrepGetCentroidArgs = brepArg\nexport type PluginGeomQueryBrepGetCentroidArgs = z.infer<typeof PluginGeomQueryBrepGetCentroidArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryBrepApi.getFaceCount}. `{ brep: BrepHandle }` */\nexport const PluginGeomQueryBrepGetFaceCountArgs = brepArg\nexport type PluginGeomQueryBrepGetFaceCountArgs = z.infer<typeof PluginGeomQueryBrepGetFaceCountArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryBrepApi.getEdgeCount}. `{ brep: BrepHandle }` */\nexport const PluginGeomQueryBrepGetEdgeCountArgs = brepArg\nexport type PluginGeomQueryBrepGetEdgeCountArgs = z.infer<typeof PluginGeomQueryBrepGetEdgeCountArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryBrepApi.getVertexCount}. `{ brep: BrepHandle }` */\nexport const PluginGeomQueryBrepGetVertexCountArgs = brepArg\nexport type PluginGeomQueryBrepGetVertexCountArgs = z.infer<\n typeof PluginGeomQueryBrepGetVertexCountArgs\n>\n\n/** Arguments for {@linkcode PluginGeomQueryBrepApi.getHalfEdgeCount}. `{ brep: BrepHandle }` */\nexport const PluginGeomQueryBrepGetHalfEdgeCountArgs = brepArg\nexport type PluginGeomQueryBrepGetHalfEdgeCountArgs = z.infer<\n typeof PluginGeomQueryBrepGetHalfEdgeCountArgs\n>\n\n/** Arguments for {@linkcode PluginGeomQueryBrepApi.getBoundingBox}. `{ brep: BrepHandle }` */\nexport const PluginGeomQueryBrepGetBoundingBoxArgs = brepArg\nexport type PluginGeomQueryBrepGetBoundingBoxArgs = z.infer<\n typeof PluginGeomQueryBrepGetBoundingBoxArgs\n>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryBrepApi.isEqual}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `brepA` | {@linkcode BrepHandle} | First brep |\n * | `brepB` | {@linkcode BrepHandle} | Second brep |\n */\nexport const PluginGeomQueryBrepIsEqualArgs = z.object({\n brepA: BrepHandle,\n brepB: BrepHandle,\n})\nexport type PluginGeomQueryBrepIsEqualArgs = z.infer<typeof PluginGeomQueryBrepIsEqualArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryBrepApi.getDistance}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `brepA` | {@linkcode BrepHandle} | First solid |\n * | `brepB` | {@linkcode BrepHandle} | Second solid |\n */\nexport const PluginGeomQueryBrepGetDistanceArgs = z.object({\n brepA: BrepHandle,\n brepB: BrepHandle,\n})\nexport type PluginGeomQueryBrepGetDistanceArgs = z.infer<typeof PluginGeomQueryBrepGetDistanceArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport {\n BrepHandle,\n FaceHandle,\n EdgeHandle,\n VertexHandle,\n HalfEdgeHandle,\n Vec3Components,\n} from \"../../../../handles\"\n\n/**\n * Face queries — read the topology and boundary geometry of a single BREP face\n * (all-handle model, §11). A {@linkcode FaceHandle} is minted by walking from a\n * {@linkcode BrepHandle}; it is session-ephemeral and goes stale on\n * undo/redo/re-topologize.\n *\n * Traversals mint fresh child topology handles; position reads return\n * {@linkcode Vec3Components}. Holes are modelled per-loop: {@linkcode PluginGeomQueryFaceApi.listHoles}\n * returns one seed half-edge per hole, and {@linkcode PluginGeomQueryFaceApi.listHoleHalfEdges}\n * returns the full (flat) half-edge loop of one hole by index. No query mutates its inputs.\n *\n * Accessed via `snaptrude.core.geom.query.face`.\n */\nexport abstract class PluginGeomQueryFaceApi {\n constructor() {}\n\n /**\n * Get the index of a face within its brep.\n * @param face The face to query\n * @returns The index as a `number` (`-1` if unindexed)\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [face] = await snaptrude.core.geom.query.brep.listFaces(brep)\n * const index = await snaptrude.core.geom.query.face.getIndex(face)\n * }\n * ```\n */\n public abstract getIndex(face: FaceHandle): PluginApiReturn<number>\n\n /**\n * Get the raw (unmapped) material index of a face.\n * @param face The face to query\n * @returns The material index as a `number`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [face] = await snaptrude.core.geom.query.brep.listFaces(brep)\n * const materialIndex = await snaptrude.core.geom.query.face.getMaterialIndex(face)\n * }\n * ```\n */\n public abstract getMaterialIndex(face: FaceHandle): PluginApiReturn<number>\n\n /**\n * Get a seed half-edge of a face's outer loop.\n * @param face The face to query\n * @returns The half-edge as a {@linkcode HalfEdgeHandle}, or `null` for a degenerate face\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [face] = await snaptrude.core.geom.query.brep.listFaces(brep)\n * const seed = await snaptrude.core.geom.query.face.getHalfEdge(face)\n * if (seed) {\n * const next = await snaptrude.core.geom.query.halfedge.getNext(seed)\n * }\n * }\n * ```\n */\n public abstract getHalfEdge(face: FaceHandle): PluginApiReturn<HalfEdgeHandle | null>\n\n /**\n * List the edges of a face's outer loop.\n * @param face The face to query\n * @returns The edges as {@linkcode EdgeHandle}`[]`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [face] = await snaptrude.core.geom.query.brep.listFaces(brep)\n * const edges = await snaptrude.core.geom.query.face.listEdges(face)\n * }\n * ```\n */\n public abstract listEdges(face: FaceHandle): PluginApiReturn<EdgeHandle[]>\n\n /**\n * List the half-edges of a face's outer loop.\n * @param face The face to query\n * @returns The half-edges as {@linkcode HalfEdgeHandle}`[]`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [face] = await snaptrude.core.geom.query.brep.listFaces(brep)\n * const halfEdges = await snaptrude.core.geom.query.face.listHalfEdges(face)\n * }\n * ```\n */\n public abstract listHalfEdges(face: FaceHandle): PluginApiReturn<HalfEdgeHandle[]>\n\n /**\n * List the vertices of a face's outer loop.\n * @param face The face to query\n * @returns The vertices as {@linkcode VertexHandle}`[]`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [face] = await snaptrude.core.geom.query.brep.listFaces(brep)\n * const vertices = await snaptrude.core.geom.query.face.listVertices(face)\n * }\n * ```\n */\n public abstract listVertices(face: FaceHandle): PluginApiReturn<VertexHandle[]>\n\n /**\n * List a face's holes as one seed half-edge per hole loop.\n * @param face The face to query\n * @returns The hole seeds as {@linkcode HalfEdgeHandle}`[]`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [face] = await snaptrude.core.geom.query.brep.listFaces(brep)\n * const holeSeeds = await snaptrude.core.geom.query.face.listHoles(face)\n * console.log(\"holes in this face:\", holeSeeds.length)\n * }\n * ```\n */\n public abstract listHoles(face: FaceHandle): PluginApiReturn<HalfEdgeHandle[]>\n\n /**\n * Get the number of holes in a face.\n * @param face The face to query\n * @returns The hole count as a `number`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [face] = await snaptrude.core.geom.query.brep.listFaces(brep)\n * const holeCount = await snaptrude.core.geom.query.face.getHoleCount(face)\n * }\n * ```\n */\n public abstract getHoleCount(face: FaceHandle): PluginApiReturn<number>\n\n /**\n * List the half-edge loop of one hole of a face (by hole index).\n * @param face The face to query\n * @param holeIndex Index of the hole (`0..getHoleCount-1`)\n * @returns The hole's half-edges as {@linkcode HalfEdgeHandle}`[]` (`[]` if the index is out of range)\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [face] = await snaptrude.core.geom.query.brep.listFaces(brep)\n * const holeCount = await snaptrude.core.geom.query.face.getHoleCount(face)\n * if (holeCount > 0) {\n * const loop = await snaptrude.core.geom.query.face.listHoleHalfEdges(face, 0)\n * }\n * }\n * ```\n */\n public abstract listHoleHalfEdges(\n face: FaceHandle,\n holeIndex: number,\n ): PluginApiReturn<HalfEdgeHandle[]>\n\n /**\n * Test whether a face has any holes.\n * @param face The face to query\n * @returns `true` if the face has at least one hole, otherwise `false`\n *\n * @examplePrompt Does this face have any holes cut into it?\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [face] = await snaptrude.core.geom.query.brep.listFaces(brep)\n * const pierced = await snaptrude.core.geom.query.face.hasHoles(face)\n * }\n * ```\n */\n public abstract hasHoles(face: FaceHandle): PluginApiReturn<boolean>\n\n /**\n * Get the seed half-edge of the hole loop containing a given half-edge.\n * @param face The face to query\n * @param halfEdge The half-edge to locate\n * @returns The hole seed as a {@linkcode HalfEdgeHandle}, or `null` if not in any hole\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [face] = await snaptrude.core.geom.query.brep.listFaces(brep)\n * const [he] = await snaptrude.core.geom.query.face.listHoleHalfEdges(face, 0)\n * if (he) {\n * const seed = await snaptrude.core.geom.query.face.getHoleContainingHalfEdge(face, he)\n * }\n * }\n * ```\n */\n public abstract getHoleContainingHalfEdge(\n face: FaceHandle,\n halfEdge: HalfEdgeHandle,\n ): PluginApiReturn<HalfEdgeHandle | null>\n\n /**\n * List the positions of a face's outer-loop vertices (needs the parent brep for coordinates).\n * @param face The face to query\n * @param brep The parent brep (source of vertex coordinates)\n * @returns The positions as {@linkcode Vec3Components}`[]`\n *\n * @examplePrompt What are the corner coordinates of this face?\n * @examplePrompt Get the boundary points of the selected face\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [face] = await snaptrude.core.geom.query.brep.listFaces(brep)\n * const corners = await snaptrude.core.geom.query.face.listVertexPositions(face, brep)\n * for (const p of corners) console.log(p.x, p.y, p.z)\n * }\n * ```\n */\n public abstract listVertexPositions(\n face: FaceHandle,\n brep: BrepHandle,\n ): PluginApiReturn<Vec3Components[]>\n\n /**\n * List the tessellated boundary points of a face's outer loop (arcs sampled,\n * sense-corrected). Not cardinality-equal to {@linkcode PluginGeomQueryFaceApi.listVertexPositions}.\n * @param face The face to query\n * @returns The boundary points as {@linkcode Vec3Components}`[]`\n *\n * @examplePrompt Trace the outline of this face including its curved edges\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [face] = await snaptrude.core.geom.query.brep.listFaces(brep)\n * const outline = await snaptrude.core.geom.query.face.listVertexPositionsWithCurves(face)\n * }\n * ```\n */\n public abstract listVertexPositionsWithCurves(\n face: FaceHandle,\n ): PluginApiReturn<Vec3Components[]>\n\n /**\n * List the faces adjacent to a face (sharing an edge). Boundary edges are skipped.\n * @param face The face to query\n * @returns The adjacent faces as {@linkcode FaceHandle}`[]`\n *\n * @examplePrompt Which faces are next to this one?\n * @examplePrompt Find the faces that share an edge with the selected face\n *\n * # Example\n * ```ts\n * const [mass] = await snaptrude.design.query.listMasses()\n * const brep = await snaptrude.design.query.geometry.getBrep(mass)\n * if (brep) {\n * const [face] = await snaptrude.core.geom.query.brep.listFaces(brep)\n * const neighbours = await snaptrude.core.geom.query.face.listAdjacentFaces(face)\n * }\n * ```\n */\n public abstract listAdjacentFaces(face: FaceHandle): PluginApiReturn<FaceHandle[]>\n}\n\nconst faceArg = z.object({ face: FaceHandle })\n\n/** Arguments for {@linkcode PluginGeomQueryFaceApi.getIndex}. `{ face: FaceHandle }` */\nexport const PluginGeomQueryFaceGetIndexArgs = faceArg\nexport type PluginGeomQueryFaceGetIndexArgs = z.infer<typeof PluginGeomQueryFaceGetIndexArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryFaceApi.getMaterialIndex}. `{ face: FaceHandle }` */\nexport const PluginGeomQueryFaceGetMaterialIndexArgs = faceArg\nexport type PluginGeomQueryFaceGetMaterialIndexArgs = z.infer<\n typeof PluginGeomQueryFaceGetMaterialIndexArgs\n>\n\n/** Arguments for {@linkcode PluginGeomQueryFaceApi.getHalfEdge}. `{ face: FaceHandle }` */\nexport const PluginGeomQueryFaceGetHalfEdgeArgs = faceArg\nexport type PluginGeomQueryFaceGetHalfEdgeArgs = z.infer<typeof PluginGeomQueryFaceGetHalfEdgeArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryFaceApi.listEdges}. `{ face: FaceHandle }` */\nexport const PluginGeomQueryFaceListEdgesArgs = faceArg\nexport type PluginGeomQueryFaceListEdgesArgs = z.infer<typeof PluginGeomQueryFaceListEdgesArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryFaceApi.listHalfEdges}. `{ face: FaceHandle }` */\nexport const PluginGeomQueryFaceListHalfEdgesArgs = faceArg\nexport type PluginGeomQueryFaceListHalfEdgesArgs = z.infer<\n typeof PluginGeomQueryFaceListHalfEdgesArgs\n>\n\n/** Arguments for {@linkcode PluginGeomQueryFaceApi.listVertices}. `{ face: FaceHandle }` */\nexport const PluginGeomQueryFaceListVerticesArgs = faceArg\nexport type PluginGeomQueryFaceListVerticesArgs = z.infer<typeof PluginGeomQueryFaceListVerticesArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryFaceApi.listHoles}. `{ face: FaceHandle }` */\nexport const PluginGeomQueryFaceListHolesArgs = faceArg\nexport type PluginGeomQueryFaceListHolesArgs = z.infer<typeof PluginGeomQueryFaceListHolesArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryFaceApi.getHoleCount}. `{ face: FaceHandle }` */\nexport const PluginGeomQueryFaceGetHoleCountArgs = faceArg\nexport type PluginGeomQueryFaceGetHoleCountArgs = z.infer<typeof PluginGeomQueryFaceGetHoleCountArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryFaceApi.listHoleHalfEdges}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `face` | {@linkcode FaceHandle} | The face to query |\n * | `holeIndex` | `number` | Index of the hole (`0..getHoleCount-1`) |\n */\nexport const PluginGeomQueryFaceListHoleHalfEdgesArgs = z.object({\n face: FaceHandle,\n holeIndex: z.number(),\n})\nexport type PluginGeomQueryFaceListHoleHalfEdgesArgs = z.infer<\n typeof PluginGeomQueryFaceListHoleHalfEdgesArgs\n>\n\n/** Arguments for {@linkcode PluginGeomQueryFaceApi.hasHoles}. `{ face: FaceHandle }` */\nexport const PluginGeomQueryFaceHasHolesArgs = faceArg\nexport type PluginGeomQueryFaceHasHolesArgs = z.infer<typeof PluginGeomQueryFaceHasHolesArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryFaceApi.getHoleContainingHalfEdge}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `face` | {@linkcode FaceHandle} | The face to query |\n * | `halfEdge` | {@linkcode HalfEdgeHandle} | The half-edge to locate |\n */\nexport const PluginGeomQueryFaceGetHoleContainingHalfEdgeArgs = z.object({\n face: FaceHandle,\n halfEdge: HalfEdgeHandle,\n})\nexport type PluginGeomQueryFaceGetHoleContainingHalfEdgeArgs = z.infer<\n typeof PluginGeomQueryFaceGetHoleContainingHalfEdgeArgs\n>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryFaceApi.listVertexPositions}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `face` | {@linkcode FaceHandle} | The face to query |\n * | `brep` | {@linkcode BrepHandle} | The parent brep (source of vertex coordinates) |\n */\nexport const PluginGeomQueryFaceListVertexPositionsArgs = z.object({\n face: FaceHandle,\n brep: BrepHandle,\n})\nexport type PluginGeomQueryFaceListVertexPositionsArgs = z.infer<\n typeof PluginGeomQueryFaceListVertexPositionsArgs\n>\n\n/** Arguments for {@linkcode PluginGeomQueryFaceApi.listVertexPositionsWithCurves}. `{ face: FaceHandle }` */\nexport const PluginGeomQueryFaceListVertexPositionsWithCurvesArgs = faceArg\nexport type PluginGeomQueryFaceListVertexPositionsWithCurvesArgs = z.infer<\n typeof PluginGeomQueryFaceListVertexPositionsWithCurvesArgs\n>\n\n/** Arguments for {@linkcode PluginGeomQueryFaceApi.listAdjacentFaces}. `{ face: FaceHandle }` */\nexport const PluginGeomQueryFaceListAdjacentFacesArgs = faceArg\nexport type PluginGeomQueryFaceListAdjacentFacesArgs = z.infer<\n typeof PluginGeomQueryFaceListAdjacentFacesArgs\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport { EdgeHandle, VertexHandle, HalfEdgeHandle, FaceHandle, CurveHandle } from \"../../../../handles\"\n\n/**\n * Edge queries — read a single BREP edge's topology and geometry (all-handle model,\n * §11). An {@linkcode EdgeHandle} is minted by walking from a brep/face/vertex handle;\n * it is session-ephemeral and goes stale on undo/redo/re-topologize.\n *\n * Accessed via `snaptrude.core.geom.query.edge`.\n */\nexport abstract class PluginGeomQueryEdgeApi {\n constructor() {}\n\n /**\n * Get the index of an edge within its brep.\n * @param edge The edge to query\n * @returns The index as a `number` (`-1` if unindexed)\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [edge] = await snaptrude.core.geom.query.brep.listEdges(brep)\n * const index = await snaptrude.core.geom.query.edge.getIndex(edge)\n * }\n * ```\n */\n public abstract getIndex(edge: EdgeHandle): PluginApiReturn<number>\n\n /**\n * Get the primary half-edge of an edge.\n * @param edge The edge to query\n * @returns The half-edge as a {@linkcode HalfEdgeHandle}, or `null` if uninitialized\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [edge] = await snaptrude.core.geom.query.brep.listEdges(brep)\n * const halfEdge = await snaptrude.core.geom.query.edge.getHalfEdge(edge)\n * if (halfEdge) {\n * const face = await snaptrude.core.geom.query.halfedge.getFace(halfEdge)\n * }\n * }\n * ```\n */\n public abstract getHalfEdge(edge: EdgeHandle): PluginApiReturn<HalfEdgeHandle | null>\n\n /**\n * List the two endpoint vertices of an edge.\n * @param edge The edge to query\n * @returns The endpoints as {@linkcode VertexHandle}`[]`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [edge] = await snaptrude.core.geom.query.brep.listEdges(brep)\n * const [a, b] = await snaptrude.core.geom.query.edge.listVertices(edge)\n * const start = await snaptrude.core.geom.query.vertex.getPosition(brep, a)\n * const end = await snaptrude.core.geom.query.vertex.getPosition(brep, b)\n * }\n * ```\n */\n public abstract listVertices(edge: EdgeHandle): PluginApiReturn<VertexHandle[]>\n\n /**\n * Get the underlying curve geometry of an edge (a line or arc).\n * @param edge The edge to query\n * @returns The curve as a {@linkcode CurveHandle}, or `null` if it is a circle or unset\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [edge] = await snaptrude.core.geom.query.brep.listEdges(brep)\n * const curve = await snaptrude.core.geom.query.edge.getCurve(edge)\n * if (curve) {\n * const length = await snaptrude.core.geom.query.curve.getLength(curve)\n * }\n * }\n * ```\n */\n public abstract getCurve(edge: EdgeHandle): PluginApiReturn<CurveHandle | null>\n\n /**\n * List the faces bordering an edge (one or two).\n * @param edge The edge to query\n * @returns The bordering faces as {@linkcode FaceHandle}`[]`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [edge] = await snaptrude.core.geom.query.brep.listEdges(brep)\n * const faces = await snaptrude.core.geom.query.edge.listFaces(edge)\n * console.log(faces.length === 1 ? \"boundary edge\" : \"interior edge\")\n * }\n * ```\n */\n public abstract listFaces(edge: EdgeHandle): PluginApiReturn<FaceHandle[]>\n}\n\nconst edgeArg = z.object({ edge: EdgeHandle })\n\n/** Arguments for {@linkcode PluginGeomQueryEdgeApi.getIndex}. `{ edge: EdgeHandle }` */\nexport const PluginGeomQueryEdgeGetIndexArgs = edgeArg\nexport type PluginGeomQueryEdgeGetIndexArgs = z.infer<typeof PluginGeomQueryEdgeGetIndexArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryEdgeApi.getHalfEdge}. `{ edge: EdgeHandle }` */\nexport const PluginGeomQueryEdgeGetHalfEdgeArgs = edgeArg\nexport type PluginGeomQueryEdgeGetHalfEdgeArgs = z.infer<typeof PluginGeomQueryEdgeGetHalfEdgeArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryEdgeApi.listVertices}. `{ edge: EdgeHandle }` */\nexport const PluginGeomQueryEdgeListVerticesArgs = edgeArg\nexport type PluginGeomQueryEdgeListVerticesArgs = z.infer<typeof PluginGeomQueryEdgeListVerticesArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryEdgeApi.getCurve}. `{ edge: EdgeHandle }` */\nexport const PluginGeomQueryEdgeGetCurveArgs = edgeArg\nexport type PluginGeomQueryEdgeGetCurveArgs = z.infer<typeof PluginGeomQueryEdgeGetCurveArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryEdgeApi.listFaces}. `{ edge: EdgeHandle }` */\nexport const PluginGeomQueryEdgeListFacesArgs = edgeArg\nexport type PluginGeomQueryEdgeListFacesArgs = z.infer<typeof PluginGeomQueryEdgeListFacesArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport { HalfEdgeHandle, VertexHandle, EdgeHandle, FaceHandle } from \"../../../../handles\"\n\n/**\n * Half-edge queries — walk and read a single BREP half-edge (all-handle model, §11).\n * A {@linkcode HalfEdgeHandle} is minted by walking from a brep/face/edge/vertex\n * handle; it is session-ephemeral and goes stale on undo/redo/re-topologize.\n *\n * The kind token is all-lowercase `\"halfedge\"` (§6.1 — no camelCase namespaces).\n * Traversals (`getNext`/`getPrev`/`getFlip`) return `null` on a boundary or a\n * malformed chain rather than throwing (§6, reads never throw).\n *\n * Accessed via `snaptrude.core.geom.query.halfedge`.\n */\nexport abstract class PluginGeomQueryHalfEdgeApi {\n constructor() {}\n\n /**\n * Get the next half-edge around the face loop.\n * @param halfEdge The half-edge to walk from\n * @returns The next half-edge as a {@linkcode HalfEdgeHandle}, or `null` on a malformed chain\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [face] = await snaptrude.core.geom.query.brep.listFaces(brep)\n * const [he] = await snaptrude.core.geom.query.face.listHalfEdges(face)\n * const next = await snaptrude.core.geom.query.halfedge.getNext(he)\n * if (next) {\n * const vertex = await snaptrude.core.geom.query.halfedge.getVertex(next)\n * }\n * }\n * ```\n */\n public abstract getNext(halfEdge: HalfEdgeHandle): PluginApiReturn<HalfEdgeHandle | null>\n\n /**\n * Get the previous half-edge around the face loop.\n * @param halfEdge The half-edge to walk from\n * @returns The previous half-edge as a {@linkcode HalfEdgeHandle}, or `null` on a malformed chain\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [he] = await snaptrude.core.geom.query.brep.listHalfEdges(brep)\n * const prev = await snaptrude.core.geom.query.halfedge.getPrev(he)\n * if (prev) {\n * const edge = await snaptrude.core.geom.query.halfedge.getEdge(prev)\n * }\n * }\n * ```\n */\n public abstract getPrev(halfEdge: HalfEdgeHandle): PluginApiReturn<HalfEdgeHandle | null>\n\n /**\n * Get the flip (opposite) half-edge of the shared edge.\n * @param halfEdge The half-edge to flip\n * @returns The flip half-edge as a {@linkcode HalfEdgeHandle}, or `null` on a boundary\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [he] = await snaptrude.core.geom.query.brep.listHalfEdges(brep)\n * const flip = await snaptrude.core.geom.query.halfedge.getFlip(he)\n * if (flip) {\n * // cross the shared edge into the neighbouring face\n * const neighbourFace = await snaptrude.core.geom.query.halfedge.getFace(flip)\n * }\n * }\n * ```\n */\n public abstract getFlip(halfEdge: HalfEdgeHandle): PluginApiReturn<HalfEdgeHandle | null>\n\n /**\n * Get the tail vertex of a half-edge.\n * @param halfEdge The half-edge to read\n * @returns The vertex as a {@linkcode VertexHandle}\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [he] = await snaptrude.core.geom.query.brep.listHalfEdges(brep)\n * const vertex = await snaptrude.core.geom.query.halfedge.getVertex(he)\n * const pos = await snaptrude.core.geom.query.vertex.getPosition(brep, vertex)\n * }\n * ```\n */\n public abstract getVertex(halfEdge: HalfEdgeHandle): PluginApiReturn<VertexHandle>\n\n /**\n * Get the edge of a half-edge.\n * @param halfEdge The half-edge to read\n * @returns The edge as an {@linkcode EdgeHandle}\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [he] = await snaptrude.core.geom.query.brep.listHalfEdges(brep)\n * const edge = await snaptrude.core.geom.query.halfedge.getEdge(he)\n * const faces = await snaptrude.core.geom.query.edge.listFaces(edge)\n * }\n * ```\n */\n public abstract getEdge(halfEdge: HalfEdgeHandle): PluginApiReturn<EdgeHandle>\n\n /**\n * Get the face of a half-edge.\n * @param halfEdge The half-edge to read\n * @returns The face as a {@linkcode FaceHandle}\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [he] = await snaptrude.core.geom.query.brep.listHalfEdges(brep)\n * const face = await snaptrude.core.geom.query.halfedge.getFace(he)\n * const index = await snaptrude.core.geom.query.face.getIndex(face)\n * }\n * ```\n */\n public abstract getFace(halfEdge: HalfEdgeHandle): PluginApiReturn<FaceHandle>\n\n /**\n * Test whether a half-edge lies on the brep boundary (has no flip).\n * @param halfEdge The half-edge to test\n * @returns `true` if on the boundary, otherwise `false`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const halfEdges = await snaptrude.core.geom.query.brep.listHalfEdges(brep)\n * let boundary = 0\n * for (const he of halfEdges) {\n * if (await snaptrude.core.geom.query.halfedge.isOnBoundary(he)) boundary++\n * }\n * console.log(\"boundary half-edges:\", boundary)\n * }\n * ```\n */\n public abstract isOnBoundary(halfEdge: HalfEdgeHandle): PluginApiReturn<boolean>\n\n /**\n * Test whether a half-edge is the forward (primary) half-edge of its edge —\n * i.e. `edge.getHalfEdge() === thisHalfEdge`.\n * @param halfEdge The half-edge to test\n * @returns `true` if forward, otherwise `false`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [he] = await snaptrude.core.geom.query.brep.listHalfEdges(brep)\n * const forward = await snaptrude.core.geom.query.halfedge.isForward(he)\n * console.log(forward ? \"primary half-edge\" : \"flip half-edge\")\n * }\n * ```\n */\n public abstract isForward(halfEdge: HalfEdgeHandle): PluginApiReturn<boolean>\n}\n\nconst heArg = z.object({ halfEdge: HalfEdgeHandle })\n\n/** Arguments for {@linkcode PluginGeomQueryHalfEdgeApi.getNext}. `{ halfEdge: HalfEdgeHandle }` */\nexport const PluginGeomQueryHalfEdgeGetNextArgs = heArg\nexport type PluginGeomQueryHalfEdgeGetNextArgs = z.infer<typeof PluginGeomQueryHalfEdgeGetNextArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryHalfEdgeApi.getPrev}. `{ halfEdge: HalfEdgeHandle }` */\nexport const PluginGeomQueryHalfEdgeGetPrevArgs = heArg\nexport type PluginGeomQueryHalfEdgeGetPrevArgs = z.infer<typeof PluginGeomQueryHalfEdgeGetPrevArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryHalfEdgeApi.getFlip}. `{ halfEdge: HalfEdgeHandle }` */\nexport const PluginGeomQueryHalfEdgeGetFlipArgs = heArg\nexport type PluginGeomQueryHalfEdgeGetFlipArgs = z.infer<typeof PluginGeomQueryHalfEdgeGetFlipArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryHalfEdgeApi.getVertex}. `{ halfEdge: HalfEdgeHandle }` */\nexport const PluginGeomQueryHalfEdgeGetVertexArgs = heArg\nexport type PluginGeomQueryHalfEdgeGetVertexArgs = z.infer<typeof PluginGeomQueryHalfEdgeGetVertexArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryHalfEdgeApi.getEdge}. `{ halfEdge: HalfEdgeHandle }` */\nexport const PluginGeomQueryHalfEdgeGetEdgeArgs = heArg\nexport type PluginGeomQueryHalfEdgeGetEdgeArgs = z.infer<typeof PluginGeomQueryHalfEdgeGetEdgeArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryHalfEdgeApi.getFace}. `{ halfEdge: HalfEdgeHandle }` */\nexport const PluginGeomQueryHalfEdgeGetFaceArgs = heArg\nexport type PluginGeomQueryHalfEdgeGetFaceArgs = z.infer<typeof PluginGeomQueryHalfEdgeGetFaceArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryHalfEdgeApi.isOnBoundary}. `{ halfEdge: HalfEdgeHandle }` */\nexport const PluginGeomQueryHalfEdgeIsOnBoundaryArgs = heArg\nexport type PluginGeomQueryHalfEdgeIsOnBoundaryArgs = z.infer<\n typeof PluginGeomQueryHalfEdgeIsOnBoundaryArgs\n>\n\n/** Arguments for {@linkcode PluginGeomQueryHalfEdgeApi.isForward}. `{ halfEdge: HalfEdgeHandle }` */\nexport const PluginGeomQueryHalfEdgeIsForwardArgs = heArg\nexport type PluginGeomQueryHalfEdgeIsForwardArgs = z.infer<typeof PluginGeomQueryHalfEdgeIsForwardArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport {\n BrepHandle,\n VertexHandle,\n HalfEdgeHandle,\n EdgeHandle,\n FaceHandle,\n Vec3Components,\n} from \"../../../../handles\"\n\n/**\n * Vertex queries — read a single BREP vertex's topology and position (all-handle\n * model, §11). A {@linkcode VertexHandle} is minted by walking from a brep/face/edge\n * handle; it is session-ephemeral and goes stale on undo/redo/re-topologize.\n *\n * Position lives on the parent brep (indexed by the vertex), so\n * {@linkcode PluginGeomQueryVertexApi.getPosition} takes both handles. No query\n * mutates its inputs.\n *\n * Accessed via `snaptrude.core.geom.query.vertex`.\n */\nexport abstract class PluginGeomQueryVertexApi {\n constructor() {}\n\n /**\n * Get the index of a vertex within its brep.\n * @param vertex The vertex to query\n * @returns The index as a `number` (`-1` if unindexed)\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [vertex] = await snaptrude.core.geom.query.brep.listVertices(brep)\n * const index = await snaptrude.core.geom.query.vertex.getIndex(vertex)\n * }\n * ```\n */\n public abstract getIndex(vertex: VertexHandle): PluginApiReturn<number>\n\n /**\n * Get a seed half-edge emanating from a vertex.\n * @param vertex The vertex to query\n * @returns The half-edge as a {@linkcode HalfEdgeHandle}, or `null` if uninitialized\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [vertex] = await snaptrude.core.geom.query.brep.listVertices(brep)\n * const halfEdge = await snaptrude.core.geom.query.vertex.getHalfEdge(vertex)\n * if (halfEdge) {\n * const edge = await snaptrude.core.geom.query.halfedge.getEdge(halfEdge)\n * }\n * }\n * ```\n */\n public abstract getHalfEdge(vertex: VertexHandle): PluginApiReturn<HalfEdgeHandle | null>\n\n /**\n * Get the position of a vertex (needs the parent brep for coordinates).\n * @param brep The parent brep (source of coordinates)\n * @param vertex The vertex whose position to read\n * @returns The position as {@linkcode Vec3Components}\n *\n * @examplePrompt What are the coordinates of this corner point?\n * @examplePrompt Get the exact x, y, z position of the selected vertex\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [vertex] = await snaptrude.core.geom.query.brep.listVertices(brep)\n * const pos = await snaptrude.core.geom.query.vertex.getPosition(brep, vertex)\n * console.log(pos.x, pos.y, pos.z)\n * }\n * ```\n */\n public abstract getPosition(\n brep: BrepHandle,\n vertex: VertexHandle\n ): PluginApiReturn<Vec3Components>\n\n /**\n * List the half-edges around a vertex.\n * @param vertex The vertex to query\n * @returns The half-edges as {@linkcode HalfEdgeHandle}`[]`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [vertex] = await snaptrude.core.geom.query.brep.listVertices(brep)\n * const halfEdges = await snaptrude.core.geom.query.vertex.listHalfEdges(vertex)\n * console.log(\"half-edges at this vertex:\", halfEdges.length)\n * }\n * ```\n */\n public abstract listHalfEdges(vertex: VertexHandle): PluginApiReturn<HalfEdgeHandle[]>\n\n /**\n * List the edges around a vertex.\n * @param vertex The vertex to query\n * @returns The edges as {@linkcode EdgeHandle}`[]`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [vertex] = await snaptrude.core.geom.query.brep.listVertices(brep)\n * const edges = await snaptrude.core.geom.query.vertex.listEdges(vertex)\n * console.log(\"edges meeting at this corner:\", edges.length)\n * }\n * ```\n */\n public abstract listEdges(vertex: VertexHandle): PluginApiReturn<EdgeHandle[]>\n\n /**\n * List the faces around a vertex.\n * @param vertex The vertex to query\n * @returns The faces as {@linkcode FaceHandle}`[]`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [vertex] = await snaptrude.core.geom.query.brep.listVertices(brep)\n * const faces = await snaptrude.core.geom.query.vertex.listFaces(vertex)\n * console.log(\"faces sharing this corner:\", faces.length)\n * }\n * ```\n */\n public abstract listFaces(vertex: VertexHandle): PluginApiReturn<FaceHandle[]>\n\n /**\n * List the neighbouring vertices of a vertex.\n * @param vertex The vertex to query\n * @returns The neighbours as {@linkcode VertexHandle}`[]`\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const brep = await snaptrude.design.query.geometry.getBrep(wall)\n * if (brep) {\n * const [vertex] = await snaptrude.core.geom.query.brep.listVertices(brep)\n * const neighbors = await snaptrude.core.geom.query.vertex.listNeighbors(vertex)\n * for (const n of neighbors) {\n * const pos = await snaptrude.core.geom.query.vertex.getPosition(brep, n)\n * }\n * }\n * ```\n */\n public abstract listNeighbors(vertex: VertexHandle): PluginApiReturn<VertexHandle[]>\n}\n\nconst vertexArg = z.object({ vertex: VertexHandle })\n\n/** Arguments for {@linkcode PluginGeomQueryVertexApi.getIndex}. `{ vertex: VertexHandle }` */\nexport const PluginGeomQueryVertexGetIndexArgs = vertexArg\nexport type PluginGeomQueryVertexGetIndexArgs = z.infer<typeof PluginGeomQueryVertexGetIndexArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryVertexApi.getHalfEdge}. `{ vertex: VertexHandle }` */\nexport const PluginGeomQueryVertexGetHalfEdgeArgs = vertexArg\nexport type PluginGeomQueryVertexGetHalfEdgeArgs = z.infer<typeof PluginGeomQueryVertexGetHalfEdgeArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomQueryVertexApi.getPosition}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `brep` | {@linkcode BrepHandle} | The parent brep (source of coordinates) |\n * | `vertex` | {@linkcode VertexHandle} | The vertex whose position to read |\n */\nexport const PluginGeomQueryVertexGetPositionArgs = z.object({\n brep: BrepHandle,\n vertex: VertexHandle,\n})\nexport type PluginGeomQueryVertexGetPositionArgs = z.infer<typeof PluginGeomQueryVertexGetPositionArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryVertexApi.listHalfEdges}. `{ vertex: VertexHandle }` */\nexport const PluginGeomQueryVertexListHalfEdgesArgs = vertexArg\nexport type PluginGeomQueryVertexListHalfEdgesArgs = z.infer<\n typeof PluginGeomQueryVertexListHalfEdgesArgs\n>\n\n/** Arguments for {@linkcode PluginGeomQueryVertexApi.listEdges}. `{ vertex: VertexHandle }` */\nexport const PluginGeomQueryVertexListEdgesArgs = vertexArg\nexport type PluginGeomQueryVertexListEdgesArgs = z.infer<typeof PluginGeomQueryVertexListEdgesArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryVertexApi.listFaces}. `{ vertex: VertexHandle }` */\nexport const PluginGeomQueryVertexListFacesArgs = vertexArg\nexport type PluginGeomQueryVertexListFacesArgs = z.infer<typeof PluginGeomQueryVertexListFacesArgs>\n\n/** Arguments for {@linkcode PluginGeomQueryVertexApi.listNeighbors}. `{ vertex: VertexHandle }` */\nexport const PluginGeomQueryVertexListNeighborsArgs = vertexArg\nexport type PluginGeomQueryVertexListNeighborsArgs = z.infer<\n typeof PluginGeomQueryVertexListNeighborsArgs\n>\n","import { PluginGeomQueryCurveApi } from \"./curve\"\nimport { PluginGeomQueryArcApi } from \"./arc\"\nimport { PluginGeomQueryCircleApi } from \"./circle\"\nimport { PluginGeomQueryProfileApi } from \"./profile\"\nimport { PluginGeomQueryContourApi } from \"./contour\"\nimport { PluginGeomQueryBrepApi } from \"./brep\"\nimport { PluginGeomQueryFaceApi } from \"./face\"\nimport { PluginGeomQueryEdgeApi } from \"./edge\"\nimport { PluginGeomQueryHalfEdgeApi } from \"./halfedge\"\nimport { PluginGeomQueryVertexApi } from \"./vertex\"\n\n/**\n * Geometry-kernel query namespace — read geometry, topology, and relationships as\n * plain values / child handles (all-handle model, §11). No query mutates its inputs.\n *\n * - {@linkcode PluginGeomQueryApi.curve} — shared line/arc queries + two-curve relationships\n * - {@linkcode PluginGeomQueryApi.arc} — arc-specific reads\n * - {@linkcode PluginGeomQueryApi.circle} — circle-specific reads\n * - {@linkcode PluginGeomQueryApi.profile} — profile reads\n * - {@linkcode PluginGeomQueryApi.contour} — contour reads (outer profile + holes)\n * - {@linkcode PluginGeomQueryApi.brep} — BREP mesh topology + geometry\n * - {@linkcode PluginGeomQueryApi.face} — BREP face reads\n * - {@linkcode PluginGeomQueryApi.edge} — BREP edge reads\n * - {@linkcode PluginGeomQueryApi.halfedge} — BREP half-edge walks\n * - {@linkcode PluginGeomQueryApi.vertex} — BREP vertex reads\n *\n * Accessed via `snaptrude.core.geom.query`.\n */\nexport abstract class PluginGeomQueryApi {\n /** Shared curve queries (line or arc) and relationships. See {@linkcode PluginGeomQueryCurveApi}. */\n public abstract curve: PluginGeomQueryCurveApi\n /** Arc-specific queries. See {@linkcode PluginGeomQueryArcApi}. */\n public abstract arc: PluginGeomQueryArcApi\n /** Circle-specific queries. See {@linkcode PluginGeomQueryCircleApi}. */\n public abstract circle: PluginGeomQueryCircleApi\n /** Profile queries. See {@linkcode PluginGeomQueryProfileApi}. */\n public abstract profile: PluginGeomQueryProfileApi\n /** Contour queries. See {@linkcode PluginGeomQueryContourApi}. */\n public abstract contour: PluginGeomQueryContourApi\n /** BREP mesh topology + geometry queries. See {@linkcode PluginGeomQueryBrepApi}. */\n public abstract brep: PluginGeomQueryBrepApi\n /** BREP face queries. See {@linkcode PluginGeomQueryFaceApi}. */\n public abstract face: PluginGeomQueryFaceApi\n /** BREP edge queries. See {@linkcode PluginGeomQueryEdgeApi}. */\n public abstract edge: PluginGeomQueryEdgeApi\n /** BREP half-edge walks. See {@linkcode PluginGeomQueryHalfEdgeApi}. */\n public abstract halfedge: PluginGeomQueryHalfEdgeApi\n /** BREP vertex queries. See {@linkcode PluginGeomQueryVertexApi}. */\n public abstract vertex: PluginGeomQueryVertexApi\n\n constructor() {}\n}\n\nexport * from \"./curve\"\nexport * from \"./arc\"\nexport * from \"./circle\"\nexport * from \"./profile\"\nexport * from \"./contour\"\nexport * from \"./brep\"\nexport * from \"./face\"\nexport * from \"./edge\"\nexport * from \"./halfedge\"\nexport * from \"./vertex\"\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport { Vec3Handle, CurveHandle } from \"../../../../handles\"\n\n/**\n * Curve editing — produce NEW curves from an existing one (all-handle model, §11).\n *\n * Every method takes an opaque {@linkcode CurveHandle} and returns a fresh handle\n * (or handles) for the resulting geometry; the input curve is never mutated. Read\n * the resulting geometry back as plain values with the query namespace.\n *\n * Accessed via `snaptrude.core.geom.update.curve`.\n */\nexport abstract class PluginGeomUpdateCurveApi {\n constructor() {}\n\n /**\n * Extend a curve by a distance from one of its ends. Host API call — returns a handle.\n *\n * @param curve - The curve to extend\n * @param distance - Distance to extend the curve by\n * @param fromStart - Extend from the start end when `true`, otherwise from the end\n * @returns The extended curve as a {@linkcode CurveHandle}\n *\n * @examplePrompt Extend this line by 1 metre\n * @examplePrompt Lengthen this edge from its start point\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(5, 0, 0)\n * const line = await snaptrude.core.geom.create.line(a, b)\n * const longer = await snaptrude.core.geom.update.curve.extend(line, 1, false)\n * const length = await snaptrude.core.geom.query.curve.getLength(longer) // 6\n * ```\n */\n public abstract extend(\n curve: CurveHandle,\n distance: number,\n fromStart: boolean,\n ): PluginApiReturn<CurveHandle>\n\n /**\n * Split a curve at a point into separate segments. Host API call — returns handles.\n *\n * @param curve - The curve to split\n * @param point - Point on the curve to split at\n * @returns The resulting segments as {@linkcode CurveHandle}`[]`\n *\n * @examplePrompt Split this line at its midpoint\n * @examplePrompt Break this edge into two pieces where it meets the gridline\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(6, 0, 0)\n * const line = await snaptrude.core.geom.create.line(a, b)\n * const mid = await snaptrude.core.math.vec3.new(3, 0, 0)\n * const [left, right] = await snaptrude.core.geom.update.curve.split(line, mid)\n * ```\n */\n public abstract split(\n curve: CurveHandle,\n point: Vec3Handle,\n ): PluginApiReturn<CurveHandle[]>\n\n /**\n * Trim a curve to the portion between two parameter values. Host API call — returns a handle.\n *\n * @param curve - The curve to trim\n * @param startParam - Start parameter of the portion to keep\n * @param endParam - End parameter of the portion to keep\n * @returns The trimmed curve as a {@linkcode CurveHandle}\n *\n * @examplePrompt Trim this line back to a shorter segment\n * @examplePrompt Keep only the middle portion of this curve\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(10, 0, 0)\n * const line = await snaptrude.core.geom.create.line(a, b)\n * const middle = await snaptrude.core.geom.update.curve.trim(line, 0.25, 0.75)\n * ```\n */\n public abstract trim(\n curve: CurveHandle,\n startParam: number,\n endParam: number,\n ): PluginApiReturn<CurveHandle>\n\n /**\n * Offset a curve by a translation vector. Host API call — returns a handle.\n *\n * @param curve - The curve to offset\n * @param vector - Translation vector to offset the curve by\n * @returns The offset curve as a {@linkcode CurveHandle}\n *\n * @examplePrompt Offset this line 500mm to the side\n * @examplePrompt Shift this edge over to make a parallel copy\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(8, 0, 0)\n * const line = await snaptrude.core.geom.create.line(a, b)\n * const sideways = await snaptrude.core.math.vec3.new(0, 0, 0.5)\n * const parallel = await snaptrude.core.geom.update.curve.offset(line, sideways)\n * ```\n */\n public abstract offset(\n curve: CurveHandle,\n vector: Vec3Handle,\n ): PluginApiReturn<CurveHandle>\n\n /**\n * Reverse the direction of a curve. Host API call — returns a handle.\n *\n * @param curve - The curve to reverse\n * @returns The reversed curve as a {@linkcode CurveHandle}\n *\n * # Example\n * ```ts\n * const a = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const b = await snaptrude.core.math.vec3.new(4, 0, 0)\n * const line = await snaptrude.core.geom.create.line(a, b)\n * const reversed = await snaptrude.core.geom.update.curve.reverse(line)\n * const start = await snaptrude.core.geom.query.curve.getStartPoint(reversed) // { x: 4, y: 0, z: 0 }\n * ```\n */\n public abstract reverse(curve: CurveHandle): PluginApiReturn<CurveHandle>\n}\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateCurveApi.extend}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to extend |\n * | `distance` | `number` | Distance to extend the curve by |\n * | `fromStart` | `boolean` | Extend from the start end when `true`, otherwise from the end |\n */\nexport const PluginGeomUpdateCurveExtendArgs = z.object({\n curve: CurveHandle,\n distance: z.number(),\n fromStart: z.boolean(),\n})\n\nexport type PluginGeomUpdateCurveExtendArgs = z.infer<typeof PluginGeomUpdateCurveExtendArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateCurveApi.split}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to split |\n * | `point` | {@linkcode Vec3Handle} | Point on the curve to split at |\n */\nexport const PluginGeomUpdateCurveSplitArgs = z.object({\n curve: CurveHandle,\n point: Vec3Handle,\n})\n\nexport type PluginGeomUpdateCurveSplitArgs = z.infer<typeof PluginGeomUpdateCurveSplitArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateCurveApi.trim}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to trim |\n * | `startParam` | `number` | Start parameter of the portion to keep |\n * | `endParam` | `number` | End parameter of the portion to keep |\n */\nexport const PluginGeomUpdateCurveTrimArgs = z.object({\n curve: CurveHandle,\n startParam: z.number(),\n endParam: z.number(),\n})\n\nexport type PluginGeomUpdateCurveTrimArgs = z.infer<typeof PluginGeomUpdateCurveTrimArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateCurveApi.offset}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to offset |\n * | `vector` | {@linkcode Vec3Handle} | Translation vector to offset the curve by |\n */\nexport const PluginGeomUpdateCurveOffsetArgs = z.object({\n curve: CurveHandle,\n vector: Vec3Handle,\n})\n\nexport type PluginGeomUpdateCurveOffsetArgs = z.infer<typeof PluginGeomUpdateCurveOffsetArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateCurveApi.reverse}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The curve to reverse |\n */\nexport const PluginGeomUpdateCurveReverseArgs = z.object({\n curve: CurveHandle,\n})\n\nexport type PluginGeomUpdateCurveReverseArgs = z.infer<typeof PluginGeomUpdateCurveReverseArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport {\n ProfileHandle,\n Vec3Handle,\n CurveHandle,\n Vec3Components,\n} from \"../../../../handles\"\n\n/**\n * Profile editing — produce NEW profiles from an existing one (all-handle model, §11).\n *\n * Every method is an immutable derive-new operation: it takes an opaque\n * {@linkcode ProfileHandle} (plus any extra inputs) and returns a fresh\n * {@linkcode ProfileHandle} for the resulting profile. The input profile is never\n * mutated — the original handle keeps referring to the unchanged profile. Read the\n * resulting geometry back as plain values with the query namespace.\n *\n * Accessed via `snaptrude.core.geom.update.profile`.\n */\nexport abstract class PluginGeomUpdateProfileApi {\n constructor() {}\n\n /**\n * Offset a closed profile inward/outward by a signed distance. Immutable —\n * returns a NEW profile and leaves the input unchanged.\n *\n * @param profile The closed profile to offset\n * @param distance Signed offset distance (outward when positive, inward when negative)\n * @returns The offset profile as a NEW {@linkcode ProfileHandle}\n *\n * @examplePrompt Offset this outline 300mm inward\n * @examplePrompt Inset this boundary by a foot all around\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(10, 8)\n * const inset = await snaptrude.core.geom.update.profile.offset(rect, -0.3)\n * const area = await snaptrude.core.geom.query.profile.getArea(inset)\n * ```\n */\n public abstract offset(\n profile: ProfileHandle,\n distance: number,\n ): PluginApiReturn<ProfileHandle>\n\n /**\n * Translate a profile by a vector. Immutable — returns a NEW profile and leaves\n * the input unchanged.\n *\n * @param profile The profile to move\n * @param translation Translation vector to move the profile by\n * @returns The moved profile as a NEW {@linkcode ProfileHandle}\n *\n * @examplePrompt Move this outline 3 metres along the x axis\n * @examplePrompt Shift this boundary to a new position\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(6, 4)\n * const shift = await snaptrude.core.math.vec3.new(3, 0, 0)\n * const moved = await snaptrude.core.geom.update.profile.move(rect, shift)\n * ```\n */\n public abstract move(\n profile: ProfileHandle,\n translation: Vec3Handle,\n ): PluginApiReturn<ProfileHandle>\n\n /**\n * Rotate a profile about an axis through an optional pivot. Immutable — returns a\n * NEW profile and leaves the input unchanged.\n *\n * @param profile The profile to rotate\n * @param axis Rotation axis direction\n * @param angle Rotation angle in radians\n * @param pivot Optional pivot point to rotate about (defaults to the origin)\n * @returns The rotated profile as a NEW {@linkcode ProfileHandle}\n *\n * @examplePrompt Rotate this outline 30 degrees\n * @examplePrompt Turn this boundary about its centre point\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(6, 4)\n * const axis = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const rotated = await snaptrude.core.geom.update.profile.rotate(rect, axis, Math.PI / 6)\n * ```\n */\n public abstract rotate(\n profile: ProfileHandle,\n axis: Vec3Handle,\n angle: number,\n pivot?: Vec3Handle,\n ): PluginApiReturn<ProfileHandle>\n\n /**\n * Scale a profile uniformly or per-axis about an optional pivot. Immutable —\n * returns a NEW profile and leaves the input unchanged.\n *\n * @param profile The profile to scale\n * @param factor Uniform scale factor, or per-axis factors\n * @param pivot Optional pivot point to scale about (defaults to the origin)\n * @returns The scaled profile as a NEW {@linkcode ProfileHandle}\n *\n * @examplePrompt Scale this outline down to half size\n * @examplePrompt Enlarge this boundary by 50 percent\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(6, 4)\n * const half = await snaptrude.core.geom.update.profile.scale(rect, 0.5)\n * const stretched = await snaptrude.core.geom.update.profile.scale(rect, { x: 2, y: 1, z: 1 })\n * ```\n */\n public abstract scale(\n profile: ProfileHandle,\n factor: number | Vec3Components,\n pivot?: Vec3Handle,\n ): PluginApiReturn<ProfileHandle>\n\n /**\n * Mirror a profile across a plane. Immutable — returns a NEW profile and leaves\n * the input unchanged.\n *\n * @param profile The profile to mirror\n * @param plane Mirror plane defined by a point on it and its normal\n * @returns The mirrored profile as a NEW {@linkcode ProfileHandle}\n *\n * @examplePrompt Mirror this outline across the vertical plane\n * @examplePrompt Flip this boundary to its mirror image\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(6, 4)\n * const point = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const normal = await snaptrude.core.math.vec3.new(1, 0, 0)\n * const mirrored = await snaptrude.core.geom.update.profile.mirror(rect, { point, normal })\n * ```\n */\n public abstract mirror(\n profile: ProfileHandle,\n plane: { point: Vec3Handle; normal: Vec3Handle },\n ): PluginApiReturn<ProfileHandle>\n\n /**\n * Append a curve to the start or end of a profile. The curve must connect to the\n * chosen end. Immutable — returns a NEW profile and leaves the input unchanged.\n *\n * @param profile The profile to append to\n * @param curve The curve to append; it must connect to the chosen end\n * @param position Optional end to append at (defaults to `\"end\"`)\n * @returns The extended profile as a NEW {@linkcode ProfileHandle}\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const open = await snaptrude.core.geom.update.profile.remove(rect, \"end\")\n * const pts = await snaptrude.core.geom.query.profile.listPoints(open)\n * const last = pts[pts.length - 1]\n * const from = await snaptrude.core.math.vec3.new(last.x, last.y, last.z)\n * const to = await snaptrude.core.math.vec3.new(pts[0].x, pts[0].y, pts[0].z)\n * const closer = await snaptrude.core.geom.create.line(from, to)\n * const closed = await snaptrude.core.geom.update.profile.add(open, closer, \"end\")\n * ```\n */\n public abstract add(\n profile: ProfileHandle,\n curve: CurveHandle,\n position?: \"start\" | \"end\",\n ): PluginApiReturn<ProfileHandle>\n\n /**\n * Remove the curve at the start or end of a profile. Immutable — returns a NEW\n * profile and leaves the input unchanged.\n *\n * @param profile The profile to remove a curve from\n * @param position Optional end to remove from (defaults to `\"end\"`)\n * @returns The trimmed profile as a NEW {@linkcode ProfileHandle}\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const open = await snaptrude.core.geom.update.profile.remove(rect, \"end\")\n * const isClosed = await snaptrude.core.geom.query.profile.isClosed(open) // false\n * ```\n */\n public abstract remove(\n profile: ProfileHandle,\n position?: \"start\" | \"end\",\n ): PluginApiReturn<ProfileHandle>\n\n /**\n * Reverse the orientation of a profile. Immutable — returns a NEW profile and\n * leaves the input unchanged.\n *\n * @param profile The profile to reverse\n * @returns The reversed profile as a NEW {@linkcode ProfileHandle}\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const reversed = await snaptrude.core.geom.update.profile.reverse(rect)\n * const clockwise = await snaptrude.core.geom.query.profile.isClockwise(reversed)\n * ```\n */\n public abstract reverse(\n profile: ProfileHandle,\n ): PluginApiReturn<ProfileHandle>\n\n /**\n * Simplify a profile by merging collinear/redundant segments. Immutable —\n * returns a NEW profile and leaves the input unchanged.\n *\n * @param profile The profile to simplify\n * @returns The simplified profile as a NEW {@linkcode ProfileHandle}\n *\n * @examplePrompt Clean up this outline by merging collinear segments\n * @examplePrompt Simplify this polyline and remove redundant points\n *\n * # Example\n * ```ts\n * const p0 = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const p1 = await snaptrude.core.math.vec3.new(2, 0, 0) // collinear with p0 and p2\n * const p2 = await snaptrude.core.math.vec3.new(4, 0, 0)\n * const p3 = await snaptrude.core.math.vec3.new(4, 0, 3)\n * const jagged = await snaptrude.core.geom.create.profileFromLinePoints([p0, p1, p2, p3])\n * const clean = await snaptrude.core.geom.update.profile.simplify(jagged)\n * const curves = await snaptrude.core.geom.query.profile.listCurves(clean) // p0→p2 merged\n * ```\n */\n public abstract simplify(\n profile: ProfileHandle,\n ): PluginApiReturn<ProfileHandle>\n}\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateProfileApi.offset}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The closed profile to offset |\n * | `distance` | `number` | Signed offset distance (outward when positive, inward when negative) |\n */\nexport const PluginGeomUpdateProfileOffsetArgs = z.object({\n profile: ProfileHandle,\n distance: z.number(),\n})\n\nexport type PluginGeomUpdateProfileOffsetArgs = z.infer<typeof PluginGeomUpdateProfileOffsetArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateProfileApi.move}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to move |\n * | `translation` | {@linkcode Vec3Handle} | Translation vector to move the profile by |\n */\nexport const PluginGeomUpdateProfileMoveArgs = z.object({\n profile: ProfileHandle,\n translation: Vec3Handle,\n})\n\nexport type PluginGeomUpdateProfileMoveArgs = z.infer<typeof PluginGeomUpdateProfileMoveArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateProfileApi.rotate}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to rotate |\n * | `axis` | {@linkcode Vec3Handle} | Rotation axis direction |\n * | `angle` | `number` | Rotation angle in radians |\n * | `pivot` | {@linkcode Vec3Handle} | Optional pivot point to rotate about (defaults to the origin) |\n */\nexport const PluginGeomUpdateProfileRotateArgs = z.object({\n profile: ProfileHandle,\n axis: Vec3Handle,\n angle: z.number(),\n pivot: Vec3Handle.optional(),\n})\n\nexport type PluginGeomUpdateProfileRotateArgs = z.infer<typeof PluginGeomUpdateProfileRotateArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateProfileApi.scale}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to scale |\n * | `factor` | `number` \\| {@linkcode Vec3Components} | Uniform scale factor, or per-axis factors |\n * | `pivot` | {@linkcode Vec3Handle} | Optional pivot point to scale about (defaults to the origin) |\n */\nexport const PluginGeomUpdateProfileScaleArgs = z.object({\n profile: ProfileHandle,\n factor: z.union([z.number(), Vec3Components]),\n pivot: Vec3Handle.optional(),\n})\n\nexport type PluginGeomUpdateProfileScaleArgs = z.infer<typeof PluginGeomUpdateProfileScaleArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateProfileApi.mirror}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to mirror |\n * | `plane` | `{ point: `{@linkcode Vec3Handle}`, normal: `{@linkcode Vec3Handle}` }` | Mirror plane defined by a point on it and its normal |\n */\nexport const PluginGeomUpdateProfileMirrorArgs = z.object({\n profile: ProfileHandle,\n plane: z.object({\n point: Vec3Handle,\n normal: Vec3Handle,\n }),\n})\n\nexport type PluginGeomUpdateProfileMirrorArgs = z.infer<typeof PluginGeomUpdateProfileMirrorArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateProfileApi.add}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to append to |\n * | `curve` | {@linkcode CurveHandle} | The curve to append; it must connect to the chosen end |\n * | `position` | `\"start\"` \\| `\"end\"` | Optional end to append at (defaults to `\"end\"`) |\n */\nexport const PluginGeomUpdateProfileAddArgs = z.object({\n profile: ProfileHandle,\n curve: CurveHandle,\n position: z.enum([\"start\", \"end\"]).optional(),\n})\n\nexport type PluginGeomUpdateProfileAddArgs = z.infer<typeof PluginGeomUpdateProfileAddArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateProfileApi.remove}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to remove a curve from |\n * | `position` | `\"start\"` \\| `\"end\"` | Optional end to remove from (defaults to `\"end\"`) |\n */\nexport const PluginGeomUpdateProfileRemoveArgs = z.object({\n profile: ProfileHandle,\n position: z.enum([\"start\", \"end\"]).optional(),\n})\n\nexport type PluginGeomUpdateProfileRemoveArgs = z.infer<typeof PluginGeomUpdateProfileRemoveArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateProfileApi.reverse}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to reverse |\n */\nexport const PluginGeomUpdateProfileReverseArgs = z.object({\n profile: ProfileHandle,\n})\n\nexport type PluginGeomUpdateProfileReverseArgs = z.infer<typeof PluginGeomUpdateProfileReverseArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateProfileApi.simplify}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The profile to simplify |\n */\nexport const PluginGeomUpdateProfileSimplifyArgs = z.object({\n profile: ProfileHandle,\n})\n\nexport type PluginGeomUpdateProfileSimplifyArgs = z.infer<typeof PluginGeomUpdateProfileSimplifyArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport {\n ContourHandle,\n ProfileHandle,\n Vec3Handle,\n Vec3Components,\n} from \"../../../../handles\"\n\n/**\n * Contour editing — produce NEW contours from an existing one (all-handle model, §11).\n *\n * A contour is an outer {@linkcode ProfileHandle} plus zero or more inner profiles\n * that punch holes in it. Every method here is an immutable derive-new operation:\n * it takes an opaque {@linkcode ContourHandle} (plus any extra inputs) and returns a\n * fresh {@linkcode ContourHandle} for the resulting contour. The input contour is\n * never mutated — the original handle keeps referring to the unchanged contour. Read\n * the resulting geometry back as plain values with the query namespace.\n *\n * Accessed via `snaptrude.core.geom.update.contour`.\n */\nexport abstract class PluginGeomUpdateContourApi {\n constructor() {}\n\n /**\n * Offset a contour inward/outward by a signed distance. Immutable — returns a NEW\n * contour and leaves the input unchanged.\n *\n * @param contour The contour to offset\n * @param distance Signed offset distance (outward when positive, inward when negative)\n * @returns The offset contour as a NEW {@linkcode ContourHandle}\n *\n * @examplePrompt Offset this boundary 500mm inward\n * @examplePrompt Shrink this floor outline by half a metre all around\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(10, 8)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const inset = await snaptrude.core.geom.update.contour.offset(contour, -0.5)\n * const area = await snaptrude.core.geom.query.contour.getArea(inset)\n * ```\n */\n public abstract offset(\n contour: ContourHandle,\n distance: number,\n ): PluginApiReturn<ContourHandle>\n\n /**\n * Translate a contour by a vector. Immutable — returns a NEW contour and leaves\n * the input unchanged.\n *\n * @param contour The contour to move\n * @param translation Translation vector to move the contour by\n * @returns The moved contour as a NEW {@linkcode ContourHandle}\n *\n * @examplePrompt Move this shape 2 metres to the right\n * @examplePrompt Shift this outline over by 5 feet\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(6, 4)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const shift = await snaptrude.core.math.vec3.new(2, 0, 0)\n * const moved = await snaptrude.core.geom.update.contour.move(contour, shift)\n * ```\n */\n public abstract move(\n contour: ContourHandle,\n translation: Vec3Handle,\n ): PluginApiReturn<ContourHandle>\n\n /**\n * Rotate a contour about an axis through an optional pivot. Immutable — returns a\n * NEW contour and leaves the input unchanged.\n *\n * @param contour The contour to rotate\n * @param axis Rotation axis direction\n * @param angle Rotation angle in radians\n * @param pivot Optional pivot point to rotate about (defaults to the origin)\n * @returns The rotated contour as a NEW {@linkcode ContourHandle}\n *\n * @examplePrompt Rotate this shape 45 degrees\n * @examplePrompt Turn this outline 90 degrees about its corner\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(6, 4)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const axis = await snaptrude.core.math.vec3.new(0, 1, 0)\n * const pivot = await snaptrude.core.math.vec3.new(3, 0, 2)\n * const rotated = await snaptrude.core.geom.update.contour.rotate(contour, axis, Math.PI / 4, pivot)\n * ```\n */\n public abstract rotate(\n contour: ContourHandle,\n axis: Vec3Handle,\n angle: number,\n pivot?: Vec3Handle,\n ): PluginApiReturn<ContourHandle>\n\n /**\n * Scale a contour uniformly or per-axis about an optional pivot. Immutable —\n * returns a NEW contour and leaves the input unchanged.\n *\n * @param contour The contour to scale\n * @param factor Uniform scale factor, or per-axis factors\n * @param pivot Optional pivot point to scale about (defaults to the origin)\n * @returns The scaled contour as a NEW {@linkcode ContourHandle}\n *\n * @examplePrompt Scale this shape up by a factor of 2\n * @examplePrompt Make this outline half its current size\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(6, 4)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const doubled = await snaptrude.core.geom.update.contour.scale(contour, 2)\n * const stretched = await snaptrude.core.geom.update.contour.scale(contour, { x: 2, y: 1, z: 1 })\n * ```\n */\n public abstract scale(\n contour: ContourHandle,\n factor: number | Vec3Components,\n pivot?: Vec3Handle,\n ): PluginApiReturn<ContourHandle>\n\n /**\n * Mirror a contour across a plane. Immutable — returns a NEW contour and leaves\n * the input unchanged.\n *\n * @param contour The contour to mirror\n * @param plane Mirror plane defined by a point on it and its normal\n * @returns The mirrored contour as a NEW {@linkcode ContourHandle}\n *\n * @examplePrompt Mirror this shape across the gridline\n * @examplePrompt Flip this outline to its mirror image\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(6, 4)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const point = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const normal = await snaptrude.core.math.vec3.new(1, 0, 0)\n * const mirrored = await snaptrude.core.geom.update.contour.mirror(contour, { point, normal })\n * ```\n */\n public abstract mirror(\n contour: ContourHandle,\n plane: { point: Vec3Handle; normal: Vec3Handle },\n ): PluginApiReturn<ContourHandle>\n\n /**\n * Add a hole to a contour by appending an inner profile. Immutable — returns a NEW\n * contour and leaves the input unchanged.\n *\n * @param contour The contour to add a hole to\n * @param hole The inner profile to add as a hole\n * @returns The contour with the added hole as a NEW {@linkcode ContourHandle}\n *\n * @examplePrompt Cut a hole in this shape\n * @examplePrompt Punch an opening through this floor outline\n *\n * # Example\n * ```ts\n * const outer = await snaptrude.core.geom.create.profileRect(10, 8)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(outer)\n * const hole = await snaptrude.core.geom.create.profileRect(2, 2)\n * const withHole = await snaptrude.core.geom.update.contour.addHole(contour, hole)\n * const count = await snaptrude.core.geom.query.contour.getHoleCount(withHole) // 1\n * ```\n */\n public abstract addHole(\n contour: ContourHandle,\n hole: ProfileHandle,\n ): PluginApiReturn<ContourHandle>\n\n /**\n * Remove a hole from a contour by inner-profile index. Immutable — returns a NEW\n * contour and leaves the input unchanged.\n *\n * @param contour The contour to remove a hole from\n * @param index Zero-based index of the inner profile (hole) to remove\n * @returns The contour with the hole removed as a NEW {@linkcode ContourHandle}\n *\n * @examplePrompt Remove the hole from this shape\n * @examplePrompt Fill in the opening in this floor outline\n *\n * # Example\n * ```ts\n * const outer = await snaptrude.core.geom.create.profileRect(10, 8)\n * const hole = await snaptrude.core.geom.create.profileRect(2, 2)\n * const contour = await snaptrude.core.geom.create.contourFromProfiles(outer, [hole])\n * const solid = await snaptrude.core.geom.update.contour.removeHole(contour, 0)\n * ```\n */\n public abstract removeHole(\n contour: ContourHandle,\n index: number,\n ): PluginApiReturn<ContourHandle>\n}\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateContourApi.offset}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to offset |\n * | `distance` | `number` | Signed offset distance (outward when positive, inward when negative) |\n */\nexport const PluginGeomUpdateContourOffsetArgs = z.object({\n contour: ContourHandle,\n distance: z.number(),\n})\n\nexport type PluginGeomUpdateContourOffsetArgs = z.infer<typeof PluginGeomUpdateContourOffsetArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateContourApi.move}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to move |\n * | `translation` | {@linkcode Vec3Handle} | Translation vector to move the contour by |\n */\nexport const PluginGeomUpdateContourMoveArgs = z.object({\n contour: ContourHandle,\n translation: Vec3Handle,\n})\n\nexport type PluginGeomUpdateContourMoveArgs = z.infer<typeof PluginGeomUpdateContourMoveArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateContourApi.rotate}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to rotate |\n * | `axis` | {@linkcode Vec3Handle} | Rotation axis direction |\n * | `angle` | `number` | Rotation angle in radians |\n * | `pivot` | {@linkcode Vec3Handle} | Optional pivot point to rotate about (defaults to the origin) |\n */\nexport const PluginGeomUpdateContourRotateArgs = z.object({\n contour: ContourHandle,\n axis: Vec3Handle,\n angle: z.number(),\n pivot: Vec3Handle.optional(),\n})\n\nexport type PluginGeomUpdateContourRotateArgs = z.infer<typeof PluginGeomUpdateContourRotateArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateContourApi.scale}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to scale |\n * | `factor` | `number` \\| {@linkcode Vec3Components} | Uniform scale factor, or per-axis factors |\n * | `pivot` | {@linkcode Vec3Handle} | Optional pivot point to scale about (defaults to the origin) |\n */\nexport const PluginGeomUpdateContourScaleArgs = z.object({\n contour: ContourHandle,\n factor: z.union([z.number(), Vec3Components]),\n pivot: Vec3Handle.optional(),\n})\n\nexport type PluginGeomUpdateContourScaleArgs = z.infer<typeof PluginGeomUpdateContourScaleArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateContourApi.mirror}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to mirror |\n * | `plane` | `{ point: `{@linkcode Vec3Handle}`, normal: `{@linkcode Vec3Handle}` }` | Mirror plane defined by a point on it and its normal |\n */\nexport const PluginGeomUpdateContourMirrorArgs = z.object({\n contour: ContourHandle,\n plane: z.object({\n point: Vec3Handle,\n normal: Vec3Handle,\n }),\n})\n\nexport type PluginGeomUpdateContourMirrorArgs = z.infer<typeof PluginGeomUpdateContourMirrorArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateContourApi.addHole}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to add a hole to |\n * | `hole` | {@linkcode ProfileHandle} | The inner profile to add as a hole |\n */\nexport const PluginGeomUpdateContourAddHoleArgs = z.object({\n contour: ContourHandle,\n hole: ProfileHandle,\n})\n\nexport type PluginGeomUpdateContourAddHoleArgs = z.infer<typeof PluginGeomUpdateContourAddHoleArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomUpdateContourApi.removeHole}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The contour to remove a hole from |\n * | `index` | `number` | Zero-based index of the inner profile (hole) to remove |\n */\nexport const PluginGeomUpdateContourRemoveHoleArgs = z.object({\n contour: ContourHandle,\n index: z.number(),\n})\n\nexport type PluginGeomUpdateContourRemoveHoleArgs = z.infer<typeof PluginGeomUpdateContourRemoveHoleArgs>\n","import { PluginGeomUpdateCurveApi } from \"./curve\"\nimport { PluginGeomUpdateProfileApi } from \"./profile\"\nimport { PluginGeomUpdateContourApi } from \"./contour\"\n\n/**\n * Curve/profile/contour update namespace — derive NEW geometry by editing existing\n * handles (all-handle model, §11). Inputs are never mutated; every method returns a\n * fresh handle.\n *\n * - {@linkcode PluginGeomUpdateApi.curve} — extend, split, trim, offset, reverse\n * - {@linkcode PluginGeomUpdateApi.profile} — offset, affine, add/remove/reverse/simplify\n * - {@linkcode PluginGeomUpdateApi.contour} — offset, affine, addHole/removeHole\n *\n * Accessed via `snaptrude.core.geom.update`.\n */\nexport abstract class PluginGeomUpdateApi {\n /** Curve editing operations. See {@linkcode PluginGeomUpdateCurveApi}. */\n public abstract curve: PluginGeomUpdateCurveApi\n /** Profile editing operations. See {@linkcode PluginGeomUpdateProfileApi}. */\n public abstract profile: PluginGeomUpdateProfileApi\n /** Contour editing operations. See {@linkcode PluginGeomUpdateContourApi}. */\n public abstract contour: PluginGeomUpdateContourApi\n\n constructor() {}\n}\n\nexport * from \"./curve\"\nexport * from \"./profile\"\nexport * from \"./contour\"\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport { ProfileHandle, ContourHandle } from \"../../../../handles\"\n\n/**\n * Geometry deletion — release transient geometry resource handles (all-handle model, §11).\n *\n * Methods here free a handle's backing registry entry and reclaim its share of the\n * plugin's resource quota. This is purely a handle-lifecycle operation: it does NOT\n * tear down or delete any engine-side geometry. Once released, the handle is no\n * longer resolvable and must not be reused.\n *\n * Accessed via `snaptrude.core.geom.delete`.\n */\nexport abstract class PluginGeomDeleteApi {\n constructor() {}\n\n /**\n * Free a transient profile resource handle — releases its registry entry and\n * reclaims its share of the plugin's resource quota. This is a handle-lifecycle\n * operation only and does NOT tear down any engine-side geometry.\n *\n * @deprecated Use `core.handles.release([profile])` — the universal, batched\n * release API for every arena handle kind.\n *\n * @param profile The transient profile handle to release\n * @returns Nothing\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const area = await snaptrude.core.geom.query.profile.getArea(rect)\n * await snaptrude.core.geom.delete.profile(rect) // rect is no longer resolvable\n * ```\n */\n public abstract profile(profile: ProfileHandle): PluginApiReturn<void>\n\n /**\n * Free a transient contour resource handle — releases its registry entry and\n * reclaims its share of the plugin's resource quota. Handle-lifecycle only; does\n * NOT tear down engine-side geometry, nor the contour's child profile handles.\n *\n * @deprecated Use `core.handles.release([contour])` — the universal, batched\n * release API for every arena handle kind.\n *\n * @param contour The transient contour handle to release\n * @returns Nothing\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const area = await snaptrude.core.geom.query.contour.getArea(contour)\n * await snaptrude.core.geom.delete.contour(contour) // contour freed; rect still valid\n * ```\n */\n public abstract contour(contour: ContourHandle): PluginApiReturn<void>\n}\n\n/**\n * Arguments for {@linkcode PluginGeomDeleteApi.profile}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | The transient profile handle to release |\n */\nexport const PluginGeomDeleteProfileArgs = z.object({\n profile: ProfileHandle,\n})\n\nexport type PluginGeomDeleteProfileArgs = z.infer<typeof PluginGeomDeleteProfileArgs>\n\n/**\n * Arguments for {@linkcode PluginGeomDeleteApi.contour}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | The transient contour handle to release |\n */\nexport const PluginGeomDeleteContourArgs = z.object({\n contour: ContourHandle,\n})\n\nexport type PluginGeomDeleteContourArgs = z.infer<typeof PluginGeomDeleteContourArgs>\n","import { PluginGeomCreateApi } from \"./create\"\nimport { PluginGeomQueryApi } from \"./query\"\nimport { PluginGeomUpdateApi } from \"./update\"\nimport { PluginGeomDeleteApi } from \"./delete\"\n\n/**\n * Geometry kernel — domain-free construction, query, editing, and disposal of\n * curves (lines / arcs) and profiles. No BIM/AEC knowledge lives here; build\n * geometry bottom-up: points ({@linkcode Vec3Handle}) → curves → profiles → entities.\n *\n * - {@linkcode PluginGeomApi.create} — construct lines, arcs, and profiles\n * - {@linkcode PluginGeomApi.query} — read curve / arc / profile geometry and relationships\n * - {@linkcode PluginGeomApi.update} — derive new curves / profiles (immutable edits)\n * - {@linkcode PluginGeomApi.delete} — free a transient profile resource handle\n *\n * Profiles can then be used with entity creation methods like\n * {@linkcode PluginSpaceApi.createFromProfile}.\n */\nexport abstract class PluginGeomApi {\n /** Curve and profile construction. See {@linkcode PluginGeomCreateApi}. */\n public abstract create: PluginGeomCreateApi\n /** Curve, arc, and profile queries. See {@linkcode PluginGeomQueryApi}. */\n public abstract query: PluginGeomQueryApi\n /** Curve and profile editing (derive-new). See {@linkcode PluginGeomUpdateApi}. */\n public abstract update: PluginGeomUpdateApi\n /** Resource-handle disposal (profile). See {@linkcode PluginGeomDeleteApi}. */\n public abstract delete: PluginGeomDeleteApi\n\n constructor() {}\n}\n\nexport * from \"./create\"\nexport * from \"./query\"\nexport * from \"./update\"\nexport * from \"./delete\"\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\n\n/**\n * Undo / redo of committed history entries.\n *\n * History steps operate on Snaptrude's command stack: {@linkcode PluginHistoryApi.undo}\n * steps back over committed entries, {@linkcode PluginHistoryApi.redo} re-applies\n * undone ones. Both take an optional step count (default 1) and report how many\n * steps were actually applied — fewer than requested when the stack runs out.\n *\n * Accessed via `snaptrude.core.history`.\n */\nexport abstract class PluginHistoryApi {\n constructor() {}\n\n /**\n * Step back over committed history entries.\n *\n * @param steps - (Optional) The number of entries to undo (default 1).\n * @returns A {@linkcode PluginHistoryStepResult} with `stepsApplied` — the\n * entries actually undone (≤ `steps`; fewer if the undo stack ran out).\n *\n * @examplePrompt Undo my last change\n * @examplePrompt Undo the last 3 actions\n * @examplePrompt Revert what I just did to the model\n * @examplePrompt Step back one change\n * @examplePrompt Take back that last edit\n *\n * # Example\n * ```ts\n * await snaptrude.core.history.undo() // one step\n * await snaptrude.core.history.undo(3) // up to three steps\n * ```\n */\n public abstract undo(steps?: number): PluginApiReturn<PluginHistoryStepResult>\n\n /**\n * Step forward over previously undone entries. Symmetric with\n * {@linkcode PluginHistoryApi.undo}.\n *\n * @param steps - (Optional) The number of entries to redo (default 1).\n * @returns A {@linkcode PluginHistoryStepResult} with `stepsApplied` — the\n * entries actually redone (≤ `steps`; fewer if the redo stack ran out).\n *\n * @examplePrompt Redo the change I just undid\n * @examplePrompt Redo the last two undone actions\n * @examplePrompt Bring back the edit I reverted\n * @examplePrompt Step forward again in the history\n *\n * # Example\n * ```ts\n * await snaptrude.core.history.redo()\n * await snaptrude.core.history.redo(2)\n * ```\n */\n public abstract redo(steps?: number): PluginApiReturn<PluginHistoryStepResult>\n}\n\n/**\n * Arguments for {@linkcode PluginHistoryApi.undo} / {@linkcode PluginHistoryApi.redo}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `steps` | `number?` | Number of history entries to step (positive integer). Defaults to 1. |\n */\nexport const PluginHistoryStepArgs = z.object({\n steps: z.number().int().positive().optional(),\n})\n\nexport type PluginHistoryStepArgs = z.infer<typeof PluginHistoryStepArgs>\n\n/**\n * Result of {@linkcode PluginHistoryApi.undo} / {@linkcode PluginHistoryApi.redo}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `stepsApplied` | `number` | History entries actually stepped — `≤ steps`, fewer when the stack ran out. |\n */\nexport const PluginHistoryStepResult = z.object({\n stepsApplied: z.number().int().nonnegative(),\n})\n\nexport type PluginHistoryStepResult = z.infer<typeof PluginHistoryStepResult>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\n\n/**\n * Unit-type queries and conversion.\n *\n * Snaptrude stores all geometry in an internal `babylon` unit. These methods\n * read the available unit types, the internal (`babylon`) type, and the current\n * project type; set the current project type; and convert values between any two\n * unit types.\n *\n * Accessed via `snaptrude.core.units`.\n */\nexport abstract class PluginUnitsApi {\n constructor() {}\n\n /**\n * List the selectable real-world unit types (excludes the internal `babylon`\n * storage type, which is read via {@linkcode PluginUnitsApi.getBabylonType}).\n *\n * @examplePrompt What units can I use in this project?\n * @examplePrompt List all the available measurement units\n * @examplePrompt Which unit systems does Snaptrude support?\n * @examplePrompt Show the unit options I could switch the model to\n *\n * # Example\n * ```ts\n * const types = await snaptrude.core.units.listTypes()\n * console.log(types) // [\"meters\", \"feet-inches\", \"inches\", ...]\n * ```\n */\n public abstract listTypes(): PluginApiReturn<PUnitType[]>\n\n /**\n * Get the internal storage unit type that all geometry is kept in — always\n * `\"babylon\"` — for use as one side of a {@linkcode PluginUnitsApi.convert} call.\n *\n * @examplePrompt What unit does Snaptrude store geometry in internally?\n * @examplePrompt Get the internal babylon unit type\n * @examplePrompt Which unit are raw model coordinates saved in?\n *\n * # Example\n * ```ts\n * const babylon = await snaptrude.core.units.getBabylonType() // \"babylon\"\n * const meters = await snaptrude.core.units.convert(10, babylon, \"meters\")\n * ```\n */\n public abstract getBabylonType(): PluginApiReturn<PUnitType>\n\n /**\n * Get the current project's unit type — the unit lengths are displayed and\n * entered in. Paired with {@linkcode PluginUnitsApi.setType}.\n *\n * @examplePrompt What units is this project using?\n * @examplePrompt Are we working in metric or imperial?\n * @examplePrompt Check the current measurement unit of the model\n * @examplePrompt Is the file set to millimetres or feet and inches?\n *\n * # Example\n * ```ts\n * const type = await snaptrude.core.units.getType()\n * console.log(type) // e.g. \"millimeters\"\n * ```\n */\n public abstract getType(): PluginApiReturn<PUnitType>\n\n /**\n * Set the current project's unit type. Paired with {@linkcode PluginUnitsApi.getType}.\n *\n * @param type - The unit type to set as the project unit.\n * @returns `true` if applied; `false` if `type` is not a settable project unit\n * (e.g. `kilometers`, `miles`, `babylon`).\n *\n * @examplePrompt Change the project units to millimeters\n * @examplePrompt Switch this model to feet and inches\n * @examplePrompt Set the working units to metric\n * @examplePrompt Make the drawing use centimetres instead of meters\n *\n * # Example\n * ```ts\n * await snaptrude.core.units.setType(\"meters\")\n * ```\n */\n public abstract setType(type: PUnitType): PluginApiReturn<boolean>\n\n /**\n * Convert a value between two unit types.\n *\n * @param value - The numeric value to convert.\n * @param from - Source unit type.\n * @param to - Target unit type.\n * @param degree - (Optional) 1 = length, 2 = area, 3 = volume. Defaults to 1.\n * @returns The converted numeric value.\n *\n * @examplePrompt Convert 5 meters to feet and inches\n * @examplePrompt How many millimetres is 12 feet?\n * @examplePrompt Change this area from internal units to square metres\n * @examplePrompt Turn 2500 mm into meters\n * @examplePrompt Convert a volume value from babylon units to cubic feet\n *\n * # Example\n * ```ts\n * // 5 meters → internal babylon units\n * const b = await snaptrude.core.units.convert(5, \"meters\", \"babylon\")\n * // an internal area → square metres\n * const m2 = await snaptrude.core.units.convert(area, \"babylon\", \"meters\", 2)\n * ```\n */\n public abstract convert(\n value: number,\n from: PUnitType,\n to: PUnitType,\n degree?: number,\n ): PluginApiReturn<number>\n}\n\n/**\n * Supported unit types. `babylon` is Snaptrude's internal storage unit; the rest\n * are real-world units. Mirrors the engine `Converter.UNITS` vocabulary.\n */\nexport const PUnitType = z.enum([\n \"meters\",\n \"feet-inches\",\n \"inches\",\n \"centimeters\",\n \"millimeters\",\n \"kilometers\",\n \"miles\",\n \"babylon\",\n])\n\nexport type PUnitType = z.infer<typeof PUnitType>\n\n/**\n * Arguments for {@linkcode PluginUnitsApi.setType}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `type` | {@linkcode PUnitType} | The unit type to set as the project unit |\n */\nexport const PluginUnitsSetTypeArgs = z.object({\n type: PUnitType,\n})\n\nexport type PluginUnitsSetTypeArgs = z.infer<typeof PluginUnitsSetTypeArgs>\n\n/**\n * Arguments for {@linkcode PluginUnitsApi.convert}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `value` | `number` | The numeric value to convert |\n * | `from` | {@linkcode PUnitType} | Source unit type |\n * | `to` | {@linkcode PUnitType} | Target unit type |\n * | `degree` | `1 \\| 2 \\| 3` | (Optional) 1 = length, 2 = area, 3 = volume. Defaults to 1 |\n */\nexport const PluginUnitsConvertArgs = z.object({\n value: z.number(),\n from: PUnitType,\n to: PUnitType,\n degree: z.number().int().min(1).max(3).optional(),\n})\n\nexport type PluginUnitsConvertArgs = z.infer<typeof PluginUnitsConvertArgs>\n","import { PluginApiReturn } from \"../../../types\"\n\n/**\n * Camera zoom controls.\n *\n * Frame the viewport camera onto scene geometry — mirrors the canvas zoom menu\n * (\"Zoom extents\" / \"Zoom to selection\"). Accessed via `snaptrude.core.zoom`.\n */\nexport abstract class PluginZoomApi {\n constructor() {}\n\n /**\n * Zoom (fit) the camera to all geometry in the scene — the \"zoom extents\" action.\n *\n * @returns `true` once the camera has been framed to the scene.\n *\n * @examplePrompt Zoom out so I can see the whole model\n * @examplePrompt Fit everything in the view\n * @examplePrompt Frame the entire building in the viewport\n * @examplePrompt Do a zoom extents on the canvas\n * @examplePrompt Show me the full scene\n *\n * # Example\n * ```ts\n * await snaptrude.core.zoom.extents()\n * ```\n *\n * @deprecated Use `core.camera.zoomExtents`\n */\n public abstract extents(): PluginApiReturn<boolean>\n\n /**\n * Zoom (fit) the camera to the current selection.\n *\n * @returns `true` if there was a selection to frame; `false` if nothing is\n * selected (no-op).\n *\n * @examplePrompt Zoom in on what I have selected\n * @examplePrompt Frame the selected walls in the view\n * @examplePrompt Focus the camera on my current selection\n * @examplePrompt Fit the view to the selected room\n *\n * # Example\n * ```ts\n * await snaptrude.core.zoom.selection()\n * ```\n *\n * @deprecated Use `core.camera.zoomSelection`\n */\n public abstract selection(): PluginApiReturn<boolean>\n}\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { Vec3Handle } from \"../../../handles\"\n\n/**\n * Camera controls.\n *\n * Position the viewport camera, snap it to a standard orthographic/isometric\n * view, and toggle between the 2D (plan) and 3D modelling modes — mirrors the\n * canvas view menu and the 2D/3D toggle. Like {@linkcode PluginZoomApi}, these\n * are transient VIEW-STATE changes: they are not model edits, so they are not\n * write-gated and are not undoable. Accessed via `snaptrude.core.camera`.\n */\nexport abstract class PluginCameraApi {\n constructor() {}\n\n /**\n * Point the camera: place its eye at `eye` looking toward `target` (world\n * coordinates, internal `babylon` units). View-state only — not undoable.\n *\n * @param eye - Camera position as a {@linkcode Vec3Handle}\n * @param target - Point the camera looks at as a {@linkcode Vec3Handle}\n * @returns `true` once the camera has been positioned.\n *\n * @examplePrompt Look at the model from above the north-east corner\n * @examplePrompt Position the camera at (50, 30, 50) looking at the origin\n * @examplePrompt Point the camera at the centre of the building from the side\n * @examplePrompt Set the eye to this point and aim it at that point\n *\n * # Example\n * ```ts\n * const { vec3 } = snaptrude.core.math\n * await snaptrude.core.camera.lookFrom(vec3.new(50, 30, 50), vec3.new(0, 0, 0))\n * ```\n */\n public abstract lookFrom(\n eye: Vec3Handle,\n target: Vec3Handle,\n ): PluginApiReturn<boolean>\n\n /**\n * Snap the camera to a standard view — the five orthographic elevations\n * (`top` / `front` / `back` / `left` / `right`) or the 3D `iso` (isometric\n * perspective) view. Mirrors the canvas view menu. View-state only — not\n * undoable. The orthographic presets exit 2D mode first if needed.\n *\n * @param view - The standard view to snap to ({@linkcode PluginStandardView})\n * @returns `true` once the view has been applied.\n *\n * @examplePrompt Switch to the top view\n * @examplePrompt Show me the front elevation\n * @examplePrompt Go to the isometric view\n * @examplePrompt Look at the model from the left\n * @examplePrompt Give me a plan view from the top\n *\n * # Example\n * ```ts\n * await snaptrude.core.camera.setStandardView(\"top\")\n * ```\n */\n public abstract setStandardView(\n view: PluginStandardView,\n ): PluginApiReturn<boolean>\n\n /**\n * Get the current camera mode. Paired with {@linkcode PluginCameraApi.setMode}.\n *\n * @returns `\"2d\"` when the editor is in plan view, `\"3d\"` otherwise.\n *\n * @examplePrompt Am I in 2D or 3D?\n * @examplePrompt What view mode is the editor in?\n *\n * # Example\n * ```ts\n * if ((await snaptrude.core.camera.getMode()) === \"2d\") {\n * await snaptrude.core.camera.setMode(\"3d\")\n * }\n * ```\n */\n public abstract getMode(): PluginApiReturn<PluginCameraMode>\n\n /**\n * Toggle the modelling mode between `2d` (plan) and `3d`. Mirrors the canvas\n * 2D/3D toggle: `3d` enters the isometric perspective view, `2d` drops to the\n * orthographic plan of the active storey. View-state only — not undoable.\n *\n * @param mode - `\"2d\"` or `\"3d\"` ({@linkcode PluginCameraMode})\n * @returns `true` once the mode switch has been requested.\n *\n * @examplePrompt Switch to 3D view\n * @examplePrompt Drop into 2D plan mode\n * @examplePrompt Go back to 3D\n * @examplePrompt Show the floor plan in 2D\n *\n * # Example\n * ```ts\n * await snaptrude.core.camera.setMode(\"3d\")\n * ```\n */\n public abstract setMode(mode: PluginCameraMode): PluginApiReturn<boolean>\n\n /**\n * Read the camera's current projection: `\"perspective\"` (the default 3D\n * view) or `\"orthographic\"`. In 2D (plan) mode this always reports\n * `\"orthographic\"` — a plan is an orthographic projection.\n *\n * @returns The current {@linkcode PluginCameraProjection}.\n *\n * @examplePrompt Is the camera in perspective or orthographic?\n * @examplePrompt What projection is the 3D view using?\n * @examplePrompt Check whether perspective is on\n *\n * # Example\n * ```ts\n * const projection = await snaptrude.core.camera.getProjection()\n * console.log(projection) // \"perspective\" | \"orthographic\"\n * ```\n */\n public abstract getProjection(): PluginApiReturn<PluginCameraProjection>\n\n /**\n * Switch the 3D camera between `\"perspective\"` and `\"orthographic\"`\n * projection — the Perspective/Orthographic control in the Design tab's\n * View Settings panel. Like {@linkcode PluginCameraApi.setMode}, this is a\n * transient view-state change: not write-gated, not undoable.\n *\n * In 2D (plan) mode the canvas is always an orthographic plan:\n * `\"orthographic\"` is accepted as a no-op, while `\"perspective\"` is\n * rejected — switch to 3D first via `setMode(\"3d\")`.\n *\n * @param projection - `\"perspective\"` or `\"orthographic\"` ({@linkcode PluginCameraProjection})\n * @returns `true` once the projection has been applied.\n *\n * @examplePrompt Switch the camera to orthographic\n * @examplePrompt Turn perspective off\n * @examplePrompt Give me a perspective view of the model\n * @examplePrompt Make the 3D view orthographic like an axonometric drawing\n *\n * # Example\n * ```ts\n * await snaptrude.core.camera.setProjection(\"orthographic\")\n * const projection = await snaptrude.core.camera.getProjection() // \"orthographic\"\n * ```\n */\n public abstract setProjection(\n projection: PluginCameraProjection,\n ): PluginApiReturn<boolean>\n\n /**\n * Zoom (fit) the camera to all geometry in the scene — the \"zoom extents\"\n * action. View-state only — not undoable. Canonical home of the zoom-extents\n * read (the deprecated `core.zoom.extents`).\n *\n * @returns `true` once the camera has been framed to the scene.\n *\n * @examplePrompt Zoom out so I can see the whole model\n * @examplePrompt Fit everything in the view\n * @examplePrompt Frame the entire building in the viewport\n * @examplePrompt Do a zoom extents on the canvas\n *\n * # Example\n * ```ts\n * await snaptrude.core.camera.zoomExtents()\n * ```\n */\n public abstract zoomExtents(): PluginApiReturn<boolean>\n\n /**\n * Zoom (fit) the camera to the current selection. View-state only — not\n * undoable. Canonical home of the zoom-to-selection read (the deprecated\n * `core.zoom.selection`).\n *\n * @returns `true` if there was a selection to frame; `false` if nothing is\n * selected (no-op).\n *\n * @examplePrompt Zoom in on what I have selected\n * @examplePrompt Frame the selected walls in the view\n * @examplePrompt Focus the camera on my current selection\n * @examplePrompt Fit the view to the selected room\n *\n * # Example\n * ```ts\n * await snaptrude.core.camera.zoomSelection()\n * ```\n */\n public abstract zoomSelection(): PluginApiReturn<boolean>\n}\n\n/**\n * A standard camera view: the five orthographic elevations (`top`/`front`/\n * `back`/`left`/`right`) or the 3D isometric perspective (`iso`).\n */\nexport const PluginStandardView = z.enum([\n \"top\",\n \"front\",\n \"back\",\n \"left\",\n \"right\",\n \"iso\",\n])\nexport type PluginStandardView = z.infer<typeof PluginStandardView>\n\n/**\n * The modelling mode: `2d` (orthographic plan) or `3d` (isometric perspective).\n */\nexport const PluginCameraMode = z.enum([\"2d\", \"3d\"])\nexport type PluginCameraMode = z.infer<typeof PluginCameraMode>\n\n/**\n * The 3D camera projection: `perspective` (the default) or `orthographic` —\n * the Perspective/Orthographic control in the Design tab's View Settings panel.\n */\nexport const PluginCameraProjection = z.enum([\"perspective\", \"orthographic\"])\nexport type PluginCameraProjection = z.infer<typeof PluginCameraProjection>\n\n/**\n * Arguments for {@linkcode PluginCameraApi.lookFrom}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `eye` | {@linkcode Vec3Handle} | Camera position (world, `babylon` units) |\n * | `target` | {@linkcode Vec3Handle} | Point the camera looks at |\n */\nexport const PluginCameraLookFromArgs = z.object({\n eye: Vec3Handle,\n target: Vec3Handle,\n})\nexport type PluginCameraLookFromArgs = z.infer<typeof PluginCameraLookFromArgs>\n\n/**\n * Arguments for {@linkcode PluginCameraApi.setStandardView}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `view` | {@linkcode PluginStandardView} | The standard view to snap to |\n */\nexport const PluginCameraSetStandardViewArgs = z.object({\n view: PluginStandardView,\n})\nexport type PluginCameraSetStandardViewArgs = z.infer<\n typeof PluginCameraSetStandardViewArgs\n>\n\n/**\n * Arguments for {@linkcode PluginCameraApi.setMode}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `mode` | {@linkcode PluginCameraMode} | `\"2d\"` or `\"3d\"` |\n */\nexport const PluginCameraSetModeArgs = z.object({\n mode: PluginCameraMode,\n})\nexport type PluginCameraSetModeArgs = z.infer<typeof PluginCameraSetModeArgs>\n\n/**\n * Arguments for {@linkcode PluginCameraApi.setProjection}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `projection` | {@linkcode PluginCameraProjection} | `\"perspective\"` or `\"orthographic\"` |\n */\nexport const PluginCameraSetProjectionArgs = z.object({\n projection: PluginCameraProjection,\n})\nexport type PluginCameraSetProjectionArgs = z.infer<\n typeof PluginCameraSetProjectionArgs\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * User locale.\n *\n * Read the viewer's timezone and language — for date/number formatting and\n * localized labels. This is locale only: NO precise geolocation, no IP, no\n * account identity. Accessed via `snaptrude.core.user`.\n */\nexport abstract class PluginUserApi {\n constructor() {}\n\n /**\n * Get the viewer's locale: IANA timezone and BCP-47 language tag, resolved\n * from the browser (`Intl.DateTimeFormat().resolvedOptions().timeZone` and\n * `navigator.language`). Locale only — no precise location.\n *\n * @returns `{ timezone, language }` — e.g. `{ timezone: \"Asia/Kolkata\", language: \"en-US\" }`.\n *\n * @examplePrompt What timezone is the user in?\n * @examplePrompt Get the viewer's language for localized labels\n * @examplePrompt Format the report date in the user's local timezone\n * @examplePrompt Which locale should I use for this plugin's UI?\n *\n * # Example\n * ```ts\n * const { timezone, language } = await snaptrude.core.user.getLocale()\n * ```\n */\n public abstract getLocale(): PluginApiReturn<PluginUserLocale>\n}\n\n/**\n * The viewer's locale.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `timezone` | `string` | IANA timezone id (e.g. `\"Asia/Kolkata\"`) |\n * | `language` | `string` | BCP-47 language tag (e.g. `\"en-US\"`) |\n */\nexport const PluginUserLocale = z.object({\n timezone: z.string(),\n language: z.string(),\n})\nexport type PluginUserLocale = z.infer<typeof PluginUserLocale>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { PUnitType } from \"../units\"\n\n/**\n * Project-level settings and info.\n *\n * Accessed via `snaptrude.core.project`. Exposes\n * {@linkcode PluginProjectApi.getInfo} (project identity) and\n * {@linkcode PluginProjectApi.settings} (snap + grid controls).\n */\nexport abstract class PluginProjectApi {\n constructor() {}\n\n /**\n * Read identity and headline facts about the currently open project — id,\n * display name, unit type, storey count, active storey, and site location\n * when the project is geo-located.\n *\n * `activeStorey` and `storeyCount` are both scoped to the ACTIVE BUILDING, so\n * they always describe the same building. In a multi-building project\n * `storeyCount` is therefore NOT the total across every building — use\n * `core.storeys.list()`, which spans all buildings, for that.\n *\n * @returns A {@linkcode PluginProjectInfo}. `name` is `null` only when the\n * project genuinely has no title; `location` is `null` only when the project\n * is not geo-located. Neither is used to signal a failure.\n * @throws PRECONDITION_FAILED when no project is open.\n * @throws OPERATION_FAILED when the project lookup or the site-location read\n * fails. A failed read is never reported as `null`.\n *\n * @examplePrompt What is this project called?\n * @examplePrompt Where is this project located?\n * @examplePrompt Give me a summary of this project\n * @examplePrompt How many storeys does this project have?\n *\n * # Example\n * ```ts\n * const info = await snaptrude.core.project.getInfo()\n * console.log(info.name, info.units, info.storeyCount)\n * ```\n */\n public abstract getInfo(): PluginApiReturn<PluginProjectInfo>\n\n /** Project settings — snaps and grid. See {@linkcode PluginProjectSettingsApi}. */\n public abstract settings: PluginProjectSettingsApi\n}\n\n/**\n * Project settings — tolerance, snaps, and grid.\n *\n * Accessed via `snaptrude.core.project.settings`.\n */\nexport abstract class PluginProjectSettingsApi {\n constructor() {}\n\n /**\n * Get the project tolerance as a numeric magnitude.\n *\n * The engine stores tolerance as a discrete per-unit precision option; this\n * returns its magnitude (decimal precision `\"0.00\"` → `0.01`; fraction `\"1/4\"`\n * → `0.25`).\n *\n * @examplePrompt What is the tolerance setting in this project?\n * @examplePrompt Check the current dimension precision of the model\n * @examplePrompt How precise are the measurements in this project?\n * @examplePrompt Show me the project's tolerance value\n *\n * # Example\n * ```ts\n * const tolerance = await snaptrude.core.project.settings.getTolerance()\n * console.log(tolerance) // e.g. 0.01\n * ```\n */\n public abstract getTolerance(): PluginApiReturn<number>\n\n /**\n * Set the project tolerance. The value snaps to the **nearest allowed**\n * per-unit precision option (tolerance is a discrete dropdown, not arbitrary).\n *\n * @param value - Target tolerance magnitude; snaps to the nearest allowed option.\n * @returns `true` once applied; `false` if no tolerance options are available.\n *\n * @examplePrompt Set the project tolerance to 0.01\n * @examplePrompt Change the dimension precision to two decimal places\n * @examplePrompt Round dimensions to the nearest quarter inch\n * @examplePrompt Make the measurements in this project more precise\n *\n * # Example\n * ```ts\n * // snaps to the nearest allowed precision option\n * const applied = await snaptrude.core.project.settings.setTolerance(0.01)\n * if (!applied) console.log(\"no tolerance options available\")\n * ```\n */\n public abstract setTolerance(value: number): PluginApiReturn<boolean>\n\n /** Snap controls (dimension / angle / parallel / normal / magnetic). See {@linkcode PluginSnapsApi}. */\n public abstract snaps: PluginSnapsApi\n /** Grid controls. See {@linkcode PluginGridApi}. */\n public abstract grid: PluginGridApi\n}\n\n/**\n * Snap controls. Dimension and angle snaps each have a numeric threshold plus an\n * on/off toggle; parallel, normal, and magnetic snaps are on/off only.\n * (`normal` is the perpendicular snap.)\n *\n * Accessed via `snaptrude.core.project.settings.snaps`.\n */\nexport abstract class PluginSnapsApi {\n constructor() {}\n\n /**\n * Set the dimension-snap threshold — the length increment that drawing and\n * editing snap to when dimension snapping is on.\n *\n * @param value - The snap threshold distance.\n *\n * @examplePrompt Set the dimension snap to 100mm\n * @examplePrompt Change the snap distance to half a metre\n * @examplePrompt Make the cursor snap every 6 inches when drawing\n * @examplePrompt Adjust the snapping increment for lengths\n *\n * # Example\n * ```ts\n * // the threshold only applies while dimension snapping is on\n * await snaptrude.core.project.settings.snaps.setDimension(100)\n * await snaptrude.core.project.settings.snaps.enableDimension()\n * ```\n */\n public abstract setDimension(value: number): PluginApiReturn<boolean>\n /**\n * Get the current dimension-snap threshold — the length increment that\n * drawing and editing snap to when dimension snapping is on.\n *\n * @examplePrompt What is the dimension snap distance?\n * @examplePrompt Check the current snap increment for drawing\n * @examplePrompt How far apart are the length snaps set?\n * @examplePrompt Show me the snap spacing value\n *\n * # Example\n * ```ts\n * const increment = await snaptrude.core.project.settings.snaps.getDimension()\n * console.log(increment)\n * ```\n */\n public abstract getDimension(): PluginApiReturn<number>\n /**\n * Turn dimension snapping on, so drawing and editing snap to the configured\n * length increment.\n *\n * @examplePrompt Turn on dimension snapping\n * @examplePrompt Enable snapping to set lengths while I draw\n * @examplePrompt Make my walls snap to round dimensions\n * @examplePrompt Switch length snapping back on\n *\n * # Example\n * ```ts\n * await snaptrude.core.project.settings.snaps.enableDimension()\n * ```\n */\n public abstract enableDimension(): PluginApiReturn<boolean>\n /**\n * Turn dimension snapping off, so lengths no longer snap to the configured\n * increment while drawing.\n *\n * @examplePrompt Turn off dimension snapping\n * @examplePrompt Stop snapping to fixed lengths when I draw\n * @examplePrompt Disable the length snap so I can draw freely\n * @examplePrompt I don't want dimensions to snap anymore\n *\n * # Example\n * ```ts\n * await snaptrude.core.project.settings.snaps.disableDimension()\n * ```\n */\n public abstract disableDimension(): PluginApiReturn<boolean>\n\n /**\n * Set the angle-snap threshold in degrees — the angular increment that\n * drawing snaps to when angle snapping is on.\n *\n * @param value - The snap threshold in degrees.\n *\n * @examplePrompt Set the angle snap to 15 degrees\n * @examplePrompt Change angle snapping to every 45 degrees\n * @examplePrompt Make drawing snap at 30 degree increments\n * @examplePrompt Adjust the rotation snap angle\n *\n * # Example\n * ```ts\n * // snap drawing to 15-degree increments (applies while angle snapping is on)\n * await snaptrude.core.project.settings.snaps.setAngle(15)\n * await snaptrude.core.project.settings.snaps.enableAngle()\n * ```\n */\n public abstract setAngle(value: number): PluginApiReturn<boolean>\n /**\n * Get the current angle-snap threshold in degrees — the angular increment\n * that drawing snaps to when angle snapping is on.\n *\n * @examplePrompt What angle does drawing snap to?\n * @examplePrompt Check the current angle snap in degrees\n * @examplePrompt Show me the angle snapping increment\n * @examplePrompt How many degrees is the angle snap set to?\n *\n * # Example\n * ```ts\n * const degrees = await snaptrude.core.project.settings.snaps.getAngle()\n * console.log(degrees)\n * ```\n */\n public abstract getAngle(): PluginApiReturn<number>\n /**\n * Turn angle snapping on, so drawn lines snap to the configured angle\n * increment.\n *\n * @examplePrompt Turn on angle snapping\n * @examplePrompt Enable snapping to angles while drawing\n * @examplePrompt Make my lines snap to set angles\n * @examplePrompt Switch angle snap back on\n *\n * # Example\n * ```ts\n * await snaptrude.core.project.settings.snaps.enableAngle()\n * ```\n */\n public abstract enableAngle(): PluginApiReturn<boolean>\n /**\n * Turn angle snapping off, so lines can be drawn at any angle without\n * snapping to fixed increments.\n *\n * @examplePrompt Turn off angle snapping\n * @examplePrompt Stop lines snapping to fixed angles\n * @examplePrompt Disable the angle snap so I can draw at any angle\n * @examplePrompt Remove angle snapping while sketching\n *\n * # Example\n * ```ts\n * await snaptrude.core.project.settings.snaps.disableAngle()\n * ```\n */\n public abstract disableAngle(): PluginApiReturn<boolean>\n\n /**\n * Turn parallel snapping on, so drawing snaps when the cursor lines up\n * parallel to existing edges.\n *\n * @examplePrompt Turn on parallel snapping\n * @examplePrompt Enable snapping to parallel lines\n * @examplePrompt Help me draw walls parallel to existing ones\n * @examplePrompt Snap to parallel edges when drawing\n *\n * # Example\n * ```ts\n * await snaptrude.core.project.settings.snaps.enableParallel()\n * ```\n */\n public abstract enableParallel(): PluginApiReturn<boolean>\n /**\n * Turn parallel snapping off, so drawing no longer snaps to directions\n * parallel to existing edges.\n *\n * @examplePrompt Turn off parallel snapping\n * @examplePrompt Stop snapping to parallel edges\n * @examplePrompt Disable the parallel snap guide\n * @examplePrompt I don't want my lines snapping parallel anymore\n *\n * # Example\n * ```ts\n * await snaptrude.core.project.settings.snaps.disableParallel()\n * ```\n */\n public abstract disableParallel(): PluginApiReturn<boolean>\n\n /**\n * Turn normal (perpendicular) snapping on, so drawing snaps when the cursor\n * is at a right angle to existing edges.\n *\n * @examplePrompt Turn on perpendicular snapping\n * @examplePrompt Enable the normal snap so lines snap at right angles\n * @examplePrompt Snap to perpendicular when I draw walls\n * @examplePrompt Help me draw at 90 degrees to existing edges\n *\n * # Example\n * ```ts\n * await snaptrude.core.project.settings.snaps.enableNormal()\n * ```\n */\n public abstract enableNormal(): PluginApiReturn<boolean>\n /**\n * Turn normal (perpendicular) snapping off, so drawing no longer snaps at\n * right angles to existing edges.\n *\n * @examplePrompt Turn off perpendicular snapping\n * @examplePrompt Disable the normal snap\n * @examplePrompt Stop snapping at right angles to edges\n * @examplePrompt Remove the 90 degree snap guide\n *\n * # Example\n * ```ts\n * await snaptrude.core.project.settings.snaps.disableNormal()\n * ```\n */\n public abstract disableNormal(): PluginApiReturn<boolean>\n\n /**\n * Turn magnetic snapping on, so the cursor is pulled to nearby geometry\n * while drawing.\n *\n * @examplePrompt Turn on magnetic snapping\n * @examplePrompt Enable the magnet snap\n * @examplePrompt Make the cursor stick to nearby geometry when drawing\n * @examplePrompt Switch magnetic snap back on\n *\n * # Example\n * ```ts\n * await snaptrude.core.project.settings.snaps.enableMagnetic()\n * ```\n */\n public abstract enableMagnetic(): PluginApiReturn<boolean>\n /**\n * Turn magnetic snapping off, so the cursor is no longer pulled to nearby\n * geometry while drawing.\n *\n * @examplePrompt Turn off magnetic snapping\n * @examplePrompt Disable the magnet snap\n * @examplePrompt Stop the cursor sticking to nearby objects\n * @examplePrompt The magnetic snap is annoying, switch it off\n *\n * # Example\n * ```ts\n * await snaptrude.core.project.settings.snaps.disableMagnetic()\n * ```\n */\n public abstract disableMagnetic(): PluginApiReturn<boolean>\n}\n\n/**\n * Grid controls — the visual snap-to grid.\n *\n * Accessed via `snaptrude.core.project.settings.grid`.\n */\nexport abstract class PluginGridApi {\n constructor() {}\n\n /**\n * Turn the grid on, showing the visual snap-to grid in the canvas.\n *\n * @examplePrompt Turn on the grid\n * @examplePrompt Show the grid in my project\n * @examplePrompt Enable the drawing grid\n * @examplePrompt Switch the snap grid back on\n *\n * # Example\n * ```ts\n * await snaptrude.core.project.settings.grid.enable()\n * ```\n */\n public abstract enable(): PluginApiReturn<boolean>\n /**\n * Turn the grid off, hiding the visual snap-to grid in the canvas.\n *\n * @examplePrompt Turn off the grid\n * @examplePrompt Hide the grid\n * @examplePrompt Disable the background grid in the canvas\n * @examplePrompt Remove the drawing grid from view\n *\n * # Example\n * ```ts\n * await snaptrude.core.project.settings.grid.disable()\n * ```\n */\n public abstract disable(): PluginApiReturn<boolean>\n /**\n * Set the grid cell value — the size of each cell in the visual snap-to\n * grid.\n *\n * @param value - The grid cell value.\n *\n * @examplePrompt Set the grid spacing to 1 metre\n * @examplePrompt Change the grid cell size to 500mm\n * @examplePrompt Make the grid squares 2 feet wide\n * @examplePrompt Adjust the grid size in this project\n *\n * # Example\n * ```ts\n * await snaptrude.core.project.settings.grid.setValue(1)\n * await snaptrude.core.project.settings.grid.enable()\n * ```\n */\n public abstract setValue(value: number): PluginApiReturn<boolean>\n /**\n * Get the current grid cell value — the size of each cell in the visual\n * snap-to grid.\n *\n * @examplePrompt What is the grid spacing?\n * @examplePrompt Check the current grid cell size\n * @examplePrompt How big are the grid squares?\n * @examplePrompt Show me the grid size setting\n *\n * # Example\n * ```ts\n * const cellSize = await snaptrude.core.project.settings.grid.getValue()\n * console.log(cellSize)\n * ```\n */\n public abstract getValue(): PluginApiReturn<number>\n}\n\n/**\n * Arguments for the snap-threshold setters\n * ({@linkcode PluginSnapsApi.setDimension} / {@linkcode PluginSnapsApi.setAngle}).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `value` | `number` | The snap threshold (distance, or degrees for angle) |\n */\nexport const PluginSnapValueArgs = z.object({\n value: z.number(),\n})\n\nexport type PluginSnapValueArgs = z.infer<typeof PluginSnapValueArgs>\n\n/**\n * Arguments for {@linkcode PluginGridApi.setValue}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `value` | `number` | The grid cell value |\n */\nexport const PluginGridValueArgs = z.object({\n value: z.number(),\n})\n\nexport type PluginGridValueArgs = z.infer<typeof PluginGridValueArgs>\n\n/**\n * Arguments for {@linkcode PluginProjectSettingsApi.setTolerance}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `value` | `number` | Target tolerance magnitude; snaps to the nearest allowed option |\n */\nexport const PluginToleranceArgs = z.object({\n value: z.number(),\n})\n\nexport type PluginToleranceArgs = z.infer<typeof PluginToleranceArgs>\n\n/**\n * Identity and headline facts about the currently open project.\n *\n * `location` is a read-only projection of {@linkcode PluginProgramSiteApi.getLocation}\n * — `program.site.*` remains the full site surface (context, weather, polygons).\n * It is mirrored here because \"what and where is this project\" is one question.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `projectId` | `string` | The open project's id (floorkey) |\n * | `name` | `string \\| null` | Display name; `null` only when the project has no title (a failed lookup throws) |\n * | `units` | {@linkcode PUnitType} | The project's unit type |\n * | `activeStorey` | `number` | The active storey value, in the active building |\n * | `storeyCount` | `number` | How many storeys the ACTIVE BUILDING has (not the project-wide total — `core.storeys.list()` spans all buildings) |\n * | `location` | `{ latitude, longitude } \\| null` | Site location; `null` only when not geo-located (a failed read throws) |\n */\nexport const PluginProjectInfo = z.object({\n projectId: z.string(),\n name: z.string().nullable(),\n units: PUnitType,\n activeStorey: z.number(),\n storeyCount: z.number(),\n location: z\n .object({ latitude: z.number(), longitude: z.number() })\n .nullable(),\n})\n\nexport type PluginProjectInfo = z.infer<typeof PluginProjectInfo>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { Vec3Handle, ComponentHandle, Vec3Components } from \"../../../handles\"\n\n/**\n * Comments — create, edit, resolve, tag, and list scene/project comments.\n *\n * A comment is an opaque {@link CommentHandle} (its server id). Comments may be\n * anchored to a component at a 3D point, carry @mentions (by email) and emojis,\n * and be resolved / reopened. Accessed via `snaptrude.core.comment`.\n */\nexport abstract class PluginCommentApi {\n constructor() {}\n\n /**\n * Create a comment in the project — optionally pinned to a component at a 3D\n * point — with optional @mentions that notify teammates by email.\n *\n * @param message - Comment text (emojis = plain unicode).\n * @param component - Optional component to anchor the comment to.\n * @param position - Optional pin point; defaults to the component's center.\n * @param mentions - Optional user emails to @mention (appended as real @mention tags).\n * @returns the new {@link CommentHandle}.\n *\n * @examplePrompt Add a comment on this wall saying the height looks wrong\n * @examplePrompt Leave a note on the selected room for the design team\n * @examplePrompt Comment \"verify the door swing\" and tag alex@acme.com\n * @examplePrompt Pin a review note at this point on the slab\n *\n * # Example\n * ```ts\n * const c = await snaptrude.core.comment.create(\n * \"Check this wall 🎉\",\n * spaceId,\n * undefined,\n * [\"alex@acme.com\"],\n * )\n * ```\n */\n public abstract create(\n message: string,\n component?: ComponentHandle,\n position?: Vec3Handle,\n mentions?: string[],\n ): PluginApiReturn<CommentHandle>\n\n /**\n * Edit a comment's message (and optional @mentions).\n *\n * @param comment - The comment to edit.\n * @param message - New message text.\n * @param mentions - Optional user emails to @mention.\n *\n * @examplePrompt Edit my comment to say the beam depth is 600mm\n * @examplePrompt Change the text of this comment\n * @examplePrompt Update the note on the wall and mention priya@studio.com\n * @examplePrompt Reword the comment I just added\n *\n * # Example\n * ```ts\n * const c = await snaptrude.core.comment.create(\"Beam depth TBD\")\n * await snaptrude.core.comment.update(c, \"Beam depth is 600mm\", [\"priya@studio.com\"])\n * ```\n */\n public abstract update(\n comment: CommentHandle,\n message: string,\n mentions?: string[],\n ): PluginApiReturn<boolean>\n\n /**\n * Mark a comment resolved. Paired with {@link PluginCommentApi.reopen}.\n *\n * @param comment - The target comment.\n *\n * @examplePrompt Mark this comment as resolved\n * @examplePrompt Resolve the comment about the staircase\n * @examplePrompt Close out this review note\n * @examplePrompt Set that comment to done\n *\n * # Example\n * ```ts\n * const c = await snaptrude.core.comment.create(\"Verify the door swing\")\n * await snaptrude.core.comment.resolve(c)\n * ```\n */\n public abstract resolve(comment: CommentHandle): PluginApiReturn<boolean>\n\n /**\n * Reopen a resolved comment. Paired with {@link PluginCommentApi.resolve}.\n *\n * @param comment - The target comment.\n *\n * @examplePrompt Reopen this resolved comment\n * @examplePrompt Mark the comment as unresolved again\n * @examplePrompt Bring back the closed note about the window heights\n * @examplePrompt Un-resolve that comment so we can keep discussing it\n *\n * # Example\n * ```ts\n * const [comment] = await snaptrude.core.comment.list()\n * await snaptrude.core.comment.reopen(comment)\n * ```\n */\n public abstract reopen(comment: CommentHandle): PluginApiReturn<boolean>\n\n /**\n * Check whether a comment has been marked resolved.\n *\n * @param comment - The target comment.\n *\n * @examplePrompt Is this comment resolved yet?\n * @examplePrompt Check if the note about the roof has been closed\n * @examplePrompt Has my comment been resolved?\n * @examplePrompt Tell me whether that comment is still open\n *\n * # Example\n * ```ts\n * const [comment] = await snaptrude.core.comment.list()\n * const resolved = await snaptrude.core.comment.isResolved(comment)\n * console.log(resolved ? \"resolved\" : \"still open\")\n * ```\n */\n public abstract isResolved(comment: CommentHandle): PluginApiReturn<boolean>\n\n /**\n * Tag (mention) a user on an existing comment, by email.\n *\n * @param comment - The comment to add a mention to.\n * @param email - The user's email to @mention.\n *\n * @examplePrompt Tag alex@acme.com on this comment\n * @examplePrompt Mention the structural engineer in that note\n * @examplePrompt Add priya@studio.com to the comment about the lobby\n * @examplePrompt Notify a teammate about an existing comment\n *\n * # Example\n * ```ts\n * const [comment] = await snaptrude.core.comment.list()\n * await snaptrude.core.comment.tag(comment, \"alex@acme.com\")\n * ```\n */\n public abstract tag(\n comment: CommentHandle,\n email: string,\n ): PluginApiReturn<boolean>\n\n /**\n * Permanently remove a comment from the project.\n *\n * @param comment - The target comment.\n *\n * @examplePrompt Delete this comment\n * @examplePrompt Remove the note about the old layout\n * @examplePrompt Get rid of my last comment\n * @examplePrompt Clear that resolved comment off the wall\n *\n * # Example\n * ```ts\n * const [comment] = await snaptrude.core.comment.list()\n * await snaptrude.core.comment.delete(comment)\n * ```\n */\n public abstract delete(comment: CommentHandle): PluginApiReturn<boolean>\n\n /**\n * List every comment in the project (deleted ones are excluded), returned as\n * comment handles you can pass to the other comment methods.\n *\n * @examplePrompt List all the comments in this project\n * @examplePrompt How many comments are on this model?\n * @examplePrompt Show every note in the file\n * @examplePrompt Get all comments so I can check which are resolved\n *\n * # Example\n * ```ts\n * const comments = await snaptrude.core.comment.list()\n * console.log(`${comments.length} comments in the project`)\n * ```\n */\n public abstract list(): PluginApiReturn<CommentHandle[]>\n\n /**\n * Read a comment's full detail — its text, author, pin location, resolved\n * state, and reply thread. Comments are otherwise write-only; this is the read\n * side. Pass a top-level comment handle or a reply handle (a reply has an empty\n * `thread`). Returns `null` if the handle doesn't match a live comment.\n *\n * @param comment - The comment (or reply) to read.\n * @returns the {@link PluginCommentDetails}, or `null` if not found.\n *\n * @examplePrompt Read the comment on this wall and tell me what it says\n * @examplePrompt Who left this comment and where is it pinned?\n * @examplePrompt Show me the full thread for this comment\n * @examplePrompt Get the text and author of the selected comment\n *\n * # Example\n * ```ts\n * const [first] = await snaptrude.core.comment.list()\n * const details = await snaptrude.core.comment.get(first)\n * console.log(details?.content, \"by\", details?.author?.name)\n * console.log(`${details?.thread.length ?? 0} replies`)\n * ```\n */\n public abstract get(\n comment: CommentHandle,\n ): PluginApiReturn<PluginCommentDetails | null>\n\n /**\n * Reply to a comment — posts a new comment threaded under `comment` (which must\n * be a top-level comment, not itself a reply). Notifies the thread's\n * participants, mirroring the sidebar Reply action.\n *\n * @param comment - The top-level comment to reply to.\n * @param text - The reply text (emojis = plain unicode).\n * @returns the new reply's {@link CommentHandle}.\n *\n * @examplePrompt Reply to this comment saying I fixed the wall height\n * @examplePrompt Add a reply to the review note confirming it's done\n * @examplePrompt Respond to the comment about the door with \"looks good now\"\n * @examplePrompt Post a follow-up on the selected comment thread\n *\n * # Example\n * ```ts\n * const [thread] = await snaptrude.core.comment.list()\n * const reply = await snaptrude.core.comment.reply(thread, \"Fixed 👍\")\n * ```\n */\n public abstract reply(\n comment: CommentHandle,\n text: string,\n ): PluginApiReturn<CommentHandle>\n}\n\n/**\n * Opaque handle to a comment — its server-assigned id (entity-style: a raw,\n * persistable string resolved live, not a registry-minted value handle).\n */\nexport type CommentHandle = string & { readonly __handle: \"comment\" }\nexport const CommentHandle = z\n .string()\n .min(1)\n .transform((s) => s as CommentHandle)\n\n/** `ComponentHandle` is defined in `handles.ts` (imported above) and exported from the\n * package root via `export * from \"./handles\"`. Not re-exported here to avoid an\n * ambiguous double `export *` at the barrel. */\n\n/**\n * Arguments for {@link PluginCommentApi.create}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `component` | {@link ComponentHandle}? | Component to anchor the comment to |\n * | `position` | {@link Vec3Handle}? | Pin point; defaults to the component's center |\n * | `message` | `string` | Comment text (emojis = unicode) |\n * | `mentions` | `string[]`? | User emails to @mention |\n */\nexport const PluginCommentCreateArgs = z.object({\n component: ComponentHandle.optional(),\n position: Vec3Handle.optional(),\n message: z.string(),\n mentions: z.array(z.string()).optional(),\n})\n\nexport type PluginCommentCreateArgs = z.infer<typeof PluginCommentCreateArgs>\n\n/**\n * Arguments for {@link PluginCommentApi.update}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `comment` | {@link CommentHandle} | The comment to edit |\n * | `message` | `string` | New message text |\n * | `mentions` | `string[]`? | User emails to @mention |\n */\nexport const PluginCommentUpdateArgs = z.object({\n comment: CommentHandle,\n message: z.string(),\n mentions: z.array(z.string()).optional(),\n})\n\nexport type PluginCommentUpdateArgs = z.infer<typeof PluginCommentUpdateArgs>\n\n/**\n * Arguments for {@link PluginCommentApi.resolve} / {@link PluginCommentApi.reopen}\n * / {@link PluginCommentApi.isResolved} / {@link PluginCommentApi.delete}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `comment` | {@link CommentHandle} | The target comment |\n */\nexport const PluginCommentRefArgs = z.object({\n comment: CommentHandle,\n})\n\nexport type PluginCommentRefArgs = z.infer<typeof PluginCommentRefArgs>\n\n/**\n * Arguments for {@link PluginCommentApi.tag}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `comment` | {@link CommentHandle} | The comment to add a mention to |\n * | `email` | `string` | The user's email to @mention |\n */\nexport const PluginCommentTagArgs = z.object({\n comment: CommentHandle,\n email: z.string(),\n})\n\nexport type PluginCommentTagArgs = z.infer<typeof PluginCommentTagArgs>\n\n/**\n * Arguments for {@link PluginCommentApi.reply}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `comment` | {@link CommentHandle} | The top-level comment to reply to |\n * | `text` | `string` | The reply text (emojis = unicode) |\n */\nexport const PluginCommentReplyArgs = z.object({\n comment: CommentHandle,\n text: z.string(),\n})\n\nexport type PluginCommentReplyArgs = z.infer<typeof PluginCommentReplyArgs>\n\n/**\n * The author of a comment or reply — the display name and email of the teammate\n * who posted it. `null` on the rare comment with no recorded author.\n */\nexport interface PluginCommentAuthor {\n /** The author's display name (falls back to their email). */\n name: string\n /** The author's email. */\n email: string\n}\n\n/**\n * One reply in a comment's thread (returned by {@link PluginCommentApi.get}).\n */\nexport interface PluginCommentReply {\n /** The reply's own {@link CommentHandle}. */\n id: CommentHandle\n /** The reply text. */\n content: string\n /** Who posted the reply. */\n author: PluginCommentAuthor | null\n /** ISO-8601 timestamp of when the reply was posted (empty if unknown). */\n createdAt: string\n}\n\n/**\n * The read model of a comment — returned by {@link PluginCommentApi.get}. The\n * inverse of {@link PluginCommentApi.create}: `content`/`component`/`position`\n * echo what was posted, plus the resolved state and reply `thread`.\n */\nexport interface PluginCommentDetails {\n /** The comment's {@link CommentHandle}. */\n id: CommentHandle\n /** The comment text. */\n content: string\n /** Who posted the comment. */\n author: PluginCommentAuthor | null\n /** The pin point (engine world coordinates), or `null` if un-anchored. */\n position: Vec3Components | null\n /** The component the comment is anchored to, or `null` if un-anchored. */\n component: ComponentHandle | null\n /** Whether the comment has been marked resolved. */\n isResolved: boolean\n /** ISO-8601 timestamp of when the comment was posted (empty if unknown). */\n createdAt: string\n /** The reply thread, oldest first. Empty for a comment with no replies. */\n thread: PluginCommentReply[]\n}\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport { ComponentHandle, Vec3Components } from \"../../handles\"\n\n/**\n * Groups — read and edit the group hierarchy of the active project.\n *\n * A **group** is a named, lockable container that bundles components (and other\n * groups) so they can be selected and transformed as one. This is the\n * plugin-facing view of that hierarchy: a group's identity, name, member count,\n * lock state, and parent — never the underlying meshes.\n *\n * Reads return plain records and never throw for a missing group (`get` returns\n * `null`, `list` returns `[]`, `isLocked` returns `false`). Ids are stable public\n * strings, never engine mesh handles.\n *\n * Members are moved and copied through the design operations by group id — use\n * {@linkcode PluginDesignApi.transform}`.move` and `design.create.copy`, not a method here.\n * Locking likewise routes through `design.lock`.\n *\n * Sub-namespace members typed as optional (`?`) are **authored ahead of the\n * host** — declared so plugins and codegen can see the shape, but kept out of the\n * discovery manifest until the host lands them (at which point they are promoted\n * to required `abstract` members).\n *\n * Accessed via `snaptrude.core.groups`.\n */\nexport abstract class PluginCoreGroupsApi {\n constructor() {}\n\n /**\n * List every group in the active project.\n *\n * Returns each group's identity, member count, lock state, and parent group\n * (when nested), for the whole project.\n *\n * @returns A {@linkcode PluginCoreGroupsListResult} with a `groups` array\n * (empty when the project has none).\n *\n * @examplePrompt List all the groups in my project\n * @examplePrompt What groups do I have and how many things are in each?\n * @examplePrompt Show every group with its lock state\n * @examplePrompt How many groups are in this model?\n *\n * # Example\n * ```ts\n * const { groups } = await snaptrude.core.groups.list()\n * for (const g of groups) console.log(g.name, g.memberCount, g.isLocked)\n * ```\n */\n public abstract list(): PluginApiReturn<PluginCoreGroupsListResult>\n\n /**\n * Get a single group by id.\n *\n * @param groupId - The id of the group to read.\n * @returns The matching {@linkcode PluginCoreGroupRef}, or `null` if no group\n * has that id.\n *\n * @examplePrompt Get the group with id group-12\n * @examplePrompt Look up a single group by its id\n * @examplePrompt Is group-3 locked and how many members does it have?\n *\n * # Example\n * ```ts\n * const group = await snaptrude.core.groups.get(\"group-12\")\n * if (group) console.log(group.name, group.memberCount)\n * ```\n */\n public abstract get(\n groupId: string,\n ): PluginApiReturn<PluginCoreGroupsGetResult>\n\n /**\n * Get the group a component belongs to.\n *\n * Returns the **innermost** group directly containing the component, or `null`\n * if the component is ungrouped or does not exist.\n *\n * @param componentId - The `Component.id` of the component to look up.\n * @returns The innermost {@linkcode PluginCoreGroupRef} containing the\n * component, or `null`.\n *\n * @examplePrompt Which group is this wall in?\n * @examplePrompt Find the group that contains component cmp_42\n * @examplePrompt Is this element grouped, and if so which group?\n *\n * # Example\n * ```ts\n * const group = await snaptrude.core.groups.getForComponent(\"cmp_42\")\n * console.log(group ? group.name : \"ungrouped\")\n * ```\n */\n public abstract getForComponent(\n componentId: ComponentHandle,\n ): PluginApiReturn<PluginCoreGroupsGetForComponentResult>\n\n /**\n * List the direct members of a group.\n *\n * Returns each direct child as an id + kind (`component` or a nested `group`).\n * Nested groups are returned as members, not flattened.\n *\n * @param groupId - The id of the group whose members to read.\n * @returns A {@linkcode PluginCoreGroupsListMembersResult} with a `members`\n * array (empty when the group is empty or missing).\n *\n * @examplePrompt What's inside group-12?\n * @examplePrompt List the members of this group\n * @examplePrompt Show the components and subgroups in group-3\n *\n * # Example\n * ```ts\n * const { members } = await snaptrude.core.groups.listMembers(\"group-12\")\n * for (const m of members) console.log(m.type, m.id)\n * ```\n */\n public abstract listMembers(\n groupId: string,\n ): PluginApiReturn<PluginCoreGroupsListMembersResult>\n\n /**\n * Report whether a group is locked.\n *\n * A predicate: it never mutates and never throws — an unknown group id returns\n * `false`.\n *\n * @param groupId - The id of the group to test.\n * @returns `true` if the group exists and is locked, otherwise `false`.\n *\n * @examplePrompt Is group-12 locked?\n * @examplePrompt Check whether this group is locked before editing it\n * @examplePrompt Tell me if the group is locked\n *\n * # Example\n * ```ts\n * if (await snaptrude.core.groups.isLocked(\"group-12\")) {\n * console.log(\"locked — unlock it first\")\n * }\n * ```\n */\n public abstract isLocked(\n groupId: string,\n ): PluginApiReturn<PluginCoreGroupsIsLockedResult>\n\n /**\n * Get the axis-aligned bounds of a group.\n *\n * Returns the world-space bounding box of everything in the group — its `min`\n * and `max` corners and the `center` — in internal units, or `null` when the\n * group is empty (has no meshes) or does not exist. `null` is distinct from a\n * real group at the origin, whose box is centered on `{0,0,0}`.\n *\n * @param groupId - The id of the group to measure.\n * @returns A {@linkcode PluginCoreGroupsGetBoundsResult} with `min`, `max`, and\n * `center` points, or `null` if the group is empty or gone.\n *\n * @examplePrompt How big is this group?\n * @examplePrompt Get the bounding box of group-12\n * @examplePrompt Where is the center of this group?\n *\n * # Example\n * ```ts\n * const bounds = await snaptrude.core.groups.getBounds(\"group-12\")\n * if (bounds) console.log(bounds.center.x, bounds.center.y, bounds.center.z)\n * ```\n */\n public abstract getBounds(\n groupId: string,\n ): PluginApiReturn<PluginCoreGroupsGetBoundsResult>\n\n /**\n * Group components into a new group.\n *\n * Bundles the given components under a new group. Pass a `name` to label it;\n * otherwise it gets an auto-generated fallback name that you can change later\n * with {@linkcode PluginCoreGroupsApi.update}.\n *\n * @param componentIds - The ids of the components to group (at least one).\n * @param name - Display name for the new group (auto-generated if omitted).\n * @returns The new group's id (read the full record back with\n * {@linkcode PluginCoreGroupsApi.get}).\n * @throws If a component id is invalid or the group could not be created.\n *\n * @examplePrompt Group the selected components\n * @examplePrompt Make a group called Kitchen from these elements\n * @examplePrompt Bundle components cmp_1 and cmp_2 into a group\n *\n * # Example\n * ```ts\n * const { groupId } = await snaptrude.core.groups.create([\"cmp_1\", \"cmp_2\"], \"Kitchen\")\n * ```\n */\n public abstract create(\n componentIds: ComponentHandle[],\n name?: string,\n ): PluginApiReturn<PluginCoreGroupsCreateResult>\n\n /**\n * Update a group's name and/or membership.\n *\n * A **sparse** update: omitted fields are left unchanged. `addComponentIds`\n * and `removeComponentIds` are applied as membership deltas (add before\n * remove); `name` renames the group.\n *\n * @param groupId - The id of the group to update.\n * @param options - Any of `name`, `addComponentIds`, `removeComponentIds`;\n * omitted fields are left unchanged.\n * @returns The updated {@linkcode PluginCoreGroupRef}.\n * @throws If no group has the given id, or a component id is invalid.\n *\n * @examplePrompt Rename group-12 to Kitchen\n * @examplePrompt Add these two walls to the group\n * @examplePrompt Remove cmp_7 from group-3\n *\n * # Example\n * ```ts\n * const group = await snaptrude.core.groups.update(\"group-12\", {\n * name: \"Kitchen\",\n * addComponentIds: [\"cmp_9\"],\n * })\n * ```\n */\n public abstract update(\n groupId: string,\n options?: {\n name?: string\n addComponentIds?: ComponentHandle[]\n removeComponentIds?: ComponentHandle[]\n },\n ): PluginApiReturn<PluginCoreGroupsUpdateResult>\n\n /**\n * Delete (dissolve) a group.\n *\n * Dissolves the group container. The member elements are **not** deleted —\n * they are returned to the group's parent (or ungrouped at the top level).\n *\n * @param groupId - The id of the group to dissolve.\n * @returns Confirmation that the group was dissolved.\n * @throws If no group has the given id.\n *\n * @examplePrompt Ungroup group-12\n * @examplePrompt Dissolve this group but keep its contents\n * @examplePrompt Delete the group without deleting its members\n *\n * # Example\n * ```ts\n * await snaptrude.core.groups.delete(\"group-12\")\n * ```\n */\n public abstract delete(\n groupId: string,\n ): PluginApiReturn<PluginCoreGroupsDeleteResult>\n}\n\n/**\n * A group in the active project.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Stable public group id (e.g. `\"group-12\"`) — never a mesh handle |\n * | `name` | `string` | Display name (auto-generated fallback until the user sets one) |\n * | `memberCount` | `number` | Number of direct members (components + nested groups) |\n * | `isLocked` | `boolean` | Whether the group is locked against edits |\n * | `parentId` | `string \\| undefined` | Parent group id when this group is nested, else absent |\n */\nexport const PluginCoreGroupRef = z.object({\n id: z.string(),\n name: z.string(),\n memberCount: z.number().int(),\n isLocked: z.boolean(),\n parentId: z.string().optional(),\n})\nexport type PluginCoreGroupRef = z.infer<typeof PluginCoreGroupRef>\n\n/**\n * A direct member of a group.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | `Component.id` for a component member, or the group id for a nested group |\n * | `type` | `\"component\" \\| \"group\"` | Whether this member is a component or a nested group |\n */\nexport const PluginCoreGroupMember = z.object({\n id: z.string(),\n type: z.enum([\"component\", \"group\"]),\n})\nexport type PluginCoreGroupMember = z.infer<typeof PluginCoreGroupMember>\n\n/**\n * Result of {@linkcode PluginCoreGroupsApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `groups` | {@linkcode PluginCoreGroupRef}`[]` | Every group in the active project |\n */\nexport const PluginCoreGroupsListResult = z.object({\n groups: z.array(PluginCoreGroupRef),\n})\nexport type PluginCoreGroupsListResult = z.infer<\n typeof PluginCoreGroupsListResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreGroupsApi.get}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `groupId` | `string` | The id of the group to read |\n */\nexport const PluginCoreGroupsGetArgs = z.object({\n groupId: z.string(),\n})\nexport type PluginCoreGroupsGetArgs = z.infer<typeof PluginCoreGroupsGetArgs>\n\n/**\n * Result of {@linkcode PluginCoreGroupsApi.get} — the group, or `null` when no\n * group has the given id.\n */\nexport const PluginCoreGroupsGetResult = PluginCoreGroupRef.nullable()\nexport type PluginCoreGroupsGetResult = z.infer<\n typeof PluginCoreGroupsGetResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreGroupsApi.getForComponent}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `componentId` | `ComponentHandle` | The `Component.id` to look up |\n */\nexport const PluginCoreGroupsGetForComponentArgs = z.object({\n componentId: ComponentHandle,\n})\nexport type PluginCoreGroupsGetForComponentArgs = z.infer<\n typeof PluginCoreGroupsGetForComponentArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreGroupsApi.getForComponent} — the innermost\n * group containing the component, or `null`.\n */\nexport const PluginCoreGroupsGetForComponentResult =\n PluginCoreGroupRef.nullable()\nexport type PluginCoreGroupsGetForComponentResult = z.infer<\n typeof PluginCoreGroupsGetForComponentResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreGroupsApi.listMembers}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `groupId` | `string` | The id of the group whose members to read |\n */\nexport const PluginCoreGroupsListMembersArgs = z.object({\n groupId: z.string(),\n})\nexport type PluginCoreGroupsListMembersArgs = z.infer<\n typeof PluginCoreGroupsListMembersArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreGroupsApi.listMembers}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `members` | {@linkcode PluginCoreGroupMember}`[]` | The group's direct members (empty when the group is empty or missing) |\n */\nexport const PluginCoreGroupsListMembersResult = z.object({\n members: z.array(PluginCoreGroupMember),\n})\nexport type PluginCoreGroupsListMembersResult = z.infer<\n typeof PluginCoreGroupsListMembersResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreGroupsApi.isLocked}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `groupId` | `string` | The id of the group to test |\n */\nexport const PluginCoreGroupsIsLockedArgs = z.object({\n groupId: z.string(),\n})\nexport type PluginCoreGroupsIsLockedArgs = z.infer<\n typeof PluginCoreGroupsIsLockedArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreGroupsApi.isLocked} — `true` when the group\n * exists and is locked, else `false`.\n */\nexport const PluginCoreGroupsIsLockedResult = z.boolean()\nexport type PluginCoreGroupsIsLockedResult = z.infer<\n typeof PluginCoreGroupsIsLockedResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreGroupsApi.getBounds}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `groupId` | `string` | The id of the group to measure |\n */\nexport const PluginCoreGroupsGetBoundsArgs = z.object({\n groupId: z.string(),\n})\nexport type PluginCoreGroupsGetBoundsArgs = z.infer<\n typeof PluginCoreGroupsGetBoundsArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreGroupsApi.getBounds} — the group's world-space\n * axis-aligned bounds in internal units, or `null` when the group is empty (has\n * no meshes) or does not exist.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `min` | {@linkcode Vec3Components} | The minimum corner (`x`, `y`, `z`) |\n * | `max` | {@linkcode Vec3Components} | The maximum corner (`x`, `y`, `z`) |\n * | `center` | {@linkcode Vec3Components} | The box center (`x`, `y`, `z`) |\n */\nexport const PluginCoreGroupsGetBoundsResult = z\n .object({\n min: Vec3Components,\n max: Vec3Components,\n center: Vec3Components,\n })\n .nullable()\nexport type PluginCoreGroupsGetBoundsResult = z.infer<\n typeof PluginCoreGroupsGetBoundsResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreGroupsApi.create}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `componentIds` | `ComponentHandle[]` | The ids of the components to group (at least one) |\n * | `name` | `string \\| undefined` | Display name for the new group (auto-generated if omitted) |\n */\nexport const PluginCoreGroupsCreateArgs = z.object({\n componentIds: z.array(ComponentHandle).min(1),\n name: z.string().optional(),\n})\nexport type PluginCoreGroupsCreateArgs = z.infer<\n typeof PluginCoreGroupsCreateArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreGroupsApi.create} — the id of the new group.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `groupId` | `string` | The id of the newly created group |\n */\nexport const PluginCoreGroupsCreateResult = z.object({\n groupId: z.string(),\n})\nexport type PluginCoreGroupsCreateResult = z.infer<\n typeof PluginCoreGroupsCreateResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreGroupsApi.update}. Sparse — only the\n * provided fields are changed.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `groupId` | `string` | The id of the group to update |\n * | `name` | `string \\| undefined` | New display name (unchanged if omitted) |\n * | `addComponentIds` | `ComponentHandle[] \\| undefined` | Components to add to the group |\n * | `removeComponentIds` | `ComponentHandle[] \\| undefined` | Components to remove from the group |\n */\nexport const PluginCoreGroupsUpdateArgs = z.object({\n groupId: z.string(),\n name: z.string().optional(),\n addComponentIds: z.array(ComponentHandle).optional(),\n removeComponentIds: z.array(ComponentHandle).optional(),\n})\nexport type PluginCoreGroupsUpdateArgs = z.infer<\n typeof PluginCoreGroupsUpdateArgs\n>\n\n/** Result of {@linkcode PluginCoreGroupsApi.update} — the updated group record. */\nexport const PluginCoreGroupsUpdateResult = PluginCoreGroupRef\nexport type PluginCoreGroupsUpdateResult = z.infer<\n typeof PluginCoreGroupsUpdateResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreGroupsApi.delete}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `groupId` | `string` | The id of the group to dissolve |\n */\nexport const PluginCoreGroupsDeleteArgs = z.object({\n groupId: z.string(),\n})\nexport type PluginCoreGroupsDeleteArgs = z.infer<\n typeof PluginCoreGroupsDeleteArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreGroupsApi.delete} — confirmation that the group\n * was dissolved (its members are preserved).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `deleted` | `true` | Always `true` on success |\n */\nexport const PluginCoreGroupsDeleteResult = z.object({\n deleted: z.literal(true),\n})\nexport type PluginCoreGroupsDeleteResult = z.infer<\n typeof PluginCoreGroupsDeleteResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * Buildings — read and edit the buildings of the active project.\n *\n * A **building** is the top of the project's spatial hierarchy: a named container\n * of storeys, positioned by its base elevation. This namespace reads a building's\n * identity and base elevation and lists the storeys it holds; storey detail is\n * read through the entity story surface (`snaptrude.entity.story`).\n *\n * Reads return plain records and never throw for a missing building (`get` and\n * `getActive` return `null`, `list` returns `[]`). Ids are stable public strings.\n *\n * Sub-namespace members typed as optional (`?`) are **authored ahead of the\n * host** — declared so plugins and codegen can see the shape, but kept out of the\n * discovery manifest until the host lands them (at which point they are promoted\n * to required `abstract` members).\n *\n * Accessed via `snaptrude.core.buildings`.\n */\nexport abstract class PluginCoreBuildingsApi {\n constructor() {}\n\n /**\n * List the buildings in the active project.\n *\n * @returns A {@linkcode PluginCoreBuildingsListResult} with a `buildings` array\n * (empty when the project has none).\n *\n * @examplePrompt List all the buildings in my project\n * @examplePrompt How many buildings are in this model?\n * @examplePrompt Show every building with its base elevation\n *\n * # Example\n * ```ts\n * const { buildings } = await snaptrude.core.buildings.list()\n * for (const b of buildings) console.log(b.name, b.baseElevation)\n * ```\n */\n public abstract list(): PluginApiReturn<PluginCoreBuildingsListResult>\n\n /**\n * Get a single building by id.\n *\n * @param buildingId - The id of the building to read.\n * @returns The matching {@linkcode PluginCoreBuildingRef}, or `null` if no\n * building has that id.\n *\n * @examplePrompt Get the building with id blg_1\n * @examplePrompt Look up a single building by its id\n * @examplePrompt What is the base elevation of building blg_2?\n *\n * # Example\n * ```ts\n * const building = await snaptrude.core.buildings.get(\"blg_1\")\n * if (building) console.log(building.name, building.baseElevation)\n * ```\n */\n public abstract get(\n buildingId: string,\n ): PluginApiReturn<PluginCoreBuildingsGetResult>\n\n /**\n * List the storeys of a building.\n *\n * Returns the building's storeys as stable storey ids, in the project's storey\n * order. Read each storey's detail through `snaptrude.entity.story`.\n *\n * @param buildingId - The id of the building whose storeys to list.\n * @returns A {@linkcode PluginCoreBuildingsListStoreysResult} with a `storeyIds`\n * array (empty when the building is missing or has no storeys).\n *\n * @examplePrompt List the storeys in building blg_1\n * @examplePrompt How many floors does this building have?\n * @examplePrompt Get the storey ids of the active building\n *\n * # Example\n * ```ts\n * const { storeyIds } = await snaptrude.core.buildings.listStoreys(\"blg_1\")\n * console.log(`${storeyIds.length} storeys`)\n * ```\n */\n public abstract listStoreys(\n buildingId: string,\n ): PluginApiReturn<PluginCoreBuildingsListStoreysResult>\n\n /**\n * Get the active building.\n *\n * The active building is the one new storeys and edits target. Paired with\n * {@linkcode PluginCoreBuildingsApi.setActive}.\n *\n * @returns A {@linkcode PluginCoreBuildingsGetActiveResult} with the active\n * `buildingId`, or `null` if there is no active building.\n *\n * @examplePrompt Which building is active?\n * @examplePrompt Get the current building\n * @examplePrompt What building am I working in?\n *\n * # Example\n * ```ts\n * const active = await snaptrude.core.buildings.getActive()\n * if (active) console.log(active.buildingId)\n * ```\n */\n public abstract getActive(): PluginApiReturn<PluginCoreBuildingsGetActiveResult>\n\n /**\n * Make a building the active building.\n *\n * Paired with {@linkcode PluginCoreBuildingsApi.getActive}.\n *\n * @param buildingId - The id of the building to activate.\n * @returns The id of the now-active building.\n * @throws If no building has the given id.\n *\n * @examplePrompt Switch to building blg_2\n * @examplePrompt Make this building the active one\n * @examplePrompt Set the active building to blg_1\n *\n * # Example\n * ```ts\n * await snaptrude.core.buildings.setActive(\"blg_2\")\n * ```\n */\n public abstract setActive(\n buildingId: string,\n ): PluginApiReturn<PluginCoreBuildingsSetActiveResult>\n\n /**\n * Create a new building.\n *\n * Adds a named building at the given base elevation (defaults to `0`). The new\n * building starts empty — add storeys through the storeys namespace.\n *\n * @param name - Display name of the new building.\n * @param baseElevation - Base elevation in meters (defaults to `0`).\n * @returns The new building's id (read the full record back with\n * {@linkcode PluginCoreBuildingsApi.get}).\n * @throws If the building could not be created.\n *\n * @examplePrompt Create a building called Tower B\n * @examplePrompt Add a new building at elevation 12 meters\n * @examplePrompt Make a second building named Annex\n *\n * # Example\n * ```ts\n * const { buildingId } = await snaptrude.core.buildings.create(\"Tower B\", 0)\n * ```\n */\n public abstract create(\n name: string,\n baseElevation?: number,\n ): PluginApiReturn<PluginCoreBuildingsCreateResult>\n\n /**\n * Update a building's name and/or base elevation.\n *\n * A **sparse** update: omitted fields are left unchanged.\n *\n * @param buildingId - The id of the building to update.\n * @param options - Any of `name`, `baseElevation`; omitted fields are left\n * unchanged.\n * @returns The updated {@linkcode PluginCoreBuildingRef}.\n * @throws If no building has the given id.\n *\n * @examplePrompt Rename building blg_1 to Tower A\n * @examplePrompt Move the building up to elevation 6 meters\n * @examplePrompt Change the base elevation of blg_2\n *\n * # Example\n * ```ts\n * const building = await snaptrude.core.buildings.update(\"blg_1\", { name: \"Tower A\" })\n * ```\n */\n public abstract update(\n buildingId: string,\n options?: { name?: string; baseElevation?: number },\n ): PluginApiReturn<PluginCoreBuildingsUpdateResult>\n\n /**\n * Copy a building.\n *\n * Copies the building — its storeys, components, and drawing layers — into a new\n * building. `asInstances` controls the copy's geometry: `true` shares the source\n * geometry as instances (low memory), `false` makes independent editable copies\n * (the host defaults to `true`). Optionally override the copy's `name` and\n * `baseElevation`.\n *\n * @param buildingId - The id of the building to copy.\n * @param options - Optional `name`, `baseElevation`, `asInstances` overrides\n * for the copy.\n * @returns The new building's id.\n * @throws If no building has the given id.\n *\n * @examplePrompt Copy building blg_1\n * @examplePrompt Make an independent editable copy of this building\n * @examplePrompt Copy Tower A as instances to save memory\n *\n * # Example\n * ```ts\n * const { buildingId } = await snaptrude.core.buildings.copy(\"blg_1\", {\n * name: \"Tower A Copy\",\n * asInstances: false,\n * })\n * ```\n */\n public abstract copy(\n buildingId: string,\n options?: { name?: string; baseElevation?: number; asInstances?: boolean },\n ): PluginApiReturn<PluginCoreBuildingsCopyResult>\n\n /**\n * Delete a building.\n *\n * **Gated and destructive.** Deleting a building cascades through its meshes,\n * storeys, and the building itself, and removes its saved views — **saved views\n * are not restored on undo.** The project's only remaining building cannot be\n * deleted.\n *\n * @param buildingId - The id of the building to delete.\n * @returns Confirmation that the building was deleted.\n * @throws If no building has the given id, or it is the only building.\n *\n * @examplePrompt Delete building blg_2\n * @examplePrompt Remove the Annex building and everything in it\n * @examplePrompt Delete this building\n *\n * # Example\n * ```ts\n * await snaptrude.core.buildings.delete(\"blg_2\")\n * ```\n */\n public abstract delete(\n buildingId: string,\n ): PluginApiReturn<PluginCoreBuildingsDeleteResult>\n}\n\n/**\n * A building in the active project.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Stable public building id (e.g. `\"blg_1\"`) |\n * | `name` | `string` | Display name |\n * | `baseElevation` | `number` | Base elevation in meters, positive up (default `0`) |\n */\nexport const PluginCoreBuildingRef = z.object({\n id: z.string(),\n name: z.string(),\n baseElevation: z.number(),\n})\nexport type PluginCoreBuildingRef = z.infer<typeof PluginCoreBuildingRef>\n\n/**\n * Result of {@linkcode PluginCoreBuildingsApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `buildings` | {@linkcode PluginCoreBuildingRef}`[]` | Every building in the active project |\n */\nexport const PluginCoreBuildingsListResult = z.object({\n buildings: z.array(PluginCoreBuildingRef),\n})\nexport type PluginCoreBuildingsListResult = z.infer<\n typeof PluginCoreBuildingsListResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreBuildingsApi.get}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `buildingId` | `string` | The id of the building to read |\n */\nexport const PluginCoreBuildingsGetArgs = z.object({\n buildingId: z.string(),\n})\nexport type PluginCoreBuildingsGetArgs = z.infer<\n typeof PluginCoreBuildingsGetArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreBuildingsApi.get} — the building, or `null` when\n * no building has the given id.\n */\nexport const PluginCoreBuildingsGetResult = PluginCoreBuildingRef.nullable()\nexport type PluginCoreBuildingsGetResult = z.infer<\n typeof PluginCoreBuildingsGetResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreBuildingsApi.listStoreys}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `buildingId` | `string` | The id of the building whose storeys to list |\n */\nexport const PluginCoreBuildingsListStoreysArgs = z.object({\n buildingId: z.string(),\n})\nexport type PluginCoreBuildingsListStoreysArgs = z.infer<\n typeof PluginCoreBuildingsListStoreysArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreBuildingsApi.listStoreys}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `storeyIds` | `string[]` | The building's storey ids in storey order (empty when missing or none) |\n */\nexport const PluginCoreBuildingsListStoreysResult = z.object({\n storeyIds: z.array(z.string()),\n})\nexport type PluginCoreBuildingsListStoreysResult = z.infer<\n typeof PluginCoreBuildingsListStoreysResult\n>\n\n/**\n * Result of {@linkcode PluginCoreBuildingsApi.getActive} — the active building's\n * id, or `null` when there is no active building.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `buildingId` | `string` | The id of the active building |\n */\nexport const PluginCoreBuildingsGetActiveResult = z\n .object({\n buildingId: z.string(),\n })\n .nullable()\nexport type PluginCoreBuildingsGetActiveResult = z.infer<\n typeof PluginCoreBuildingsGetActiveResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreBuildingsApi.setActive}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `buildingId` | `string` | The id of the building to activate |\n */\nexport const PluginCoreBuildingsSetActiveArgs = z.object({\n buildingId: z.string(),\n})\nexport type PluginCoreBuildingsSetActiveArgs = z.infer<\n typeof PluginCoreBuildingsSetActiveArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreBuildingsApi.setActive} — the now-active\n * building's id.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `buildingId` | `string` | The id of the now-active building |\n */\nexport const PluginCoreBuildingsSetActiveResult = z.object({\n buildingId: z.string(),\n})\nexport type PluginCoreBuildingsSetActiveResult = z.infer<\n typeof PluginCoreBuildingsSetActiveResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreBuildingsApi.create}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | Display name of the new building |\n * | `baseElevation` | `number \\| undefined` | Base elevation in meters (defaults to `0`) |\n */\nexport const PluginCoreBuildingsCreateArgs = z.object({\n name: z.string(),\n baseElevation: z.number().optional(),\n})\nexport type PluginCoreBuildingsCreateArgs = z.infer<\n typeof PluginCoreBuildingsCreateArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreBuildingsApi.create} — the id of the new\n * building.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `buildingId` | `string` | The id of the newly created building |\n */\nexport const PluginCoreBuildingsCreateResult = z.object({\n buildingId: z.string(),\n})\nexport type PluginCoreBuildingsCreateResult = z.infer<\n typeof PluginCoreBuildingsCreateResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreBuildingsApi.update}. Sparse — only the\n * provided fields are changed.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `buildingId` | `string` | The id of the building to update |\n * | `name` | `string \\| undefined` | New display name (unchanged if omitted) |\n * | `baseElevation` | `number \\| undefined` | New base elevation in meters (unchanged if omitted) |\n */\nexport const PluginCoreBuildingsUpdateArgs = z.object({\n buildingId: z.string(),\n name: z.string().optional(),\n baseElevation: z.number().optional(),\n})\nexport type PluginCoreBuildingsUpdateArgs = z.infer<\n typeof PluginCoreBuildingsUpdateArgs\n>\n\n/** Result of {@linkcode PluginCoreBuildingsApi.update} — the updated record. */\nexport const PluginCoreBuildingsUpdateResult = PluginCoreBuildingRef\nexport type PluginCoreBuildingsUpdateResult = z.infer<\n typeof PluginCoreBuildingsUpdateResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreBuildingsApi.copy}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `buildingId` | `string` | The id of the building to copy |\n * | `name` | `string \\| undefined` | Name for the copy (auto-generated if omitted) |\n * | `baseElevation` | `number \\| undefined` | Base elevation for the copy in meters (source's if omitted) |\n * | `asInstances` | `boolean \\| undefined` | `true` shares source geometry as instances; `false` makes independent copies (host default `true`) |\n */\nexport const PluginCoreBuildingsCopyArgs = z.object({\n buildingId: z.string(),\n name: z.string().optional(),\n baseElevation: z.number().optional(),\n asInstances: z.boolean().optional(),\n})\nexport type PluginCoreBuildingsCopyArgs = z.infer<\n typeof PluginCoreBuildingsCopyArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreBuildingsApi.copy} — the id of the new\n * building.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `buildingId` | `string` | The id of the copied building |\n */\nexport const PluginCoreBuildingsCopyResult = z.object({\n buildingId: z.string(),\n})\nexport type PluginCoreBuildingsCopyResult = z.infer<\n typeof PluginCoreBuildingsCopyResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreBuildingsApi.delete}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `buildingId` | `string` | The id of the building to delete |\n */\nexport const PluginCoreBuildingsDeleteArgs = z.object({\n buildingId: z.string(),\n})\nexport type PluginCoreBuildingsDeleteArgs = z.infer<\n typeof PluginCoreBuildingsDeleteArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreBuildingsApi.delete} — confirmation that the\n * building was deleted.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `deleted` | `true` | Always `true` on success |\n */\nexport const PluginCoreBuildingsDeleteResult = z.object({\n deleted: z.literal(true),\n})\nexport type PluginCoreBuildingsDeleteResult = z.infer<\n typeof PluginCoreBuildingsDeleteResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { ComponentHandle } from \"../../../handles\"\nimport {\n PluginStoryGetProperty,\n PluginStoryGetResult,\n PluginStoryGetAllResult,\n PluginStoryCreateResult,\n PluginStoryUpdateResult,\n PluginStorySetActiveResult,\n PluginStoryDeleteResult,\n PluginStoryDuplicateResult,\n} from \"../../entity/story\"\n\n/**\n * Storey (floor) management.\n *\n * A storey represents a building floor in the Snaptrude project. Storeys are\n * identified by their integer **storey value** (e.g. `1` for the ground floor,\n * `2` for the first floor, `-1` for a basement).\n *\n * This is the canonical home for storey operations; it shares its implementation\n * and schemas with the deprecated `entity.story` surface. All methods are **host\n * API calls** that return Promises and support undo/redo via Snaptrude's command\n * system (except {@linkcode PluginCoreStoreysApi.setActive}, a view change).\n *\n * Accessed via `snaptrude.core.storeys`.\n */\nexport abstract class PluginCoreStoreysApi {\n constructor() {}\n\n /**\n * Get properties of a storey by its storey number.\n *\n * Only the properties listed in `properties` are returned — unlisted\n * properties will be `undefined` in the result.\n *\n * @param storyValue - Integer storey number\n * (e.g. `1` for ground floor, `2` for first floor, `-1` for basement)\n * @param properties - Array of property names to retrieve. See\n * {@linkcode PluginStoryGetProperty} for available values.\n * @returns A partial {@linkcode PluginStoryGetResult} containing only the requested properties\n * @throws If the storey with the given value does not exist\n *\n * @examplePrompt What is the height of the ground floor?\n * @examplePrompt How many rooms are on the second storey?\n * @examplePrompt Get the total floor area of level 3\n * @examplePrompt Is the first floor hidden in the viewport?\n * @examplePrompt Show me the name and height of storey 2\n *\n * # Example\n * ```ts\n * const info = await snaptrude.core.storeys.get(1, [\"height\", \"name\", \"spacesCount\"])\n * console.log(info.name, info.height, info.spacesCount)\n * ```\n */\n public abstract get(\n storyValue: number,\n properties: PluginStoryGetProperty[],\n ): PluginApiReturn<PluginStoryGetResult>\n\n /**\n * List all storeys in the current project.\n *\n * Returns basic identification data for every storey. Storeys are sorted\n * from **top to bottom** (highest storey value first).\n *\n * @returns A {@linkcode PluginCoreStoreysListResult} with a `storeys` array,\n * each entry containing `value`, `id`, and `name`\n *\n * @examplePrompt How many floors does this building have?\n * @examplePrompt List all the storeys in the project\n * @examplePrompt Show me every level with its name\n * @examplePrompt Which storeys exist in this model?\n *\n * # Example\n * ```ts\n * const { storeys } = await snaptrude.core.storeys.list()\n * for (const s of storeys) {\n * console.log(`Storey ${s.value}: ${s.name} (id: ${s.id})`)\n * }\n * ```\n */\n public abstract list(): PluginApiReturn<PluginCoreStoreysListResult>\n\n /**\n * Create a new storey (floor) in the project.\n *\n * The new storey is inserted at the position specified by `storyValue`. This\n * operation is undoable.\n *\n * @param storyValue - Integer storey number\n * to create (e.g. `3` to add a third floor)\n * @param height - Optional height in Babylon\n * units. If omitted, the project's default storey height is used.\n * @returns A {@linkcode PluginStoryCreateResult} with `storyId` and `storyValue`\n * @throws If a storey with the given value already exists or creation fails\n *\n * @examplePrompt Add a third floor to the building\n * @examplePrompt Create a new storey above the second floor\n * @examplePrompt Add a basement level to this project\n * @examplePrompt Insert a new level with a custom floor height\n *\n * # Example\n * ```ts\n * // Create a new third floor with custom height\n * const { storyId } = await snaptrude.core.storeys.create(3, 4.5)\n * ```\n */\n public abstract create(\n storyValue: number,\n height?: number,\n ): PluginApiReturn<PluginStoryCreateResult>\n\n /**\n * Update a storey's floor-to-floor `height` and/or `name`.\n *\n * **Height** is the same operation as editing the height in the Storeys panel:\n * the walls, columns, and masses on the storey are **stretched** to the new\n * height, every storey **above shifts up/down** by the delta so the stack stays\n * contiguous, and coupled elements (staircases, parametric curtain walls,\n * furniture offsets) are re-fitted. The whole height cascade is committed as\n * **one undo step**. Height-locked elements are left untouched.\n *\n * **Name** is the same as renaming the storey in the Storeys panel: it is\n * persisted immediately but, mirroring the panel, is **not** part of the height\n * undo step.\n *\n * At least one of `height` or `options.name` must be supplied. Omitting the\n * `height` argument (e.g. for a rename-only update) leaves the height untouched.\n *\n * @param storyValue - Integer storey number identifying the storey to update\n * @param height - New floor-to-floor height in\n * Babylon units. Omit to leave the height unchanged.\n * @param options - `name` (new display name for the storey)\n * @returns A {@linkcode PluginStoryUpdateResult} with the storey's\n * `storyValue`, `height`, and `name` after the update\n * @throws `PRECONDITION_FAILED` if no storey has the given value;\n * `VALIDATION` if neither `height` nor `options.name` is supplied;\n * `STORY_HEIGHT_REJECTED` if the engine rejects the height;\n * `STORY_UPDATE_FAILED` if the storey cannot be re-read after the update\n *\n * @examplePrompt Change the ground floor height to 3.5 metres\n * @examplePrompt Make the second storey taller\n * @examplePrompt Rename the ground floor to \"Lobby\"\n * @examplePrompt Set the floor-to-floor height of level 1\n *\n * # Example\n * ```ts\n * // Set ground floor height to 5 Babylon units — walls stretch and the\n * // floors above move up to match, all in a single undo step.\n * const result = await snaptrude.core.storeys.update(1, 5)\n * // Rename only, leaving the height untouched.\n * await snaptrude.core.storeys.update(1, undefined, { name: \"Lobby\" })\n * ```\n */\n public abstract update(\n storyValue: number,\n height?: number,\n options?: { name?: string },\n ): PluginApiReturn<PluginStoryUpdateResult>\n\n /**\n * Get the active storey's value. Paired with {@linkcode PluginCoreStoreysApi.setActive}.\n *\n * @returns The active storey value, in the same numbering `list` and `get` use.\n *\n * @examplePrompt Which storey am I on?\n * @examplePrompt What is the current storey?\n *\n * # Example\n * ```ts\n * const storey = await snaptrude.core.storeys.getActive()\n * ```\n */\n public abstract getActive(): PluginApiReturn<number>\n\n /**\n * Make a storey the active storey — the same as clicking it in the storey/layer\n * panel. Subsequent draws and creates target this storey, and in 2D the\n * viewport switches to it. This is a view/navigation change: it is **not**\n * undoable and commits nothing to the model.\n *\n * @param storyValue - Integer storey number to activate\n * @returns A {@linkcode PluginStorySetActiveResult} echoing the now-active `storyValue`\n * @throws `PRECONDITION_FAILED` if no storey has the given value\n *\n * @examplePrompt Switch to the second floor\n * @examplePrompt Make the ground storey active\n * @examplePrompt Go to the basement level\n * @examplePrompt Set level 3 as the current storey\n *\n * # Example\n * ```ts\n * // Activate storey 2, then draw a wall — it lands on storey 2.\n * await snaptrude.core.storeys.setActive(2)\n * ```\n */\n public abstract setActive(\n storyValue: number,\n ): PluginApiReturn<PluginStorySetActiveResult>\n\n /**\n * Delete a storey and everything on it — the same as removing it from the\n * storey panel. Every element placed on the storey (walls, floors, masses, …)\n * is deleted with it, the remaining storeys are re-stacked, and the active\n * storey falls back to an adjacent one. Committed as a single undo step.\n *\n * @param storyValue - Integer storey number to delete\n * @returns A {@linkcode PluginStoryDeleteResult} with the deleted `storyValue`\n * and the `newActiveStory` the editor fell back to\n * @throws `PRECONDITION_FAILED` if no storey has the given value; or if plugin\n * writes are disabled\n *\n * @examplePrompt Delete the top floor\n * @examplePrompt Remove the basement level\n * @examplePrompt Get rid of storey 3\n * @examplePrompt Delete the second floor and everything on it\n *\n * # Example\n * ```ts\n * const { newActiveStory } = await snaptrude.core.storeys.delete(3)\n * console.log(`Deleted storey 3; now on storey ${newActiveStory}`)\n * ```\n */\n public abstract delete(\n storyValue: number,\n ): PluginApiReturn<PluginStoryDeleteResult>\n\n /**\n * Copy a storey into the adjacent level, up or down.\n *\n * By default the **whole storey** is copied: every eligible element on the\n * active storey (walls, floors, roofs, masses, columns, beams, staircases,\n * ceilings, parametric curtain walls, and furniture) is copied into the storey\n * one level `\"up\"` or `\"down\"`. Pass `options.components` to copy only a subset\n * instead. The target storey is **created automatically** if it does not yet\n * exist, inheriting the source storey's height.\n *\n * Copies are **instanced** by default (`unique: false`) — they share geometry\n * with the source, so later edits propagate. Set `unique: true` for independent\n * geometry. Doors and windows are never copied on their own; they ride along\n * with their host wall. Locked, throw-away, and unsupported elements are skipped\n * and counted in `skipped`. The whole operation is a single undo step.\n *\n * @param direction - `\"up\"` to copy into the storey above, `\"down\"` for below\n * @param options - `components` (restrict to a subset of the storey's elements;\n * omit for the whole storey) and `unique` (default `false` = instanced copies;\n * `true` = independent geometry)\n * @returns A {@linkcode PluginStoryDuplicateResult} with the `sourceStory`, the\n * `targetStories` the copies landed on, the `created` component handles, the\n * `createdStoryValues` for any storeys generated to receive them, and the\n * `skipped` count of ineligible elements\n * @throws `NO_ACTIVE_STRUCTURE` if there is no active structure to copy into;\n * `NO_ELIGIBLE_ELEMENTS` if nothing on the source storey (or in\n * `options.components`) is copyable; `HANDLE_INVALID` for a stale or foreign\n * supplied handle; or if plugin writes are disabled\n *\n * @examplePrompt Copy this floor to the storey above\n * @examplePrompt Copy the ground floor down into a new basement\n * @examplePrompt Repeat this level upwards with its own independent geometry\n * @examplePrompt Copy just the selected walls onto the next floor up\n *\n * # Example\n * ```ts\n * // Copy the whole active storey one level up (instanced copies).\n * const { targetStories, created, skipped } =\n * await snaptrude.core.storeys.copy(\"up\")\n * console.log(`Copied ${created.length} elements onto storey ${targetStories}`)\n * if (skipped) console.log(`${skipped} elements were skipped`)\n * ```\n */\n public abstract copy(\n direction: \"up\" | \"down\",\n options?: { components?: ComponentHandle[]; unique?: boolean },\n ): PluginApiReturn<PluginStoryDuplicateResult>\n}\n\n/**\n * Result of {@linkcode PluginCoreStoreysApi.list} — the {@linkcode PluginStoryGetAllResult}\n * shape with the array field renamed `stories` → `storeys`. Each entry shares the\n * same element type: `{ value, id, name }`, sorted top to bottom.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `storeys` | `Array<{ value, id, name }>` | All storeys, sorted top to bottom |\n */\nexport const PluginCoreStoreysListResult = z.object({\n storeys: PluginStoryGetAllResult.shape.stories,\n})\n\nexport type PluginCoreStoreysListResult = z.infer<\n typeof PluginCoreStoreysListResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport { ComponentHandle } from \"../../handles\"\n\n/**\n * Story (floor/storey) management.\n *\n * A story represents a building floor in the Snaptrude project. Stories\n * are identified by their integer **story value** (e.g. `1` for ground\n * floor, `2` for first floor, `-1` for a basement).\n *\n * All methods are **host API calls** that return Promises and support\n * undo/redo via Snaptrude's command system.\n *\n * Accessed via `snaptrude.entity.story`.\n */\nexport abstract class PluginStoryApi {\n constructor() {}\n\n /**\n * Get properties of a story by its storey number.\n *\n * Only the properties listed in `properties`\n * are returned — unlisted properties will be `undefined` in the result.\n *\n * @param storyValue - Integer storey number\n * (e.g. `1` for ground floor, `2` for first floor, `-1` for basement)\n * @param properties - Array of property names\n * to retrieve. See {@linkcode PluginStoryGetProperty} for available values.\n * @returns A partial {@linkcode PluginStoryGetResult} containing only the requested properties\n * @throws If the story with the given value does not exist\n *\n * @examplePrompt What is the height of the ground floor?\n * @examplePrompt How many rooms are on the second storey?\n * @examplePrompt Get the total floor area of level 3\n * @examplePrompt Is the first floor hidden in the viewport?\n * @examplePrompt Show me the name and height of story 2\n *\n * # Example\n * ```ts\n * const info = await snaptrude.entity.story.get(1, [\"height\", \"name\", \"spacesCount\"])\n * console.log(info.name, info.height, info.spacesCount)\n * ```\n *\n * @deprecated Use `core.storeys.get`\n */\n public abstract get(\n storyValue: number,\n properties: PluginStoryGetProperty[],\n ): PluginApiReturn<PluginStoryGetResult>\n\n /**\n * Get all stories in the current project.\n *\n * Returns basic identification data for every storey. Stories are sorted\n * from **top to bottom** (highest story value first).\n *\n * @returns A {@linkcode PluginStoryGetAllResult} with a `stories` array,\n * each entry containing `value`, `id`, and `name`\n *\n * @examplePrompt How many floors does this building have?\n * @examplePrompt List all the storeys in the project\n * @examplePrompt Show me every level with its name\n * @examplePrompt Which stories exist in this model?\n *\n * # Example\n * ```ts\n * const { stories } = await snaptrude.entity.story.getAll()\n * for (const s of stories) {\n * console.log(`Story ${s.value}: ${s.name} (id: ${s.id})`)\n * }\n * ```\n *\n * @deprecated Use `core.storeys.list`\n */\n public abstract getAll(): PluginApiReturn<PluginStoryGetAllResult>\n\n /**\n * Create a new story (floor) in the project.\n *\n * The new story is inserted at the position specified by\n * `storyValue`. This operation is undoable.\n *\n * @param storyValue - Integer storey number\n * to create (e.g. `3` to add a third floor)\n * @param height - Optional height in Babylon\n * units. If omitted, the project's default storey height is used.\n * @returns A {@linkcode PluginStoryCreateResult} with `storyId` and `storyValue`\n * @throws If a story with the given value already exists or creation fails\n *\n * @examplePrompt Add a third floor to the building\n * @examplePrompt Create a new storey above the second floor\n * @examplePrompt Add a basement level to this project\n * @examplePrompt Insert a new level with a custom floor height\n * @examplePrompt Add another floor on top of the building\n *\n * # Example\n * ```ts\n * // Create a new third floor with custom height\n * const { storyId } = await snaptrude.entity.story.create(3, 4.5)\n * ```\n *\n * @deprecated Use `core.storeys.create`\n */\n public abstract create(\n storyValue: number,\n height?: number,\n ): PluginApiReturn<PluginStoryCreateResult>\n\n /**\n * Update a story's floor-to-floor `height` and/or `name`.\n *\n * **Height** is the same operation as editing the height in the Stories panel:\n * the walls, columns, and masses on the story are **stretched** to the new\n * height, every story **above shifts up/down** by the delta so the stack stays\n * contiguous, and coupled elements (staircases, parametric curtain walls,\n * furniture offsets) are re-fitted. The whole height cascade is committed as\n * **one undo step**. Height-locked elements are left untouched.\n *\n * **Name** is the same as renaming the story in the Stories panel: it is\n * persisted immediately (saved to the project) but, mirroring the panel, is\n * **not** part of the height undo step.\n *\n * At least one of `height` or `options.name` must be supplied. Omitting the\n * `height` argument (e.g. for a rename-only update) leaves the height\n * untouched.\n *\n * @param storyValue - Integer storey number\n * identifying the story to update\n * @param height - New floor-to-floor height in\n * Babylon units. Omit to leave the height unchanged.\n * @param options - `name` (new display name for the story)\n * @returns A {@linkcode PluginStoryUpdateResult} with the story's\n * `storyValue`, `height`, and `name` after the update\n * @throws `PRECONDITION_FAILED` if no story has the given value;\n * `VALIDATION` if neither `height` nor `options.name` is supplied;\n * `STORY_HEIGHT_REJECTED` if the engine rejects the height (e.g. out of\n * range — the change silently reverts host-side and is surfaced as this\n * error); `STORY_UPDATE_FAILED` if the story cannot be re-read after the\n * update\n *\n * @examplePrompt Change the ground floor height to 3.5 metres\n * @examplePrompt Make the second storey taller\n * @examplePrompt Rename the ground floor to \"Lobby\"\n * @examplePrompt Set the floor-to-floor height of level 1\n * @examplePrompt Rename storey 2 and make it taller in one go\n *\n * # Example\n * ```ts\n * // Set ground floor height to 5 Babylon units — walls stretch and the\n * // floors above move up to match, all in a single undo step.\n * const result = await snaptrude.entity.story.update(1, 5)\n * // Rename only, leaving the height untouched.\n * await snaptrude.entity.story.update(1, undefined, { name: \"Lobby\" })\n * ```\n *\n * @deprecated Use `core.storeys.update`\n */\n public abstract update(\n storyValue: number,\n height?: number,\n options?: { name?: string },\n ): PluginApiReturn<PluginStoryUpdateResult>\n\n /**\n * Make a story the active story — the same as clicking it in the storey/layer\n * panel. Subsequent draws and creates target this story, and in 2D the\n * viewport switches to it. This is a view/navigation change: it is **not**\n * undoable and commits nothing to the model.\n *\n * @param storyValue - Integer storey number to activate\n * @returns A {@linkcode PluginStorySetActiveResult} echoing the now-active `storyValue`\n * @throws `PRECONDITION_FAILED` if no story has the given value\n *\n * @examplePrompt Switch to the second floor\n * @examplePrompt Make the ground storey active\n * @examplePrompt Go to the basement level\n * @examplePrompt Set level 3 as the current storey\n *\n * # Example\n * ```ts\n * // Activate story 2, then draw a wall — it lands on story 2.\n * await snaptrude.entity.story.setActive(2)\n * ```\n *\n * @deprecated Use `core.storeys.setActive`\n */\n public abstract setActive(\n storyValue: number,\n ): PluginApiReturn<PluginStorySetActiveResult>\n\n /**\n * Delete a story and everything on it — the same as removing it from the\n * storey panel. Every element placed on the story (walls, floors, masses, …)\n * is deleted with it, the remaining stories are re-stacked, and the active\n * story falls back to an adjacent one. Committed as a single undo step.\n *\n * @param storyValue - Integer storey number to delete\n * @returns A {@linkcode PluginStoryDeleteResult} with the deleted `storyValue`\n * and the `newActiveStory` the editor fell back to\n * @throws `PRECONDITION_FAILED` if no story has the given value; or if plugin\n * writes are disabled\n *\n * @examplePrompt Delete the top floor\n * @examplePrompt Remove the basement level\n * @examplePrompt Get rid of storey 3\n * @examplePrompt Delete the second floor and everything on it\n *\n * # Example\n * ```ts\n * const { newActiveStory } = await snaptrude.entity.story.delete(3)\n * console.log(`Deleted story 3; now on story ${newActiveStory}`)\n * ```\n *\n * @deprecated Use `core.storeys.delete`\n */\n public abstract delete(\n storyValue: number,\n ): PluginApiReturn<PluginStoryDeleteResult>\n\n /**\n * Duplicate a story into the adjacent level, up or down.\n *\n * By default the **whole story** is duplicated: every eligible element on the\n * active story (walls, floors, roofs, masses, columns, beams, staircases,\n * ceilings, parametric curtain walls, and furniture) is copied into the story\n * one level `\"up\"` or `\"down\"`. Pass `options.components` to duplicate only a\n * subset instead. The target story is **created automatically** if it does not\n * yet exist, inheriting the source story's height.\n *\n * Copies are **instanced** by default (`unique: false`) — they share geometry\n * with the source, so later edits propagate. Set `unique: true` for independent\n * geometry. Doors and windows are never duplicated on their own; they ride along\n * with their host wall. Locked, throw-away, and unsupported elements are skipped\n * and counted in `skipped`. The whole operation is a single undo step.\n *\n * @param direction - `\"up\"` to duplicate into the story above, `\"down\"` for below\n * @param options - `components` (restrict to a subset of the story's elements;\n * omit for the whole story) and `unique` (default `false` = instanced copies;\n * `true` = independent geometry)\n * @returns A {@linkcode PluginStoryDuplicateResult} with the `sourceStory`, the\n * `targetStories` the copies landed on, the `created` component handles, the\n * `createdStoryValues` for any stories generated to receive them, and the\n * `skipped` count of ineligible elements\n * @throws `NO_ACTIVE_STRUCTURE` if there is no active structure to duplicate\n * into; `NO_ELIGIBLE_ELEMENTS` if nothing on the source story (or in\n * `options.components`) is duplicable; `HANDLE_INVALID` for a stale or\n * foreign supplied handle; or if plugin writes are disabled\n *\n * @examplePrompt Duplicate this floor to the storey above\n * @examplePrompt Copy the ground floor down into a new basement\n * @examplePrompt Repeat this level upwards with its own independent geometry\n * @examplePrompt Duplicate just the selected walls onto the next floor up\n *\n * @performance For storeys that share a layout, duplicate the storey instead of recreating\n * its contents floor by floor — this copies every eligible element in one call, and the\n * default instanced copies keep the floors linked. To stack many identical floors, either\n * call this per level or capture the storey's component ids and\n * `design.create.copy(ids, oneFloorRise, { count })` to lay them all down in a single call.\n *\n * # Example\n * ```ts\n * // Duplicate the whole active story one level up (instanced copies).\n * const { targetStories, created, skipped } =\n * await snaptrude.entity.story.duplicate(\"up\")\n * console.log(`Copied ${created.length} elements onto story ${targetStories}`)\n * if (skipped) console.log(`${skipped} elements were skipped`)\n * ```\n *\n * @deprecated Use `core.storeys.copy`\n */\n public abstract duplicate(\n direction: \"up\" | \"down\",\n options?: { components?: ComponentHandle[]; unique?: boolean },\n ): PluginApiReturn<PluginStoryDuplicateResult>\n}\n\n/**\n * Available properties that can be queried on a story.\n *\n * | Value | Type | Description |\n * |---|---|---|\n * | `\"value\"` | `number` | The integer storey number |\n * | `\"id\"` | `string` | Unique story identifier |\n * | `\"name\"` | `string` | Display name of the story |\n * | `\"height\"` | `number` | Floor-to-floor height in Babylon units |\n * | `\"base\"` | `number` | Base elevation in Babylon units |\n * | `\"hidden\"` | `boolean` | Whether the story is hidden in the viewport |\n * | `\"spacesCount\"` | `number` | Number of spaces on this story |\n * | `\"totalArea\"` | `number` | Total floor area of this story |\n */\nexport const PluginStoryGetProperty = z.enum([\n \"value\",\n \"id\",\n \"name\",\n \"height\",\n \"base\",\n \"hidden\",\n \"spacesCount\",\n \"totalArea\",\n])\n\nexport type PluginStoryGetProperty = z.infer<typeof PluginStoryGetProperty>\n\n/**\n * Arguments for {@linkcode PluginStoryApi.get}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `storyValue` | `number` (int) | The storey number to query |\n * | `properties` | {@linkcode PluginStoryGetProperty}`[]` | Properties to retrieve |\n */\nexport const PluginStoryGetArgs = z.object({\n storyValue: z.number().int(),\n properties: z.array(PluginStoryGetProperty),\n})\n\nexport type PluginStoryGetArgs = z.infer<typeof PluginStoryGetArgs>\n\n/**\n * Result of {@linkcode PluginStoryApi.get}.\n *\n * A partial object — only the properties that were requested in\n * {@linkcode PluginStoryGetArgs.properties} will be present.\n */\nexport const PluginStoryGetResult = z\n .object({\n value: z.number(),\n id: z.string(),\n name: z.string(),\n height: z.number(),\n base: z.number(),\n hidden: z.boolean(),\n spacesCount: z.number(),\n totalArea: z.number(),\n })\n .partial()\n\nexport type PluginStoryGetResult = z.infer<typeof PluginStoryGetResult>\n\n/**\n * Result of {@linkcode PluginStoryApi.getAll}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `stories` | `Array<{ value, id, name }>` | All stories, sorted top to bottom |\n */\nexport const PluginStoryGetAllResult = z.object({\n stories: z.array(\n z.object({\n value: z.number(),\n id: z.string(),\n name: z.string(),\n })\n ),\n})\n\nexport type PluginStoryGetAllResult = z.infer<typeof PluginStoryGetAllResult>\n\n/**\n * Arguments for {@linkcode PluginStoryApi.create}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `storyValue` | `number` (int) | The storey number for the new story |\n * | `height` | `number?` | Optional height in Babylon units |\n */\nexport const PluginStoryCreateArgs = z.object({\n storyValue: z.number().int(),\n height: z.number().optional(),\n})\n\nexport type PluginStoryCreateArgs = z.infer<typeof PluginStoryCreateArgs>\n\n/**\n * Result of {@linkcode PluginStoryApi.create}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `storyId` | `string` | Unique ID of the created story |\n * | `storyValue` | `number` | The storey number of the created story |\n */\nexport const PluginStoryCreateResult = z.object({\n storyId: z.string(),\n storyValue: z.number(),\n})\n\nexport type PluginStoryCreateResult = z.infer<typeof PluginStoryCreateResult>\n\n/**\n * Arguments for {@linkcode PluginStoryApi.update}. At least one of `height` or\n * `name` must be supplied.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `storyValue` | `number` (int) | Storey number of the story to update |\n * | `height` | `number?` | New height in Babylon units (omit to leave unchanged) |\n * | `name` | `string?` | New display name for the story (omit to leave unchanged) |\n */\nexport const PluginStoryUpdateArgs = z.object({\n storyValue: z.number().int(),\n height: z.number().optional(),\n name: z.string().optional(),\n})\n\nexport type PluginStoryUpdateArgs = z.infer<typeof PluginStoryUpdateArgs>\n\n/**\n * Result of {@linkcode PluginStoryApi.update} — the story's state after the update.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `storyValue` | `number` | The storey number of the updated story |\n * | `height` | `number` | The height after the update |\n * | `name` | `string` | The name after the update |\n */\nexport const PluginStoryUpdateResult = z.object({\n storyValue: z.number(),\n height: z.number(),\n name: z.string(),\n})\n\nexport type PluginStoryUpdateResult = z.infer<typeof PluginStoryUpdateResult>\n\n/**\n * Arguments for {@linkcode PluginStoryApi.setActive}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `storyValue` | `number` (int) | Storey number to activate |\n */\nexport const PluginStorySetActiveArgs = z.object({\n storyValue: z.number().int(),\n})\n\nexport type PluginStorySetActiveArgs = z.infer<typeof PluginStorySetActiveArgs>\n\n/**\n * Result of {@linkcode PluginStoryApi.setActive}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `storyValue` | `number` | The storey number that is now active |\n */\nexport const PluginStorySetActiveResult = z.object({\n storyValue: z.number(),\n})\n\nexport type PluginStorySetActiveResult = z.infer<typeof PluginStorySetActiveResult>\n\n/**\n * Arguments for {@linkcode PluginStoryApi.delete}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `storyValue` | `number` (int) | Storey number to delete |\n */\nexport const PluginStoryDeleteArgs = z.object({\n storyValue: z.number().int(),\n})\n\nexport type PluginStoryDeleteArgs = z.infer<typeof PluginStoryDeleteArgs>\n\n/**\n * Result of {@linkcode PluginStoryApi.delete}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `storyValue` | `number` | The storey number that was deleted |\n * | `newActiveStory` | `number` | The storey the editor fell back to as active |\n */\nexport const PluginStoryDeleteResult = z.object({\n storyValue: z.number(),\n newActiveStory: z.number(),\n})\n\nexport type PluginStoryDeleteResult = z.infer<typeof PluginStoryDeleteResult>\n\n/**\n * Arguments for {@linkcode PluginStoryApi.duplicate} (options flattened).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `direction` | `'up' \\| 'down'` | Which adjacent story to duplicate into |\n * | `components` | {@linkcode ComponentHandle}`[]?` | Restrict to a subset; omit for the whole story |\n * | `unique` | `boolean?` | `false` (default) = instanced copies; `true` = independent geometry |\n */\nexport const PluginStoryDuplicateArgs = z.object({\n direction: z.enum([\"up\", \"down\"]),\n components: z.array(ComponentHandle).optional(),\n unique: z.boolean().optional(),\n})\n\nexport type PluginStoryDuplicateArgs = z.infer<typeof PluginStoryDuplicateArgs>\n\n/**\n * Result of {@linkcode PluginStoryApi.duplicate}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sourceStory` | `number` | Storey value the copies were duplicated from |\n * | `targetStories` | `number[]` | Storey values the copies landed on |\n * | `created` | {@linkcode ComponentHandle}`[]` | Handles of the newly created components |\n * | `createdStoryValues` | `number[]` | Storey values that were generated to receive the copies |\n * | `skipped` | `number` | Count of ineligible elements that were skipped |\n */\nexport const PluginStoryDuplicateResult = z.object({\n sourceStory: z.number(),\n targetStories: z.array(z.number()),\n created: z.array(ComponentHandle),\n createdStoryValues: z.array(z.number()),\n skipped: z.number(),\n})\n\nexport type PluginStoryDuplicateResult = z.infer<\n typeof PluginStoryDuplicateResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\n\n/**\n * The editor's top-bar application modes — the tabs a user switches between\n * while working on a project.\n */\nexport const PluginAppMode = z.enum([\"design\", \"bim\", \"present\", \"program\"])\nexport type PluginAppMode = z.infer<typeof PluginAppMode>\n\nexport const PluginCoreModeSetArgs = z.object({\n mode: PluginAppMode,\n})\nexport type PluginCoreModeSetArgs = z.infer<typeof PluginCoreModeSetArgs>\n\nexport interface PluginCoreModeListResult {\n modes: PluginAppMode[]\n}\n\nexport interface PluginCoreModeSetResult {\n mode: PluginAppMode\n}\n\n/**\n * Application-mode control — read and switch the editor's top-bar mode\n * (Design, BIM, Present, Program), the same tabs a user clicks in the\n * top menu bar. Accessed via `snaptrude.core.mode`.\n *\n * Not to be confused with the viewport's 2D/3D camera mode\n * (`core.camera.setMode`) or saved presentation views (`presentation.views`).\n *\n * `set` covers `design`, `bim`, and `present` — in-page switches — and\n * `program`, which opens the Program tab (a paired browser window) through the\n * same path as the top bar's Program button. Because the open happens outside a\n * user gesture the browser may block the pop-up; the editor then shows its\n * \"Allow pop-up to continue\" modal and the tab opens when the user confirms.\n * `get()` reports `\"program\"` only for code running in the Program tab itself.\n * Prefer {@linkcode PluginWorkspaceApi.openProgramMode} when you need to know\n * the tab is ready before calling `program.spreadsheet.*`.\n */\nexport abstract class PluginCoreModeApi {\n constructor() {}\n\n /**\n * List every application mode the editor has.\n *\n * @returns The full set of mode tokens accepted by {@linkcode get} / {@linkcode set}.\n *\n * @examplePrompt What modes does Snaptrude have?\n * @examplePrompt List the editor modes I can switch to\n *\n * # Example\n * ```ts\n * const { modes } = await snaptrude.core.mode.list()\n * // [\"design\", \"bim\", \"present\", \"program\"]\n * ```\n */\n public abstract list(): PluginApiReturn<PluginCoreModeListResult>\n\n /**\n * Get the currently active application mode.\n *\n * @returns The active mode token (e.g. `\"design\"`).\n *\n * @examplePrompt Which mode am I in right now?\n * @examplePrompt Am I in present mode?\n * @examplePrompt Check whether the editor is in BIM mode\n *\n * # Example\n * ```ts\n * const mode = await snaptrude.core.mode.get()\n * if (mode !== \"present\") await snaptrude.core.mode.set(\"present\")\n * ```\n */\n public abstract get(): PluginApiReturn<PluginAppMode>\n\n /**\n * Switch the editor to another application mode — the same action as\n * clicking that tab in the top menu bar.\n *\n * `\"design\"`, `\"bim\"` and `\"present\"` switch in place. `\"program\"` opens\n * the Program tab (a paired browser window) exactly as the top bar's Program\n * button does, and resolves once that tab has connected to the editor (up to\n * ~90s — the browser may block the pop-up, in which case the editor shows an\n * \"Allow pop-up to continue\" modal and waits for the user to confirm).\n * Idempotent when the Program tab is already open. Note `get()` in the model\n * tab keeps reporting the model tab's own mode afterwards; the Program tab is\n * a separate window.\n *\n * @param mode - Target mode token from {@linkcode PluginAppMode}\n * @returns The committed `{ mode }` after the switch.\n * @throws `PRECONDITION_FAILED` for `\"program\"` when the user dismisses the\n * pop-up modal or the tab does not connect within the timeout.\n *\n * @examplePrompt Switch to present mode\n * @examplePrompt Open BIM mode\n * @examplePrompt Go back to design mode\n * @examplePrompt Open program mode\n *\n * # Example\n * ```ts\n * await snaptrude.core.mode.set(\"present\")\n * // ... presentation work ...\n * await snaptrude.core.mode.set(\"design\")\n * ```\n */\n public abstract set(mode: PluginAppMode): PluginApiReturn<PluginCoreModeSetResult>\n}\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * Layers — read and toggle the drawing/reference layers of a storey.\n *\n * These are the **drawing/reference** layers shown in the storey's Layers panel —\n * the surfaces you sketch on and the imported underlays (CAD, PDF, images,\n * terrain). They are **not** wall/floor construction-assembly material layers,\n * which are a property of the built element, not this namespace.\n *\n * Reads return plain records and never throw for a missing layer (`get` and\n * `getActive` return `null`, `list` returns `[]`).\n *\n * v1 exposes reads plus visibility edits and the active-layer switch.\n * Creating and deleting layers are intentionally not part of this contract yet.\n *\n * Sub-namespace members typed as optional (`?`) are **authored ahead of the\n * host** — declared so plugins and codegen can see the shape, but kept out of the\n * discovery manifest until the host lands them (at which point they are promoted\n * to required `abstract` members).\n *\n * Accessed via `snaptrude.core.layers`.\n */\nexport abstract class PluginCoreLayersApi {\n constructor() {}\n\n /**\n * List the drawing layers of a storey.\n *\n * Optionally filter to a single {@linkcode PluginCoreLayerType}.\n *\n * @param storeyId - The storey whose layers to read.\n * @param layerType - Optional {@linkcode PluginCoreLayerType} filter.\n * @returns A {@linkcode PluginCoreLayersListResult} with a `layers` array\n * (empty when the storey is missing or has no matching layers).\n *\n * @examplePrompt List the drawing layers on this storey\n * @examplePrompt Show all the PDF underlays on storey st_2\n * @examplePrompt What CAD layers are on the ground floor?\n *\n * # Example\n * ```ts\n * const { layers } = await snaptrude.core.layers.list(\"st_2\")\n * for (const l of layers) console.log(l.name, l.layerType, l.hidden)\n * ```\n */\n public abstract list(\n storeyId: string,\n layerType?: PluginCoreLayerType,\n ): PluginApiReturn<PluginCoreLayersListResult>\n\n /**\n * Get a single drawing layer by id.\n *\n * @param layerId - The id of the layer to read.\n * @returns The matching {@linkcode PluginCoreLayer}, or `null` if no layer has\n * that id.\n *\n * @examplePrompt Get the drawing layer with id ly_5\n * @examplePrompt Is layer ly_5 hidden or locked?\n * @examplePrompt Look up a single drawing layer by its id\n *\n * # Example\n * ```ts\n * const layer = await snaptrude.core.layers.get(\"ly_5\")\n * if (layer) console.log(layer.name, layer.hidden, layer.locked)\n * ```\n */\n public abstract get(\n layerId: string,\n ): PluginApiReturn<PluginCoreLayersGetResult>\n\n /**\n * Get the active drawing layer of a storey.\n *\n * The active layer is the one new sketch geometry is drawn onto. Paired with\n * {@linkcode PluginCoreLayersApi.setActive}.\n *\n * @param storeyId - The storey whose active layer to read.\n * @returns The active {@linkcode PluginCoreLayer}, or `null` if the storey is\n * missing or has no active layer.\n *\n * @examplePrompt Which drawing layer is active on this storey?\n * @examplePrompt Get the current layer on storey st_2\n * @examplePrompt What layer am I drawing on?\n *\n * # Example\n * ```ts\n * const active = await snaptrude.core.layers.getActive(\"st_2\")\n * if (active) console.log(active.name)\n * ```\n */\n public abstract getActive(\n storeyId: string,\n ): PluginApiReturn<PluginCoreLayersGetActiveResult>\n\n /**\n * Make a drawing layer the active layer of its storey.\n *\n * Paired with {@linkcode PluginCoreLayersApi.getActive}.\n *\n * @param layerId - The id of the layer to activate.\n * @returns The id of the now-active layer.\n * @throws If no layer has the given id.\n *\n * @examplePrompt Draw on layer ly_5\n * @examplePrompt Make this the active layer\n * @examplePrompt Switch the active layer to the CAD underlay\n *\n * # Example\n * ```ts\n * await snaptrude.core.layers.setActive(\"ly_5\")\n * ```\n */\n public abstract setActive(\n layerId: string,\n ): PluginApiReturn<PluginCoreLayersSetActiveResult>\n\n /**\n * Update a drawing layer's visibility.\n *\n * A **sparse** update: omit `hidden` to leave it unchanged. This edit is **not\n * undoable**. Locking and renaming are not exposed — layer `locked` is a\n * read-only status here (setting it at runtime does not lock the live meshes or\n * flip the panel padlock until a reload), and a rename cascades to every member\n * mesh's `layerName`, a corruption hazard.\n *\n * @param layerId - The id of the layer to update.\n * @param hidden - New hidden state (unchanged if omitted).\n * @returns The updated {@linkcode PluginCoreLayer}.\n * @throws If no layer has the given id.\n *\n * @examplePrompt Hide the PDF underlay layer\n * @examplePrompt Show the CAD layer again\n * @examplePrompt Hide layer ly_5\n *\n * # Example\n * ```ts\n * const layer = await snaptrude.core.layers.update(\"ly_5\", true)\n * ```\n */\n public abstract update(\n layerId: string,\n hidden?: boolean,\n ): PluginApiReturn<PluginCoreLayersUpdateResult>\n}\n\n/**\n * The kind of a drawing layer.\n *\n * Author-drawable kinds (`floor`, `wall`, `ceiling`, `rough`, `default`) plus\n * read-only imported underlays (`image`, `cad`, `pdf`, `terrain`, `buildings`).\n */\nexport const PluginCoreLayerType = z.enum([\n \"floor\",\n \"wall\",\n \"ceiling\",\n \"rough\",\n \"default\",\n \"image\",\n \"cad\",\n \"pdf\",\n \"terrain\",\n \"buildings\",\n])\nexport type PluginCoreLayerType = z.infer<typeof PluginCoreLayerType>\n\n/**\n * A drawing/reference layer on a storey.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Stable public layer id (e.g. `\"ly_5\"`) |\n * | `name` | `string` | Display name |\n * | `layerType` | {@linkcode PluginCoreLayerType} | The layer's kind |\n * | `storeyId` | `string` | The stable id of the storey this layer belongs to |\n * | `buildingId` | `string` | The stable id of the building this layer belongs to |\n * | `hidden` | `boolean` | Whether the layer is hidden |\n * | `locked` | `boolean` | Whether the layer is locked against edits |\n * | `contentSummary` | `object` | A summary of what the layer holds (see below) |\n *\n * `contentSummary`:\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `hasFloorplan` | `boolean` | Whether the layer holds drawn floorplan geometry |\n * | `pdfCount` | `number` | Number of PDF underlays on the layer |\n * | `hasTerrain` | `boolean` | Whether the layer holds terrain |\n */\nexport const PluginCoreLayer = z.object({\n id: z.string(),\n name: z.string(),\n layerType: PluginCoreLayerType,\n storeyId: z.string(),\n buildingId: z.string(),\n hidden: z.boolean(),\n locked: z.boolean(),\n contentSummary: z.object({\n hasFloorplan: z.boolean(),\n pdfCount: z.number().int(),\n hasTerrain: z.boolean(),\n }),\n})\nexport type PluginCoreLayer = z.infer<typeof PluginCoreLayer>\n\n/**\n * Arguments for {@linkcode PluginCoreLayersApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `storeyId` | `string` | The storey whose layers to read |\n * | `layerType` | {@linkcode PluginCoreLayerType}` \\| undefined` | Optional layer-type filter |\n */\nexport const PluginCoreLayersListArgs = z.object({\n storeyId: z.string(),\n layerType: PluginCoreLayerType.optional(),\n})\nexport type PluginCoreLayersListArgs = z.infer<\n typeof PluginCoreLayersListArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreLayersApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `layers` | {@linkcode PluginCoreLayer}`[]` | The storey's drawing layers (empty when missing or none match) |\n */\nexport const PluginCoreLayersListResult = z.object({\n layers: z.array(PluginCoreLayer),\n})\nexport type PluginCoreLayersListResult = z.infer<\n typeof PluginCoreLayersListResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreLayersApi.get}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `layerId` | `string` | The id of the layer to read |\n */\nexport const PluginCoreLayersGetArgs = z.object({\n layerId: z.string(),\n})\nexport type PluginCoreLayersGetArgs = z.infer<\n typeof PluginCoreLayersGetArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreLayersApi.get} — the layer, or `null`\n * when no layer has the given id.\n */\nexport const PluginCoreLayersGetResult = PluginCoreLayer.nullable()\nexport type PluginCoreLayersGetResult = z.infer<\n typeof PluginCoreLayersGetResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreLayersApi.getActive}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `storeyId` | `string` | The storey whose active layer to read |\n */\nexport const PluginCoreLayersGetActiveArgs = z.object({\n storeyId: z.string(),\n})\nexport type PluginCoreLayersGetActiveArgs = z.infer<\n typeof PluginCoreLayersGetActiveArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreLayersApi.getActive} — the active layer,\n * or `null` when the storey is missing or has no active layer.\n */\nexport const PluginCoreLayersGetActiveResult = PluginCoreLayer.nullable()\nexport type PluginCoreLayersGetActiveResult = z.infer<\n typeof PluginCoreLayersGetActiveResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreLayersApi.setActive}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `layerId` | `string` | The id of the layer to activate |\n */\nexport const PluginCoreLayersSetActiveArgs = z.object({\n layerId: z.string(),\n})\nexport type PluginCoreLayersSetActiveArgs = z.infer<\n typeof PluginCoreLayersSetActiveArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreLayersApi.setActive} — the now-active\n * layer's id.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `layerId` | `string` | The id of the now-active layer |\n */\nexport const PluginCoreLayersSetActiveResult = z.object({\n layerId: z.string(),\n})\nexport type PluginCoreLayersSetActiveResult = z.infer<\n typeof PluginCoreLayersSetActiveResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreLayersApi.update}. Sparse — only the\n * provided fields are changed.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `layerId` | `string` | The id of the layer to update |\n * | `hidden` | `boolean \\| undefined` | New hidden state (unchanged if omitted) |\n */\nexport const PluginCoreLayersUpdateArgs = z.object({\n layerId: z.string(),\n hidden: z.boolean().optional(),\n})\nexport type PluginCoreLayersUpdateArgs = z.infer<\n typeof PluginCoreLayersUpdateArgs\n>\n\n/** Result of {@linkcode PluginCoreLayersApi.update} — the updated layer. */\nexport const PluginCoreLayersUpdateResult = PluginCoreLayer\nexport type PluginCoreLayersUpdateResult = z.infer<\n typeof PluginCoreLayersUpdateResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport { ComponentHandle } from \"../../handles\"\n\n/**\n * Tags — read and edit the tag catalog and its assignments.\n *\n * A **tag** is a colored label that lives in a **category**; a space carries at\n * most one tag per category. This namespace is the catalog + assignment surface:\n * the categories and tags themselves, which components carry which tags, and the\n * assign/unassign operations. Tags attach to **Mass and Floor spaces only**.\n *\n * **Reserved sentinels.** The built-in **`DEPARTMENT`** category is owned by the\n * program layer — it and the tags inside it are read-only here; create/update/\n * delete reject it and direct you to {@linkcode PluginProgramApi}`.departments`.\n * Every category also has a built-in **`DEFAULT`** tag that cannot be deleted;\n * unassigning resets a component's category slot back to `DEFAULT` (a component\n * is never left with a blank slot).\n *\n * Reads return plain records and never throw for a miss (`get*` return `null`,\n * `list*` return `[]`). Ids are stable public strings.\n *\n * Sub-namespace members typed as optional (`?`) are **authored ahead of the\n * host** — declared so plugins and codegen can see the shape, but kept out of the\n * discovery manifest until the host lands them (at which point they are promoted\n * to required `abstract` members).\n *\n * Accessed via `snaptrude.core.tags`.\n */\nexport abstract class PluginCoreTagsApi {\n constructor() {}\n\n /**\n * List the tag categories.\n *\n * @returns A {@linkcode PluginCoreTagsListCategoriesResult} with a `categories`\n * array (empty when there are none), including the reserved `DEPARTMENT`\n * category.\n *\n * @examplePrompt List all the tag categories\n * @examplePrompt What tag categories do I have?\n * @examplePrompt Show every category and its tags\n *\n * # Example\n * ```ts\n * const { categories } = await snaptrude.core.tags.listCategories()\n * for (const c of categories) console.log(c.name, c.tagIds.length)\n * ```\n */\n public abstract listCategories(): PluginApiReturn<PluginCoreTagsListCategoriesResult>\n\n /**\n * Get a single tag category by id.\n *\n * @param categoryId - The id of the category to read.\n * @returns The matching {@linkcode PluginCoreTagCategory}, or `null` if no\n * category has that id.\n *\n * @examplePrompt Get the tag category with id cat_3\n * @examplePrompt Look up a category by its id\n * @examplePrompt Which tags are in this category?\n *\n * # Example\n * ```ts\n * const category = await snaptrude.core.tags.getCategory(\"cat_3\")\n * if (category) console.log(category.name, category.tagIds)\n * ```\n */\n public abstract getCategory(\n categoryId: string,\n ): PluginApiReturn<PluginCoreTagsGetCategoryResult>\n\n /**\n * List tags, optionally within one category.\n *\n * @param categoryId - Optional category filter; omit to list every tag.\n * @returns A {@linkcode PluginCoreTagsListResult} with a `tags` array (empty\n * when there are none).\n *\n * @examplePrompt List every tag in the project\n * @examplePrompt Show the tags in category cat_3\n * @examplePrompt What tags are available?\n *\n * # Example\n * ```ts\n * const { tags } = await snaptrude.core.tags.list(\"cat_3\")\n * for (const t of tags) console.log(t.name, t.color)\n * ```\n */\n public abstract list(\n categoryId?: string,\n ): PluginApiReturn<PluginCoreTagsListResult>\n\n /**\n * Get a single tag by id.\n *\n * @param tagId - The id of the tag to read.\n * @returns The matching {@linkcode PluginCoreTag}, or `null` if no tag has that\n * id.\n *\n * @examplePrompt Get the tag with id tag_9\n * @examplePrompt Look up a single tag by its id\n * @examplePrompt What color is this tag?\n *\n * # Example\n * ```ts\n * const tag = await snaptrude.core.tags.get(\"tag_9\")\n * if (tag) console.log(tag.name, tag.color)\n * ```\n */\n public abstract get(\n tagId: string,\n ): PluginApiReturn<PluginCoreTagsGetResult>\n\n /**\n * List the tags carried by a component.\n *\n * Returns one entry per category the component is tagged in, each carrying the\n * category and tag names and the tag color.\n *\n * @param componentId - The `Component.id` to read.\n * @returns A {@linkcode PluginCoreTagsGetTagsForComponentResult} with a `tags`\n * array (empty when the component has none or does not exist).\n *\n * @examplePrompt What tags does this space have?\n * @examplePrompt List the tags on component cmp_42\n * @examplePrompt Show the labels applied to this room\n *\n * # Example\n * ```ts\n * const { tags } = await snaptrude.core.tags.getTagsForComponent(\"cmp_42\")\n * for (const t of tags) console.log(t.categoryName, t.tagName)\n * ```\n *\n * @deprecated Use `core.tags.listForComponent`\n */\n public abstract getTagsForComponent(\n componentId: ComponentHandle,\n ): PluginApiReturn<PluginCoreTagsGetTagsForComponentResult>\n\n /**\n * List the tags carried by a component.\n *\n * Returns one entry per category the component is tagged in, each carrying the\n * category and tag names and the tag color. Canonical home of the read formerly\n * at `getTagsForComponent` (now deprecated).\n *\n * @param componentId - The `Component.id` to read.\n * @returns A {@linkcode PluginCoreTagsGetTagsForComponentResult} with a `tags`\n * array (empty when the component has none or does not exist).\n *\n * @examplePrompt What tags does this space have?\n * @examplePrompt List the tags on component cmp_42\n * @examplePrompt Show the labels applied to this room\n *\n * # Example\n * ```ts\n * const { tags } = await snaptrude.core.tags.listForComponent(\"cmp_42\")\n * for (const t of tags) console.log(t.categoryName, t.tagName)\n * ```\n */\n public abstract listForComponent(\n componentId: ComponentHandle,\n ): PluginApiReturn<PluginCoreTagsGetTagsForComponentResult>\n\n /**\n * List the tags carried by many components in one call.\n *\n * The plural of {@linkcode PluginCoreTagsApi.listForComponent}: pass\n * `componentIds` to read specific components (unknown ids are skipped), or\n * omit it to read **every** taggable component (Mass/Floor space) in the\n * project. Each entry carries the component id and its tags — one per tagged\n * category, with the tag's `color` — so a plugin can color every space by a\n * chosen category's tag in a single round-trip.\n *\n * @param componentIds - Optional `Component.id`s to read; omit for all spaces.\n * @returns A {@linkcode PluginCoreTagsListForComponentsResult} with a\n * `components` array, one entry per component (in input order when\n * `componentIds` is given).\n *\n * @performance One round-trip for any number of components — use this instead of a `listForComponent` loop.\n *\n * @examplePrompt Get the tags of every space with their colors\n * @examplePrompt List the tags on all rooms so I can color them by Occupancy\n * @examplePrompt Which tag does each selected space carry?\n *\n * # Example\n * ```ts\n * const { components } = await snaptrude.core.tags.listForComponents()\n * for (const c of components) {\n * const occupancy = c.tags.find((t) => t.categoryName === \"Occupancy\")\n * if (occupancy) paint(c.componentId, occupancy.color)\n * }\n * ```\n */\n public abstract listForComponents(\n componentIds?: ComponentHandle[],\n ): PluginApiReturn<PluginCoreTagsListForComponentsResult>\n\n /**\n * List the components carrying a given tag or category.\n *\n * Provide **exactly one** of `tagId` (components with that specific tag) or\n * `categoryId` (components carrying any non-default tag in that category). With\n * `categoryId`, pass `untagged: true` to instead return the components that have\n * **no** non-default tag in that category. `untagged` is valid only alongside\n * `categoryId`.\n *\n * @param filter - A {@linkcode PluginCoreTagsListComponentsArgs} filter with\n * exactly one of `tagId`, `categoryId`, plus optional `untagged` (with\n * `categoryId`).\n * @returns A {@linkcode PluginCoreTagsListComponentsResult} with a\n * `componentIds` array (empty when none match).\n *\n * @examplePrompt Which spaces have the tag tag_9?\n * @examplePrompt List every component tagged in category cat_3\n * @examplePrompt Which spaces are untagged in the Occupancy category?\n *\n * # Example\n * ```ts\n * const { componentIds } = await snaptrude.core.tags.listComponents({ tagId: \"tag_9\" })\n * console.log(`${componentIds.length} components`)\n * ```\n */\n public abstract listComponents(\n filter: { tagId?: string; categoryId?: string; untagged?: boolean },\n ): PluginApiReturn<PluginCoreTagsListComponentsResult>\n\n /**\n * Create a new tag category.\n *\n * @param name - Display name of the new category.\n * @returns The new {@linkcode PluginCoreTagCategory}.\n * @throws If the name collides with the reserved `DEPARTMENT` category, or the\n * category could not be created.\n *\n * @examplePrompt Create a tag category called Fire Rating\n * @examplePrompt Add a new category named Zone\n * @examplePrompt Make a category for occupancy type\n *\n * # Example\n * ```ts\n * const category = await snaptrude.core.tags.createCategory(\"Fire Rating\")\n * ```\n */\n public abstract createCategory(\n name: string,\n ): PluginApiReturn<PluginCoreTagsCreateCategoryResult>\n\n /**\n * Rename a tag category.\n *\n * @param categoryId - The id of the category to rename.\n * @param name - The category's new display name.\n * @returns The updated {@linkcode PluginCoreTagCategory}.\n * @throws If no category has the given id, or it is the reserved `DEPARTMENT`\n * category.\n *\n * @examplePrompt Rename category cat_3 to Occupancy\n * @examplePrompt Change the name of this tag category\n * @examplePrompt Update the Zone category's name\n *\n * # Example\n * ```ts\n * const category = await snaptrude.core.tags.updateCategory(\"cat_3\", \"Occupancy\")\n * ```\n */\n public abstract updateCategory(\n categoryId: string,\n name: string,\n ): PluginApiReturn<PluginCoreTagsUpdateCategoryResult>\n\n /**\n * Delete a tag category and all its tags.\n *\n * **Non-undoable.** Every component tagged in this category is reset to the\n * category's `DEFAULT`; the result reports how many tags were removed and how\n * many components were reset.\n *\n * @param categoryId - The id of the category to delete.\n * @returns The impact counts (`deletedTagCount`, `resetComponentCount`).\n * @throws If no category has the given id, or it is the reserved `DEPARTMENT`\n * category.\n *\n * @examplePrompt Delete the Zone tag category\n * @examplePrompt Remove category cat_3 and all its tags\n * @examplePrompt Delete this category\n *\n * # Example\n * ```ts\n * const { deletedTagCount } = await snaptrude.core.tags.deleteCategory(\"cat_3\")\n * ```\n */\n public abstract deleteCategory(\n categoryId: string,\n ): PluginApiReturn<PluginCoreTagsDeleteCategoryResult>\n\n /**\n * Create a new tag in a category.\n *\n * @param categoryId - The category the new tag belongs to.\n * @param name - Display name of the new tag.\n * @param color - Optional CSS hex color string (auto-assigned if omitted).\n * @returns The new {@linkcode PluginCoreTag}.\n * @throws If no category has the given id, or it is the reserved `DEPARTMENT`\n * category.\n *\n * @examplePrompt Add a tag called 2-hour to the Fire Rating category\n * @examplePrompt Create a red High Priority tag in category cat_3\n * @examplePrompt Make a new tag named Retail\n *\n * # Example\n * ```ts\n * const tag = await snaptrude.core.tags.create(\"cat_3\", \"Retail\", \"#b5e1dc\")\n * ```\n */\n public abstract create(\n categoryId: string,\n name: string,\n color?: string,\n ): PluginApiReturn<PluginCoreTagsCreateResult>\n\n /**\n * Update a tag's name and/or color.\n *\n * A **sparse** update: omitted fields are left unchanged.\n *\n * @param tagId - The id of the tag to update.\n * @param options - Any of `name`, `color`; omitted fields are left unchanged.\n * @returns The updated {@linkcode PluginCoreTag}.\n * @throws If no tag has the given id, or it belongs to the reserved\n * `DEPARTMENT` category.\n *\n * @examplePrompt Rename tag tag_9 to Retail\n * @examplePrompt Change this tag's color to #ff8800\n * @examplePrompt Recolor the High Priority tag\n *\n * # Example\n * ```ts\n * const tag = await snaptrude.core.tags.update(\"tag_9\", { name: \"Retail\" })\n * ```\n */\n public abstract update(\n tagId: string,\n options?: { name?: string; color?: string },\n ): PluginApiReturn<PluginCoreTagsUpdateResult>\n\n /**\n * Delete a tag.\n *\n * **Non-undoable.** Every component carrying this tag is reset to its\n * category's `DEFAULT`; the result reports how many components were reset. The\n * built-in `DEFAULT` tag of a category cannot be deleted.\n *\n * @param tagId - The id of the tag to delete.\n * @returns The impact count (`resetComponentCount`).\n * @throws If no tag has the given id, it is a `DEFAULT` tag, or it belongs to\n * the reserved `DEPARTMENT` category.\n *\n * @examplePrompt Delete the Retail tag\n * @examplePrompt Remove tag tag_9\n * @examplePrompt Delete this label\n *\n * # Example\n * ```ts\n * const { resetComponentCount } = await snaptrude.core.tags.delete(\"tag_9\")\n * ```\n */\n public abstract delete(\n tagId: string,\n ): PluginApiReturn<PluginCoreTagsDeleteResult>\n\n /**\n * Assign a tag to components, creating the category and tag if needed.\n *\n * Resolves `tagName` within the category named `categoryName`: if the category\n * or the tag does not exist yet it is created, otherwise the existing one is\n * reused. The resolved tag is then applied to each taggable component (Mass/Floor\n * spaces) — resolve-or-create and assign are **one atomic, undoable action**.\n * Because a component carries one tag per category, this replaces any existing\n * tag from that category. Components that are locked, not taggable, or not found\n * are returned in `skipped` with a reason — never silently dropped. Rejects the\n * reserved `DEPARTMENT` category.\n *\n * @param componentIds - The components to tag (at least one).\n * @param categoryName - The tag category; created if it does not exist (not\n * `DEPARTMENT`).\n * @param tagName - The tag within the category; created if it does not exist,\n * else reused.\n * @returns A {@linkcode PluginCoreTagsAssignResult} — the resolved\n * `categoryId`/`tagId`, whether each was created, and the `assigned`/`skipped`\n * split.\n * @throws If `categoryName` is the reserved `DEPARTMENT` category.\n *\n * @examplePrompt Tag the selected spaces as Retail in the Zone category\n * @examplePrompt Tag these rooms High Priority, creating the tag if it doesn't exist\n * @examplePrompt Put cmp_1 and cmp_2 in Zone / Retail\n *\n * # Example\n * ```ts\n * const { tagId, tagCreated, assigned } = await snaptrude.core.tags.assign(\n * [\"cmp_1\", \"cmp_2\"],\n * \"Zone\",\n * \"Retail\",\n * )\n * ```\n */\n public abstract assign(\n componentIds: ComponentHandle[],\n categoryName: string,\n tagName: string,\n ): PluginApiReturn<PluginCoreTagsAssignResult>\n\n /**\n * Clear a category's tag from components.\n *\n * Resets each component's slot for the given category back to `DEFAULT` (a\n * component always has a `DEFAULT` — the field is never blanked). Components\n * that are locked, not taggable, or not found are returned in `skipped`.\n *\n * @param categoryId - The category slot to reset to default.\n * @param componentIds - The components to clear (at least one).\n * @returns A {@linkcode PluginCoreAssignOutcome} splitting `assigned` (reset to\n * default) from `skipped`.\n * @throws If no category has the given id.\n *\n * @examplePrompt Remove the Occupancy tag from the selected spaces\n * @examplePrompt Clear category cat_3 on components cmp_1 and cmp_2\n * @examplePrompt Untag these rooms in the Zone category\n *\n * # Example\n * ```ts\n * await snaptrude.core.tags.unassign(\"cat_3\", [\"cmp_1\", \"cmp_2\"])\n * ```\n */\n public abstract unassign(\n categoryId: string,\n componentIds: ComponentHandle[],\n ): PluginApiReturn<PluginCoreTagsUnassignResult>\n}\n\n/**\n * A tag category — a named slot that holds a set of mutually-exclusive tags.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Stable public category id |\n * | `name` | `string` | Display name |\n * | `tagIds` | `string[]` | Ids of the tags in this category |\n */\nexport const PluginCoreTagCategory = z.object({\n id: z.string(),\n name: z.string(),\n tagIds: z.array(z.string()),\n})\nexport type PluginCoreTagCategory = z.infer<typeof PluginCoreTagCategory>\n\n/**\n * A tag — a colored label within a category.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Stable public tag id |\n * | `categoryId` | `string` | The id of the category this tag belongs to |\n * | `name` | `string` | Display name |\n * | `color` | `string` | CSS hex color string (e.g. `\"#b5e1dc\"`) |\n */\nexport const PluginCoreTag = z.object({\n id: z.string(),\n categoryId: z.string(),\n name: z.string(),\n color: z.string(),\n})\nexport type PluginCoreTag = z.infer<typeof PluginCoreTag>\n\n/**\n * A tag as carried by a component — the category and tag names resolved for\n * display.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `categoryId` | `string` | The category's id |\n * | `categoryName` | `string` | The category's display name |\n * | `tagId` | `string` | The tag's id |\n * | `tagName` | `string` | The tag's display name |\n * | `color` | `string` | The tag's CSS hex color string |\n */\nexport const PluginCoreComponentTag = z.object({\n categoryId: z.string(),\n categoryName: z.string(),\n tagId: z.string(),\n tagName: z.string(),\n color: z.string(),\n})\nexport type PluginCoreComponentTag = z.infer<typeof PluginCoreComponentTag>\n\n/**\n * The outcome of an assign/unassign — which components changed and which were\n * skipped (with a reason).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `assigned` | `string[]` | Ids of the components that were changed |\n * | `skipped` | `{ id: string; reason: \"locked\" \\| \"not-taggable\" \\| \"not-found\" }[]` | Components that were not changed, each with a reason |\n */\nexport const PluginCoreAssignOutcome = z.object({\n assigned: z.array(z.string()),\n skipped: z.array(\n z.object({\n id: z.string(),\n reason: z.enum([\"locked\", \"not-taggable\", \"not-found\"]),\n }),\n ),\n})\nexport type PluginCoreAssignOutcome = z.infer<typeof PluginCoreAssignOutcome>\n\n/**\n * Result of {@linkcode PluginCoreTagsApi.listCategories}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `categories` | {@linkcode PluginCoreTagCategory}`[]` | Every tag category (including the reserved `DEPARTMENT`) |\n */\nexport const PluginCoreTagsListCategoriesResult = z.object({\n categories: z.array(PluginCoreTagCategory),\n})\nexport type PluginCoreTagsListCategoriesResult = z.infer<\n typeof PluginCoreTagsListCategoriesResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreTagsApi.getCategory}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `categoryId` | `string` | The id of the category to read |\n */\nexport const PluginCoreTagsGetCategoryArgs = z.object({\n categoryId: z.string(),\n})\nexport type PluginCoreTagsGetCategoryArgs = z.infer<\n typeof PluginCoreTagsGetCategoryArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreTagsApi.getCategory} — the category, or `null`\n * when no category has the given id.\n */\nexport const PluginCoreTagsGetCategoryResult = PluginCoreTagCategory.nullable()\nexport type PluginCoreTagsGetCategoryResult = z.infer<\n typeof PluginCoreTagsGetCategoryResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreTagsApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `categoryId` | `string \\| undefined` | Optional category filter; omit to list every tag |\n */\nexport const PluginCoreTagsListArgs = z.object({\n categoryId: z.string().optional(),\n})\nexport type PluginCoreTagsListArgs = z.infer<typeof PluginCoreTagsListArgs>\n\n/**\n * Result of {@linkcode PluginCoreTagsApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `tags` | {@linkcode PluginCoreTag}`[]` | The matching tags (empty when none) |\n */\nexport const PluginCoreTagsListResult = z.object({\n tags: z.array(PluginCoreTag),\n})\nexport type PluginCoreTagsListResult = z.infer<typeof PluginCoreTagsListResult>\n\n/**\n * Arguments for {@linkcode PluginCoreTagsApi.get}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `tagId` | `string` | The id of the tag to read |\n */\nexport const PluginCoreTagsGetArgs = z.object({\n tagId: z.string(),\n})\nexport type PluginCoreTagsGetArgs = z.infer<typeof PluginCoreTagsGetArgs>\n\n/**\n * Result of {@linkcode PluginCoreTagsApi.get} — the tag, or `null` when no tag\n * has the given id.\n */\nexport const PluginCoreTagsGetResult = PluginCoreTag.nullable()\nexport type PluginCoreTagsGetResult = z.infer<typeof PluginCoreTagsGetResult>\n\n/**\n * Arguments for {@linkcode PluginCoreTagsApi.getTagsForComponent}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `componentId` | `ComponentHandle` | The `Component.id` to read |\n */\nexport const PluginCoreTagsGetTagsForComponentArgs = z.object({\n componentId: ComponentHandle,\n})\nexport type PluginCoreTagsGetTagsForComponentArgs = z.infer<\n typeof PluginCoreTagsGetTagsForComponentArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreTagsApi.getTagsForComponent}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `tags` | {@linkcode PluginCoreComponentTag}`[]` | The component's tags, one per tagged category (empty when none) |\n */\nexport const PluginCoreTagsGetTagsForComponentResult = z.object({\n tags: z.array(PluginCoreComponentTag),\n})\nexport type PluginCoreTagsGetTagsForComponentResult = z.infer<\n typeof PluginCoreTagsGetTagsForComponentResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreTagsApi.listForComponents}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `componentIds` | `ComponentHandle[]?` | Components to read; omit for every taggable component |\n */\nexport const PluginCoreTagsListForComponentsArgs = z.object({\n componentIds: z.array(ComponentHandle).optional(),\n})\nexport type PluginCoreTagsListForComponentsArgs = z.infer<\n typeof PluginCoreTagsListForComponentsArgs\n>\n\n/**\n * One component's tags, for {@linkcode PluginCoreTagsApi.listForComponents}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `componentId` | `ComponentHandle` | The component's id |\n * | `tags` | {@linkcode PluginCoreComponentTag}`[]` | Its tags, one per tagged category (empty when none) |\n */\nexport const PluginCoreComponentTags = z.object({\n componentId: ComponentHandle,\n tags: z.array(PluginCoreComponentTag),\n})\nexport type PluginCoreComponentTags = z.infer<typeof PluginCoreComponentTags>\n\n/**\n * Result of {@linkcode PluginCoreTagsApi.listForComponents}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `components` | {@linkcode PluginCoreComponentTags}`[]` | One entry per component |\n */\nexport const PluginCoreTagsListForComponentsResult = z.object({\n components: z.array(PluginCoreComponentTags),\n})\nexport type PluginCoreTagsListForComponentsResult = z.infer<\n typeof PluginCoreTagsListForComponentsResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreTagsApi.listComponents}. Provide exactly one\n * of `tagId`, `categoryId`. `untagged` is valid only with `categoryId`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `tagId` | `string \\| undefined` | Components carrying this specific tag |\n * | `categoryId` | `string \\| undefined` | Components carrying any non-default tag in this category |\n * | `untagged` | `boolean \\| undefined` | With `categoryId`, return components with **no** non-default tag in that category instead |\n */\nexport const PluginCoreTagsListComponentsArgs = z\n .object({\n tagId: z.string().optional(),\n categoryId: z.string().optional(),\n untagged: z.boolean().optional(),\n })\n .refine((v) => (v.tagId == null) !== (v.categoryId == null), {\n message: \"Provide exactly one of tagId or categoryId\",\n })\n .refine((v) => v.untagged == null || v.categoryId != null, {\n message: \"untagged is valid only together with categoryId\",\n })\nexport type PluginCoreTagsListComponentsArgs = z.infer<\n typeof PluginCoreTagsListComponentsArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreTagsApi.listComponents}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `componentIds` | `ComponentHandle[]` | Ids of the matching components (empty when none) |\n */\nexport const PluginCoreTagsListComponentsResult = z.object({\n componentIds: z.array(ComponentHandle),\n})\nexport type PluginCoreTagsListComponentsResult = z.infer<\n typeof PluginCoreTagsListComponentsResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreTagsApi.createCategory}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | Display name of the new category |\n */\nexport const PluginCoreTagsCreateCategoryArgs = z.object({\n name: z.string(),\n})\nexport type PluginCoreTagsCreateCategoryArgs = z.infer<\n typeof PluginCoreTagsCreateCategoryArgs\n>\n\n/** Result of {@linkcode PluginCoreTagsApi.createCategory} — the new category. */\nexport const PluginCoreTagsCreateCategoryResult = PluginCoreTagCategory\nexport type PluginCoreTagsCreateCategoryResult = z.infer<\n typeof PluginCoreTagsCreateCategoryResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreTagsApi.updateCategory}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `categoryId` | `string` | The id of the category to rename |\n * | `name` | `string` | The category's new display name |\n */\nexport const PluginCoreTagsUpdateCategoryArgs = z.object({\n categoryId: z.string(),\n name: z.string(),\n})\nexport type PluginCoreTagsUpdateCategoryArgs = z.infer<\n typeof PluginCoreTagsUpdateCategoryArgs\n>\n\n/** Result of {@linkcode PluginCoreTagsApi.updateCategory} — the updated category. */\nexport const PluginCoreTagsUpdateCategoryResult = PluginCoreTagCategory\nexport type PluginCoreTagsUpdateCategoryResult = z.infer<\n typeof PluginCoreTagsUpdateCategoryResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreTagsApi.deleteCategory}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `categoryId` | `string` | The id of the category to delete |\n */\nexport const PluginCoreTagsDeleteCategoryArgs = z.object({\n categoryId: z.string(),\n})\nexport type PluginCoreTagsDeleteCategoryArgs = z.infer<\n typeof PluginCoreTagsDeleteCategoryArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreTagsApi.deleteCategory} — the impact of the\n * (non-undoable) delete.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `deletedTagCount` | `number` | Number of tags removed with the category |\n * | `resetComponentCount` | `number` | Number of components reset to their default |\n */\nexport const PluginCoreTagsDeleteCategoryResult = z.object({\n deletedTagCount: z.number().int(),\n resetComponentCount: z.number().int(),\n})\nexport type PluginCoreTagsDeleteCategoryResult = z.infer<\n typeof PluginCoreTagsDeleteCategoryResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreTagsApi.create}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `categoryId` | `string` | The category the new tag belongs to |\n * | `name` | `string` | Display name of the new tag |\n * | `color` | `string \\| undefined` | CSS hex color string (auto-assigned if omitted) |\n */\nexport const PluginCoreTagsCreateArgs = z.object({\n categoryId: z.string(),\n name: z.string(),\n color: z.string().optional(),\n})\nexport type PluginCoreTagsCreateArgs = z.infer<typeof PluginCoreTagsCreateArgs>\n\n/** Result of {@linkcode PluginCoreTagsApi.create} — the new tag. */\nexport const PluginCoreTagsCreateResult = PluginCoreTag\nexport type PluginCoreTagsCreateResult = z.infer<\n typeof PluginCoreTagsCreateResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreTagsApi.update}. Sparse — only the provided\n * fields are changed.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `tagId` | `string` | The id of the tag to update |\n * | `name` | `string \\| undefined` | New display name (unchanged if omitted) |\n * | `color` | `string \\| undefined` | New CSS hex color (unchanged if omitted) |\n */\nexport const PluginCoreTagsUpdateArgs = z.object({\n tagId: z.string(),\n name: z.string().optional(),\n color: z.string().optional(),\n})\nexport type PluginCoreTagsUpdateArgs = z.infer<typeof PluginCoreTagsUpdateArgs>\n\n/** Result of {@linkcode PluginCoreTagsApi.update} — the updated tag. */\nexport const PluginCoreTagsUpdateResult = PluginCoreTag\nexport type PluginCoreTagsUpdateResult = z.infer<\n typeof PluginCoreTagsUpdateResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreTagsApi.delete}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `tagId` | `string` | The id of the tag to delete |\n */\nexport const PluginCoreTagsDeleteArgs = z.object({\n tagId: z.string(),\n})\nexport type PluginCoreTagsDeleteArgs = z.infer<typeof PluginCoreTagsDeleteArgs>\n\n/**\n * Result of {@linkcode PluginCoreTagsApi.delete} — the impact of the\n * (non-undoable) delete.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `resetComponentCount` | `number` | Number of components reset to their category default |\n */\nexport const PluginCoreTagsDeleteResult = z.object({\n resetComponentCount: z.number().int(),\n})\nexport type PluginCoreTagsDeleteResult = z.infer<\n typeof PluginCoreTagsDeleteResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreTagsApi.assign}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `componentIds` | `ComponentHandle[]` | The components to tag (at least one) |\n * | `categoryName` | `string` | The tag category; created if it does not exist (not `DEPARTMENT`) |\n * | `tagName` | `string` | The tag within the category; created if it does not exist, else reused |\n */\nexport const PluginCoreTagsAssignArgs = z.object({\n componentIds: z.array(ComponentHandle).min(1),\n categoryName: z.string(),\n tagName: z.string(),\n})\nexport type PluginCoreTagsAssignArgs = z.infer<typeof PluginCoreTagsAssignArgs>\n\n/**\n * Result of {@linkcode PluginCoreTagsApi.assign} — the resolved category/tag, what\n * was created, and the assign outcome.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `categoryId` | `string` | Id of the resolved (or newly created) category |\n * | `tagId` | `string` | Id of the resolved (or newly created) tag |\n * | `categoryCreated` | `boolean` | Whether the category was created by this call |\n * | `tagCreated` | `boolean` | Whether the tag was created by this call |\n * | `assigned` | `string[]` | Ids of the components that were tagged |\n * | `skipped` | `{ id: string; reason: \"locked\" \\| \"not-taggable\" \\| \"not-found\" }[]` | Components not tagged, each with a reason |\n */\nexport const PluginCoreTagsAssignResult = PluginCoreAssignOutcome.extend({\n categoryId: z.string(),\n tagId: z.string(),\n categoryCreated: z.boolean(),\n tagCreated: z.boolean(),\n})\nexport type PluginCoreTagsAssignResult = z.infer<\n typeof PluginCoreTagsAssignResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreTagsApi.unassign}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `categoryId` | `string` | The category slot to reset to default |\n * | `componentIds` | `ComponentHandle[]` | The components to clear (at least one) |\n */\nexport const PluginCoreTagsUnassignArgs = z.object({\n categoryId: z.string(),\n componentIds: z.array(ComponentHandle).min(1),\n})\nexport type PluginCoreTagsUnassignArgs = z.infer<\n typeof PluginCoreTagsUnassignArgs\n>\n\n/** Result of {@linkcode PluginCoreTagsApi.unassign} — the unassign outcome. */\nexport const PluginCoreTagsUnassignResult = PluginCoreAssignOutcome\nexport type PluginCoreTagsUnassignResult = z.infer<\n typeof PluginCoreTagsUnassignResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport {\n Vec3Handle,\n UnderlayHandle,\n TerrainHandle,\n ImportJobHandle,\n ComponentHandle,\n} from \"../../../../handles\"\n\n/**\n * A file `source` accepted by every importer: an `https://` URL or a `data:`\n * URL (base64). Validated for shape at the boundary — other schemes (`http:`,\n * `blob:`, `file:`) are rejected, since the host page (HTTPS) cannot fetch them.\n */\nexport const ImportSource = z\n .string()\n .min(1)\n .refine((s) => s.startsWith(\"https://\") || s.startsWith(\"data:\"), {\n message: \"source must be an https:// URL or a data: URL\",\n })\n\n/**\n * File import — bring external files into the Snaptrude **scene**.\n *\n * `core.io.import.*` ingests images, PDFs, CAD drawings, 3D models, and site\n * terrain and commits them to the model as traceable underlays, placed geometry,\n * or topography. It is the scene counterpart to `presentation.import.*` (which\n * drops assets on the Present canvas, not the model).\n *\n * ## The `source` argument\n * Every file importer takes a single `source` **string** — either an `https://`\n * URL or a `data:` URL (base64). A plugin worker cannot hand a browser `File`\n * across the boundary, so the host fetches `source` and rebuilds the `File`\n * itself. Both forms go through the same path.\n *\n * ## Return values\n * - `image` / `pdf` / `cadJson` → an {@linkcode UnderlayHandle} (a placed 2D\n * reference plane; drive it with `core.io.underlay.*`).\n * - `dwg` → an {@linkcode ImportJobHandle} — DWG conversion is a long server job,\n * so `dwg` returns immediately and you poll it with `core.io.job.*`.\n * - `model` → a {@linkcode ComponentHandle} (a placed 3D component).\n * - `terrain` → a {@linkcode TerrainHandle} (the project's single site terrain).\n *\n * Every importer **throws** on failure (bad source, unsupported format, engine\n * error). Accessed via `snaptrude.core.io.import`.\n */\nexport abstract class PluginCoreIoImportApi {\n constructor() {}\n\n /**\n * Import a raster image as a scene **underlay** — a flat, textured reference\n * plane placed on a storey that you can trace over.\n *\n * Supported formats: PNG, JPG/JPEG, BMP. The image is placed on the target\n * storey's \"Image\" layer at ~50% opacity by default.\n *\n * To place the image on a **Present-mode sheet** instead of the scene, use\n * `presentation.import.image`.\n *\n * The plane is placed at the storey origin (the engine's import placement);\n * there is no placement parameter — calibrate size with\n * {@link PluginCoreIoUnderlayApi.setScale} after import.\n *\n * @param source - The image to import: an `https://` URL or a `data:` URL.\n * @param storey - Target storey number. Defaults to the active storey.\n * @param scale - Initial uniform scale factor applied to the plane. Calibrate\n * precisely afterwards with {@link PluginCoreIoUnderlayApi.setScale}.\n * @param opacity - Plane opacity, `0`..`1` (default ~`0.5`).\n * @param label - A name for the created underlay.\n * @returns the created {@linkcode UnderlayHandle}.\n * @throws if writes are disabled, the source can't be loaded, the format is\n * unsupported, or the engine fails to place the plane.\n *\n * @examplePrompt Import this floor plan image onto the ground floor to trace over\n * @examplePrompt Drop this survey PNG in as a reference underlay at 30% opacity\n * @examplePrompt Add a site plan image on storey 2\n * @examplePrompt Bring in a sketch to trace, faded to half opacity\n *\n * # Example\n * ```ts\n * // Place a floor plan on storey 1, then calibrate it to a real-world size.\n * const plan = await snaptrude.core.io.import.image(\n * \"https://example.com/floorplan.png\",\n * 1, // storey\n * undefined, // scale\n * 0.4, // opacity\n * \"Ground floor plan\",\n * )\n * // Make the plan's longest side exactly 40 project-units:\n * await snaptrude.core.io.underlay.setScale(plan, { planSize: 40 })\n * ```\n */\n public abstract image(\n source: string,\n storey?: number,\n scale?: number,\n opacity?: number,\n label?: string,\n ): PluginApiReturn<UnderlayHandle>\n\n /**\n * Import one page of a PDF as a scene **underlay** (a traceable reference plane).\n *\n * One PDF per storey, one page per call. To bring in several pages, call once\n * per page onto different storeys.\n *\n * @param source - The PDF to import: an `https://` URL or a `data:` URL.\n * @param storey - Target storey number. Defaults to the active storey.\n * @param page - 1-based page number to import (multi-page PDFs). Default `1`.\n * @param scale - A drawing-scale **label** that sizes the page in real-world\n * units, matching the project's unit mode: metric `\"1:10\"`..`\"1:1000\"` (e.g.\n * `\"1:100\"`) or imperial `` `1/8\" = 1'` `` / `` `1\" = 10'` ``. Must be a valid\n * label for the current unit mode (invalid → throws). Defaults to `\"1:100\"`\n * in metric projects and `` `1\" = 1'` `` in imperial projects. Re-calibrate\n * afterwards with {@link PluginCoreIoUnderlayApi.setScale}.\n * @param opacity - Plane opacity, `0`..`1` (default ~`0.5`).\n * @returns the created {@linkcode UnderlayHandle}.\n * @throws if writes are disabled, the source can't be loaded, the storey\n * already has a PDF, the page is out of range, the scale label is invalid for\n * the unit mode, or the engine fails.\n *\n * @examplePrompt Import page 1 of this PDF floor plan onto storey 1\n * @examplePrompt Bring in the second page of this PDF as an underlay at 1:50\n * @examplePrompt Add this PDF plan to the ground floor to trace over\n * @examplePrompt Place page 3 of the drawing set on storey 3\n *\n * # Example\n * ```ts\n * const sheet = await snaptrude.core.io.import.pdf(\n * \"https://example.com/plans.pdf\",\n * 1, // storey\n * 2, // page\n * \"1:100\", // drawing scale\n * )\n * ```\n */\n public abstract pdf(\n source: string,\n storey?: number,\n page?: number,\n scale?: string,\n opacity?: number,\n ): PluginApiReturn<UnderlayHandle>\n\n /**\n * Import a **DWG** CAD drawing as a scene underlay. `.dwg` only (no DXF).\n *\n * DWG conversion runs on a server and can take minutes, so this **starts a job\n * and returns immediately** with an {@linkcode ImportJobHandle}. One CAD\n * import runs at a time: starting a second while one is in flight throws —\n * poll the first job to completion first. Poll it with\n * {@link PluginCoreIoJobApi.isComplete} / {@link PluginCoreIoJobApi.getStatus}, then call\n * {@link PluginCoreIoJobApi.getResult} to get the placed {@linkcode UnderlayHandle}.\n * For CAD you already have parsed as JSON, use {@link PluginCoreIoImportApi.cadJson}\n * (synchronous, no server round-trip).\n *\n * All CAD layers are imported (there is no layer filter).\n *\n * @param source - The `.dwg` file: an `https://` URL or a `data:` URL.\n * @param storey - Target storey number. Defaults to the active storey.\n * @returns an {@linkcode ImportJobHandle} to poll via `core.io.job.*`.\n * @throws if writes are disabled or the upload cannot be started. (Conversion\n * failures surface later via {@link PluginCoreIoJobApi.getError}.)\n *\n * @examplePrompt Import this DWG floor plan onto storey 1\n * @examplePrompt Bring in a CAD drawing as an underlay and tell me when it's ready\n * @examplePrompt Start importing this CAD file onto the ground floor\n *\n * # Example\n * ```ts\n * const job = await snaptrude.core.io.import.dwg(\"https://example.com/plan.dwg\", 1)\n * // Poll until the server-side conversion finishes — always with a timeout\n * // (a UI-cancelled import stays \"processing\" forever).\n * const deadline = Date.now() + 5 * 60_000\n * while (!(await snaptrude.core.io.job.isComplete(job))) {\n * if (Date.now() > deadline) throw new Error(\"DWG import timed out\")\n * await new Promise((r) => setTimeout(r, 2000))\n * }\n * // The DWG lands as a CAD underlay. CAD scaling isn't supported; manage it\n * // (list / fade / delete) via core.io.underlay.*.\n * const cad = await snaptrude.core.io.job.getResult(job)\n * if (cad) console.log(\"DWG imported as CAD underlay:\", cad)\n * ```\n */\n public abstract dwg(\n source: string,\n storey?: number,\n ): PluginApiReturn<ImportJobHandle>\n\n /**\n * Sketch a CAD underlay from **already-parsed CAD JSON** — synchronous, with no\n * server round-trip. Use this when you hold parsed CAD data (e.g. produced by\n * your own converter); use {@link PluginCoreIoImportApi.dwg} for raw `.dwg`\n * files.\n *\n * All layers in the JSON are sketched (there is no layer filter), at the\n * drawing's own coordinates.\n *\n * @param cad - The parsed CAD data: `{ unit, geometry: [...] }` describing the\n * drawing's lines/arcs/layers.\n * @param storey - Target storey number. Defaults to the active storey.\n * @returns the created {@linkcode UnderlayHandle}.\n * @throws if writes are disabled, the JSON yields no drawable geometry, or the\n * drawing is out of bounds.\n *\n * @examplePrompt Sketch this parsed CAD JSON onto the active storey\n * @examplePrompt Draw these CAD curves as an underlay on storey 2\n * @examplePrompt Create a CAD sketch from this geometry data\n * @examplePrompt Import my converted CAD JSON onto the ground floor\n *\n * # Example\n * ```ts\n * const cad = await snaptrude.core.io.import.cadJson(\n * { unit: \"mm\", geometry: [{ type: \"line\", layer: \"WALLS\", ... }] },\n * 1,\n * )\n * ```\n */\n public abstract cadJson(\n cad: CadJsonInput,\n storey?: number,\n ): PluginApiReturn<UnderlayHandle>\n\n /**\n * Import a **3D model** file and place it in the scene as a component.\n *\n * Supported formats: SketchUp (`.skp`), FBX (`.fbx`), OBJ (`.obj`), 3DS\n * (`.3ds`), and zipped bundles (`.zip`). The file is converted on the server,\n * then placed. `.glb`/`.gltf`/`.ifc`/`.stl` and Rhino/Revit are not supported.\n *\n * The model is placed at its source dimensions (interpreted in the file's own\n * units); there is no import-time scale override.\n *\n * Unlike `dwg`, this call **waits for the server conversion**: a large model\n * may exceed the 60-second call cap and reject at the API boundary while the\n * import still completes in the background (the model then appears without a\n * handle being returned — re-query the scene to find it). Keep sources small.\n *\n * @param source - The model file: an `https://` URL or a `data:` URL.\n * @param storey - Target storey number. Defaults to the active storey.\n * @param format - The file's format. Inferred from `source` when omitted.\n * @param position - Where to place the model. Defaults to the storey origin.\n * @param label - A name for the placed component.\n * @returns the placed {@linkcode ComponentHandle}.\n * @throws if writes are disabled, the source can't be loaded, the format is\n * unsupported, conversion/placement fails, or the converted model cannot be\n * found in the library after upload; `PRECONDITION_FAILED`\n * (`details.engineCode: \"TOOL_ACTIVE\"`) while the interactive furniture tool is\n * active — finish or cancel it first.\n *\n * @examplePrompt Import this SketchUp model onto storey 1\n * @examplePrompt Bring in this OBJ file and place it at the origin\n * @examplePrompt Add this 3D model to the ground floor\n *\n * # Example\n * ```ts\n * const model = await snaptrude.core.io.import.model(\n * \"https://example.com/tree.skp\",\n * 1, // storey\n * \"skp\",\n * )\n * ```\n */\n public abstract model(\n source: string,\n storey?: number,\n format?: ModelFormat,\n position?: Vec3Handle,\n label?: string,\n ): PluginApiReturn<ComponentHandle>\n\n /**\n * Import **site terrain** for a location — Mapbox topography (elevation +\n * satellite/streets imagery, optional neighborhood buildings and parcels).\n *\n * One terrain per project (a singleton). `width`/`length` describe the\n * real-world extent of the area to load **in metres** (regardless of the\n * project's unit mode); the host converts them to the right map zoom\n * internally — you do not pass a zoom level.\n *\n * @param lat - Site latitude (degrees), `-90`..`90`.\n * @param lng - Site longitude (degrees), `-180`..`180`.\n * @param width - East–west extent of the area to load, in metres.\n * @param length - North–south extent of the area to load, in metres.\n * @param elevation - Include DEM elevation (a real terrain surface). Default `true`.\n * @param satellite - Drape satellite imagery. Default `true`.\n * @param neighborhood - Include surrounding context buildings. Default `false`.\n * @param parcels - Include site parcel boundaries. Default `false`.\n * @returns the created {@linkcode TerrainHandle}.\n * @throws if writes are disabled, a terrain already exists, or the map/mesh\n * generation fails.\n *\n * @examplePrompt Import the site terrain for this location, about 500m across\n * @examplePrompt Load the topography at 40.7128, -74.0060 for a 300 by 300 meter site\n * @examplePrompt Bring in satellite terrain for the site with the surrounding buildings\n * @examplePrompt Add site elevation for a 1km by 1km area at these coordinates\n *\n * # Example\n * ```ts\n * // 300m × 300m site with elevation + satellite imagery.\n * const terrain = await snaptrude.core.io.import.terrain(\n * 40.7128, // lat\n * -74.006, // lng\n * 300, // width (metres)\n * 300, // length (metres)\n * true, // elevation\n * true, // satellite\n * )\n * ```\n */\n public abstract terrain(\n lat: number,\n lng: number,\n width: number,\n length: number,\n elevation?: boolean,\n satellite?: boolean,\n neighborhood?: boolean,\n parcels?: boolean,\n ): PluginApiReturn<TerrainHandle>\n\n /**\n * Import an **EPW weather file** into the project's weather catalog.\n *\n * Uploads an EnergyPlus Weather (`.epw`) file and registers it as the\n * project's site weather, returning its catalog `weatherFileId` — the same id\n * {@link PluginAnalysisWeatherApi.getSeries}, `program.site.getWeather`, and a\n * daylight run's provenance report. Uploading a user file makes the site's\n * `selectionMethod` `\"user-upload\"`, overriding nearest-station resolution.\n *\n * **Mutating**: this writes project state and is subject to the plugin\n * write-gate — it throws when plugin writes are disabled. Unlike the file\n * underlay/model importers it returns no scene handle (a weather file is not\n * a scene object), only the catalog `{ weatherFileId, label }`.\n *\n * @param source - The `.epw` file to import: an `https://` URL or a `data:` URL.\n * @returns A {@linkcode PluginImportEpwResult} — the catalog `weatherFileId`\n * and a human-readable `label` (the station/file name).\n * @throws if writes are disabled, the source can't be loaded, or the file is\n * not a valid EPW.\n *\n * @examplePrompt Import this EPW weather file for the site\n * @examplePrompt Upload a custom weather file for the daylight analysis\n * @examplePrompt Use this .epw as the project's weather source\n *\n * # Example\n * ```ts\n * const { weatherFileId, label } = await snaptrude.core.io.import.epw(\n * \"https://example.com/Chicago-OHare.epw\",\n * )\n * console.log(`Weather set to ${label} (${weatherFileId})`)\n * ```\n */\n public abstract epw(source: string): PluginApiReturn<PluginImportEpwResult>\n}\n\n/**\n * Supported 3D model import formats. `.glb`/`.gltf`/`.ifc`/`.stl` and Rhino/Revit\n * are intentionally excluded (no in-app import path).\n */\nexport const ModelFormat = z.enum([\"skp\", \"fbx\", \"obj\", \"3ds\", \"zip\"])\nexport type ModelFormat = z.infer<typeof ModelFormat>\n\n/**\n * Parsed CAD JSON accepted by {@link PluginCoreIoImportApi.cadJson}. `unit` sets\n * the drawing's length unit; `geometry` is the array of CAD entities (lines,\n * polylines, arcs, circles), each tagged with its CAD `layer`. Additional fields\n * are passed through untouched.\n */\nexport const CadJsonInput = z\n .object({\n unit: z.string().optional(),\n geometry: z.array(z.record(z.string(), z.unknown())).optional(),\n })\n .passthrough()\nexport type CadJsonInput = z.infer<typeof CadJsonInput>\n\n/**\n * Arguments for {@link PluginCoreIoImportApi.image}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `source` | `string` | Image `https://` URL or `data:` URL (png/jpg/jpeg/bmp) |\n * | `storey` | `number`? | Target storey (default: active) |\n * | `scale` | `number`? | Initial uniform scale |\n * | `opacity` | `number`? | `0`..`1` (default ~`0.5`) |\n * | `label` | `string`? | Name for the underlay |\n */\nexport const PluginImportImageArgs = z.object({\n source: ImportSource,\n storey: z.number().int().optional(),\n scale: z.number().positive().optional(),\n opacity: z.number().min(0).max(1).optional(),\n label: z.string().optional(),\n})\nexport type PluginImportImageArgs = z.infer<typeof PluginImportImageArgs>\n\n/**\n * Arguments for {@link PluginCoreIoImportApi.pdf}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `source` | `string` | PDF `https://` URL or `data:` URL |\n * | `storey` | `number`? | Target storey (default: active) |\n * | `page` | `number`? | 1-based page (default `1`) |\n * | `scale` | `string`? | Drawing-scale label, e.g. `\"1:100\"` |\n * | `opacity` | `number`? | `0`..`1` (default ~`0.5`) |\n */\nexport const PluginImportPdfArgs = z.object({\n source: ImportSource,\n storey: z.number().int().optional(),\n page: z.number().int().positive().optional(),\n scale: z.string().optional(),\n opacity: z.number().min(0).max(1).optional(),\n})\nexport type PluginImportPdfArgs = z.infer<typeof PluginImportPdfArgs>\n\n/** Arguments for {@link PluginCoreIoImportApi.dwg}. */\nexport const PluginImportDwgArgs = z.object({\n source: ImportSource,\n storey: z.number().int().optional(),\n})\nexport type PluginImportDwgArgs = z.infer<typeof PluginImportDwgArgs>\n\n/** Arguments for {@link PluginCoreIoImportApi.cadJson}. */\nexport const PluginImportCadJsonArgs = z.object({\n cad: CadJsonInput,\n storey: z.number().int().optional(),\n})\nexport type PluginImportCadJsonArgs = z.infer<typeof PluginImportCadJsonArgs>\n\n/**\n * Arguments for {@link PluginCoreIoImportApi.model}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `source` | `string` | Model `https://` URL or `data:` URL |\n * | `storey` | `number`? | Target storey (default: active) |\n * | `format` | {@link ModelFormat}? | File format (inferred when omitted) |\n * | `position` | {@link Vec3Handle}? | Placement (default: storey origin) |\n * | `label` | `string`? | Name for the component |\n */\nexport const PluginImportModelArgs = z.object({\n source: ImportSource,\n storey: z.number().int().optional(),\n format: ModelFormat.optional(),\n position: Vec3Handle.optional(),\n label: z.string().optional(),\n})\nexport type PluginImportModelArgs = z.infer<typeof PluginImportModelArgs>\n\n/**\n * Arguments for {@link PluginCoreIoImportApi.terrain}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `lat` | `number` | Latitude (degrees), `-90`..`90` |\n * | `lng` | `number` | Longitude (degrees), `-180`..`180` |\n * | `width` | `number` | East–west extent, metres |\n * | `length` | `number` | North–south extent, metres |\n * | `elevation` | `boolean`? | Include DEM elevation (default `true`) |\n * | `satellite` | `boolean`? | Drape satellite imagery (default `true`) |\n * | `neighborhood` | `boolean`? | Include context buildings (default `false`) |\n * | `parcels` | `boolean`? | Include parcel boundaries (default `false`) |\n */\nexport const PluginImportTerrainArgs = z.object({\n lat: z.number().min(-90).max(90),\n lng: z.number().min(-180).max(180),\n width: z.number().positive().finite(),\n length: z.number().positive().finite(),\n elevation: z.boolean().optional(),\n satellite: z.boolean().optional(),\n neighborhood: z.boolean().optional(),\n parcels: z.boolean().optional(),\n})\nexport type PluginImportTerrainArgs = z.infer<typeof PluginImportTerrainArgs>\n\n/**\n * Arguments for {@link PluginCoreIoImportApi.epw}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `source` | `string` | EPW `https://` URL or `data:` URL |\n */\nexport const PluginImportEpwArgs = z.object({\n source: ImportSource,\n})\nexport type PluginImportEpwArgs = z.infer<typeof PluginImportEpwArgs>\n\n/**\n * Result of {@link PluginCoreIoImportApi.epw} — the imported weather file's\n * catalog entry.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `weatherFileId` | `string` | Catalog id (same key as `analysis.weather`'s `weatherFileId`) |\n * | `label` | `string` | Human-readable name (station / file name) |\n */\nexport const PluginImportEpwResult = z.object({\n weatherFileId: z.string(),\n label: z.string(),\n})\nexport type PluginImportEpwResult = z.infer<typeof PluginImportEpwResult>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport { ImportJobHandle, UnderlayHandle } from \"../../../../handles\"\n\n/**\n * Import jobs — poll a long-running asynchronous import.\n *\n * Some imports run on a server and take a while (today: `core.io.import.dwg`,\n * whose DWG → Snaptrude conversion can take minutes). Those importers return an\n * {@linkcode ImportJobHandle} immediately instead of blocking; you drive the job\n * to completion here:\n *\n * 1. poll {@link PluginCoreIoJobApi.isComplete} (or read {@link PluginCoreIoJobApi.getStatus}),\n * 2. once complete, call {@link PluginCoreIoJobApi.getResult} for the placed handle,\n * 3. or read {@link PluginCoreIoJobApi.getError} if it failed.\n *\n * **Always poll with a timeout.** If the import is cancelled from the app UI\n * (or the tab reloads mid-conversion), the job never reaches a terminal state\n * and `getStatus` keeps returning `\"processing\"` — an unbounded poll loop would\n * spin forever. Give up after a few minutes and surface that to the user.\n *\n * Accessed via `snaptrude.core.io.job`.\n */\nexport abstract class PluginCoreIoJobApi {\n constructor() {}\n\n /**\n * Read a job's current status: `\"pending\"` (queued), `\"processing\"` (running),\n * `\"complete\"` (result ready), or `\"failed\"`.\n *\n * Limitation: a job cancelled from the app UI never turns `\"failed\"` — it\n * stays `\"processing\"` indefinitely. Bound your poll loop with a timeout.\n *\n * @param job - The import job to inspect.\n * @returns the {@linkcode ImportJobStatus}.\n * @throws {@link HandleInvalidError} if the job handle is unknown.\n *\n * @examplePrompt What's the status of my DWG import?\n * @examplePrompt Is the CAD import still processing?\n * @examplePrompt Check where the import job is up to\n * @examplePrompt Did the import job fail?\n *\n * # Example\n * ```ts\n * const job = await snaptrude.core.io.import.dwg(url, 1)\n * console.log(await snaptrude.core.io.job.getStatus(job)) // \"processing\"\n * ```\n */\n public abstract getStatus(job: ImportJobHandle): PluginApiReturn<ImportJobStatus>\n\n /**\n * Whether the job has finished successfully (status is `\"complete\"` and a\n * result is ready). Returns `false` while pending/processing and on failure.\n *\n * @param job - The import job to inspect.\n * @throws {@link HandleInvalidError} if the job handle is unknown.\n *\n * @examplePrompt Is the DWG import done yet?\n * @examplePrompt Has the CAD import finished?\n * @examplePrompt Tell me when the import job completes\n * @examplePrompt Is my import ready?\n *\n * # Example\n * ```ts\n * const job = await snaptrude.core.io.import.dwg(url, 1)\n * const deadline = Date.now() + 5 * 60_000 // cap the poll — see getStatus limitation\n * while (!(await snaptrude.core.io.job.isComplete(job))) {\n * if (Date.now() > deadline) throw new Error(\"DWG import timed out\")\n * await new Promise((r) => setTimeout(r, 2000))\n * }\n * ```\n */\n public abstract isComplete(job: ImportJobHandle): PluginApiReturn<boolean>\n\n /**\n * The imported result once the job is complete — the placed\n * {@linkcode UnderlayHandle}. Returns `null` while the job is still\n * pending/processing or if it failed (check {@link PluginCoreIoJobApi.getError}).\n *\n * @param job - The completed import job.\n * @throws {@link HandleInvalidError} if the job handle is unknown.\n *\n * @examplePrompt Get the underlay from my finished DWG import\n * @examplePrompt Give me the result of the CAD import job\n * @examplePrompt Fetch the imported CAD sketch once it's ready\n * @examplePrompt Return the handle for the completed import\n *\n * # Example\n * ```ts\n * const job = await snaptrude.core.io.import.dwg(url, 1)\n * const deadline = Date.now() + 5 * 60_000\n * while (!(await snaptrude.core.io.job.isComplete(job))) {\n * if (Date.now() > deadline) throw new Error(\"DWG import timed out\")\n * await new Promise((r) => setTimeout(r, 2000))\n * }\n * // DWG results are CAD underlays (CAD scaling isn't supported); manage them\n * // via core.io.underlay.* (list / setOpacity / delete).\n * const cad = await snaptrude.core.io.job.getResult(job)\n * if (cad) console.log(\"import complete:\", cad)\n * ```\n */\n public abstract getResult(job: ImportJobHandle): PluginApiReturn<UnderlayHandle | null>\n\n /**\n * The failure message if the job's status is `\"failed\"`; otherwise `null`.\n *\n * @param job - The import job to inspect.\n * @throws {@link HandleInvalidError} if the job handle is unknown.\n *\n * @examplePrompt Why did my DWG import fail?\n * @examplePrompt What went wrong with the CAD import?\n * @examplePrompt Show the error from the failed import job\n * @examplePrompt Did the import error out, and why?\n *\n * # Example\n * ```ts\n * const job = await snaptrude.core.io.import.dwg(url, 1)\n * if ((await snaptrude.core.io.job.getStatus(job)) === \"failed\") {\n * console.error(await snaptrude.core.io.job.getError(job))\n * }\n * ```\n */\n public abstract getError(job: ImportJobHandle): PluginApiReturn<string | null>\n}\n\n/** Lifecycle status of an asynchronous import job. */\nexport const ImportJobStatus = z.enum([\"pending\", \"processing\", \"complete\", \"failed\"])\nexport type ImportJobStatus = z.infer<typeof ImportJobStatus>\n\n/**\n * Arguments for the `core.io.job.*` readers.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `job` | {@link ImportJobHandle} | The import job to inspect |\n */\nexport const PluginImportJobArgs = z.object({\n job: ImportJobHandle,\n})\nexport type PluginImportJobArgs = z.infer<typeof PluginImportJobArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport {\n UnderlayHandle,\n BBoxComponents,\n Vec3Components,\n} from \"../../../../handles\"\n\n/**\n * Underlays — inspect and edit the placed reference planes imported into the\n * scene (`core.io.import.image` / `pdf` / `dwg` / `cadJson`).\n *\n * An underlay is an image, PDF, or CAD sketch dropped on a storey to trace over.\n * This namespace lists them, calibrates their scale, sets opacity, and removes\n * them. Every underlay is an {@linkcode UnderlayHandle}.\n *\n * ## Set-scale\n * {@link PluginCoreIoUnderlayApi.setScale} takes **either** a numeric factor **or**\n * a `{ planSize }` — the real-world length of the plan's longest side. A numeric\n * factor is **absolute**: it is the total scale relative to the underlay's\n * import-time size, the same number {@link PluginCoreIoUnderlayApi.getScale}\n * returns as `scaleFactor` (so `setScale(u, 2)` is idempotent, and\n * `setScale(u, (await getScale(u)).scaleFactor)` is a no-op). Use `planSize` to\n * calibrate programmatically (\"this plan is 50 m across\") without clicking\n * points on the canvas. Scale is supported for **image and PDF** underlays;\n * **CAD scaling is not supported** (`getScale` returns `null`, `setScale`/`resetScale`\n * throw). Image scale is tracked on the plan (and is **not undoable**); PDF scale is\n * baked into the mesh (X/Z) and **is undoable**.\n *\n * Accessed via `snaptrude.core.io.underlay`.\n */\nexport abstract class PluginCoreIoUnderlayApi {\n constructor() {}\n\n /**\n * List the underlays in the scene, optionally limited to one storey.\n *\n * @param storey - When given, only underlays on this storey number.\n * @returns an array of {@linkcode UnderlayHandle} (empty, never `null`).\n *\n * @examplePrompt List all the reference underlays in the model\n * @examplePrompt What underlays are on the ground floor?\n * @examplePrompt Show every imported floor plan and PDF\n * @examplePrompt How many trace-over images are on storey 2?\n *\n * # Example\n * ```ts\n * const underlays = await snaptrude.core.io.underlay.list(1)\n * console.log(`${underlays.length} underlays on storey 1`)\n * ```\n */\n public abstract list(storey?: number): PluginApiReturn<UnderlayHandle[]>\n\n /**\n * Read an underlay's world-space bounding box (image, PDF, or CAD). `null` if\n * the underlay no longer resolves or has no measurable mesh. Useful for fitting\n * drawn geometry to a reference, or computing a plan size before scaling.\n *\n * @param underlay - The underlay to measure.\n *\n * @examplePrompt How big is this floor plan in the scene?\n * @examplePrompt Get the bounding box of the underlay\n * @examplePrompt What's the extent of the imported PDF?\n * @examplePrompt Measure the CAD sketch's size\n *\n * # Example\n * ```ts\n * const bb = await snaptrude.core.io.underlay.getBounds(plan)\n * if (bb) console.log(`width ${bb.max.x - bb.min.x}`)\n * ```\n */\n public abstract getBounds(\n underlay: UnderlayHandle,\n ): PluginApiReturn<BBoxComponents | null>\n\n /**\n * List the distinct original AutoCAD layer names retained by a placed CAD\n * underlay. Returns `[]` for non-CAD underlays, legacy imports created before\n * source-layer retention, missing underlays, or CAD drawings without layer tags.\n *\n * @param underlay - The placed CAD underlay to inspect.\n * @returns the retained AutoCAD layer names in first-appearance order.\n *\n * @examplePrompt List the original AutoCAD layers in this imported drawing\n * @examplePrompt Which CAD layers are available in this underlay?\n *\n * # Example\n * ```ts\n * const layers = await snaptrude.core.io.underlay.listCadLayers(cad)\n * console.log(layers)\n * ```\n */\n public abstract listCadLayers(\n underlay: UnderlayHandle,\n ): PluginApiReturn<string[]>\n\n /**\n * Read one original AutoCAD layer's retained line and arc geometry in\n * **Snaptrude world space** and Snaptrude internal units. Results are paged so\n * large drawings can be inspected without returning the whole DWG at once.\n *\n * The world-space coordinates include the CAD underlay's current position,\n * rotation, scale, and storey elevation. `null` means the underlay no longer\n * resolves. An unknown layer returns a page with `total: 0`.\n *\n * @param underlay - The placed CAD underlay to inspect.\n * @param layer - Exact, case-sensitive original AutoCAD layer name.\n * @param options - Optional zero-based offset and page limit (default 500,\n * maximum 1000).\n * @returns a page of retained CAD curves, or `null` if the underlay is gone.\n * @throws if the handle resolves to an image/PDF rather than a CAD underlay.\n *\n * @examplePrompt Read the wall geometry from the A-WALL layer in this CAD underlay\n * @examplePrompt Get the next 500 curves from the structural CAD layer\n *\n * # Example\n * ```ts\n * const page = await snaptrude.core.io.underlay.getCadLayerGeometry(\n * cad,\n * \"A-WALL\",\n * { offset: 0, limit: 500 },\n * )\n * if (page) console.log(`${page.curves.length} of ${page.total} curves`)\n * ```\n */\n public abstract getCadLayerGeometry(\n underlay: UnderlayHandle,\n layer: string,\n options?: PluginCadLayerGeometryOptions,\n ): PluginApiReturn<PluginCadLayerGeometryPage | null>\n\n /**\n * Read an underlay's scale. Works for **image and PDF** underlays. Returns\n * `null` for CAD (scaling not supported) or if the handle no longer resolves.\n *\n * - `scaleFactor` — the **cumulative user scale** relative to the underlay's\n * import-time size (`1` = as imported). This is the same number\n * {@link PluginCoreIoUnderlayApi.setScale} accepts.\n * - `initialScaleFactor` — the engine's import-time fit factor (how the raw\n * image was sized to the canvas on import); informational only. For PDF it\n * is always `1`.\n *\n * @param underlay - The underlay to read.\n *\n * @examplePrompt What scale is this floor plan set to?\n * @examplePrompt Get the current and original scale of the underlay\n * @examplePrompt How much has this image been scaled since import?\n * @examplePrompt Read the scale factor of the reference plan\n *\n * # Example\n * ```ts\n * const s = await snaptrude.core.io.underlay.getScale(plan)\n * if (s) console.log(`scale ${s.scaleFactor} (was ${s.initialScaleFactor})`)\n * ```\n */\n public abstract getScale(\n underlay: UnderlayHandle,\n ): PluginApiReturn<{ scaleFactor: number; initialScaleFactor: number } | null>\n\n /**\n * Set an underlay's scale — the **calibration** step after import. Works for\n * **image and PDF** underlays (CAD scaling is not supported — throws).\n *\n * Pass the `scale` argument as **either**:\n * - a **number** — the **absolute** scale factor relative to the underlay's\n * import-time size (e.g. `2` makes it twice its imported size, `0.5` half —\n * no matter what the current scale is). It is the same number\n * {@link PluginCoreIoUnderlayApi.getScale} returns as `scaleFactor`, so the\n * call is idempotent: repeating `setScale(u, 2)` leaves the underlay at 2×.\n * **Or**\n * - a **`{ planSize }`** object — the real-world **length of the plan's longest\n * side**, in the project's length unit. The factor is computed precisely so the\n * underlay's longest side becomes exactly `planSize`. This is the way to\n * calibrate programmatically without clicking points on the canvas: if you\n * know the plan is 50 m across, pass `{ planSize: 50 }`.\n *\n * Image scale is not undoable; PDF scale is undoable.\n *\n * @param underlay - The underlay to scale (image or PDF).\n * @param scale - An absolute numeric factor, **or** `{ planSize }` (the real\n * length of the underlay's longest side, in project units).\n * @returns the applied `{ scaleFactor }` (the new absolute factor).\n * @throws if writes are disabled, the underlay is CAD / can't be resolved, the plan\n * has zero size, or the engine fails.\n *\n * @examplePrompt Scale this floor plan so it's 50 meters across\n * @examplePrompt Set the plan size to 30m — it's imported too small\n * @examplePrompt Make the reference image twice its imported size\n * @examplePrompt Calibrate the PDF: the site is 120 meters long\n *\n * # Example\n * ```ts\n * // Absolute factor — set the underlay to 2× its imported size:\n * await snaptrude.core.io.underlay.setScale(plan, 2)\n *\n * // Fit to a real-world size — make the plan's longest side 50 project-units:\n * await snaptrude.core.io.underlay.setScale(plan, { planSize: 50 })\n * ```\n */\n public abstract setScale(\n underlay: UnderlayHandle,\n scale: number | { planSize: number },\n ): PluginApiReturn<{ scaleFactor: number }>\n\n /**\n * Reset an underlay's scale back to its import-time size (image or PDF; CAD\n * scaling is not supported — throws). Equivalent to `setScale(underlay, 1)`.\n *\n * @param underlay - The underlay to reset (image or PDF).\n * @returns the restored `{ scaleFactor }` (`1`).\n * @throws if writes are disabled, the underlay is CAD / can't be resolved, or the engine fails.\n *\n * @examplePrompt Reset this floor plan's scale to how it was imported\n * @examplePrompt Undo the scaling on the underlay\n * @examplePrompt Put the reference image back to its original size\n * @examplePrompt Restore the plan's default scale\n *\n * # Example\n * ```ts\n * await snaptrude.core.io.underlay.resetScale(plan)\n * ```\n */\n public abstract resetScale(\n underlay: UnderlayHandle,\n ): PluginApiReturn<{ scaleFactor: number }>\n\n /**\n * Read an underlay's opacity (`0`..`1`), or `null` if it has no material.\n *\n * @param underlay - The underlay to read.\n *\n * @examplePrompt How transparent is this underlay?\n * @examplePrompt Get the opacity of the floor plan\n * @examplePrompt What's the fade level on the reference image?\n * @examplePrompt Read the underlay's opacity\n *\n * # Example\n * ```ts\n * const o = await snaptrude.core.io.underlay.getOpacity(plan)\n * ```\n */\n public abstract getOpacity(\n underlay: UnderlayHandle,\n ): PluginApiReturn<number | null>\n\n /**\n * Set an underlay's opacity (`0` = transparent .. `1` = opaque). Undoable.\n * The engine keeps underlays faintly visible: values below `0.01` clamp to\n * `0.01`, so `setOpacity(u, 0)` reads back as `0.01`, not `0`.\n *\n * @param underlay - The underlay to change.\n * @param opacity - Target opacity, `0`..`1` (engine floor `0.01`).\n * @throws if writes are disabled, the underlay can't be resolved, or the\n * underlay is **locked** (unlock it in the app first).\n *\n * @examplePrompt Fade this floor plan to 30% so I can see through it\n * @examplePrompt Make the underlay half transparent\n * @examplePrompt Set the reference image opacity to 0.2\n * @examplePrompt Dim the trace-over plan\n *\n * # Example\n * ```ts\n * await snaptrude.core.io.underlay.setOpacity(plan, 0.3)\n * ```\n */\n public abstract setOpacity(\n underlay: UnderlayHandle,\n opacity: number,\n ): PluginApiReturn<void>\n\n /**\n * Delete an underlay from the scene (and its backend record). Applies to image,\n * PDF, and CAD underlays.\n *\n * @param underlay - The underlay to delete.\n * @throws if writes are disabled or the underlay can't be resolved.\n *\n * @examplePrompt Delete this floor plan underlay\n * @examplePrompt Remove the imported PDF from the scene\n * @examplePrompt Get rid of the CAD sketch I traced over\n * @examplePrompt Clear the reference image off storey 1\n *\n * # Example\n * ```ts\n * const [first] = await snaptrude.core.io.underlay.list(1)\n * if (first) await snaptrude.core.io.underlay.delete(first)\n * ```\n */\n public abstract delete(underlay: UnderlayHandle): PluginApiReturn<void>\n\n /**\n * Extract **continuous wall centrelines** from one retained AutoCAD layer's\n * double-line wall faces — the deterministic tracing primitive behind\n * CAD-to-BIM wall conversion. Instead of paging raw curves and re-deriving\n * the geometry in worker code, this reads the whole layer and runs the vetted\n * pipeline: cull fragments, dedupe, merge collinear runs into *faces*, pair\n * parallel faces a wall-thickness apart into centrelines (midline = axis,\n * separation = thickness), bridge collinear gaps up to\n * `bridgeOpenings.maxWidth` into ONE continuous centreline while recording\n * each bridged span as an opening, then snap near-touching endpoints at\n * junctions.\n *\n * CAD drafters interrupt wall faces at every door/window, so raw wall layers\n * always have gaps; Snaptrude doors/windows must host into a continuous wall.\n * The returned centrelines are ready for `design.create.walls`, and each\n * centreline's `openings` records where the CAD had a gap — feed those to the\n * door/window placement step. `unpaired` faces (no parallel partner at wall\n * thickness) are evidence, not noise: a run of unpaired perimeter faces\n * usually means that stretch of facade is glazed, not solid.\n *\n * All lengths are in **Snaptrude internal units** (convert from project units\n * with `core.units.convert`). Coordinates are world-space plan (x, z).\n *\n * Thin parallel pairs (glass lines, mullion faces) are the same algorithm at\n * a smaller separation: pass e.g. `thicknessRange: [0.01, 0.25]` with\n * `bridgeOpenings: null` to detect glazing runs on a glazing layer.\n *\n * @param underlay - The placed CAD underlay to read.\n * @param layer - Exact, case-sensitive original AutoCAD layer name.\n * @param options - Tolerances; see {@linkcode PluginCadCenterlineOptions}.\n * @returns centrelines + unpaired faces + stats, or `null` if the underlay is gone.\n * @throws if the handle resolves to an image/PDF rather than a CAD underlay.\n *\n * @examplePrompt Trace the walls from the a-wall CAD layer\n * @examplePrompt Convert the AutoCAD wall linework into Snaptrude walls\n * @examplePrompt Extract wall centrelines with thickness from the imported DWG\n * @examplePrompt Find the glazing runs on the a-glazing layer\n *\n * # Example\n * ```ts\n * const [cad] = await snaptrude.core.io.underlay.list()\n * const r = await snaptrude.core.io.underlay.extractCenterlines(cad, \"a-wall\")\n * if (r) {\n * const items = r.centerlines.map((c) => ({\n * profile: [c.start, c.end],\n * thickness: c.thickness,\n * }))\n * console.log(`${items.length} walls, ${r.unpaired.length} unpaired faces`)\n * }\n * ```\n */\n public abstract extractCenterlines(\n underlay: UnderlayHandle,\n layer: string,\n options?: PluginCadCenterlineOptions,\n ): PluginApiReturn<PluginCadCenterlinesResult | null>\n\n /**\n * Classify one retained AutoCAD layer's **arcs as door swings** — the\n * deterministic tracing primitive behind CAD-to-BIM door placement. A door\n * swing is drawn as a ~90° arc whose radius IS the leaf width and whose\n * centre IS the hinge point. This filters the layer's arcs by plausible\n * radius and sweep (rejecting inch-scale fillets, full-circle symbols and\n * long shallow curves), reads each survivor as a door candidate (hinge, leaf\n * width, swing direction, hosting wall direction), merges mirrored pairs\n * sharing a chord line into double doors, and returns a radius histogram —\n * real drawings use a handful of standard door sizes, so the histogram's\n * clusters are the drawing's door widths (sanity-check them against the\n * catalog; distrust arcs in no cluster).\n *\n * All lengths are in **Snaptrude internal units**. `rejected` lists filtered\n * arcs with reasons so nothing is silently dropped.\n *\n * @param underlay - The placed CAD underlay to read.\n * @param layer - Exact, case-sensitive original AutoCAD layer name.\n * @param options - Gates and binning; see {@linkcode PluginCadArcClassifyOptions}.\n * @returns door candidates + rejected arcs + histogram, or `null` if the underlay is gone.\n * @throws if the handle resolves to an image/PDF rather than a CAD underlay.\n *\n * @examplePrompt Find the doors on the a-door CAD layer\n * @examplePrompt Classify the door swing arcs in the imported drawing\n * @examplePrompt What door sizes does this DWG use?\n * @examplePrompt Read hinge points and leaf widths from the CAD door layer\n *\n * # Example\n * ```ts\n * const [cad] = await snaptrude.core.io.underlay.list()\n * const r = await snaptrude.core.io.underlay.classifyArcs(cad, \"a-door\")\n * if (r) {\n * console.log(`${r.doors.length} doors; sizes:`,\n * r.radiusHistogram.filter((b) => b.count > 1))\n * }\n * ```\n */\n public abstract classifyArcs(\n underlay: UnderlayHandle,\n layer: string,\n options?: PluginCadArcClassifyOptions,\n ): PluginApiReturn<PluginCadArcClassifyResult | null>\n}\n\n/**\n * Arguments for {@link PluginCoreIoUnderlayApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `storey` | `number`? | Only underlays on this storey (default: all) |\n */\nexport const PluginUnderlayListArgs = z.object({\n storey: z.number().int().optional(),\n})\nexport type PluginUnderlayListArgs = z.infer<typeof PluginUnderlayListArgs>\n\n/**\n * Fit-to-size calibration: the real-world **length of the plan's longest side**, in\n * the project's length unit. Passed to {@link PluginCoreIoUnderlayApi.setScale}; the\n * factor is computed as `planSize / currentLongestSide`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `planSize` | `number` | Real-world length of the underlay's longest side (project units) |\n */\nexport const PluginUnderlayPlanSize = z.object({\n planSize: z.number().positive(),\n})\nexport type PluginUnderlayPlanSize = z.infer<typeof PluginUnderlayPlanSize>\n\n/** The `scale` argument of {@link PluginCoreIoUnderlayApi.setScale}: a direct factor or a `{ planSize }`. */\nexport const PluginUnderlaySetScaleValue = z.union([\n z.number().positive(),\n PluginUnderlayPlanSize,\n])\nexport type PluginUnderlaySetScaleValue = z.infer<\n typeof PluginUnderlaySetScaleValue\n>\n\n/**\n * Arguments for {@link PluginCoreIoUnderlayApi.setScale}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `underlay` | {@link UnderlayHandle} | The image/PDF underlay to scale |\n * | `scale` | `number \\| {@link PluginUnderlayPlanSize}` | Direct factor, or `{ planSize }` |\n */\nexport const PluginUnderlaySetScaleArgs = z.object({\n underlay: UnderlayHandle,\n scale: PluginUnderlaySetScaleValue,\n})\nexport type PluginUnderlaySetScaleArgs = z.infer<\n typeof PluginUnderlaySetScaleArgs\n>\n\n/**\n * Arguments for {@link PluginCoreIoUnderlayApi.setOpacity}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `underlay` | {@link UnderlayHandle} | The underlay to change |\n * | `opacity` | `number` | Target opacity, `0`..`1` |\n */\nexport const PluginUnderlaySetOpacityArgs = z.object({\n underlay: UnderlayHandle,\n opacity: z.number().min(0).max(1),\n})\nexport type PluginUnderlaySetOpacityArgs = z.infer<\n typeof PluginUnderlaySetOpacityArgs\n>\n\n/**\n * Arguments for the single-underlay methods\n * ({@link PluginCoreIoUnderlayApi.getScale} / `resetScale` / `getOpacity` / `delete`).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `underlay` | {@link UnderlayHandle} | The target underlay |\n */\nexport const PluginUnderlayRefArgs = z.object({\n underlay: UnderlayHandle,\n})\nexport type PluginUnderlayRefArgs = z.infer<typeof PluginUnderlayRefArgs>\n\n/** A retained AutoCAD line represented as a world-space read record. */\nexport const PluginCadLineGeometry = z.object({\n type: z.literal(\"line\"),\n start: Vec3Components,\n end: Vec3Components,\n})\nexport type PluginCadLineGeometry = z.infer<typeof PluginCadLineGeometry>\n\n/** A retained AutoCAD arc represented as a world-space read record. */\nexport const PluginCadArcGeometry = z.object({\n type: z.literal(\"arc\"),\n centre: Vec3Components,\n axis: Vec3Components,\n start: Vec3Components,\n end: Vec3Components,\n})\nexport type PluginCadArcGeometry = z.infer<typeof PluginCadArcGeometry>\n\n/** Line or arc geometry returned by `getCadLayerGeometry`. */\nexport const PluginCadGeometry = z.discriminatedUnion(\"type\", [\n PluginCadLineGeometry,\n PluginCadArcGeometry,\n])\nexport type PluginCadGeometry = z.infer<typeof PluginCadGeometry>\n\n/** Pagination options for `getCadLayerGeometry`. */\nexport const PluginCadLayerGeometryOptions = z.object({\n offset: z.number().int().nonnegative().default(0),\n limit: z.number().int().positive().max(1000).default(500),\n})\nexport type PluginCadLayerGeometryOptions = z.input<\n typeof PluginCadLayerGeometryOptions\n>\n\n/** Arguments for {@link PluginCoreIoUnderlayApi.getCadLayerGeometry}. */\nexport const PluginCadLayerGeometryArgs = z.object({\n underlay: UnderlayHandle,\n layer: z.string(),\n options: PluginCadLayerGeometryOptions.default({ offset: 0, limit: 500 }),\n})\nexport type PluginCadLayerGeometryArgs = z.infer<\n typeof PluginCadLayerGeometryArgs\n>\n\n/** A page of one original AutoCAD layer's geometry in Snaptrude world space. */\nexport const PluginCadLayerGeometryPage = z.object({\n layer: z.string(),\n coordinateSpace: z.literal(\"snaptrude-world\"),\n units: z.literal(\"snaptrude-internal\"),\n total: z.number().int().nonnegative(),\n offset: z.number().int().nonnegative(),\n curves: z.array(PluginCadGeometry),\n})\nexport type PluginCadLayerGeometryPage = z.infer<\n typeof PluginCadLayerGeometryPage\n>\n\n\n/** A point in the world-space plan (x, z) projection, Snaptrude internal units. */\nexport const PluginPlanPoint = z.object({ x: z.number(), z: z.number() })\nexport type PluginPlanPoint = z.infer<typeof PluginPlanPoint>\n\n/**\n * Options for {@link PluginCoreIoUnderlayApi.extractCenterlines}. All lengths in\n * Snaptrude internal units.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `thicknessRange` | `[number, number]`? | Plausible wall thickness `[min, max]` (default `[0.3, 1.6]` ≈ 3–16 in) |\n * | `mergeGap` | `number`? | Noise tolerance joining collinear segments that are really one face (default `0.05`) |\n * | `minLength` | `number`? | Cull fragments shorter than this (default `0.1`) |\n * | `parallelTolDeg` | `number`? | Max angle in degrees between faces still considered parallel (default `2`) |\n * | `minOverlap` | `number`? | Fraction of the shorter face that must overlap its pair (default `0.6`) |\n * | `bridgeOpenings` | `{ maxWidth }` \\| `null`? | Close collinear centreline gaps up to `maxWidth`, recording each as an opening (default `{ maxWidth: 9.6 }` ≈ 8 ft); `null` disables bridging |\n */\nexport const PluginCadCenterlineOptions = z.object({\n thicknessRange: z\n .tuple([z.number().positive(), z.number().positive()])\n .default([0.3, 1.6]),\n mergeGap: z.number().nonnegative().default(0.05),\n minLength: z.number().nonnegative().default(0.1),\n parallelTolDeg: z.number().positive().max(15).default(2),\n minOverlap: z.number().min(0).max(1).default(0.6),\n bridgeOpenings: z\n .object({ maxWidth: z.number().positive() })\n .nullable()\n .default({ maxWidth: 9.6 }),\n})\nexport type PluginCadCenterlineOptions = z.input<\n typeof PluginCadCenterlineOptions\n>\n\n/** Arguments for {@link PluginCoreIoUnderlayApi.extractCenterlines}. */\nexport const PluginCadCenterlinesArgs = z.object({\n underlay: UnderlayHandle,\n layer: z.string(),\n options: PluginCadCenterlineOptions.default({\n thicknessRange: [0.3, 1.6],\n mergeGap: 0.05,\n minLength: 0.1,\n parallelTolDeg: 2,\n minOverlap: 0.6,\n bridgeOpenings: { maxWidth: 9.6 },\n }),\n})\nexport type PluginCadCenterlinesArgs = z.infer<typeof PluginCadCenterlinesArgs>\n\n/** A span bridged over a CAD gap in a continuous centreline — a door/window opening candidate. */\nexport const PluginCadOpening = z.object({\n start: PluginPlanPoint,\n end: PluginPlanPoint,\n width: z.number(),\n})\nexport type PluginCadOpening = z.infer<typeof PluginCadOpening>\n\n/**\n * One continuous wall centreline extracted from CAD wall faces. `thickness` is\n * the measured face separation; `openings` are the CAD gaps bridged into this\n * centreline (feed the door/window step).\n */\nexport const PluginCadCenterline = z.object({\n start: PluginPlanPoint,\n end: PluginPlanPoint,\n thickness: z.number(),\n length: z.number(),\n sourceFaceCount: z.number().int().nonnegative(),\n openings: z.array(PluginCadOpening),\n})\nexport type PluginCadCenterline = z.infer<typeof PluginCadCenterline>\n\n/** A merged face with no parallel partner at wall thickness — evidence (often glazing), not noise. */\nexport const PluginCadUnpairedFace = z.object({\n start: PluginPlanPoint,\n end: PluginPlanPoint,\n length: z.number(),\n})\nexport type PluginCadUnpairedFace = z.infer<typeof PluginCadUnpairedFace>\n\n/** Result of {@link PluginCoreIoUnderlayApi.extractCenterlines}. */\nexport const PluginCadCenterlinesResult = z.object({\n layer: z.string(),\n coordinateSpace: z.literal(\"snaptrude-world\"),\n units: z.literal(\"snaptrude-internal\"),\n centerlines: z.array(PluginCadCenterline),\n unpaired: z.array(PluginCadUnpairedFace),\n stats: z.object({\n curvesRead: z.number().int().nonnegative(),\n arcsSkipped: z.number().int().nonnegative(),\n fragmentsCulled: z.number().int().nonnegative(),\n duplicates: z.number().int().nonnegative(),\n faces: z.number().int().nonnegative(),\n }),\n})\nexport type PluginCadCenterlinesResult = z.infer<\n typeof PluginCadCenterlinesResult\n>\n\n/**\n * Options for {@link PluginCoreIoUnderlayApi.classifyArcs}. Lengths in Snaptrude\n * internal units, sweeps in degrees.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `radiusRange` | `[number, number]`? | Plausible door-leaf widths (default `[3, 5.4]` ≈ 2.5–4.5 ft) |\n * | `sweepRangeDeg` | `[number, number]`? | Accepted arc sweep in degrees (default `[60, 120]`; a swing is ~90°) |\n * | `clusterTol` | `number`? | Radius bin size for the histogram (default `0.1`) |\n */\nexport const PluginCadArcClassifyOptions = z.object({\n radiusRange: z\n .tuple([z.number().positive(), z.number().positive()])\n .default([3, 5.4]),\n sweepRangeDeg: z\n .tuple([z.number().positive(), z.number().positive()])\n .default([60, 120]),\n clusterTol: z.number().positive().default(0.1),\n})\nexport type PluginCadArcClassifyOptions = z.input<\n typeof PluginCadArcClassifyOptions\n>\n\n/** Arguments for {@link PluginCoreIoUnderlayApi.classifyArcs}. */\nexport const PluginCadArcClassifyArgs = z.object({\n underlay: UnderlayHandle,\n layer: z.string(),\n options: PluginCadArcClassifyOptions.default({\n radiusRange: [3, 5.4],\n sweepRangeDeg: [60, 120],\n clusterTol: 0.1,\n }),\n})\nexport type PluginCadArcClassifyArgs = z.infer<typeof PluginCadArcClassifyArgs>\n\n/**\n * One door candidate read from a CAD swing arc. `hinge` is the arc centre,\n * `leafWidth` the arc radius, `swingDir` the unit direction from hinge toward\n * the open leaf, `wallDir` the unit direction of the hosting wall (hinge →\n * closed-leaf chord end). `openingWidth` equals `leafWidth` for singles and\n * `2 × leafWidth` for doubles.\n */\nexport const PluginCadDoorCandidate = z.object({\n hinge: PluginPlanPoint,\n leafWidth: z.number(),\n swingDir: PluginPlanPoint,\n wallDir: PluginPlanPoint,\n openingWidth: z.number(),\n double: z.boolean(),\n})\nexport type PluginCadDoorCandidate = z.infer<typeof PluginCadDoorCandidate>\n\n/** Result of {@link PluginCoreIoUnderlayApi.classifyArcs}. */\nexport const PluginCadArcClassifyResult = z.object({\n layer: z.string(),\n coordinateSpace: z.literal(\"snaptrude-world\"),\n units: z.literal(\"snaptrude-internal\"),\n doors: z.array(PluginCadDoorCandidate),\n rejected: z.array(\n z.object({\n centre: PluginPlanPoint,\n radius: z.number(),\n sweepDeg: z.number(),\n reason: z.string(),\n }),\n ),\n radiusHistogram: z.array(\n z.object({ radius: z.number(), count: z.number().int().positive() }),\n ),\n stats: z.object({\n curvesRead: z.number().int().nonnegative(),\n arcsConsidered: z.number().int().nonnegative(),\n }),\n})\nexport type PluginCadArcClassifyResult = z.infer<\n typeof PluginCadArcClassifyResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport { TerrainHandle } from \"../../../../handles\"\n\n/**\n * Terrain — inspect and manage the project's site terrain (the singleton created\n * by `core.io.import.terrain`).\n *\n * There is at most one terrain per project. These methods read and edit it: the\n * vertical **datum**, elevation/satellite layers, opacity, the cut/fill report,\n * and removal. All edits are undoable.\n *\n * ## Datum\n * The **datum** is the terrain's vertical position relative to where it was\n * imported. {@link PluginCoreIoTerrainApi.setDatum} shifts the whole terrain\n * vertically by a **relative** offset (each call moves it further — calls are\n * cumulative, not absolute); {@link PluginCoreIoTerrainApi.getDatum} reads the\n * current vertical offset from the imported position.\n *\n * Accessed via `snaptrude.core.io.terrain`.\n */\nexport abstract class PluginCoreIoTerrainApi {\n constructor() {}\n\n /**\n * Whether the project has a site terrain.\n *\n * @examplePrompt Does this project have site terrain?\n * @examplePrompt Is there a terrain imported?\n * @examplePrompt Check if the site topography exists\n * @examplePrompt Has a terrain been added yet?\n *\n * # Example\n * ```ts\n * if (!(await snaptrude.core.io.terrain.exists())) {\n * await snaptrude.core.io.import.terrain(40.7128, -74.006, 300, 300)\n * }\n * ```\n */\n public abstract exists(): PluginApiReturn<boolean>\n\n /**\n * Resolve the project's terrain handle, or `null` if there is none. The same\n * handle {@link core.io.import.terrain} returns — use it to obtain the handle\n * without re-importing.\n *\n * @examplePrompt Get the terrain handle\n * @examplePrompt Give me the site terrain\n * @examplePrompt Fetch the current terrain\n * @examplePrompt Return the topography handle if it exists\n */\n public abstract get(): PluginApiReturn<TerrainHandle | null>\n\n /**\n * Read the terrain's current vertical position (world Y, internal units).\n * `null` if there is no terrain. Import places the terrain so its highest\n * point sits at `y = 0`, so a never-shifted terrain reads a negative\n * baseline, not `0`; each {@link PluginCoreIoTerrainApi.setDatum} shift\n * moves this value.\n *\n * @examplePrompt What's the terrain's datum?\n * @examplePrompt Read the current site elevation reference\n * @examplePrompt Get the terrain vertical offset\n * @examplePrompt How far has the datum been shifted?\n */\n public abstract getDatum(): PluginApiReturn<number | null>\n\n /**\n * Shift the terrain's **datum** — move the whole terrain vertically DOWN by\n * `offset` (internal units) from its current position; pass a negative value to\n * raise it. Undoable.\n *\n * **Relative and cumulative**, not absolute: `setDatum(5)` called twice shifts\n * the terrain down by 10 total, it does not settle at a fixed level. Read the\n * terrain's resulting vertical position (world Y — not the accumulated\n * shift) back with {@link PluginCoreIoTerrainApi.getDatum}.\n *\n * @param offset - How far to shift the terrain down, in internal units\n * (negative raises it).\n * @throws if writes are disabled, there is no terrain, or the terrain is\n * **locked** (unlock it in the app first).\n *\n * @examplePrompt Shift the terrain datum down by 12\n * @examplePrompt Lower the site terrain by 10\n * @examplePrompt Raise the terrain a little\n * @examplePrompt Nudge the terrain datum down\n *\n * # Example\n * ```ts\n * await snaptrude.core.io.terrain.setDatum(12) // shift terrain down by 12 (relative)\n * ```\n */\n public abstract setDatum(offset: number): PluginApiReturn<void>\n\n /**\n * Delete the site terrain. Undoable.\n *\n * @throws if writes are disabled, there is no terrain, or the terrain is\n * **locked** (unlock it in the app first).\n *\n * @examplePrompt Delete the site terrain\n * @examplePrompt Remove the topography\n * @examplePrompt Clear the imported site\n * @examplePrompt Get rid of the terrain\n */\n public abstract delete(): PluginApiReturn<void>\n\n /**\n * The terrain cut/fill earthwork report — cut, fill, and net volumes — or\n * `null` if there is no terrain.\n *\n * @examplePrompt What are the cut and fill volumes for the site?\n * @examplePrompt Get the terrain earthwork report\n * @examplePrompt How much cut and fill does the grading need?\n * @examplePrompt Show the net volume of the terrain edits\n *\n * # Example\n * ```ts\n * const r = await snaptrude.core.io.terrain.getReport()\n * if (r) console.log(`cut ${r.cutVolume}, fill ${r.fillVolume}, net ${r.netVolume}`)\n * ```\n */\n public abstract getReport(): PluginApiReturn<TerrainReport | null>\n\n /** Whether terrain elevation (DEM height) is on. `null` if no terrain. */\n public abstract isElevationEnabled(): PluginApiReturn<boolean | null>\n /**\n * Turn terrain elevation (DEM height) ON — a real 3D topography surface. Undoable.\n * @throws if writes are disabled, there is no terrain, or the terrain is\n * **locked** (unlock it in the app first).\n * @examplePrompt Turn on terrain elevation\n * @examplePrompt Enable the site heightmap\n * @examplePrompt Make the terrain 3D\n * @examplePrompt Show the topography relief\n */\n public abstract enableElevation(): PluginApiReturn<void>\n /**\n * Turn terrain elevation OFF — flatten to a plane. Undoable.\n * @throws if writes are disabled, there is no terrain, or the terrain is\n * **locked** (unlock it in the app first).\n * @examplePrompt Flatten the terrain\n * @examplePrompt Turn off site elevation\n * @examplePrompt Disable the heightmap\n * @examplePrompt Make the terrain flat\n */\n public abstract disableElevation(): PluginApiReturn<void>\n\n /** Whether satellite imagery is draped on the terrain. `null` if no terrain. */\n public abstract isSatelliteEnabled(): PluginApiReturn<boolean | null>\n /**\n * Turn satellite imagery ON. Undoable.\n * @throws if writes are disabled, there is no terrain, or the terrain is\n * **locked** (unlock it in the app first).\n * @examplePrompt Show satellite imagery on the site\n * @examplePrompt Enable the satellite texture\n * @examplePrompt Turn on aerial imagery\n * @examplePrompt Drape satellite over the terrain\n */\n public abstract enableSatellite(): PluginApiReturn<void>\n /**\n * Turn satellite imagery OFF. Undoable.\n * @throws if writes are disabled, there is no terrain, or the terrain is\n * **locked** (unlock it in the app first).\n * @examplePrompt Hide the satellite imagery\n * @examplePrompt Turn off the aerial texture\n * @examplePrompt Disable satellite on the site\n * @examplePrompt Remove the satellite drape\n */\n public abstract disableSatellite(): PluginApiReturn<void>\n\n /**\n * Replace the terrain surface with a caller-supplied triangle mesh. Undoable\n * (one step), and the replacement persists with the project. The returned\n * promise resolves only after the surface is fully applied and recorded —\n * it is safe to toggle terrain resolution or read the surface back the\n * moment it resolves.\n *\n * `positions` is a flat `[x, y, z, …]` array in **world space** — the same\n * frame `design.query.geometry.getTriangulatedMeshes` reads — so a\n * read-modify-write round trip needs no conversion. `indices` is a flat\n * triangle list into `positions` (three indices per triangle). A\n * predominantly down-facing surface is flipped automatically (reported in\n * `warnings`), and zero-area sliver triangles are tolerated and counted.\n *\n * **What survives the replacement:** the datum, geolocation, opacity, lock\n * state, and the elevation/satellite toggles. The map/satellite texture is\n * re-projected onto the new surface from above (best effort — alignment is\n * approximate, reported in `warnings`). **What resets:** cut/fill history\n * and the earthwork report — the replaced surface starts with a clean\n * grading slate.\n *\n * Limits: at most 500,000 triangles and 500,000 vertices; every coordinate\n * must be finite and within the scene bound.\n *\n * @param positions - Flat `[x, y, z, …]` world-space vertex positions.\n * @param indices - Flat triangle list into `positions`.\n * @param units - `\"world\"` (default): coordinates are raw internal units;\n * `\"meters\"`: coordinates are metres and are converted on the way in.\n * @param options - Optional {@linkcode PluginTerrainReplaceMeshOptions}:\n * optimistic-concurrency expectations (`expectedTerrain`,\n * `expectedModelRevision`), `preserve` opt-outs, idempotent replay via\n * `clientMutationId`, and the `coordinateSpace`/`baseline` declarations.\n * @throws if writes are disabled, there is no terrain (import one first),\n * the terrain is **locked**, the mesh fails validation (non-finite or\n * out-of-bound coordinates, bad indices, degenerate-only geometry, over\n * the size caps), `expectedTerrain` does not match the live terrain, or\n * `expectedModelRevision` is stale.\n *\n * @examplePrompt Replace the terrain with this surveyed mesh\n * @examplePrompt Flatten a building pad into the site surface\n * @examplePrompt Load a custom DEM surface onto the terrain\n * @examplePrompt Regrade the terrain from these points\n *\n * # Example\n * ```ts\n * // Projects with no terrain yet: import first, then replace.\n * if (!(await snaptrude.core.io.terrain.exists())) {\n * await snaptrude.core.io.import.terrain(40.7128, -74.006, 300, 300)\n * }\n * const result = await snaptrude.core.io.terrain.replaceMesh(\n * positions, // [x0, y0, z0, x1, y1, z1, …] — world space\n * indices, // [a0, b0, c0, a1, b1, c1, …]\n * \"meters\",\n * {\n * coordinateSpace: \"world\",\n * expectedTerrain: await snaptrude.core.io.terrain.get(),\n * baseline: \"make-replacement-original\",\n * preserve: { datum: true, geolocation: true, satellite: true, material: true },\n * clientMutationId: \"survey-2026-08-05-r1\",\n * },\n * )\n * // result.modelRevision — pass back as expectedModelRevision on the next call\n * ```\n */\n public abstract replaceMesh(\n positions: number[],\n indices: number[],\n units?: \"world\" | \"meters\",\n options?: PluginTerrainReplaceMeshOptions,\n ): PluginApiReturn<PluginTerrainReplaceMeshResult>\n\n /** Read the terrain opacity, `0`..`1`, or `null` if no terrain. */\n public abstract getOpacity(): PluginApiReturn<number | null>\n /**\n * Set the terrain opacity (`0` transparent .. `1` opaque). Undoable.\n * @param opacity - Target opacity, `0`..`1`.\n * @throws if writes are disabled, there is no terrain, or the terrain is\n * **locked** (unlock it in the app first).\n * @examplePrompt Fade the terrain to 50%\n * @examplePrompt Make the site semi-transparent\n * @examplePrompt Set terrain opacity to 0.3\n * @examplePrompt Dim the topography\n *\n * # Example\n * ```ts\n * await snaptrude.core.io.terrain.setOpacity(0.5)\n * ```\n */\n public abstract setOpacity(opacity: number): PluginApiReturn<void>\n}\n\n/**\n * Terrain cut/fill earthwork report. Volumes are in the **project's display\n * volume unit** (what the UI shows), not internal units.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `cutVolume` | `number` | Total excavated (cut) volume |\n * | `fillVolume` | `number` | Total added (fill) volume |\n * | `netVolume` | `number` | Net volume (`fill − cut`) |\n */\nexport const TerrainReport = z.object({\n cutVolume: z.number(),\n fillVolume: z.number(),\n netVolume: z.number(),\n})\nexport type TerrainReport = z.infer<typeof TerrainReport>\n\n/**\n * Result of {@link PluginCoreIoTerrainApi.replaceMesh}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `terrain` | `TerrainHandle` | The terrain (unchanged singleton handle) |\n * | `modelRevision` | `number` | Session-scoped terrain-mesh revision after this replace — pass back as `expectedModelRevision` for compare-and-swap semantics |\n * | `vertexCount` | `number` | Vertices in the applied mesh |\n * | `triangleCount` | `number` | Triangles in the applied mesh |\n * | `warnings` | `string[]` | Non-fatal notes: winding flipped, sliver triangles, approximate texture re-projection |\n */\nexport type PluginTerrainReplaceMeshResult = {\n terrain: TerrainHandle\n modelRevision: number\n vertexCount: number\n triangleCount: number\n warnings: string[]\n}\n\n/**\n * Options for {@link PluginCoreIoTerrainApi.replaceMesh}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `coordinateSpace` | `\"world\"`? | Declares the input frame. `world` is the only supported space (the same frame `getTriangulatedMeshes` reads) |\n * | `expectedTerrain` | `TerrainHandle`? | Optimistic check: throws `HANDLE_INVALID` if the live terrain is not this handle (session-scoped — handles do not survive reloads) |\n * | `expectedModelRevision` | `number`? | Optimistic check against the session terrain-mesh revision (starts at `0`, increments per successful `replaceMesh`; the result returns the new value). Stale → `PRECONDITION_FAILED` with the current revision in `details` |\n * | `baseline` | `\"make-replacement-original\"`? | Declares replacement semantics. This is the only (and default) behavior: the replacement becomes the persisted surface; the app's terrain Reset restores the original map heightmap as the escape hatch |\n * | `preserve` | object? | Opt-outs, all defaulting `true`. `datum: false` resets the terrain transform (authored coordinates become the local frame). `satellite: false` switches the drape off after the replace. `material: false` skips texture re-projection — the whole surface takes the ground material. `geolocation: false` is rejected (`VALIDATION`): geolocation is always preserved in this version |\n * | `clientMutationId` | `string`? | Session-scoped idempotency: a repeated call with the same id returns the original result without re-executing |\n */\nexport const PluginTerrainReplaceMeshOptions = z\n .object({\n coordinateSpace: z.literal(\"world\").optional(),\n expectedTerrain: z.string().min(1).optional(),\n expectedModelRevision: z.number().int().nonnegative().optional(),\n baseline: z.literal(\"make-replacement-original\").optional(),\n preserve: z\n .object({\n datum: z.boolean().optional(),\n geolocation: z.boolean().optional(),\n satellite: z.boolean().optional(),\n material: z.boolean().optional(),\n })\n .strict()\n .optional(),\n clientMutationId: z.string().min(1).max(128).optional(),\n })\n .strict()\nexport type PluginTerrainReplaceMeshOptions = z.infer<typeof PluginTerrainReplaceMeshOptions>\n\n/**\n * Arguments for {@link PluginCoreIoTerrainApi.replaceMesh}. The caps are the\n * plugin surface's explicit payload ceiling: 500k vertices / 500k triangles.\n */\nexport const PluginTerrainReplaceMeshArgs = z.object({\n positions: z\n .array(z.number().finite())\n .min(9)\n .max(1_500_000)\n .refine((a) => a.length % 3 === 0, {\n message: \"positions must be a flat [x, y, z, …] array (length a multiple of 3)\",\n }),\n indices: z\n .array(z.number().int().nonnegative())\n .min(3)\n .max(1_500_000)\n .refine((a) => a.length % 3 === 0, {\n message: \"indices must be a flat triangle list (length a multiple of 3)\",\n }),\n units: z.enum([\"world\", \"meters\"]).optional(),\n options: PluginTerrainReplaceMeshOptions.optional(),\n})\nexport type PluginTerrainReplaceMeshArgs = z.infer<typeof PluginTerrainReplaceMeshArgs>\n\n/** Arguments for {@link PluginCoreIoTerrainApi.setDatum}. */\nexport const PluginTerrainSetDatumArgs = z.object({ offset: z.number().finite() })\nexport type PluginTerrainSetDatumArgs = z.infer<typeof PluginTerrainSetDatumArgs>\n\n/** Arguments for {@link PluginCoreIoTerrainApi.setOpacity}. */\nexport const PluginTerrainSetOpacityArgs = z.object({ opacity: z.number().min(0).max(1) })\nexport type PluginTerrainSetOpacityArgs = z.infer<typeof PluginTerrainSetOpacityArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport { CadJsonInput } from \"../import\"\n\n/**\n * Import queries — read metadata about a source **before** importing it.\n *\n * These reads let a plugin discover a source's shape before importing it:\n * how many pages a PDF has (for `import.pdf`'s `page`), and which CAD layers a\n * drawing defines (imports always bring in every layer — there is no filter —\n * but the names tell you what the drawing contains).\n *\n * Accessed via `snaptrude.core.io.query`.\n */\nexport abstract class PluginCoreIoQueryApi {\n constructor() {}\n\n /**\n * Count the pages of a PDF without importing it. `null` if the source can't be\n * read as a PDF.\n *\n * @param source - The PDF: an `https://` URL or a `data:` URL.\n * @returns the page count, or `null`.\n *\n * @examplePrompt How many pages does this PDF have?\n * @examplePrompt Count the pages in the drawing set before importing\n * @examplePrompt Get the PDF page count\n * @examplePrompt Is this a multi-page PDF, and how many?\n *\n * # Example\n * ```ts\n * const pages = await snaptrude.core.io.query.getPdfPageCount(url)\n * for (let p = 1; p <= (pages ?? 0); p++) {\n * await snaptrude.core.io.import.pdf(url, p, p) // one page per storey\n * }\n * ```\n */\n public abstract getPdfPageCount(source: string): PluginApiReturn<number | null>\n\n /**\n * List the distinct CAD layer names tagged on a parsed CAD JSON's entities.\n * Read-only inspection: imports always bring in **every** layer (there is no\n * layer filter on {@link core.io.import.dwg} / {@link core.io.import.cadJson}) —\n * use the names to decide whether to import at all, or what to tell the user.\n *\n * @param cad - Parsed CAD JSON (same shape `import.cadJson` accepts).\n * @returns the layer names (empty array if none).\n *\n * @examplePrompt What CAD layers are in this drawing?\n * @examplePrompt List the layers before importing the CAD\n * @examplePrompt Show the DWG layer names so I can pick which to import\n * @examplePrompt Enumerate the CAD JSON layers\n *\n * # Example\n * ```ts\n * const layers = await snaptrude.core.io.query.listCadLayers(cad)\n * if (layers.some((l) => l.toUpperCase().includes(\"WALL\"))) {\n * await snaptrude.core.io.import.cadJson(cad, 1) // all layers import together\n * }\n * ```\n */\n public abstract listCadLayers(cad: CadJsonInput): PluginApiReturn<string[]>\n}\n\n/** Arguments for {@link PluginCoreIoQueryApi.getPdfPageCount}. */\nexport const PluginQueryPdfPageCountArgs = z.object({ source: z.string().min(1) })\nexport type PluginQueryPdfPageCountArgs = z.infer<typeof PluginQueryPdfPageCountArgs>\n\n/** Arguments for {@link PluginCoreIoQueryApi.listCadLayers}. */\nexport const PluginQueryCadLayersArgs = z.object({ cad: CadJsonInput })\nexport type PluginQueryCadLayersArgs = z.infer<typeof PluginQueryCadLayersArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\n\n/**\n * Serialization format for {@link PluginCoreIoExportApi.model}.\n *\n * `glb` / `obj` serialize in the browser; `fbx` / `3ds` run the same server\n * conversion the app's Export dialog uses and return a **zip** containing the\n * converted file.\n */\nexport const ExportModelFormat = z.enum([\"glb\", \"obj\", \"fbx\", \"3ds\"])\nexport type ExportModelFormat = z.infer<typeof ExportModelFormat>\n\n/**\n * What to export: the whole model (`\"scene\"`, default) or only the current\n * selection (`\"selection\"`).\n */\nexport const ExportScope = z.enum([\"scene\", \"selection\"])\nexport type ExportScope = z.infer<typeof ExportScope>\n\n/**\n * A serialized model file returned by {@link PluginCoreIoExportApi.model}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `fileName` | `string` | Suggested file name incl. extension (e.g. `\"model.glb\"`) |\n * | `mimeType` | `string` | MIME type (`\"model/gltf-binary\"` for glb, `\"text/plain\"` for obj) |\n * | `dataBase64` | `string` | The file's bytes, base64-encoded (decode to save/download) |\n */\nexport const ExportedFile = z.object({\n fileName: z.string(),\n mimeType: z.string(),\n dataBase64: z.string(),\n})\nexport type ExportedFile = z.infer<typeof ExportedFile>\n\n/**\n * Model export — serialize the scene (or selection) to a downloadable 3D file.\n *\n * `core.io.export.model` hands you the file bytes as base64 — no download\n * dialog opens; the plugin decides what to do with the result (save it, POST\n * it, diff it). For a different export intent, see `presentation.export`\n * (Present-mode layout sheets to PDF/PNG/JPG) and `program.spreadsheet.export`\n * (the area-program / takeoff workbook). Four formats:\n *\n * - `\"glb\"` — binary glTF (`model/gltf-binary`); geometry + materials,\n * serialized in the browser (nothing is uploaded).\n * - `\"obj\"` — Wavefront OBJ text (`text/plain`); geometry only, serialized in\n * the browser.\n * - `\"fbx\"` / `\"3ds\"` — Autodesk FBX / 3D Studio; the scene is uploaded to the\n * same `/exportmodel/` conversion service the app's Export dialog uses, and\n * the call resolves once the converted artifact is fetched back — a **zip**\n * (`application/zip`) containing the `.fbx`/`.3ds` file. Whole-scene only\n * (`scope: \"selection\"` is rejected). Conversion runs on the server and is\n * bounded by the per-call plugin timeout (60 s) — very large models may\n * exceed it and reject with `TIMEOUT`.\n *\n * For the browser formats, only real model geometry is exported (walls, slabs,\n * roofs, floors, doors, windows, furniture, staircases, masses, ceilings,\n * mullions, beams, columns, terrain); UI helpers, hidden objects, and\n * hidden-bucket objects are excluded — matching the app's GLB export. The\n * server formats convert the full serialized scene (hidden-bucket objects\n * excluded), exactly like the Export dialog. This is a **read**: it never\n * mutates the model.\n *\n * > Server-backed BIM exports (RVT / IFC / DWG) are **not** exposed here — those\n * > run as fire-and-forget Forge jobs that stream a browser download and return\n * > no retrievable bytes. Use the app's Export menu for those.\n *\n * Accessed via `snaptrude.core.io.export`.\n */\nexport abstract class PluginCoreIoExportApi {\n constructor() {}\n\n /**\n * Export the model to a 3D file and return its bytes as base64.\n *\n * @param format - `\"glb\"` (binary glTF, geometry + materials), `\"obj\"`\n * (Wavefront OBJ text, geometry only) — both serialized in the browser —\n * or `\"fbx\"` / `\"3ds\"`, converted by the app's export service and returned\n * as a zip containing the converted file.\n * @param scope - `\"scene\"` (default) for the whole model, or `\"selection\"` for\n * only the currently selected objects (browser formats only — `\"fbx\"`/`\"3ds\"`\n * reject it). An empty selection yields an empty file.\n * @returns an {@linkcode ExportedFile} — `{ fileName, mimeType, dataBase64 }`.\n * @throws if the format is unsupported, `scope: \"selection\"` is combined with a\n * server format, or the serialization/conversion fails.\n *\n * @examplePrompt Export the model as a GLB\n * @examplePrompt Give me the whole scene as an OBJ file\n * @examplePrompt Export just my selection to GLB\n * @examplePrompt Download the model as an FBX file\n * @examplePrompt Export the scene to 3DS for 3ds Max\n *\n * # Example\n * ```ts\n * const file = await snaptrude.core.io.export.model(\"glb\")\n * // decode base64 → bytes and save/download in the plugin UI\n * const bytes = Uint8Array.from(atob(file.dataBase64), (c) => c.charCodeAt(0))\n * const blob = new Blob([bytes], { type: file.mimeType })\n * // ...offer blob for download as file.fileName\n *\n * // Only the current selection, as OBJ text:\n * const obj = await snaptrude.core.io.export.model(\"obj\", \"selection\")\n * const text = atob(obj.dataBase64)\n * ```\n */\n public abstract model(\n format: ExportModelFormat,\n scope?: ExportScope,\n ): PluginApiReturn<ExportedFile>\n}\n\n/**\n * Arguments for {@link PluginCoreIoExportApi.model}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `format` | {@link ExportModelFormat} | `\"glb\"`, `\"obj\"`, `\"fbx\"`, or `\"3ds\"` |\n * | `scope` | {@link ExportScope} | `\"scene\"` (default) or `\"selection\"` (browser formats only) |\n */\nexport const PluginExportModelArgs = z.object({\n format: ExportModelFormat,\n scope: ExportScope.optional(),\n})\nexport type PluginExportModelArgs = z.infer<typeof PluginExportModelArgs>\n","import { PluginCoreIoImportApi } from \"./import\"\nimport { PluginCoreIoJobApi } from \"./job\"\nimport { PluginCoreIoUnderlayApi } from \"./underlay\"\nimport { PluginCoreIoTerrainApi } from \"./terrain\"\nimport { PluginCoreIoQueryApi } from \"./query\"\nimport { PluginCoreIoExportApi } from \"./export\"\n\n/**\n * I/O — bring external files into the project and manage what was imported.\n *\n * - {@linkcode PluginCoreIoApi.import} — import images, PDFs, CAD, 3D models, terrain\n * - {@linkcode PluginCoreIoApi.query} — read source metadata before importing (pdf pages, cad layers)\n * - {@linkcode PluginCoreIoApi.job} — poll long-running async imports (e.g. DWG)\n * - {@linkcode PluginCoreIoApi.underlay} — inspect/scale/opacity/delete placed underlays\n * - {@linkcode PluginCoreIoApi.terrain} — inspect/edit the site terrain (datum, layers, delete)\n * - {@linkcode PluginCoreIoApi.export} — serialize the model to glb/obj (base64 bytes)\n *\n * Accessed via `snaptrude.core.io`.\n */\nexport abstract class PluginCoreIoApi {\n /** Import external files into the scene. See {@linkcode PluginCoreIoImportApi}. */\n public abstract import: PluginCoreIoImportApi\n /** Read source metadata before import. See {@linkcode PluginCoreIoQueryApi}. */\n public abstract query: PluginCoreIoQueryApi\n /** Poll long-running import jobs. See {@linkcode PluginCoreIoJobApi}. */\n public abstract job: PluginCoreIoJobApi\n /** Inspect and edit placed underlays. See {@linkcode PluginCoreIoUnderlayApi}. */\n public abstract underlay: PluginCoreIoUnderlayApi\n /** Inspect and manage the site terrain. See {@linkcode PluginCoreIoTerrainApi}. */\n public abstract terrain: PluginCoreIoTerrainApi\n /** Serialize the model to a downloadable 3D file. See {@linkcode PluginCoreIoExportApi}. */\n public abstract export: PluginCoreIoExportApi\n\n constructor() {}\n}\n\nexport * from \"./import\"\nexport * from \"./job\"\nexport * from \"./underlay\"\nexport * from \"./terrain\"\nexport * from \"./query\"\nexport * from \"./export\"\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\n// Type-only: src/handles.ts type-only-imports ArenaKind from this file, so the\n// cycle stays purely type-level and is erased at emit.\nimport type { AnyArenaHandle } from \"../../../handles\"\nimport { Handle } from \"../../../handles\"\n\n/**\n * The 12 registry-arena handle kinds — the releasable families. Entity-style\n * handles (component / material / underlay / terrain / importJob / comment) are\n * raw engine ids with no arena entry and therefore no lifecycle: passing one to\n * `release` is a silent no-op like any other unknown id.\n */\nexport const ArenaKind = z.enum([\n \"vec3\",\n \"quat\",\n \"line\",\n \"arc\",\n \"circle\",\n \"profile\",\n \"contour\",\n \"brep\",\n \"face\",\n \"edge\",\n \"halfedge\",\n \"vertex\",\n])\nexport type ArenaKind = z.infer<typeof ArenaKind>\n\n/** Max handles accepted by a single `release` / `endScope(retain)` call. */\nexport const RELEASE_BATCH_MAX = 10_000\n\n/**\n * An opaque token identifying a handle scope opened by\n * {@linkcode PluginCoreHandlesApi.beginScope}. It is NOT an arena handle —\n * it pins no memory, and `release(token)` is a silent no-op; only\n * {@linkcode PluginCoreHandlesApi.endScope} consumes it.\n */\nexport type ScopeToken = string\n\n/**\n * Handle lifecycle — free host-side handle registry entries the plugin no\n * longer needs, individually ({@linkcode release}), by kind\n * ({@linkcode releaseAll}), or as a mint-recording region\n * ({@linkcode beginScope} / {@linkcode endScope}).\n *\n * Releasing is a handle-lifecycle operation only: it frees the registry entry\n * (host memory + quota room) and makes the handle unresolvable. It never\n * deletes engine-side geometry or scene entities.\n *\n * Accessed via `snaptrude.core.handles`.\n */\nexport abstract class PluginCoreHandlesApi {\n constructor() {}\n\n /**\n * Free arena handles in bulk. Unknown, foreign, or already-released ids are\n * silent no-ops — there is no per-id result and no count, so responses can\n * never be used as an existence oracle.\n *\n * @param handles The handles to release (max {@linkcode RELEASE_BATCH_MAX} per call)\n * @returns Nothing\n *\n * # Example\n * ```ts\n * const faces = await snaptrude.core.geom.query.brep.listFaces(brep)\n * // ... read what you need ...\n * await snaptrude.core.handles.release(faces)\n * ```\n */\n public abstract release(handles: ReadonlyArray<AnyArenaHandle>): PluginApiReturn<void>\n\n /**\n * Free every live arena handle of one kind, or ALL arena handles when the\n * kind is omitted. Useful at batch boundaries — e.g. a poll loop that mints\n * a fresh footprint contour per tick.\n *\n * @param kind Optional arena kind to restrict the sweep to\n * @returns Nothing\n *\n * # Example\n * ```ts\n * const footprint = await snaptrude.design.query.spaces.getFootprint(space)\n * // ... measure ...\n * await snaptrude.core.handles.releaseAll(\"contour\")\n * ```\n */\n public abstract releaseAll(kind?: ArenaKind): PluginApiReturn<void>\n\n /**\n * Open a mint-recording scope: every arena handle MINTED after this call\n * (fresh mints only — identity-deduped re-registrations of pre-existing\n * objects are not captured) is freed when the matching\n * {@linkcode endScope} runs. Scopes nest, up to 64 deep — always pair\n * begin/end in try/finally.\n *\n * @returns The scope token to pass to {@linkcode endScope}\n *\n * # Example\n * ```ts\n * for (const space of spaces) {\n * const scope = await snaptrude.core.handles.beginScope()\n * try {\n * const footprint = await snaptrude.design.query.spaces.getFootprint(space)\n * const area = await snaptrude.core.geom.query.contour.getArea(footprint)\n * } finally {\n * await snaptrude.core.handles.endScope(scope) // footprint (and intermediates) freed\n * }\n * }\n * ```\n */\n public abstract beginScope(): PluginApiReturn<ScopeToken>\n\n /**\n * Close a scope by its token and free every handle it recorded, except those\n * listed in `retain`. Retained handles are promoted to the enclosing scope\n * (if any), so nesting composes like block scopes. Closing is\n * token-addressed: an out-of-order close (interleaved async tasks) frees\n * only that scope's mints and leaves other open scopes intact. Always pair\n * begin/end in try/finally so a thrown body cannot leak the scope.\n *\n * @param scope The token returned by the matching {@linkcode beginScope}\n * @param retain Handles minted inside the scope that must survive it\n * @returns Nothing\n *\n * # Example\n * ```ts\n * const scope = await snaptrude.core.handles.beginScope()\n * const outline = await snaptrude.design.query.spaces.getFootprint(space)\n * const outer = await snaptrude.core.geom.query.contour.getOuterProfile(outline)\n * await snaptrude.core.handles.endScope(scope, [outer]) // outline freed, outer survives\n * ```\n */\n public abstract endScope(\n scope: ScopeToken,\n retain?: ReadonlyArray<AnyArenaHandle>\n ): PluginApiReturn<void>\n\n /**\n * Live handle-registry statistics for the calling plugin, plus the host\n * page's JS heap usage (when the browser exposes it). Diagnostic surface for\n * verifying that releases actually shrink host memory.\n *\n * @returns Arena entry counts, open scope depth, and host heap bytes\n *\n * # Example\n * ```ts\n * const before = await snaptrude.core.handles.stats()\n * await snaptrude.core.handles.releaseAll(\"vec3\")\n * const after = await snaptrude.core.handles.stats()\n * console.log(`freed ${before.values - after.values} value entries`)\n * ```\n */\n public abstract stats(): PluginApiReturn<PluginHandlesStatsResult>\n}\n\n/**\n * Wire-normalize one handle argument: a raw id string passes through; a live\n * `Handle` instance (direct host callers — console harness, host-internal) or a\n * structured-clone-stripped `{id}` / tagged `{__h}` shape collapses to its id.\n * Deliberately shape-lax beyond that (non-empty string) — a malformed id is\n * just an id that resolves nowhere (anti-oracle).\n */\nconst handleIdWire = z.preprocess((v) => {\n if (typeof v === \"string\") return v\n if (v instanceof Handle) return v.id\n if (v && typeof v === \"object\") {\n const o = v as Record<string, unknown>\n if (typeof o.__h === \"string\") return o.__h\n if (typeof o.id === \"string\") return o.id\n }\n return v\n}, z.string().min(1))\n\n/**\n * Arguments for {@linkcode PluginCoreHandlesApi.release}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `handles` | `string[]` | Handles to release (≤ {@linkcode RELEASE_BATCH_MAX}) |\n */\nexport const PluginHandlesReleaseArgs = z.object({\n handles: z.array(handleIdWire).max(RELEASE_BATCH_MAX),\n})\nexport type PluginHandlesReleaseArgs = z.infer<typeof PluginHandlesReleaseArgs>\n\n/**\n * Arguments for {@linkcode PluginCoreHandlesApi.releaseAll}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `kind` | {@linkcode ArenaKind} (optional) | Restrict the sweep to one kind |\n */\nexport const PluginHandlesReleaseAllArgs = z.object({\n kind: ArenaKind.optional(),\n})\nexport type PluginHandlesReleaseAllArgs = z.infer<typeof PluginHandlesReleaseAllArgs>\n\n/**\n * Arguments for {@linkcode PluginCoreHandlesApi.endScope}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `scope` | {@linkcode ScopeToken} | Token from the matching `beginScope` |\n * | `retain` | `string[]` (optional) | Scope-minted handles that must survive |\n */\nexport const PluginHandlesEndScopeArgs = z.object({\n scope: z.string().regex(/^scope_[A-Za-z0-9_-]+$/, 'expected a \"scope\" token'),\n retain: z.array(handleIdWire).max(RELEASE_BATCH_MAX).optional(),\n})\nexport type PluginHandlesEndScopeArgs = z.infer<typeof PluginHandlesEndScopeArgs>\n\n/**\n * Result of {@linkcode PluginCoreHandlesApi.stats}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `resources` | `number` | Live resource-arena entries (profile/contour) |\n * | `values` | `number` | Live value-arena entries (vec3/quat/line/arc/circle) |\n * | `topology` | `number` | Live topology-arena entries (brep/face/edge/halfedge/vertex) |\n * | `scopes` | `number` | Currently open handle scopes |\n * | `hostHeapUsedBytes` | `number \\| null` | Host page `usedJSHeapSize` (null where unsupported) |\n * | `hostHeapTotalBytes` | `number \\| null` | Host page `totalJSHeapSize` (null where unsupported) |\n */\nexport const PluginHandlesStatsResult = z.object({\n resources: z.number().int().nonnegative(),\n values: z.number().int().nonnegative(),\n topology: z.number().int().nonnegative(),\n scopes: z.number().int().nonnegative(),\n hostHeapUsedBytes: z.number().nullable(),\n hostHeapTotalBytes: z.number().nullable(),\n})\nexport type PluginHandlesStatsResult = z.infer<typeof PluginHandlesStatsResult>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { ComponentHandle } from \"../../../handles\"\n\n/**\n * Proposals — read and edit the project's design options (\"proposals\").\n *\n * A **proposal** is a design option: one of several variants of the same\n * project. Each proposal owns a set of buckets (a bucket == a drawing layer),\n * and switching the active proposal shows/hides the buckets it owns. Proposals\n * are **infrastructure**, not design — they own the buckets/layers geometry\n * lives in — so they sit under `core`, not `design` (constitution §2A).\n *\n * Reads return plain records and never throw for a missing proposal (`get` and\n * `getActive` return `null`, `list` / `listForComponent` return an empty list,\n * `isActive` returns `false`). Ids are stable public strings.\n *\n * On legacy / no-proposal projects the scoping predicates degrade gracefully:\n * `getActive` returns `null` and the reads return empty.\n *\n * Proposal writes are **not undoable** — create / rename / setActive / delete\n * persist directly and bypass the undo stack; a `core.history.undo` will not\n * reverse them. Each proposal keeps its own isolated undo stack for the\n * geometry edits made inside it.\n *\n * Accessed via `snaptrude.core.proposals`.\n */\nexport abstract class PluginCoreProposalsApi {\n constructor() {}\n\n /**\n * List the design options (proposals) in the active project.\n *\n * @returns A {@linkcode PluginCoreProposalsListResult} with a `proposals`\n * array (empty on a legacy / no-proposal project).\n *\n * @examplePrompt List all the design options in this project\n * @examplePrompt How many proposals does this project have?\n * @examplePrompt Show every design variant with its name\n *\n * # Example\n * ```ts\n * const { proposals } = await snaptrude.core.proposals.list()\n * for (const p of proposals) console.log(p.id, p.name, p.isActive)\n * ```\n */\n public abstract list(): PluginApiReturn<PluginCoreProposalsListResult>\n\n /**\n * Get a single proposal by id.\n *\n * @param proposalId - The id of the proposal to read.\n * @returns The matching {@linkcode PluginCoreProposalRef}, or `null` if no\n * proposal has that id.\n *\n * @examplePrompt Get the proposal with id prop_1\n * @examplePrompt Look up a single design option by its id\n * @examplePrompt Which buckets belong to proposal prop_2?\n *\n * # Example\n * ```ts\n * const proposal = await snaptrude.core.proposals.get(\"prop_1\")\n * if (proposal) console.log(proposal.name, proposal.bucketIds)\n * ```\n */\n public abstract get(\n proposalId: string,\n ): PluginApiReturn<PluginCoreProposalsGetResult>\n\n /**\n * Get the active proposal.\n *\n * The active proposal is the design option currently shown and edited.\n * Honors a present-mode capture override: while a present-mode capture is in\n * flight this returns the captured proposal, not the redux-active one. Paired\n * with {@linkcode PluginCoreProposalsApi.setActive}.\n *\n * @returns The active {@linkcode PluginCoreProposalRef}, or `null` on a legacy\n * / no-proposal project where nothing is active.\n *\n * @examplePrompt Which design option is active?\n * @examplePrompt Get the current proposal\n * @examplePrompt What proposal am I working in?\n *\n * # Example\n * ```ts\n * const active = await snaptrude.core.proposals.getActive()\n * if (active) console.log(active.id, active.name)\n * ```\n */\n public abstract getActive(): PluginApiReturn<PluginCoreProposalsGetActiveResult>\n\n /**\n * List the proposals a component belongs to.\n *\n * Returns **every** proposal whose buckets include the component — a bucket\n * can be shared across proposals, so a component may appear in more than one.\n *\n * @param component - The component to look up.\n * @returns A {@linkcode PluginCoreProposalsListForComponentResult} with a\n * `proposals` array (empty when the component is out of scope or the project\n * has no proposals).\n *\n * @examplePrompt Which design options is this component part of?\n * @examplePrompt List the proposals that contain this wall\n * @examplePrompt Is this element shared across multiple design variants?\n *\n * # Example\n * ```ts\n * const { proposals } = await snaptrude.core.proposals.listForComponent(wall)\n * console.log(`shared across ${proposals.length} proposals`)\n * ```\n */\n public abstract listForComponent(\n component: ComponentHandle,\n ): PluginApiReturn<PluginCoreProposalsListForComponentResult>\n\n /**\n * Test whether a proposal is the active one.\n *\n * @param proposalId - The id of the proposal to test.\n * @returns `true` if it is the active proposal, otherwise `false` (including\n * when no proposal has that id).\n *\n * @examplePrompt Is proposal prop_2 the active one?\n * @examplePrompt Check whether this design option is currently shown\n * @examplePrompt Am I working in proposal prop_1?\n *\n * # Example\n * ```ts\n * if (await snaptrude.core.proposals.isActive(\"prop_1\")) {\n * console.log(\"prop_1 is active\")\n * }\n * ```\n */\n public abstract isActive(proposalId: string): PluginApiReturn<boolean>\n\n /**\n * Create a new design option (proposal) — blank, or configured from a base\n * proposal (the New Proposal modal's two paths).\n *\n * **Pro-gated** (creating proposals requires a Pro plan, matching the UI) and\n * **not undoable**. The name is capped at 24 characters.\n *\n * **Blank** (no `options`) — creates a bare proposal with a single empty\n * default bucket; it does **not** carry forward the current proposal's\n * layers, views, or targets, and the active proposal does **not** change.\n *\n * **Configured** (`options.baseProposalId`) — the modal's Configure path:\n * carries the base proposal's buckets (layers) forward per `options.layers`,\n * where each entry names a bucket of the base proposal and an action —\n * `\"share\"` (the new proposal references the same live bucket) or\n * `\"duplicate\"` (an independent copy of the bucket and its contents).\n * Buckets not listed are left behind. Omitting `layers` carries **every**\n * base bucket forward as a `\"duplicate\"` (the modal's default). The base\n * proposal's views and area targets are copied either way, and the new\n * proposal becomes the **active** one (implicit switch — blocked with\n * `PROPOSAL_SWITCH_BLOCKED` while an in-canvas agent run is in flight, like\n * {@linkcode PluginCoreProposalsApi.setActive}).\n *\n * @param name - Display name of the new proposal (capped at 24 chars).\n * @param options - Optional configure path: `baseProposalId` (the proposal to\n * carry forward from) and `layers` (per-bucket `\"share\"` / `\"duplicate\"`\n * actions; omitted buckets are excluded, omitted `layers` duplicates all).\n * @returns The new proposal's id (read the full record back with\n * {@linkcode PluginCoreProposalsApi.get}).\n * @throws If the caller is not on a Pro plan, the name is empty/duplicate,\n * `layers` is passed without `baseProposalId`, the base proposal or a\n * listed bucket does not exist / does not belong to the base proposal, or\n * (configured only) `PROPOSAL_SWITCH_BLOCKED` while a canvas-agent run is\n * in flight.\n *\n * @examplePrompt Create a design option called Scheme B\n * @examplePrompt Add a new proposal named Option 2\n * @examplePrompt Duplicate the current proposal as Scheme C\n * @examplePrompt Create a design option based on Proposal 1 that shares its site layer\n *\n * # Example\n * ```ts\n * // blank design option\n * const { id } = await snaptrude.core.proposals.create(\"Scheme B\")\n *\n * // configured from the active proposal: duplicate every layer\n * const base = await snaptrude.core.proposals.getActive()\n * if (base) {\n * const { id: dup } = await snaptrude.core.proposals.create(\"Scheme C\", {\n * baseProposalId: base.id,\n * })\n * // …or share one layer and leave the rest behind\n * const { id: shared } = await snaptrude.core.proposals.create(\"Scheme D\", {\n * baseProposalId: base.id,\n * layers: [{ bucketId: base.defaultBucketId, action: \"share\" }],\n * })\n * }\n * ```\n */\n public abstract create(\n name: string,\n options?: {\n baseProposalId: string\n layers?: { bucketId: string; action: \"share\" | \"duplicate\" }[]\n },\n ): PluginApiReturn<PluginCoreProposalsCreateResult>\n\n /**\n * Rename a proposal.\n *\n * **Not undoable.** The name is capped at 24 characters.\n *\n * @param proposalId - The id of the proposal to rename.\n * @param name - The new display name (capped at 24 chars).\n * @returns The updated {@linkcode PluginCoreProposalRef}.\n * @throws If no proposal has the given id, or the name is empty / duplicates\n * another proposal.\n *\n * @examplePrompt Rename proposal prop_1 to Final Scheme\n * @examplePrompt Change the name of this design option\n * @examplePrompt Call proposal prop_2 \"Option A\"\n *\n * # Example\n * ```ts\n * const proposal = await snaptrude.core.proposals.rename(\"prop_1\", \"Final Scheme\")\n * ```\n */\n public abstract rename(\n proposalId: string,\n name: string,\n ): PluginApiReturn<PluginCoreProposalsRenameResult>\n\n /**\n * Switch the active proposal.\n *\n * A **heavy scene switch** and **not undoable**. Switching clears the current\n * selection, resets AI / streaming state, cancels proposal-scoped operations,\n * retoggles bucket visibility to the new proposal's owned set, restores its\n * per-proposal site/FSI settings, and realigns the views panel. After the\n * switch, **writes** are guarded to the new active proposal (edits targeting\n * components outside it are rejected), but **list reads are NOT\n * proposal-filtered** — `design.query.list*` and similar reads stay\n * project-wide; filter their results via\n * {@linkcode PluginCoreProposalsApi.listForComponent} if you need\n * proposal-scoped sets.\n *\n * **Blocked while an in-canvas agent run is in flight** — throws\n * `PROPOSAL_SWITCH_BLOCKED` if a canvas-agent run is active (matching the UI's\n * hard block), because a switch would cancel the in-flight run.\n *\n * @param proposalId - The id of the proposal to activate.\n * @returns The id of the now-active proposal.\n * @throws `PROPOSAL_SWITCH_BLOCKED` while a canvas-agent run is in flight; or\n * if no proposal has the given id.\n *\n * @examplePrompt Switch to proposal prop_2\n * @examplePrompt Make Scheme B the active design option\n * @examplePrompt Show me the other proposal\n *\n * # Example\n * ```ts\n * await snaptrude.core.proposals.setActive(\"prop_2\")\n * ```\n */\n public abstract setActive(\n proposalId: string,\n ): PluginApiReturn<PluginCoreProposalsSetActiveResult>\n\n /**\n * Delete a proposal.\n *\n * **DANGEROUS AND IRREVERSIBLE — not undoable.** You must choose **exactly\n * one** explicit disposition (mirroring the product's delete dialog):\n *\n * - `{ deleteObjects: true }` — delete the proposal AND **permanently\n * destroy the geometry** in its exclusive buckets. Buckets shared with\n * other proposals are never destroyed.\n * - `{ targetProposalId }` — transfer **all** of this proposal's buckets to\n * another proposal first (keeping the geometry alive), then delete the\n * emptied proposal — \"merge and remove\". The target must be a\n * **different** proposal.\n *\n * A bare `delete(proposalId)` is **rejected**: there is no safe default —\n * without a disposition the engine would drop the proposal's bucket records\n * while leaving their components orphaned. Passing both dispositions at once\n * is also rejected as ambiguous.\n *\n * It is **server-validated**: the backend refuses the delete (throws) if a\n * collaborator is active in that proposal, and the check times out after\n * ~10s. The project's **last remaining proposal cannot be deleted**. If the\n * proposal is active, the host switches away first.\n *\n * @param proposalId - The id of the proposal to delete.\n * @param options - Exactly one disposition: `{ deleteObjects: true }`\n * (destroy exclusive-bucket geometry) or `{ targetProposalId }` (transfer\n * buckets to that proposal, then delete).\n * @returns Confirmation that the proposal was deleted.\n * @throws If no disposition (or both) is given, `targetProposalId` equals\n * `proposalId`, no proposal has the given id, it is the only proposal, a\n * collaborator lock refuses the delete, or the server validation times out.\n *\n * @examplePrompt Delete proposal prop_2 and everything in it\n * @examplePrompt Remove this design option and destroy its geometry\n * @examplePrompt Merge prop_2's layers into prop_1 and delete prop_2\n *\n * # Example\n * ```ts\n * // destroy the proposal and its exclusive geometry\n * await snaptrude.core.proposals.delete(\"prop_2\", { deleteObjects: true })\n * // …or keep the geometry by transferring its buckets to prop_1 first\n * await snaptrude.core.proposals.delete(\"prop_2\", { targetProposalId: \"prop_1\" })\n * ```\n */\n public abstract delete(\n proposalId: string,\n options:\n | { deleteObjects: true; targetProposalId?: never }\n | { deleteObjects?: false; targetProposalId: string },\n ): PluginApiReturn<PluginCoreProposalsDeleteResult>\n}\n\n/**\n * A design option (proposal) in the active project.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Stable public proposal id (e.g. `\"prop_1\"`) |\n * | `name` | `string` | Display name (≤ 24 chars) |\n * | `isActive` | `boolean` | Whether this is the active proposal |\n * | `defaultBucketId` | `string` | Id of the proposal's default bucket (cannot be unshared) |\n * | `bucketIds` | `string[]` | Ids of every bucket (layer) the proposal owns |\n * | `createdAt` | `string` | ISO creation timestamp |\n * | `createdBy` | `string` | User id of the creator |\n */\nexport const PluginCoreProposalRef = z.object({\n id: z.string(),\n name: z.string(),\n isActive: z.boolean(),\n defaultBucketId: z.string(),\n bucketIds: z.array(z.string()),\n createdAt: z.string(),\n createdBy: z.string(),\n})\nexport type PluginCoreProposalRef = z.infer<typeof PluginCoreProposalRef>\n\n/**\n * Result of {@linkcode PluginCoreProposalsApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `proposals` | {@linkcode PluginCoreProposalRef}`[]` | Every proposal in the active project |\n */\nexport const PluginCoreProposalsListResult = z.object({\n proposals: z.array(PluginCoreProposalRef),\n})\nexport type PluginCoreProposalsListResult = z.infer<\n typeof PluginCoreProposalsListResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreProposalsApi.get} /\n * {@linkcode PluginCoreProposalsApi.isActive}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `proposalId` | `string` | The id of the proposal |\n */\nexport const PluginCoreProposalsGetArgs = z.object({\n proposalId: z.string(),\n})\nexport type PluginCoreProposalsGetArgs = z.infer<\n typeof PluginCoreProposalsGetArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreProposalsApi.get} — the proposal, or `null`\n * when no proposal has the given id.\n */\nexport const PluginCoreProposalsGetResult = PluginCoreProposalRef.nullable()\nexport type PluginCoreProposalsGetResult = z.infer<\n typeof PluginCoreProposalsGetResult\n>\n\n/**\n * Result of {@linkcode PluginCoreProposalsApi.getActive} — the active proposal,\n * or `null` when nothing is active (legacy / no-proposal project).\n */\nexport const PluginCoreProposalsGetActiveResult =\n PluginCoreProposalRef.nullable()\nexport type PluginCoreProposalsGetActiveResult = z.infer<\n typeof PluginCoreProposalsGetActiveResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreProposalsApi.listForComponent}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `component` | {@linkcode ComponentHandle} | The component to look up |\n */\nexport const PluginCoreProposalsListForComponentArgs = z.object({\n component: ComponentHandle,\n})\nexport type PluginCoreProposalsListForComponentArgs = z.infer<\n typeof PluginCoreProposalsListForComponentArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreProposalsApi.listForComponent} — the proposals\n * the component belongs to (empty when out of scope or none). Same shape as\n * {@linkcode PluginCoreProposalsListResult}.\n */\nexport const PluginCoreProposalsListForComponentResult =\n PluginCoreProposalsListResult\nexport type PluginCoreProposalsListForComponentResult = z.infer<\n typeof PluginCoreProposalsListForComponentResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreProposalsApi.isActive}. Alias of\n * {@linkcode PluginCoreProposalsGetArgs}.\n */\nexport const PluginCoreProposalsIsActiveArgs = PluginCoreProposalsGetArgs\nexport type PluginCoreProposalsIsActiveArgs = z.infer<\n typeof PluginCoreProposalsIsActiveArgs\n>\n\n/**\n * Arguments for {@linkcode PluginCoreProposalsApi.create}. A bare `name` makes\n * a blank proposal; `baseProposalId` switches to the configured (carry-forward)\n * path. `layers` requires `baseProposalId` — each entry names a bucket of the\n * base proposal; omitted buckets are excluded, and omitting `layers` entirely\n * duplicates every base bucket.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | Display name of the new proposal (capped at 24 chars) |\n * | `baseProposalId` | `string \\| undefined` | Proposal to carry forward from (configured path) |\n * | `layers` | `{ bucketId: string; action: \"share\" \\| \"duplicate\" }[] \\| undefined` | Per-bucket carry-forward actions (requires `baseProposalId`) |\n */\nexport const PluginCoreProposalsCreateArgs = z\n .object({\n name: z.string(),\n baseProposalId: z.string().optional(),\n layers: z\n .array(\n z.object({\n bucketId: z.string(),\n action: z.enum([\"share\", \"duplicate\"]),\n }),\n )\n .optional(),\n })\n .refine((v) => v.layers === undefined || v.baseProposalId !== undefined, {\n message:\n \"layers requires baseProposalId — the layers describe how to carry the base proposal's buckets forward\",\n })\nexport type PluginCoreProposalsCreateArgs = z.infer<\n typeof PluginCoreProposalsCreateArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreProposalsApi.create} — the id of the new\n * proposal.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | The id of the newly created proposal |\n */\nexport const PluginCoreProposalsCreateResult = z.object({\n id: z.string(),\n})\nexport type PluginCoreProposalsCreateResult = z.infer<\n typeof PluginCoreProposalsCreateResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreProposalsApi.rename}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `proposalId` | `string` | The id of the proposal to rename |\n * | `name` | `string` | The new display name (capped at 24 chars) |\n */\nexport const PluginCoreProposalsRenameArgs = z.object({\n proposalId: z.string(),\n name: z.string(),\n})\nexport type PluginCoreProposalsRenameArgs = z.infer<\n typeof PluginCoreProposalsRenameArgs\n>\n\n/** Result of {@linkcode PluginCoreProposalsApi.rename} — the updated record. */\nexport const PluginCoreProposalsRenameResult = PluginCoreProposalRef\nexport type PluginCoreProposalsRenameResult = z.infer<\n typeof PluginCoreProposalsRenameResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreProposalsApi.setActive}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `proposalId` | `string` | The id of the proposal to activate |\n */\nexport const PluginCoreProposalsSetActiveArgs = z.object({\n proposalId: z.string(),\n})\nexport type PluginCoreProposalsSetActiveArgs = z.infer<\n typeof PluginCoreProposalsSetActiveArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreProposalsApi.setActive} — the now-active\n * proposal's id.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | The id of the now-active proposal |\n */\nexport const PluginCoreProposalsSetActiveResult = z.object({\n id: z.string(),\n})\nexport type PluginCoreProposalsSetActiveResult = z.infer<\n typeof PluginCoreProposalsSetActiveResult\n>\n\n/**\n * Arguments for {@linkcode PluginCoreProposalsApi.delete}. Exactly one\n * disposition is required: `deleteObjects: true` XOR `targetProposalId` (a\n * **different** proposal). A bare delete with neither is rejected.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `proposalId` | `string` | The id of the proposal to delete |\n * | `deleteObjects` | `true \\| undefined` | Destroy the geometry in exclusive buckets (mutually exclusive with `targetProposalId`) |\n * | `targetProposalId` | `string \\| undefined` | Transfer this proposal's buckets here before deleting (must differ from `proposalId`) |\n */\nexport const PluginCoreProposalsDeleteArgs = z\n .object({\n proposalId: z.string(),\n deleteObjects: z.boolean().optional(),\n targetProposalId: z.string().optional(),\n })\n .refine((v) => (v.deleteObjects === true) !== (v.targetProposalId != null), {\n message:\n \"Provide exactly one disposition: deleteObjects: true (destroy exclusive-bucket geometry) or targetProposalId (transfer buckets, then delete)\",\n })\n .refine(\n (v) => v.targetProposalId == null || v.targetProposalId !== v.proposalId,\n {\n message: \"targetProposalId must be a different proposal than proposalId\",\n },\n )\nexport type PluginCoreProposalsDeleteArgs = z.infer<\n typeof PluginCoreProposalsDeleteArgs\n>\n\n/**\n * Result of {@linkcode PluginCoreProposalsApi.delete} — confirmation that the\n * proposal was deleted.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `deleted` | `true` | Always `true` on success |\n */\nexport const PluginCoreProposalsDeleteResult = z.object({\n deleted: z.literal(true),\n})\nexport type PluginCoreProposalsDeleteResult = z.infer<\n typeof PluginCoreProposalsDeleteResult\n>\n","import { PluginMathApi } from \"./math\"\nimport { PluginGeomApi } from \"./geom\"\nimport { PluginHistoryApi } from \"./history\"\nimport { PluginUnitsApi } from \"./units\"\nimport { PluginZoomApi } from \"./zoom\"\nimport { PluginProjectApi } from \"./project\"\nimport { PluginCommentApi } from \"./comment\"\nimport { PluginCoreGroupsApi } from \"./groups\"\nimport { PluginCoreBuildingsApi } from \"./buildings\"\nimport { PluginCoreLayersApi } from \"./layers\"\nimport { PluginCoreTagsApi } from \"./tags\"\nimport { PluginCoreIoApi } from \"./io\"\nimport { PluginCoreHandlesApi } from \"./handles\"\nimport { PluginCoreProposalsApi } from \"./proposals\"\nimport { PluginCameraApi } from \"./camera\"\nimport { PluginCoreStoreysApi } from \"./storeys\"\nimport { PluginCoreModeApi } from \"./mode\"\nimport { PluginUserApi } from \"./user\"\n\n/**\n * Core primitives and infrastructure used across the plugin API.\n *\n * - {@linkcode PluginCoreApi.math} — Vector and quaternion operations\n * - {@linkcode PluginCoreApi.geom} — Geometry primitives (lines, arcs, curves, profiles)\n * - {@linkcode PluginCoreApi.history} — Undo / redo of committed history entries\n * - {@linkcode PluginCoreApi.units} — Unit-type queries and conversion\n * - {@linkcode PluginCoreApi.zoom} — Camera zoom controls (extents / selection)\n * - {@linkcode PluginCoreApi.camera} — Camera controls — position, standard views, 2D/3D mode\n * - {@linkcode PluginCoreApi.user} — User locale — timezone & language\n * - {@linkcode PluginCoreApi.project} — Project-level settings and info\n * - {@linkcode PluginCoreApi.comment} — Comments: create, edit, resolve, tag, list\n * - {@linkcode PluginCoreApi.groups} — Read & edit the group hierarchy\n * - {@linkcode PluginCoreApi.buildings} — Read & edit buildings and their storeys\n * - {@linkcode PluginCoreApi.storeys} — Create, read & edit storeys (floors)\n * - {@linkcode PluginCoreApi.layers} — Read & toggle storey drawing/reference layers\n * - {@linkcode PluginCoreApi.tags} — Tag catalog + assignment\n * - {@linkcode PluginCoreApi.io} — Import files (image/pdf/cad/model/terrain), poll jobs, manage underlays\n * - {@linkcode PluginCoreApi.handles} — Handle lifecycle: release, releaseAll, scopes\n * - {@linkcode PluginCoreApi.proposals} — Design options: read, create, rename, switch, delete\n */\nexport abstract class PluginCoreApi {\n /** Vector and quaternion math utilities. See {@linkcode PluginMathApi}. */\n public abstract math: PluginMathApi\n /** Geometry primitives and constructors. See {@linkcode PluginGeomApi}. */\n public abstract geom: PluginGeomApi\n /** Undo / redo of committed history entries. See {@linkcode PluginHistoryApi}. */\n public abstract history: PluginHistoryApi\n /** Unit-type queries and conversion. See {@linkcode PluginUnitsApi}. */\n public abstract units: PluginUnitsApi\n /** Camera zoom controls. See {@linkcode PluginZoomApi}. */\n public abstract zoom: PluginZoomApi\n /** Camera controls — position, standard views, 2D/3D mode. See {@linkcode PluginCameraApi}. */\n public abstract camera: PluginCameraApi\n /** User locale — timezone & language. See {@linkcode PluginUserApi}. */\n public abstract user: PluginUserApi\n /** Project-level settings and info. See {@linkcode PluginProjectApi}. */\n public abstract project: PluginProjectApi\n /** Comments — create, edit, resolve, tag, list. See {@linkcode PluginCommentApi}. */\n public abstract comment: PluginCommentApi\n /** Groups — read & edit the group hierarchy. See {@linkcode PluginCoreGroupsApi}. */\n public abstract groups: PluginCoreGroupsApi\n /** Buildings — read & edit buildings and their storeys. See {@linkcode PluginCoreBuildingsApi}. */\n public abstract buildings: PluginCoreBuildingsApi\n /** Storeys — create, read & edit storeys (floors). See {@linkcode PluginCoreStoreysApi}. */\n public abstract storeys: PluginCoreStoreysApi\n /** Application mode — read & switch the editor's top-bar mode. See {@linkcode PluginCoreModeApi}. */\n public abstract mode: PluginCoreModeApi\n /** Layers — read & toggle storey drawing/reference layers. See {@linkcode PluginCoreLayersApi}. */\n public abstract layers: PluginCoreLayersApi\n /** Tags — tag catalog + assignment. See {@linkcode PluginCoreTagsApi}. */\n public abstract tags: PluginCoreTagsApi\n /** I/O — file import, import jobs, and underlay management. See {@linkcode PluginCoreIoApi}. */\n public abstract io: PluginCoreIoApi\n /** Handle lifecycle — release, releaseAll, and mint-recording scopes. See {@linkcode PluginCoreHandlesApi}. */\n public abstract handles: PluginCoreHandlesApi\n /** Proposals — design options: read, create, rename, switch, delete. See {@linkcode PluginCoreProposalsApi}. */\n public abstract proposals: PluginCoreProposalsApi\n\n constructor() {}\n}\n\nexport * from \"./math\"\nexport * from \"./geom\"\nexport * from \"./history\"\nexport * from \"./units\"\nexport * from \"./zoom\"\nexport * from \"./camera\"\nexport * from \"./user\"\nexport * from \"./project\"\nexport * from \"./comment\"\nexport * from \"./groups\"\nexport * from \"./buildings\"\nexport * from \"./storeys\"\nexport * from \"./mode\"\nexport * from \"./layers\"\nexport * from \"./tags\"\nexport * from \"./io\"\nexport * from \"./handles\"\nexport * from \"./proposals\"\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport {\n BrepHandle,\n ComponentHandle,\n ContourHandle,\n CurveHandle,\n ProfileHandle,\n Vec3Handle,\n} from \"../../../handles\"\nimport { PluginGridStyleOptions } from \"./grids\"\nimport {\n PluginSpaceType,\n PluginMassType,\n PluginDepartmentId,\n} from \"../../entity/space\"\nimport {\n PluginBuildableEnvelopePolygonVertex,\n PluginBuildableEnvelopeSetbackTier,\n PluginBuildableEnvelopeVerticalCap,\n PluginBuildableEnvelopeCreateResult,\n} from \"../../entity/buildableEnvelope\"\nimport { PluginOpeningBaseOptions } from \"./opening-fields\"\nimport type {\n PluginCreateDoorItem,\n PluginCreateFloorItem,\n PluginCreateFurnitureItem,\n PluginCreateWallRunItem,\n PluginCreateWindowItem,\n} from \"./bulk-items\"\n\n/**\n * `design.create.*` — author new scene-committed BIM entities.\n *\n * Every creator takes geometry handles + scalars and returns a\n * {@linkcode ComponentHandle} (or `ComponentHandle[]` for plural creators;\n * `wallRuns` returns one `ComponentHandle[]` per run) — the\n * `Component.id` of the created entity, resolvable across the rest of the\n * `design.*` surface. Creation is an undoable host call; it **throws** on failure\n * (no `Result` wrapper — consistent with `core.geom.create.*`).\n *\n * Footprint objects take a {@linkcode ContourHandle} (outer profile + holes);\n * build one with `snaptrude.core.geom.create.contourFromProfile(s)`.\n *\n * `door`/`window` place a catalog item into a host wall (the world point is\n * projected onto the wall face to locate the opening; the wall is re-cut in one\n * undo entry) and throw if the point projects outside the wall. `smartLayout` is\n * the one creator returning a grouped {@linkcode PluginSmartLayoutResult} rather\n * than a handle — it clones an in-scene template into one or more target spaces,\n * merging every target into a single undo batch (see its JSDoc).\n *\n * Accessed via `snaptrude.design.create`.\n */\nexport abstract class PluginDesignCreateApi {\n constructor() {}\n\n /**\n * Create a **space** (a room / mass) by extruding a footprint contour upward.\n *\n * The `contour` is the bottom footprint (outer profile + optional holes); it\n * is extruded by `height` along +Y. The space is placed on the active story\n * and assigned a default department unless overridden.\n *\n * @param contour - Bottom footprint (outer + holes)\n * @param height - Extrusion height (Snaptrude units, > 0)\n * @param label - Room label (maps to the space's `room_type`)\n * @param position - Offset from origin (default origin)\n * @param spaceType - Space-type classification (default `Room`)\n * @param massType - Mass-type classification (default `Room`)\n * @param departmentId - Department assignment (default department)\n * @param storey - Target storey number to place the space on — the same integer\n * `entity.story` uses (`1` ground, `2` first floor, `-1` basement). Default:\n * the active storey. Folds a `core.storeys.setActive` call into the create,\n * placing the space at that storey's floor elevation (any `position` offset is\n * applied on top). Throws if no storey has that value.\n * @returns the {@linkcode ComponentHandle} of the created space\n * @throws if the contour is invalid, the height is not positive, or the target\n * storey does not exist\n *\n * @examplePrompt Create a room called Living Room that's 3m tall from this outline\n * @examplePrompt Add a new space on the current storey using this footprint\n * @examplePrompt Make a bedroom here and assign it to the Residential department\n * @examplePrompt Extrude this closed outline into a 10 foot high room\n * @examplePrompt Can you create a Kitchen space from the sketch I just drew?\n * @examplePrompt Create a bedroom on the second floor from this outline\n *\n * @performance For MORE THAN ONE space, call `design.create.spaces(items[])` — the whole\n * batch is one host round-trip. Looping this single-space creator is N round-trips (slow,\n * and a large loop can trip the plugin rate limit).\n *\n * # Example\n * ```ts\n * const v = snaptrude.core.math.vec3\n * const outer = await snaptrude.core.geom.create.profileFromLinePoints([\n * await v.new(0, 0, 0),\n * await v.new(10, 0, 0),\n * await v.new(10, 0, 8),\n * await v.new(0, 0, 8),\n * ])\n * const contour = await snaptrude.core.geom.create.contourFromProfile(outer)\n * const space = await snaptrude.design.create.space(contour, 3, \"Living\")\n * // …place one directly on the second floor (no setActive dance):\n * const upstairs = await snaptrude.design.create.space(\n * contour, 3, \"Bedroom\", undefined, undefined, undefined, undefined, 2,\n * )\n * ```\n */\n public abstract space(\n contour: ContourHandle,\n height: number,\n label?: string,\n position?: Vec3Handle,\n spaceType?: PluginSpaceType,\n massType?: PluginMassType,\n departmentId?: PluginDepartmentId,\n storey?: number,\n ): PluginApiReturn<ComponentHandle>\n\n /**\n * Create a **mass** — a generic extruded prism (default `massType` \"Generic mass\").\n * Same footprint-extrude path as {@linkcode PluginDesignCreateApi.space}.\n *\n * @param contour - Footprint (outer + holes)\n * @param height - Extrusion height (> 0)\n * @param label - Label (maps to `room_type`)\n * @param position - Offset from origin\n * @param massType - Mass type (default `Generic mass`)\n * @returns the {@linkcode ComponentHandle} of the created mass\n * @throws if the contour is invalid or the height is not positive\n *\n * @examplePrompt Create a generic mass 30m tall from this footprint\n * @examplePrompt Block out a conceptual massing volume for the site\n * @examplePrompt Extrude this shape into a building mass\n * @examplePrompt Add a simple study-model volume at the origin\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(20, 15)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const tower = await snaptrude.design.create.mass(contour, 30, \"Tower A\", undefined, \"Building\")\n * ```\n */\n public abstract mass(\n contour: ContourHandle,\n height: number,\n label?: string,\n position?: Vec3Handle,\n massType?: PluginMassType,\n ): PluginApiReturn<ComponentHandle>\n\n /**\n * Create a **mass** from a custom solid built with a `core.geom.create` brep\n * constructor (`brepFromFaces`, `brepFromExtrusion`, `brepFromLoft`,\n * `brepFromMesh`, or a brep boolean) — the free-form counterpart of\n * {@linkcode PluginDesignCreateApi.mass}, for solids that are not simple\n * footprint extrusions (pyramids, wedges, faceted volumes).\n *\n * Only accepts breps **minted by a `core.geom.create` brep constructor** —\n * scene-derived breps from `design.query.geometry.getBrep` are read-only and\n * throw. The mass is created as a generic mass (`massType` \"Generic mass\") at\n * the exact coordinates the solid was authored in, committed as a single\n * undo step, and synced to collaborators like any other creator.\n *\n * @param brep - A solid from a `core.geom.create` brep constructor\n * @param label - Label (maps to `room_type`)\n * @returns the {@linkcode ComponentHandle} of the created mass\n * @throws if the brep handle is invalid, or the brep was not created by a\n * `core.geom.create` brep constructor\n *\n * @examplePrompt Create a mass from these faces\n * @examplePrompt Turn this custom brep into a scene mass\n * @examplePrompt Add a pyramid-shaped mass to the model\n *\n * # Example\n * ```ts\n * const apex = { x: 0, y: 4, z: 0 }\n * const a = { x: -2, y: 0, z: -2 }\n * const b = { x: 2, y: 0, z: -2 }\n * const c = { x: 2, y: 0, z: 2 }\n * const d = { x: -2, y: 0, z: 2 }\n * const brep = await snaptrude.core.geom.create.brepFromFaces([\n * [a, d, c, b],\n * [a, b, apex],\n * [b, c, apex],\n * [c, d, apex],\n * [d, a, apex],\n * ])\n * const pyramid = await snaptrude.design.create.massFromBrep(brep, \"Pavilion\")\n * ```\n */\n public abstract massFromBrep(\n brep: BrepHandle,\n label?: string,\n ): PluginApiReturn<ComponentHandle>\n\n /**\n * Create a **slab** by extruding a footprint contour by `thickness`\n * (default direction: down).\n *\n * @param contour - Footprint (outer + holes)\n * @param thickness - Slab thickness (> 0)\n * @param direction - Extrusion direction (default `\"down\"`)\n * @param slabType - Slab-type classification\n * @returns the {@linkcode ComponentHandle} of the created slab\n * @throws if the contour is invalid or the thickness is not positive\n *\n * @examplePrompt Add a 150mm thick slab under this footprint\n * @examplePrompt Create a floor slab from this outline\n * @examplePrompt Pour a plinth slab over the building footprint\n * @examplePrompt Make a basement slab that extrudes downward\n * @examplePrompt Give this room outline a 6 inch slab\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(12, 10)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const slab = await snaptrude.design.create.slab(contour, 0.15, \"down\", \"Plinth\")\n * ```\n */\n public abstract slab(\n contour: ContourHandle,\n thickness: number,\n direction?: \"up\" | \"down\",\n slabType?: PluginSlabType,\n ): PluginApiReturn<ComponentHandle>\n\n /**\n * Create a **floor** by extruding a footprint contour by `thickness`\n * (extruded upward).\n *\n * @param contour - Footprint (outer + holes)\n * @param thickness - Floor thickness (> 0)\n * @param position - Offset from origin\n * @returns the {@linkcode ComponentHandle} of the created floor\n * @throws if the contour is invalid or the thickness is not positive\n *\n * @examplePrompt Create a floor from this room outline\n * @examplePrompt Add a 100mm thick floor over the footprint\n * @examplePrompt Lay flooring across the selected outline\n * @examplePrompt Put a floor at this position with 150mm thickness\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(5, 4)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const floor = await snaptrude.design.create.floor(contour, 0.1)\n * ```\n *\n * @performance For MORE THAN ONE floor, call `design.create.floors(items[])` — one\n * host round-trip and ONE undo entry for the whole batch. Looping this single-floor\n * creator is N round-trips and N undo entries.\n */\n public abstract floor(\n contour: ContourHandle,\n thickness: number,\n position?: Vec3Handle,\n ): PluginApiReturn<ComponentHandle>\n\n /**\n * Create **many floors** in one undoable operation (bulk plural of\n * {@linkcode floor}). Each item extrudes its own footprint contour (outer\n * profile + holes) upward by its own `thickness`, at its own optional\n * position offset. Validate-all-or-throw; one command.\n *\n * @param items - One {@linkcode PluginCreateFloorItem} per floor to create\n * (≥1, ≤1000)\n * @returns the created floors as {@linkcode ComponentHandle}`[]`, in input order\n * @throws `VALIDATION` if the items fail the schema (empty array, more than\n * 1000 items, a non-positive thickness) or a contour is degenerate;\n * `HANDLE_INVALID` if a `contour` or `position` handle is unknown or\n * released; `OPERATION_FAILED` if extrusion fails (nothing is created —\n * `details.itemIndex` is the failing item); `METHOD_NOT_PERMITTED` if the\n * plugin may not write.\n *\n * @examplePrompt Create floors for all these room outlines at once\n * @examplePrompt Add a floor to every space in one operation\n * @examplePrompt Bulk create the floor plates for this building\n * @examplePrompt Lay 100mm floors across these five footprints in one undo step\n * @examplePrompt Create the ground and first floor slabs together\n *\n * @performance Bulk creator — the whole batch is ONE host round-trip and ONE undo\n * entry. Always prefer this over calling `design.create.floor` in a loop: build the\n * full `items[]` array first (all contours and offsets up front), then make one call.\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(5, 4)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const [ground, upper] = await snaptrude.design.create.floors([\n * { contour, thickness: 0.1 },\n * { contour, thickness: 0.1, position: await snaptrude.core.math.vec3.new(0, 3, 0) },\n * ])\n * ```\n */\n public abstract floors(\n items: PluginCreateFloorItem[],\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * Create a **roof** by extruding a footprint contour by `thickness`. Created\n * flat (extruded downward); pitch/slope is a separate post-creation edit.\n *\n * @param contour - Footprint (outer + holes)\n * @param thickness - Roof-slab thickness (> 0)\n * @returns the {@linkcode ComponentHandle} of the created roof\n * @throws if the contour is invalid or the thickness is not positive\n *\n * @examplePrompt Add a flat roof over the building footprint\n * @examplePrompt Create a roof 200mm thick from this outline\n * @examplePrompt Cover the top storey with a roof slab\n * @examplePrompt Put a roof on this building\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(12, 10)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const roof = await snaptrude.design.create.roof(contour, 0.2)\n * ```\n */\n public abstract roof(\n contour: ContourHandle,\n thickness: number,\n ): PluginApiReturn<ComponentHandle>\n\n /**\n * Create a **ceiling** by extruding a footprint contour by `thickness`, lifted\n * `heightFromFloor` above the floor.\n *\n * @param contour - Footprint (outer + holes)\n * @param thickness - Ceiling thickness (> 0)\n * @param heightFromFloor - Lift above the floor (default 0)\n * @returns the {@linkcode ComponentHandle} of the created ceiling\n * @throws if the contour is invalid or the thickness is not positive\n *\n * @examplePrompt Add a false ceiling 2.7m above the floor\n * @examplePrompt Create a ceiling over this room footprint\n * @examplePrompt Put a dropped ceiling in the living room at 3 metres\n * @examplePrompt Add a 50mm thick ceiling lifted 2800mm from floor level\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(5, 4)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const ceiling = await snaptrude.design.create.ceiling(contour, 0.05, 2.8)\n * ```\n */\n public abstract ceiling(\n contour: ContourHandle,\n thickness: number,\n heightFromFloor?: number,\n ): PluginApiReturn<ComponentHandle>\n\n /**\n * Create a **column** by extruding a cross-section contour upward by `height`\n * from a base `position`. The column is built to EXACTLY `height` — unlike\n * the interactive draw tool, no slab-thickness deduction is applied.\n *\n * @param position - Base point\n * @param crossSection - Column cross-section footprint\n * @param height - Column height (> 0), built exactly\n * @param directionUp - Up axis (default world up); normalized by the host, so\n * magnitude does not scale the height\n * @returns the {@linkcode ComponentHandle} of the created column\n * @throws if the cross-section is invalid, the height is not positive, or the\n * direction is zero / lies in the cross-section plane (degenerate extrusion)\n *\n * @examplePrompt Add a 3m tall column at this point\n * @examplePrompt Place a column at the grid intersection\n * @examplePrompt Create a 450mm square concrete column 4 metres high\n * @examplePrompt Stand a column up from this base point\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(0.45, 0.45)\n * const crossSection = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const base = await snaptrude.core.math.vec3.new(4, 0, 4)\n * const column = await snaptrude.design.create.column(base, crossSection, 3)\n * ```\n */\n public abstract column(\n position: Vec3Handle,\n crossSection: ContourHandle,\n height: number,\n directionUp?: Vec3Handle,\n ): PluginApiReturn<ComponentHandle>\n\n /**\n * Create a **beam** by extruding a cross-section contour along `direction` by\n * `length`. Matches the Snaptrude beam tool's semantics: the beam body hangs\n * BELOW the plane the section is authored on.\n *\n * For a **horizontal** `direction` (the common spanning beam), author the\n * section as an axis-aligned plan rectangle: its X extent is the beam's\n * width, its Z extent is the beam's depth (the vertical drop). The beam runs\n * `length` along `direction` starting at the section's centre, hanging below\n * the authoring plane by its depth — exactly like an interactively drawn\n * beam. Only axis-aligned rectangular sections are supported horizontally.\n *\n * For a **vertical** `direction` (the default), the section itself is\n * extruded by `length`, again hanging below the authoring plane. Diagonal\n * directions are rejected.\n *\n * @param section - Beam cross-section, an axis-aligned rectangle in plan\n * (X extent = width, Z extent = depth)\n * @param length - Beam length along `direction` (> 0); direction is\n * normalized by the host, so magnitude does not scale the length\n * @param direction - Beam axis: horizontal for a spanning beam, or vertical\n * (default world up). Diagonal directions throw.\n * @returns the {@linkcode ComponentHandle} of the created beam\n * @throws if the section is invalid (horizontal beams: not an axis-aligned\n * plan rectangle), the length is not positive, or the direction is zero or\n * diagonal\n *\n * @examplePrompt Add a beam spanning 6 metres between the columns\n * @examplePrompt Create a horizontal beam along the X axis\n * @examplePrompt Run a 300x600 beam across the room\n * @examplePrompt Extrude this cross-section into a 5m long beam\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(0.3, 0.6)\n * const section = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const alongX = await snaptrude.core.math.vec3.new(1, 0, 0)\n * const beam = await snaptrude.design.create.beam(section, 6, alongX)\n * ```\n */\n public abstract beam(\n section: ContourHandle,\n length: number,\n direction?: Vec3Handle,\n ): PluginApiReturn<ComponentHandle>\n\n /**\n * Create a connected run of **walls** from a profile — one wall per curve in\n * the profile's chain, with mitred corners at shared endpoints.\n *\n * @param profile - Ordered curve chain (wall centerlines)\n * @param height - Wall height (default engine default)\n * @param thickness - Wall thickness (default: the `wallType`'s total layer\n * thickness when `wallType` is given, else the engine default)\n * @param wallType - Wall type to build with — the same searchable list the\n * Draw tab's Wall Type dropdown offers. Pass a name from\n * `design.types.list(\"wall\")` (the summary's `label`, or its `\"wall:…\"` id).\n * Drives the wall's construction layers and default material, and — when\n * `thickness` is omitted — its total layer thickness. Default: the engine's\n * generic wall.\n * @param storey - Target storey (integer: `1` ground, `2` first floor, `-1`\n * basement — same convention as `design.create.space`). The walls are\n * assigned to it and lifted to its base elevation, all inside the same\n * single undo entry. The storey must already exist\n * (`core.storeys.create` first). Default: geometry-derived — a profile\n * drawn at y = 0 lands on storey 1; the ACTIVE storey is NOT consulted.\n * @returns a {@linkcode ComponentHandle}`[]` (one per wall), in profile order\n * @throws if the profile is empty, `wallType` names no wall type in the\n * project, `storey` does not exist, or wall creation fails\n *\n * @examplePrompt Draw walls along this centerline\n * @examplePrompt Create 3m high, 200mm thick walls from these points\n * @examplePrompt Build a wall run around the room outline\n * @examplePrompt Turn this polyline sketch into walls\n * @examplePrompt Add walls with mitred corners along the path\n * @examplePrompt Draw brick walls with plaster along this outline\n * @examplePrompt Build the perimeter in exposed concrete walls\n *\n * # Example\n * ```ts\n * const v = snaptrude.core.math.vec3\n * const centerlines = await snaptrude.core.geom.create.profileFromLinePoints([\n * await v.new(0, 0, 0),\n * await v.new(8, 0, 0),\n * await v.new(8, 0, 6),\n * ])\n * const walls = await snaptrude.design.create.walls(centerlines, 3, 0.2)\n * // …built as a specific wall type (layers/material/thickness from the type):\n * const [brick] = await snaptrude.design.types.list(\"wall\")\n * const brickWalls = await snaptrude.design.create.walls(\n * centerlines, 3, undefined, brick.label,\n * )\n * ```\n *\n * @performance For MORE THAN ONE run, call `design.create.wallRuns(items[])` — one\n * host round-trip and ONE undo entry for every run in the batch. Looping this\n * single-run creator is N round-trips and N undo entries.\n */\n public abstract walls(\n profile: ProfileHandle,\n height?: number,\n thickness?: number,\n wallType?: string,\n storey?: number,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * Create **many wall runs** in one undoable operation (bulk plural of\n * {@linkcode walls}). Each item is one full run — a wall per curve in that\n * item's profile chain, mitred at shared endpoints. Junctions resolve within a\n * run, not between runs. Each item carries its own\n * dimensions, `wallType` and `storey`, so a single call can build a whole\n * floor plate or several storeys at once. Validate-all-or-throw; one command.\n *\n * @param items - One {@linkcode PluginCreateWallRunItem} per run to create\n * (≥1, ≤1000)\n * @returns one {@linkcode ComponentHandle}`[]` per item, in input order —\n * each inner array holding that run's walls in profile-curve order\n * @throws `VALIDATION` if the items fail the schema (empty array, more than\n * 1000 items, a non-positive height or thickness, a zero-length curve);\n * `HANDLE_INVALID` if a `profile` handle is unknown or released;\n * `PRECONDITION_FAILED` if a profile has no curves, `wallType` names no\n * wall type in the project, or `storey` does not exist;\n * `OPERATION_FAILED` if wall creation fails (nothing is created —\n * `details.itemIndex` is the failing item); `METHOD_NOT_PERMITTED` if the\n * plugin may not write.\n *\n * @examplePrompt Draw all the walls of this floor plan at once\n * @examplePrompt Build every room's perimeter in one operation\n * @examplePrompt Bulk create wall runs from these centerlines\n * @examplePrompt Create the walls for both storeys in a single undo step\n * @examplePrompt Turn these polylines into 200mm brick walls together\n *\n * @performance Bulk creator — the whole batch is ONE host round-trip and ONE undo\n * entry. Always prefer this over calling `design.create.walls` in a loop: build the\n * full `items[]` array first (all profiles up front), then make one call.\n *\n * # Example\n * ```ts\n * const v = snaptrude.core.math.vec3\n * const ground = await snaptrude.core.geom.create.profileFromLinePoints([\n * await v.new(0, 0, 0),\n * await v.new(8, 0, 0),\n * await v.new(8, 0, 6),\n * ])\n * const upper = await snaptrude.core.geom.create.profileFromLinePoints([\n * await v.new(0, 0, 0),\n * await v.new(8, 0, 0),\n * ])\n * const [groundWalls, upperWalls] = await snaptrude.design.create.wallRuns([\n * { profile: ground, height: 3, thickness: 0.2 },\n * { profile: upper, height: 3, storey: 2 },\n * ])\n * console.log(groundWalls.length, \"walls on the ground floor\")\n * ```\n */\n public abstract wallRuns(\n items: PluginCreateWallRunItem[],\n ): PluginApiReturn<ComponentHandle[][]>\n\n /**\n * Create a **staircase** from a parametric preset, placed at a point.\n *\n * Geometry is procedurally generated from the chosen `preset`; there is no\n * footprint input. Placed on the active structure/story unless overridden.\n * The promise resolves only after the staircase's asynchronous geometry union\n * and creation-command capture settle; a union failure rejects the call.\n *\n * @param preset - The parametric preset to generate\n * @param position - Placement point\n * @param label - Optional label\n * @param structureId - Target structure (default active)\n * @param level - Target level name (default active/`\"01\"`)\n * @param dimensions - Optional positive dimension overrides in Snaptrude engine\n * units, plus a placement angle in 90-degree increments. Overrides are\n * applied to the staircase parameters before the creation command runs.\n * @returns the {@linkcode ComponentHandle} of the created staircase\n * @throws if the preset is unknown or placement fails\n *\n * @examplePrompt Add a dog-legged staircase at this location\n * @examplePrompt Place a straight stair on level 01\n * @examplePrompt Create an L-shaped staircase in the lobby\n * @examplePrompt Insert a staircase near the entrance\n * @examplePrompt Put amphitheatre steps at these coordinates\n *\n * # Example\n * ```ts\n * const stair = await snaptrude.design.create.staircase(\n * \"dogLegged\",\n * await snaptrude.core.math.vec3.new(5, 0, 5),\n * )\n * ```\n */\n public abstract staircase(\n preset: PluginStaircasePreset,\n position: Vec3Handle,\n label?: string,\n structureId?: string,\n level?: string,\n dimensions?: PluginStaircaseDimensions,\n ): PluginApiReturn<ComponentHandle>\n\n /**\n * Create **reference lines** from a profile — one reference line per curve in\n * the profile's ordered chain. Returns a handle per created line.\n *\n * Optional style (`color`, `thickness`, `style`, `gridTag`) applies to\n * every created line.\n *\n * @param profile - Ordered curve chain (centerlines)\n * @param color - Hex color `\"#RRGGBB\"` (default white)\n * @param thickness - Line thickness (default 1)\n * @param style - Line style (default `SOLID`)\n * @param gridTag - Grid tag label (default `\"A\"`)\n * @returns a {@linkcode ComponentHandle}`[]` (one per curve), in profile order\n * @throws if the profile is empty or creation fails\n *\n * @examplePrompt Draw a reference line between these two points\n * @examplePrompt Add dashed guide lines along this path\n * @examplePrompt Create grid lines tagged A through the plan\n * @examplePrompt Set up construction lines for the column grid\n * @examplePrompt Mark a red reference line across the site\n *\n * # Example\n * ```ts\n * const v = snaptrude.core.math.vec3\n * const profile = await snaptrude.core.geom.create.profileFromLinePoints([\n * await v.new(0, 0, 0),\n * await v.new(10, 0, 0),\n * ])\n * const lines = await snaptrude.design.create.referenceLines(\n * profile,\n * undefined,\n * undefined,\n * \"DASHED\",\n * )\n * ```\n */\n public abstract referenceLines(\n profile: ProfileHandle,\n color?: string,\n thickness?: number,\n style?: PluginReferenceLineStyle,\n gridTag?: string,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * Create a free rectangular **structural grid** — one grid group with two\n * perpendicular line families (`linesX` lines spaced `spacingX` apart, and\n * `linesY` lines spaced `spacingY` apart) on the active storey. One undoable\n * creation.\n *\n * Units: spacings in Snaptrude units; `angle` in degrees (CCW about +Y,\n * default 0). Without `center` the grid is centred on the current viewport\n * (the native tool's behavior) — pass `center` for deterministic placement.\n *\n * @param linesX - Line count of the first family (integer ≥ 1)\n * @param linesY - Line count of the second family (integer ≥ 1)\n * @param spacingX - First-family spacing (Snaptrude units, > 0)\n * @param spacingY - Second-family spacing (Snaptrude units, > 0)\n * @param options - `angle`, `center`, and {@linkcode PluginGridStyleOptions}\n * @returns the created grid group's {@linkcode ComponentHandle}\n *\n * @examplePrompt Create a 5 by 8 structural grid with 6 metre spacing\n * @examplePrompt Add a column grid rotated 30 degrees\n * @examplePrompt Set up a structural grid at the site origin\n *\n * # Example\n * ```ts\n * const centre = await snaptrude.core.math.vec3.new(0, 0, 0)\n * const grid = await snaptrude.design.create.grid(5, 8, 6, 6, { center: centre })\n * ```\n */\n public abstract grid(\n linesX: number,\n linesY: number,\n spacingX: number,\n spacingY: number,\n options?: {\n angle?: number\n center?: Vec3Handle\n } & PluginGridStyleOptions,\n ): PluginApiReturn<ComponentHandle>\n\n /**\n * Create a **structural grid from a single curve** — one grid group holding\n * one wing with one line (the draw tool's output). The curve may be a line\n * or an arc; circles are rejected with `VALIDATION`. Undoable.\n *\n * @param curve - The grid line's geometry ({@linkcode CurveHandle}, world space)\n * @param options - `tag` (defaults to the draw tool's next tag) and\n * {@linkcode PluginGridStyleOptions}\n * @returns the created grid group's {@linkcode ComponentHandle}\n *\n * @examplePrompt Draw a grid line from A to B\n * @examplePrompt Add a curved grid line through these points\n * @examplePrompt Create grid line \"C\" along this wall\n *\n * # Example\n * ```ts\n * const { vec3 } = snaptrude.core.math\n * const from = await vec3.new(0, 0, 0)\n * const to = await vec3.new(30, 0, 0)\n * const curve = await snaptrude.core.geom.create.line(from, to)\n * const grid = await snaptrude.design.create.gridFromCurve(curve, { tag: \"A\" })\n * ```\n */\n public abstract gridFromCurve(\n curve: CurveHandle,\n options?: {\n tag?: string\n } & PluginGridStyleOptions,\n ): PluginApiReturn<ComponentHandle>\n\n /**\n * Generate **structural grids fitted to mass footprints** — one grid group\n * per footprint cluster, with counts and orientation derived from each\n * footprint's geometry and the given spacings. All created groups commit as\n * ONE undoable unit.\n *\n * Lenient like the native tool: masses without an extractable footprint\n * (no B-rep, degenerate top face) are skipped; throws `VALIDATION` only when\n * NONE of the given masses yields a footprint.\n *\n * @param masses - Masses whose footprints seat the grids\n * @param spacingX - First-family spacing (Snaptrude units, > 0)\n * @param spacingY - Second-family spacing (Snaptrude units, > 0)\n * @param options - {@linkcode PluginGridStyleOptions}\n * @returns the created grid groups' {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt Generate grids for the selected masses\n * @examplePrompt Auto-generate a 6m structural grid on this building\n * @examplePrompt Fit column grids to every mass on this storey\n *\n * # Example\n * ```ts\n * const masses = await snaptrude.design.query.listMasses({ storeys: [1] })\n * const grids = await snaptrude.design.create.gridsFromMasses(masses, 6, 6)\n * ```\n */\n public abstract gridsFromMasses(\n masses: ComponentHandle[],\n spacingX: number,\n spacingY: number,\n options?: PluginGridStyleOptions,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * Create **columns at every grid-line intersection** of the given grid\n * groups, sized `width` × `depth` and oriented to the grid axes, on the\n * grids' storey. All columns commit as ONE undoable unit.\n *\n * @param grids - Grid groups whose intersections seat the columns\n * @param width - Column width (Snaptrude units, > 0)\n * @param depth - Column depth (Snaptrude units, > 0)\n * @returns the created columns' {@linkcode ComponentHandle}`[]`\n * @throws `VALIDATION` when an id does not resolve to a grid group;\n * `OPERATION_FAILED` when the grids have no line intersections\n *\n * @examplePrompt Generate columns on this grid\n * @examplePrompt Place 450mm square columns at every grid intersection\n *\n * # Example\n * ```ts\n * const grids = await snaptrude.design.query.listGrids()\n * const columns = await snaptrude.design.create.columnsFromGrids(grids, 0.45, 0.45)\n * ```\n */\n public abstract columnsFromGrids(\n grids: ComponentHandle[],\n width: number,\n depth: number,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * Place a **furniture** item from the project library at a world position.\n *\n * Identified by a **catalog (library) id** — the team object's stable `_id` for\n * project uploads, or the item's `fullName` for the general library. Discover ids\n * with {@linkcode PluginDesignFurnitureApi.listCatalog}. Placement is asynchronous\n * (the source mesh is fetched if not cached) and creates one undo entry.\n *\n * Placed at the item's default rotation unless `angleInDegrees` is given, which\n * rotates it about the vertical axis at creation time (baked into the same undo\n * entry). To rotate an existing instance instead, use `design.transform.rotate`.\n *\n * @param catalogId - Library id: team `_id` or general `fullName`\n * @param position - Absolute world placement point. `position.y` is the REST\n * elevation: the item is grounded so its bounding-box base sits exactly at\n * `position.y` (the same surface-flush contract as interactive drag-drop) —\n * pass the floor/storey elevation to stand furniture on it; never add half\n * the item's height yourself.\n * @param options - Optional placement options: `label` — instance name and\n * readable label (`design.query.getLabel`; default auto `${name}Ins${n}`);\n * `createNewSourceMesh` — legacy flag, kept for compatibility. The host owns\n * source-mesh persistence: a catalog source is recorded exactly once, by the\n * first placement that brings it in, and this flag cannot skip that record.\n * @param angleInDegrees - Optional signed rotation about the vertical axis, in\n * degrees (same convention as {@linkcode PluginDesignTransformApi.rotate}).\n * Applied at creation time so it is part of the placement's single undo entry.\n * Default: the item's own (unrotated) orientation.\n * @returns the {@linkcode ComponentHandle} of the placed furniture instance\n * @throws if the catalog id is unknown, the source mesh fails to load, or placement fails;\n * `PRECONDITION_FAILED` (`details.engineCode: \"TOOL_ACTIVE\"`) while the interactive\n * furniture tool is active — finish or cancel it first\n *\n * @examplePrompt Place a chair from the library at this spot\n * @examplePrompt Add a sofa from the furniture catalog to the living room\n * @examplePrompt Drop a desk into the scene at 3, 0, 5\n * @examplePrompt Insert the uploaded bed model in the bedroom\n * @examplePrompt Put a table from my project library here and name it Table-01\n * @examplePrompt Place a chair here rotated 90 degrees\n *\n * # Example\n * ```ts\n * const chair = await snaptrude.design.create.furniture(\n * \"6620f1a…\", // from design.furniture.listCatalog()\n * await snaptrude.core.math.vec3.new(3, 0, 5),\n * { label: \"Chair-01\" },\n * )\n * // …placed already turned 90° about the vertical axis:\n * const turned = await snaptrude.design.create.furniture(\n * \"6620f1a…\",\n * await snaptrude.core.math.vec3.new(3, 0, 5),\n * undefined,\n * 90,\n * )\n * ```\n *\n * @performance For MORE THAN ONE item, call `design.create.furnitureItems(items[])` —\n * one host round-trip and ONE undo entry, and a catalog source shared by several\n * items is fetched once. Looping this single-item creator is N round-trips.\n */\n public abstract furniture(\n catalogId: string,\n position: Vec3Handle,\n options?: { label?: string; createNewSourceMesh?: boolean },\n angleInDegrees?: number,\n ): PluginApiReturn<ComponentHandle>\n\n /**\n * Place **many furniture items** in one undoable operation (bulk plural of\n * {@linkcode furniture}). Each item names a catalog id and an absolute world\n * position (`position.y` is the REST elevation — the same grounding contract\n * as the singular creator), with optional label and rotation. A catalog\n * source shared by several items is fetched and recorded once for the whole\n * batch, so the host — not the caller — owns source persistence (there is no\n * per-item `createNewSourceMesh`). Validate-all-or-throw; one command.\n *\n * @param items - One {@linkcode PluginCreateFurnitureItem} per instance to\n * place (≥1, ≤1000)\n * @returns the placed instances as {@linkcode ComponentHandle}`[]`, in input order\n * @throws `VALIDATION` if the items fail the schema (empty array, more than\n * 1000 items); `HANDLE_INVALID` if a `position` handle is unknown or\n * released; `PRECONDITION_FAILED` if a catalog id is unknown, a furniture\n * tool is active, or a parametric group is active (exit it first, or place\n * items one at a time with `furniture`); `OPERATION_FAILED` if a source\n * mesh fails to load or placement fails (nothing is created —\n * `details.itemIndex` is the failing item); `METHOD_NOT_PERMITTED` if the plugin may not write.\n *\n * @examplePrompt Place all the desks for this office at once\n * @examplePrompt Furnish every bedroom in one operation\n * @examplePrompt Bulk place these chairs around the table\n * @examplePrompt Add the whole furniture layout in a single undo step\n * @examplePrompt Drop twenty copies of this chair at these positions\n *\n * @performance Bulk creator — the whole batch is ONE host round-trip and ONE undo\n * entry, and each distinct catalog source loads once. Always prefer this over\n * calling `design.create.furniture` in a loop: build the full `items[]` array\n * first (all positions and angles up front), then make one call.\n *\n * # Example\n * ```ts\n * const v = snaptrude.core.math.vec3\n * const [chair] = await snaptrude.design.furniture.listCatalog()\n * const placed = await snaptrude.design.create.furnitureItems([\n * { catalogId: chair.id, position: await v.new(3, 0, 5), label: \"Chair-01\" },\n * { catalogId: chair.id, position: await v.new(4, 0, 5), angleInDegrees: 90 },\n * { catalogId: chair.id, position: await v.new(5, 0, 5), angleInDegrees: 180 },\n * ])\n * ```\n */\n public abstract furnitureItems(\n items: PluginCreateFurnitureItem[],\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * Place a **door** from the catalog into a host wall.\n *\n * Identified by a **catalog (library) id** — the team object's stable `_id`\n * or the general library's `fullName`. Discover ids with\n * {@linkcode PluginDesignDoorsApi.listCatalog}. The world `position` is\n * projected onto the host wall's face to locate the opening; the wall is\n * re-cut and the door hosted in a single undo entry. Placement is\n * asynchronous (the source mesh is fetched if not cached).\n *\n * @param catalogId - Library id: team `_id` or general `fullName`\n * @param hostWall - The wall to host the door\n * @param position - World point projected onto the wall to locate the opening\n * @param options - Optional placement options: `label` — instance name;\n * `hinge` — world point near the jamb the door is hinged on (the door is\n * reflected along the wall so its hinged jamb is the one nearer this\n * point; default the catalog item's authored hinge side)\n * @param facing - World point on the side of the wall the door swings open\n * to (the room it opens into — where the plan symbol draws the swing arc).\n * Any point clearly on that side works (e.g. the room's center). Default:\n * the engine picks a side (nondeterministic when `position` sits on the\n * wall centerline).\n * @returns the {@linkcode ComponentHandle} of the placed door\n * @throws if the catalog id is unknown, the host is not a wall, the source\n * mesh fails to load, or the projected point falls **outside** the host wall\n *\n * @examplePrompt Add a door to this wall here\n * @examplePrompt Place a sliding door on the selected wall\n * @examplePrompt Put a door from the catalog at this point on the wall\n * @examplePrompt Insert the entrance door into this wall\n * @examplePrompt Add a door to the wall and call it Entry-01\n * @examplePrompt Add a door that opens into the living room\n * @examplePrompt Put a door here hinged on the north jamb\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls({ isSelected: true })\n * const [entry] = await snaptrude.design.doors.listCatalog()\n * const door = await snaptrude.design.create.door(\n * entry.id,\n * wall,\n * await snaptrude.core.math.vec3.new(3, 0, 5),\n * { label: \"Entry-01\" },\n * )\n * // …facing a specific side of the wall (pass a point inside that room):\n * const intoKitchen = await snaptrude.design.create.door(\n * entry.id,\n * wall,\n * await snaptrude.core.math.vec3.new(3, 0, 5),\n * undefined,\n * await snaptrude.core.math.vec3.new(3, 0, 9),\n * )\n * // …and hinged on the jamb nearest a point (here the +x end of the opening):\n * const hingedRight = await snaptrude.design.create.door(\n * entry.id,\n * wall,\n * await snaptrude.core.math.vec3.new(3, 0, 5),\n * { hinge: await snaptrude.core.math.vec3.new(3.5, 0, 5) },\n * await snaptrude.core.math.vec3.new(3, 0, 9),\n * )\n * ```\n *\n * @performance For MORE THAN ONE door, call `design.create.doors(items[])` — one host\n * round-trip and ONE undo entry, with each host wall re-cut once per opening inside\n * that single call. Looping this single-door creator is N round-trips.\n */\n public abstract door(\n catalogId: string,\n hostWall: ComponentHandle,\n position: Vec3Handle,\n options?: { label?: string; hinge?: Vec3Handle },\n facing?: Vec3Handle,\n ): PluginApiReturn<ComponentHandle>\n\n /**\n * Place **many doors** into host walls in one undoable operation (bulk plural\n * of {@linkcode door}). Each item carries its own catalog id, host wall,\n * world position, and optional `facing`, `label`, `width` and `height` — the\n * same fields as a `\"door\"` {@linkcode PluginDesignCreateOpeningOptions}.\n * Validate-all-or-throw; one command.\n *\n * As with the singular creator, placing an opening **re-cuts its host wall**:\n * the wall you passed as `hostWall` stops resolving once the call returns.\n * Several items MAY name the same original wall handle in one call — the host\n * chains the re-cuts internally — but after the call recover the surviving\n * wall with `design.query.getHost(opening)`, never by reusing the handle you\n * passed in.\n *\n * @param items - One {@linkcode PluginCreateDoorItem} per door to place\n * (≥1, ≤1000)\n * @returns the placed doors as {@linkcode ComponentHandle}`[]`, in input order\n * @throws `VALIDATION` if the items fail the schema (empty array, more than\n * 1000 items, unknown fields, a non-positive `width`/`height`);\n * `HANDLE_INVALID` if a `position`, `facing` or `hinge` handle is unknown\n * or released; `PRECONDITION_FAILED` if a catalog id is unknown, a `hostWall`\n * is not a wall / is locked / is not in the active proposal, the projected\n * point falls outside its host wall, or a door tool is active;\n * `OPERATION_FAILED` if a source mesh fails to load or placement fails\n * (nothing is created — `details.itemIndex` is the failing item);\n * `METHOD_NOT_PERMITTED` if the plugin may not write.\n *\n * @examplePrompt Add doors to all of these walls at once\n * @examplePrompt Place a door in every room in one operation\n * @examplePrompt Bulk add the entrance doors from this schedule\n * @examplePrompt Put three doors on this wall in a single undo step\n * @examplePrompt Add all the doors for this floor plan together\n *\n * @performance Bulk creator — the whole batch is ONE host round-trip and ONE undo\n * entry, and each distinct catalog source loads once. Always prefer this over\n * calling `design.create.door` in a loop: build the full `items[]` array first\n * (all host walls and points up front), then make one call.\n *\n * # Example\n * ```ts\n * const v = snaptrude.core.math.vec3\n * const [wall] = await snaptrude.design.query.listWalls({ isSelected: true })\n * const [entry] = await snaptrude.design.doors.listCatalog()\n * const [front, side] = await snaptrude.design.create.doors([\n * { catalogId: entry.id, hostWall: wall, position: await v.new(2, 0, 5), label: \"D-01\" },\n * { catalogId: entry.id, hostWall: wall, position: await v.new(6, 0, 5), width: 1.2 },\n * ])\n * // `wall` was re-cut twice and no longer resolves — ask the opening for its host:\n * const currentWall = await snaptrude.design.query.getHost(front)\n * ```\n */\n public abstract doors(\n items: PluginCreateDoorItem[],\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * Place a **window** from the catalog into a host wall.\n *\n * Identified by a **catalog (library) id** — the team object's stable `_id`\n * or the general library's `fullName`. Discover ids with\n * {@linkcode PluginDesignWindowsApi.listCatalog}. The world `position` is\n * projected onto the host wall's face to locate the opening; the wall is\n * re-cut and the window hosted in a single undo entry. Placement is\n * asynchronous (the source mesh is fetched if not cached).\n *\n * @param catalogId - Library id: team `_id` or general `fullName`\n * @param hostWall - The wall to host the window\n * @param position - World point projected onto the wall to locate the opening\n * @param options - Optional placement options: `label` — instance name;\n * `hinge` — world point near the jamb the window is hinged on (same rule\n * as {@linkcode PluginDesignCreateApi.door}; default the catalog item's\n * authored side)\n * @param facing - World point on the side of the wall the window opens to\n * (matters for asymmetric windows, e.g. casement swing) — same convention\n * as {@linkcode PluginDesignCreateApi.door}. Default: the engine picks a\n * side (nondeterministic when `position` sits on the centerline).\n * @returns the {@linkcode ComponentHandle} of the placed window\n * @throws if the catalog id is unknown, the host is not a wall, the source\n * mesh fails to load, or the projected point falls **outside** the host wall\n *\n * @examplePrompt Add a window to this wall here\n * @examplePrompt Place a casement window on the selected wall\n * @examplePrompt Put a window from the catalog at this point on the wall\n * @examplePrompt Insert a window into the living room wall\n * @examplePrompt Add a window to the wall and name it Win-01\n * @examplePrompt Add a casement window opening toward the garden side\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls({ isSelected: true })\n * const [entry] = await snaptrude.design.windows.listCatalog()\n * const window = await snaptrude.design.create.window(\n * entry.id,\n * wall,\n * await snaptrude.core.math.vec3.new(3, 0, 5),\n * { label: \"Win-01\" },\n * )\n * ```\n *\n * @performance For MORE THAN ONE window, call `design.create.windows(items[])` — one\n * host round-trip and ONE undo entry, with each host wall re-cut once per opening\n * inside that single call. Looping this single-window creator is N round-trips.\n */\n public abstract window(\n catalogId: string,\n hostWall: ComponentHandle,\n position: Vec3Handle,\n options?: { label?: string; hinge?: Vec3Handle },\n facing?: Vec3Handle,\n ): PluginApiReturn<ComponentHandle>\n\n /**\n * Place **many windows** into host walls in one undoable operation (bulk\n * plural of {@linkcode window}). Each item carries its own catalog id, host\n * wall, world position, and optional `facing`, `label`, `width`, `height` and\n * `sillHeight` — the same fields as a `\"window\"`\n * {@linkcode PluginDesignCreateOpeningOptions}. `sillHeight` is measured from\n * the host wall base to the BOTTOM of the window. Validate-all-or-throw; one\n * command.\n *\n * As with the singular creator, placing an opening **re-cuts its host wall**:\n * the wall you passed as `hostWall` stops resolving once the call returns.\n * Several items MAY name the same original wall handle in one call — the host\n * chains the re-cuts internally — but after the call recover the surviving\n * wall with `design.query.getHost(opening)`, never by reusing the handle you\n * passed in.\n *\n * @param items - One {@linkcode PluginCreateWindowItem} per window to place\n * (≥1, ≤1000)\n * @returns the placed windows as {@linkcode ComponentHandle}`[]`, in input order\n * @throws `VALIDATION` if the items fail the schema (empty array, more than\n * 1000 items, unknown fields, a non-positive `width`/`height`, a negative\n * `sillHeight`); `HANDLE_INVALID` if a `position`, `facing` or `hinge`\n * handle is unknown or released; `PRECONDITION_FAILED` if a catalog id is unknown, a\n * `hostWall` is not a wall / is locked / is not in the active proposal, the\n * projected point falls outside its host wall, or a window tool is active;\n * `OPERATION_FAILED` if a source mesh fails to load or placement fails\n * (nothing is created — `details.itemIndex` is the failing item);\n * `METHOD_NOT_PERMITTED` if the plugin may not write.\n *\n * @examplePrompt Add windows along this whole facade at once\n * @examplePrompt Place a window in every bedroom in one operation\n * @examplePrompt Bulk add the windows from this schedule\n * @examplePrompt Put four windows on this wall in a single undo step\n * @examplePrompt Add all the windows with a 0.9m sill height together\n *\n * @performance Bulk creator — the whole batch is ONE host round-trip and ONE undo\n * entry, and each distinct catalog source loads once. Always prefer this over\n * calling `design.create.window` in a loop: build the full `items[]` array first\n * (all host walls and points up front), then make one call.\n *\n * # Example\n * ```ts\n * const v = snaptrude.core.math.vec3\n * const [wall] = await snaptrude.design.query.listWalls({ isSelected: true })\n * const [casement] = await snaptrude.design.windows.listCatalog()\n * const placed = await snaptrude.design.create.windows([\n * { catalogId: casement.id, hostWall: wall, position: await v.new(2, 0, 5), sillHeight: 0.9 },\n * { catalogId: casement.id, hostWall: wall, position: await v.new(5, 0, 5), sillHeight: 0.9 },\n * ])\n * const currentWall = await snaptrude.design.query.getHost(placed[0])\n * ```\n */\n public abstract windows(\n items: PluginCreateWindowItem[],\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * Place a catalog **door or window** into a host wall with optional size overrides.\n *\n * The world `position` is projected onto the host wall to locate the opening.\n * All supplied dimensions use Snaptrude engine units. Window `sillHeight` is\n * measured from the host wall base to the bottom of the window, not to\n * its center. Placement is asynchronous and committed as one undoable creation.\n *\n * @param options - Opening kind, catalog and host references, placement, and\n * optional facing (swing side), hinge (hinged jamb), label, and dimensions\n * @returns the {@linkcode ComponentHandle} of the placed door or window\n * @throws if the options are invalid, the catalog id is unknown, the host is\n * not a wall, loading fails, or the opening cannot be placed on the host\n *\n * @examplePrompt Add a 1m wide door to this wall here\n * @examplePrompt Place a window with a 0.9m sill height on the selected wall\n *\n * @performance Single-opening creator — use it for one hosted opening. For MORE\n * THAN ONE, call `design.create.doors(items[])` / `design.create.windows(items[])`:\n * the same per-item fields, one host round-trip, ONE undo entry.\n *\n * # Example\n * ```ts\n * const opening = await snaptrude.design.create.opening({\n * kind: \"window\",\n * catalogId: windowType.id,\n * hostWall: wall,\n * position: await snaptrude.core.math.vec3.new(3, 0, 5),\n * width: 1.2,\n * height: 1.5,\n * sillHeight: 0.9,\n * })\n * ```\n */\n public abstract opening(\n options: PluginDesignCreateOpeningOptions,\n ): PluginApiReturn<ComponentHandle>\n\n /**\n * Place a **smart layout** — clone an in-scene template cluster (walls,\n * furniture, doors, windows, floors) into one or more target ROOM/DEPARTMENT\n * spaces, adapting orientation and fit to each target.\n *\n * Supply the template as **exactly one** of `templateGroup` (a group **id**\n * from `snaptrude.core.groups.list` — groups are string-id entities, not\n * component handles) or\n * `templateComponents` (an explicit component set); every target in `targets`\n * (≥1) receives its own placement. By default the target spaces are hidden\n * after placement (`hideTargets`, mirroring the product) — pass `false` to keep\n * them visible.\n *\n * The whole operation — every wall/opening/furniture/floor across every target,\n * plus the target-hide — is committed as a **single undo step**. Because all\n * targets merge into one creation batch, the created entities are returned\n * **flat, grouped by kind** ({@linkcode PluginSmartLayoutResult}), NOT attributed\n * per target (per-target attribution would break the single-undo guarantee).\n * `skippedWalls` is the interior-optimizer's best-candidate count of walls it\n * could not place. Synchronous and heavy — many targets can jank the main thread.\n *\n * @param options - {@linkcode PluginSmartLayoutArgs}: template source, targets, and `hideTargets`\n * @returns a {@linkcode PluginSmartLayoutResult} — created entities grouped by kind + `skippedWalls`\n * @throws if smart layout is not enabled, the template group is invalid, there\n * is no template / no targets / no valid mappings, or component creation fails\n *\n * @examplePrompt Copy this room's layout into the selected rooms\n * @examplePrompt Apply this furniture arrangement to the other offices\n * @examplePrompt Replicate this template into these two spaces\n * @examplePrompt Place this desk cluster into every meeting room\n * @examplePrompt Use this room as a template for the rooms I selected but keep them visible\n *\n * # Example\n * ```ts\n * const [templateRoom, ...targets] = await snaptrude.design.query.listSpaces({ isSelected: true })\n * const result = await snaptrude.design.create.smartLayout({\n * templateComponents: [templateRoom],\n * targets,\n * })\n * console.log(\"placed walls:\", result.created.walls.length, \"skipped:\", result.skippedWalls)\n *\n * // …or clone a saved group by its id (from core.groups.list)\n * const { groups } = await snaptrude.core.groups.list()\n * await snaptrude.design.create.smartLayout({ templateGroup: groups[0].id, targets })\n * ```\n */\n public abstract smartLayout(\n options: PluginSmartLayoutArgs,\n ): PluginApiReturn<PluginSmartLayoutResult>\n\n /**\n * **Copy** existing entities, offsetting each copy by `displacement * i` (i = 1…count),\n * source positions preserved. Migrated from the removed `tools.copy`. In `\"instance\"`\n * mode copies stay in the source's instance family where possible; `\"unique\"` makes\n * independent geometry. The created copies become the active selection. Undoable.\n *\n * Runs on the engine's array-copy tool, so its semantics carry over: an\n * `\"instance\"` copy of a source that is not yet an instance **replaces that\n * source** with a fresh instance component — the handle you passed in stops\n * resolving after the call. Re-query (e.g. `design.query.listSpaces`) to pick\n * up the replacement before operating on that entity again; copying the stale\n * handle throws. `\"unique\"` mode never replaces sources.\n *\n * @param components - Entities to copy (≥1)\n * @param displacement - Per-copy offset as a {@linkcode Vec3Handle} (copy `i` at `displacement * i`)\n * @param options - `count` (copies per component, default 1), `mode` (`\"instance\"` | `\"unique\"`, default `\"instance\"`)\n * @returns the created copies as {@linkcode ComponentHandle}`[]`\n * @throws if a component handle is unknown or stale (replaced by an earlier\n * `\"instance\"` copy), `count` is not a positive integer, or the engine fails\n *\n * @examplePrompt Copy this room 3 times spaced 6m apart\n * @examplePrompt Duplicate the selected furniture along the X axis\n * @examplePrompt Make 5 copies of this column at 4 metre intervals\n * @examplePrompt Array these walls with a 3m offset\n * @examplePrompt Clone the desk as a unique copy, not an instance\n *\n * @performance To place N repeats of the same geometry, bulk-create the seed(s) once then\n * call this ONCE with `{ count: N - 1 }` — one host round-trip for all copies. Never\n * reproduce geometry by looping `design.create.*` N times. Copy only after the create\n * that mints the seed ids.\n *\n * # Example\n * ```ts\n * const { vec3 } = snaptrude.core.math\n * const copies = await snaptrude.design.create.copy([\"space-id\"], vec3.new(6, 0, 0), { count: 3 })\n * ```\n */\n public abstract copy(\n components: ComponentHandle[],\n displacement: Vec3Handle,\n options?: { count?: number; mode?: PluginCopyMode },\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * Create **many spaces** in one undoable operation (bulk plural of {@linkcode space}).\n * Migrated from the removed `entity.space.bulkCreate`. Each item extrudes a contour\n * footprint up `height`. Validate-all-or-throw; one command. For a rectangular\n * (box) space, build the footprint with `core.geom.create.profileRect` →\n * `contourFromProfile`, then pass it as an item. Each item may carry its own\n * `storey` (see {@linkcode PluginCreateSpaceItem}) so a single call can populate\n * several floors at once.\n *\n * @param items - One {@linkcode PluginCreateSpaceItem} per space to create (≥1)\n * @returns the created spaces as {@linkcode ComponentHandle}`[]`, in input order\n * @throws if any item's contour is invalid, its height is not positive, or its\n * target `storey` does not exist (all-or-nothing — nothing is created)\n *\n * @examplePrompt Create several rooms at once from these footprints\n * @examplePrompt Generate all the bedrooms in one operation\n * @examplePrompt Bulk create the spaces for this floor plan\n * @examplePrompt Add three rooms with different heights in a single undo step\n * @examplePrompt Create one room on the ground floor and one on the first floor\n *\n * @performance Bulk creator — the whole batch is ONE host round-trip. Always prefer this\n * over calling `design.create.space` in a loop: build the full `items[]` array first\n * (all per-item math and geometry up front), then make one call.\n *\n * # Example\n * ```ts\n * const rect = await snaptrude.core.geom.create.profileRect(4, 3)\n * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)\n * const [a, b] = await snaptrude.design.create.spaces([\n * { contour, height: 3, label: \"R1\" },\n * { contour, height: 4, label: \"R2\", position: await vec3.new(10, 0, 0) },\n * ])\n * ```\n */\n public abstract spaces(\n items: PluginCreateSpaceItem[],\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * Create a new parametric buildable envelope — the zoning-regulated volume a\n * building may occupy — generated from a site polygon, tiered setbacks, a\n * height or floor-count cap, and optional FAR and lot-coverage limits. The host\n * mints the envelope id and returns it. Canonical home of the create formerly\n * at `entity.buildableEnvelope.create` (now deprecated).\n *\n * @param sitePolygon - Site polygon vertices in `lengthUnit`; minimum 3 vertices.\n * @param lengthUnit - Unit used by all length fields (`\"ft\" | \"m\"`).\n * @param setbacks - Setback profile, ground tier first; minimum 1 tier.\n * @param verticalCap - Maximum height or floor count.\n * @param floorToFloor - Required floor-to-floor height in `lengthUnit`.\n * @param farRatio - Optional FAR value, positive when provided.\n * @param lotCoverageMaxPct - Optional lot coverage cap, `0..100`.\n * @returns A {@linkcode PluginBuildableEnvelopeCreateResult} with the\n * `buildableEnvelopeId` of the created envelope.\n * @throws If validation fails or generation produces no renderable geometry.\n *\n * @examplePrompt Create a buildable envelope for my site\n * @examplePrompt Generate the zoning envelope with 10ft front setbacks\n * @examplePrompt Show the maximum buildable volume with a 150ft height limit\n * @examplePrompt Create a zoning envelope capped at 12 floors with FAR 3\n *\n * # Example\n * ```ts\n * const { buildableEnvelopeId } = await snaptrude.design.create.buildableEnvelope(\n * [\n * { x: 0, z: 0 },\n * { x: 100, z: 0 },\n * { x: 100, z: 80 },\n * { x: 0, z: 80 },\n * ],\n * \"ft\",\n * [\n * { aboveHeight: 0, front: 10, side: 5, rear: 10 },\n * { aboveHeight: 100, front: 20, side: 10, rear: 20 },\n * ],\n * { kind: \"max_height\", maxHeight: 150 },\n * 12,\n * )\n * ```\n */\n public abstract buildableEnvelope(\n sitePolygon: PluginBuildableEnvelopePolygonVertex[],\n lengthUnit: \"ft\" | \"m\",\n setbacks: PluginBuildableEnvelopeSetbackTier[],\n verticalCap: PluginBuildableEnvelopeVerticalCap,\n floorToFloor: number,\n farRatio?: number,\n lotCoverageMaxPct?: number,\n ): PluginApiReturn<PluginBuildableEnvelopeCreateResult>\n}\n\n// ---------------------------------------------------------------------------\n// Enums\n// ---------------------------------------------------------------------------\n\n/**\n * Line style of a reference line (mirrors the engine `ReferenceLineMode` verbatim).\n *\n * | Value | Description |\n * |---|---|\n * | `\"SOLID\"` | Solid line (default) |\n * | `\"DASHED\"` | Dashed line |\n * | `\"DOT_DASH\"` | Dot-dash line |\n */\nexport const PluginReferenceLineStyle = z.enum([\"SOLID\", \"DASHED\", \"DOT_DASH\"])\nexport type PluginReferenceLineStyle = z.infer<typeof PluginReferenceLineStyle>\n\n/**\n * Slab type (mirrors the engine `SLAB_TYPES` values verbatim).\n *\n * | Value | Description |\n * |---|---|\n * | `\"Intermediate Slab\"` | Intermediate floor slab (default) |\n * | `\"Basement Slab\"` | Basement slab |\n * | `\"Plinth\"` | Plinth slab |\n * | `\"Roof\"` | Roof slab |\n */\nexport const PluginSlabType = z.enum([\n \"Intermediate Slab\",\n \"Basement Slab\",\n \"Plinth\",\n \"Roof\",\n])\nexport type PluginSlabType = z.infer<typeof PluginSlabType>\n\n/**\n * A staircase preset — a base type or a named parametric preset. Mirrors the\n * engine `STAIRCASE_PRESETS` / `StaircaseType`. Use the value that matches the\n * shape you want (e.g. `\"straight\"`, `\"dogLegged\"`, `\"lShaped\"`).\n */\nexport const PluginStaircasePreset = z.enum([\n \"straight\",\n \"straightWoLanding\",\n \"straightFlightWoMidLanding\",\n \"dogLegged\",\n \"openWell\",\n \"lShaped\",\n \"3StepLShaped\",\n \"square\",\n \"singleStep\",\n \"plinthLevel\",\n \"amphitheatre\",\n \"connectingStepsToAmphitheatre\",\n \"mezzanineFloorStraight\",\n \"mezzanineFloorDogLegged\",\n \"mezzanineFloorLShaped\",\n \"custom\",\n])\nexport type PluginStaircasePreset = z.infer<typeof PluginStaircasePreset>\n\n/**\n * Optional staircase dimension overrides. Lengths use Snaptrude engine units\n * and must be positive. `angleInDegrees` controls placement about the vertical\n * axis and must be a multiple of 90 degrees.\n */\nexport const PluginStaircaseDimensions = z\n .object({\n width: z.number().finite().positive().optional(),\n tread: z.number().finite().positive().optional(),\n riser: z.number().finite().positive().optional(),\n landingWidth: z.number().finite().positive().optional(),\n angleInDegrees: z.number().finite().multipleOf(90).optional(),\n })\n .strict()\nexport type PluginStaircaseDimensions = z.infer<\n typeof PluginStaircaseDimensions\n>\n\n// ---------------------------------------------------------------------------\n// space\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.space}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | Bottom footprint (outer + holes) |\n * | `height` | `number` | Extrusion height (Snaptrude units, > 0) |\n * | `label` | `string`? | Room label (maps to the space's `room_type`) |\n * | `position` | {@linkcode Vec3Handle}? | Offset from origin (default origin) |\n * | `spaceType` | {@linkcode PluginSpaceType}? | Space-type classification (default `Room`) |\n * | `massType` | {@linkcode PluginMassType}? | Mass-type classification (default `Room`) |\n * | `departmentId` | {@linkcode PluginDepartmentId}? | Department assignment (default department) |\n * | `storey` | `number`? (int) | Target storey number (default active storey) |\n */\nexport const PluginDesignCreateSpaceArgs = z.object({\n contour: ContourHandle,\n height: z.number(),\n label: z.string().optional(),\n position: Vec3Handle.optional(),\n spaceType: PluginSpaceType.optional(),\n massType: PluginMassType.optional(),\n departmentId: PluginDepartmentId.optional(),\n storey: z.number().int().optional(),\n})\nexport type PluginDesignCreateSpaceArgs = z.infer<\n typeof PluginDesignCreateSpaceArgs\n>\n// TRANSPORT: positional signature shipped — storey is the trailing 8th arg:\n// space(contour, height, label?, position?, spaceType?, massType?, departmentId?, storey?)\n\n// ---------------------------------------------------------------------------\n// mass\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.mass}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | Footprint (outer + holes) |\n * | `height` | `number` | Extrusion height (> 0) |\n * | `label` | `string`? | Label (maps to `room_type`) |\n * | `position` | {@linkcode Vec3Handle}? | Offset from origin |\n * | `massType` | {@linkcode PluginMassType}? | Mass type (default `Generic mass`) |\n */\nexport const PluginDesignCreateMassArgs = z.object({\n contour: ContourHandle,\n height: z.number(),\n label: z.string().optional(),\n position: Vec3Handle.optional(),\n massType: PluginMassType.optional(),\n})\nexport type PluginDesignCreateMassArgs = z.infer<\n typeof PluginDesignCreateMassArgs\n>\n\n// ---------------------------------------------------------------------------\n// massFromBrep\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.massFromBrep}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `brep` | {@linkcode BrepHandle} | A solid from a `core.geom.create` brep constructor |\n * | `label` | `string`? | Label (maps to `room_type`) |\n */\nexport const PluginDesignCreateMassFromBrepArgs = z.object({\n brep: BrepHandle,\n label: z.string().optional(),\n})\nexport type PluginDesignCreateMassFromBrepArgs = z.infer<\n typeof PluginDesignCreateMassFromBrepArgs\n>\n\n// ---------------------------------------------------------------------------\n// slab\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.slab}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | Footprint (outer + holes) |\n * | `thickness` | `number` | Slab thickness (> 0) |\n * | `direction` | `\"up\" \\| \"down\"`? | Extrusion direction (default `\"down\"`) |\n * | `slabType` | {@linkcode PluginSlabType}? | Slab-type classification |\n */\nexport const PluginDesignCreateSlabArgs = z.object({\n contour: ContourHandle,\n thickness: z.number(),\n direction: z.enum([\"up\", \"down\"]).optional(),\n slabType: PluginSlabType.optional(),\n})\nexport type PluginDesignCreateSlabArgs = z.infer<\n typeof PluginDesignCreateSlabArgs\n>\n\n// ---------------------------------------------------------------------------\n// floor\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.floor}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | Footprint (outer + holes) |\n * | `thickness` | `number` | Floor thickness (> 0) |\n * | `position` | {@linkcode Vec3Handle}? | Offset from origin |\n */\nexport const PluginDesignCreateFloorArgs = z.object({\n contour: ContourHandle,\n thickness: z.number(),\n position: Vec3Handle.optional(),\n})\nexport type PluginDesignCreateFloorArgs = z.infer<\n typeof PluginDesignCreateFloorArgs\n>\n\n// ---------------------------------------------------------------------------\n// roof\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.roof}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | Footprint (outer + holes) |\n * | `thickness` | `number` | Roof-slab thickness (> 0) |\n */\nexport const PluginDesignCreateRoofArgs = z.object({\n contour: ContourHandle,\n thickness: z.number(),\n})\nexport type PluginDesignCreateRoofArgs = z.infer<\n typeof PluginDesignCreateRoofArgs\n>\n\n// ---------------------------------------------------------------------------\n// ceiling\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.ceiling}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | Footprint (outer + holes) |\n * | `thickness` | `number` | Ceiling thickness (> 0) |\n * | `heightFromFloor` | `number`? | Lift above the floor (default 0) |\n */\nexport const PluginDesignCreateCeilingArgs = z.object({\n contour: ContourHandle,\n thickness: z.number(),\n heightFromFloor: z.number().optional(),\n})\nexport type PluginDesignCreateCeilingArgs = z.infer<\n typeof PluginDesignCreateCeilingArgs\n>\n\n// ---------------------------------------------------------------------------\n// column\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.column}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `position` | {@linkcode Vec3Handle} | Base point |\n * | `crossSection` | {@linkcode ContourHandle} | Column cross-section footprint |\n * | `height` | `number` | Column height (> 0) |\n * | `directionUp` | {@linkcode Vec3Handle}? | Up axis (default world up) |\n */\nexport const PluginDesignCreateColumnArgs = z.object({\n position: Vec3Handle,\n crossSection: ContourHandle,\n height: z.number(),\n directionUp: Vec3Handle.optional(),\n})\nexport type PluginDesignCreateColumnArgs = z.infer<\n typeof PluginDesignCreateColumnArgs\n>\n\n// ---------------------------------------------------------------------------\n// beam\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.beam}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `section` | {@linkcode ContourHandle} | Beam cross-section footprint |\n * | `length` | `number` | Beam length along `direction` (> 0) |\n * | `direction` | {@linkcode Vec3Handle}? | Extrusion axis (default world up) |\n */\nexport const PluginDesignCreateBeamArgs = z.object({\n section: ContourHandle,\n length: z.number(),\n direction: Vec3Handle.optional(),\n})\nexport type PluginDesignCreateBeamArgs = z.infer<\n typeof PluginDesignCreateBeamArgs\n>\n\n// ---------------------------------------------------------------------------\n// walls\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.walls}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | Ordered curve chain (wall centerlines) |\n * | `height` | `number`? | Wall height (default engine default) |\n * | `thickness` | `number`? | Wall thickness (default: `wallType`'s total layer thickness when given, else engine default) |\n * | `wallType` | `string`? | Wall type name from `design.types.list(\"wall\")` — layers/material/thickness defaults (default generic) |\n * | `storey` | `number`? | Target storey (integer, must exist) — walls assigned + lifted to its base in the same undo entry (default: geometry-derived) |\n */\nexport const PluginDesignCreateWallsArgs = z.object({\n profile: ProfileHandle,\n height: z.number().optional(),\n thickness: z.number().optional(),\n wallType: z.string().min(1).optional(),\n storey: z.number().int().optional(),\n})\nexport type PluginDesignCreateWallsArgs = z.infer<\n typeof PluginDesignCreateWallsArgs\n>\n// TRANSPORT: positional signature shipped — storey is the trailing 5th arg:\n// walls(profile: ProfileHandle, height?: number, thickness?: number, wallType?: string, storey?: number)\n\n// ---------------------------------------------------------------------------\n// staircase\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.staircase}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `preset` | {@linkcode PluginStaircasePreset} | The parametric preset to generate |\n * | `position` | {@linkcode Vec3Handle} | Placement point |\n * | `label` | `string`? | Optional label |\n * | `structureId` | `string`? | Target structure (default active) |\n * | `level` | `string`? | Target level name (default active/`\"01\"`) |\n * | `dimensions` | {@linkcode PluginStaircaseDimensions}? | Positive dimension overrides in engine units, applied before creation |\n */\nexport const PluginDesignCreateStaircaseArgs = z.object({\n preset: PluginStaircasePreset,\n position: Vec3Handle,\n label: z.string().optional(),\n structureId: z.string().optional(),\n level: z.string().optional(),\n dimensions: PluginStaircaseDimensions.optional(),\n})\nexport type PluginDesignCreateStaircaseArgs = z.infer<\n typeof PluginDesignCreateStaircaseArgs\n>\n\n// ---------------------------------------------------------------------------\n// furniture\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.furniture}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `catalogId` | `string` | Library id: team `_id` or general `fullName` |\n * | `position` | {@linkcode Vec3Handle} | Absolute world placement point |\n * | `label` | `string`? | Instance name (default auto `${name}Ins${n}`) |\n * | `createNewSourceMesh` | `boolean`? | Legacy, kept for compatibility; the host records a catalog source exactly once and this flag cannot skip it |\n * | `angleInDegrees` | `number`? | Rotation about the vertical axis, in degrees (default unrotated) |\n */\nexport const PluginDesignCreateFurnitureArgs = z.object({\n catalogId: z.string().min(1),\n position: Vec3Handle,\n label: z.string().optional(),\n createNewSourceMesh: z.boolean().optional(),\n angleInDegrees: z.number().optional(),\n})\nexport type PluginDesignCreateFurnitureArgs = z.infer<\n typeof PluginDesignCreateFurnitureArgs\n>\n// TRANSPORT: positional signature shipped — angleInDegrees is the trailing 4th arg:\n// furniture(catalogId: string, position: Vec3Handle, options?: { label?, createNewSourceMesh? }, angleInDegrees?: number)\n\n// ---------------------------------------------------------------------------\n// door\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.door}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `catalogId` | `string` | Library id: team `_id` or general `fullName` |\n * | `hostWall` | {@linkcode ComponentHandle} | The wall to host the door |\n * | `position` | {@linkcode Vec3Handle} | World point projected onto the wall |\n * | `label` | `string`? | Instance name (optional) |\n * | `facing` | {@linkcode Vec3Handle}? | World point on the side of the wall the door swings open to (default engine-chosen) |\n * | `hinge` | {@linkcode Vec3Handle}? | World point near the jamb the door is hinged on (default the catalog item's authored side) |\n */\nexport const PluginDesignCreateDoorArgs = z.object({\n catalogId: z.string().min(1),\n hostWall: ComponentHandle,\n position: Vec3Handle,\n label: z.string().optional(),\n facing: Vec3Handle.optional(),\n hinge: Vec3Handle.optional(),\n})\nexport type PluginDesignCreateDoorArgs = z.infer<\n typeof PluginDesignCreateDoorArgs\n>\n// TRANSPORT: positional signature shipped — facing is the trailing 5th arg, hinge rides in options:\n// door(catalogId: string, hostWall: ComponentHandle, position: Vec3Handle, options?: { label?, hinge? }, facing?: Vec3Handle)\n\n// ---------------------------------------------------------------------------\n// window\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.window}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `catalogId` | `string` | Library id: team `_id` or general `fullName` |\n * | `hostWall` | {@linkcode ComponentHandle} | The wall to host the window |\n * | `position` | {@linkcode Vec3Handle} | World point projected onto the wall |\n * | `label` | `string`? | Instance name (optional) |\n * | `facing` | {@linkcode Vec3Handle}? | World point on the side of the wall the window opens to (default engine-chosen) |\n * | `hinge` | {@linkcode Vec3Handle}? | World point near the jamb the window is hinged on (default the catalog item's authored side) |\n */\nexport const PluginDesignCreateWindowArgs = z.object({\n catalogId: z.string().min(1),\n hostWall: ComponentHandle,\n position: Vec3Handle,\n label: z.string().optional(),\n facing: Vec3Handle.optional(),\n hinge: Vec3Handle.optional(),\n})\nexport type PluginDesignCreateWindowArgs = z.infer<\n typeof PluginDesignCreateWindowArgs\n>\n// TRANSPORT: positional signature shipped — facing is the trailing 5th arg, hinge rides in options:\n// window(catalogId: string, hostWall: ComponentHandle, position: Vec3Handle, options?: { label?, hinge? }, facing?: Vec3Handle)\n\n// ---------------------------------------------------------------------------\n// opening\n// ---------------------------------------------------------------------------\n\n/**\n * Options for {@linkcode PluginDesignCreateApi.opening}, discriminated by\n * `kind`. Width and height are positive Snaptrude engine-unit values.\n * `sillHeight` is available only for windows, is nonnegative, and is precisely\n * the distance from the host wall base to the bottom of the window.\n */\nexport const PluginDesignCreateOpeningOptions = z.discriminatedUnion(\"kind\", [\n z\n .object({\n kind: z.literal(\"door\"),\n ...PluginOpeningBaseOptions,\n })\n .strict(),\n z\n .object({\n kind: z.literal(\"window\"),\n ...PluginOpeningBaseOptions,\n sillHeight: z.number().finite().nonnegative().optional(),\n })\n .strict(),\n])\nexport type PluginDesignCreateOpeningOptions = z.infer<\n typeof PluginDesignCreateOpeningOptions\n>\n\n// ---------------------------------------------------------------------------\n// smartLayout\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.smartLayout}. Provide **exactly\n * one** template source: `templateGroup` or a non-empty `templateComponents`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `templateGroup` | `string`? | Id of the group to clone as the template (a group id from `snaptrude.core.groups.list`, NOT a component handle) |\n * | `templateComponents` | {@linkcode ComponentHandle}`[]`? | Explicit component set to clone as the template |\n * | `targets` | {@linkcode ComponentHandle}`[]` | ROOM/DEPARTMENT spaces to place into (≥1) |\n * | `hideTargets` | `boolean`? | Hide the target spaces after placement (default `true`) |\n */\nexport const PluginSmartLayoutArgs = z\n .object({\n templateGroup: z.string().min(1).optional(),\n templateComponents: z.array(ComponentHandle).optional(),\n targets: z.array(ComponentHandle).min(1),\n hideTargets: z.boolean().optional(),\n })\n .refine(\n (v) =>\n (v.templateGroup != null) !== (v.templateComponents?.length ?? 0) > 0,\n {\n message:\n \"Provide exactly one template source: templateGroup or a non-empty templateComponents\",\n },\n )\nexport type PluginSmartLayoutArgs = z.infer<typeof PluginSmartLayoutArgs>\n\n/**\n * Result of {@linkcode PluginDesignCreateApi.smartLayout} — the entities the run\n * created, grouped by kind (flat across all targets; see the method JSDoc for why\n * this is not attributed per target).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `created.walls` | {@linkcode ComponentHandle}`[]` | Created walls |\n * | `created.furniture` | {@linkcode ComponentHandle}`[]` | Created furniture |\n * | `created.doors` | {@linkcode ComponentHandle}`[]` | Created doors |\n * | `created.windows` | {@linkcode ComponentHandle}`[]` | Created windows |\n * | `created.floors` | {@linkcode ComponentHandle}`[]` | Created floors |\n * | `skippedWalls` | `number` | Walls the interior optimizer could not place |\n */\nexport const PluginSmartLayoutResult = z.object({\n created: z.object({\n walls: z.array(ComponentHandle),\n furniture: z.array(ComponentHandle),\n doors: z.array(ComponentHandle),\n windows: z.array(ComponentHandle),\n floors: z.array(ComponentHandle),\n }),\n skippedWalls: z.number(),\n})\nexport type PluginSmartLayoutResult = z.infer<typeof PluginSmartLayoutResult>\n\n// ---------------------------------------------------------------------------\n// referenceLines\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.referenceLines}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | Ordered curve chain (centerlines) |\n * | `color` | `string`? | Hex color `\"#RRGGBB\"` (default white) |\n * | `thickness` | `number`? | Line thickness (default 1) |\n * | `style` | {@linkcode PluginReferenceLineStyle}? | Line style (default `SOLID`) |\n * | `gridTag` | `string`? | Grid tag label (default `\"A\"`) |\n */\nexport const PluginDesignCreateReferenceLinesArgs = z.object({\n profile: ProfileHandle,\n color: z.string().optional(),\n thickness: z.number().optional(),\n style: PluginReferenceLineStyle.optional(),\n gridTag: z.string().optional(),\n})\nexport type PluginDesignCreateReferenceLinesArgs = z.infer<\n typeof PluginDesignCreateReferenceLinesArgs\n>\n\n// ---------------------------------------------------------------------------\n// copy\n// ---------------------------------------------------------------------------\n\n/**\n * Copy mode for {@linkcode PluginDesignCreateApi.copy}.\n *\n * | Value | Description |\n * |---|---|\n * | `\"instance\"` | Keep copies in the source's instance family where possible |\n * | `\"unique\"` | Independent geometry copies |\n */\nexport const PluginCopyMode = z.enum([\"instance\", \"unique\"])\nexport type PluginCopyMode = z.infer<typeof PluginCopyMode>\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.copy} (options flattened).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `components` | {@linkcode ComponentHandle}`[]` | Entities to copy (≥1) |\n * | `displacement` | {@linkcode Vec3Handle} | Per-copy offset (copy `i` at `displacement * i`) |\n * | `count` | `number`? | Copies per component (default 1) |\n * | `mode` | {@linkcode PluginCopyMode}? | `\"instance\"` (default) or `\"unique\"` |\n */\nexport const PluginDesignCreateCopyArgs = z.object({\n components: z.array(ComponentHandle).min(1),\n displacement: Vec3Handle,\n count: z.number().int().positive().optional(),\n mode: PluginCopyMode.optional(),\n})\nexport type PluginDesignCreateCopyArgs = z.infer<\n typeof PluginDesignCreateCopyArgs\n>\n// TRANSPORT: positional signature shipped —\n// copy(components: ComponentHandle[], displacement: Vec3Handle, options?: { count?, mode? })\n\n// ---------------------------------------------------------------------------\n// spaces (bulk)\n// ---------------------------------------------------------------------------\n\n/**\n * One space to create via {@linkcode PluginDesignCreateApi.spaces} — the same\n * footprint-extrude inputs as {@linkcode PluginDesignCreateApi.space}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | Footprint (outer profile + holes) |\n * | `height` | `number` | Extrude height |\n * | `label` | `string`? | Room label |\n * | `position` | {@linkcode Vec3Handle}? | Placement offset |\n * | `spaceType` | `PluginSpaceType`? | Space type |\n * | `massType` | `PluginMassType`? | Mass type |\n * | `departmentId` | `PluginDepartmentId`? | Department |\n * | `storey` | `number`? (int) | Target storey number (default active storey) |\n */\nexport const PluginCreateSpaceItem = z.object({\n contour: ContourHandle,\n height: z.number(),\n label: z.string().optional(),\n position: Vec3Handle.optional(),\n spaceType: PluginSpaceType.optional(),\n massType: PluginMassType.optional(),\n departmentId: PluginDepartmentId.optional(),\n storey: z.number().int().optional(),\n})\nexport type PluginCreateSpaceItem = z.infer<typeof PluginCreateSpaceItem>\n\nexport * from \"./opening-fields\"\nexport * from \"./bulk-items\"\nexport * from \"./grids\"\n","// Space value types + schemas. The `entity.space.*` API was removed and its\n// operations migrated to `design.create.spaces` / `design.update.*` /\n// `design.query.*` / `design.delete.entities`. These shared space types survive\n// here because `design.create` / `design.query` / `design.update` import them.\nimport * as z from \"zod\"\nimport {\n ProfileHandle,\n Vec3Handle,\n Vec3Components,\n QuatComponents,\n CurveHandle,\n} from \"../../handles\"\n\n\n/**\n * Arguments for `createRectangular`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `position` | {@linkcode Vec3Handle} | Origin position in Babylon units |\n * | `dimensions` | `{ width, height, depth }` | Box dimensions in Babylon units |\n */\nexport const PluginSpaceCreateRectangularArgs = z.object({\n position: Vec3Handle,\n dimensions: z.object({\n width: z.number(),\n height: z.number(),\n depth: z.number(),\n }),\n})\n\nexport type PluginSpaceCreateRectangularArgs = z.infer<\n typeof PluginSpaceCreateRectangularArgs\n>\n\n/**\n * Result of `createRectangular`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaceId` | `string` | Unique ID of the created space |\n */\nexport const PluginSpaceCreateRectangularResult = z.object({\n spaceId: z.string(),\n})\n\nexport type PluginSpaceCreateRectangularResult = z.infer<\n typeof PluginSpaceCreateRectangularResult\n>\n\n/**\n * Arguments for `createFromProfile`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | Handle to a closed 2D profile (the outer floor shape) |\n * | `innerProfiles` | {@linkcode ProfileHandle}`[]` | Optional profile handles to subtract as holes |\n * | `extrudeHeight` | `number` | Extrusion height in Babylon units |\n * | `position` | {@linkcode Vec3Handle} | Offset position in Babylon units |\n */\nexport const PluginSpaceCreateFromProfileArgs = z.object({\n profile: ProfileHandle,\n innerProfiles: z.array(ProfileHandle).optional(),\n extrudeHeight: z.number(),\n position: Vec3Handle,\n})\n\nexport type PluginSpaceCreateFromProfileArgs = z.infer<\n typeof PluginSpaceCreateFromProfileArgs\n>\n\n/**\n * Result of `createFromProfile`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaceId` | `string` | Unique ID of the created space |\n */\nexport const PluginSpaceCreateFromProfileResult = z.object({\n spaceId: z.string(),\n})\n\nexport type PluginSpaceCreateFromProfileResult = z.infer<\n typeof PluginSpaceCreateFromProfileResult\n>\n\n/**\n * Arguments for `updateGeometryFromProfile`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaceId` | `string` | Space to update |\n * | `profile` | {@linkcode ProfileHandle} | Handle to the closed 2D profile defining the new floor shape |\n * | `extrudeHeight` | `number` | New extrusion height in Babylon units |\n */\nexport const PluginSpaceUpdateGeometryFromProfileArgs = z.object({\n spaceId: z.string(),\n profile: ProfileHandle,\n extrudeHeight: z.number(),\n})\n\nexport type PluginSpaceUpdateGeometryFromProfileArgs = z.infer<\n typeof PluginSpaceUpdateGeometryFromProfileArgs\n>\n\n/**\n * Result of `updateGeometryFromProfile`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaceId` | `string` | Unique ID of the updated space |\n */\nexport const PluginSpaceUpdateGeometryFromProfileResult = z.object({\n spaceId: z.string(),\n})\n\nexport type PluginSpaceUpdateGeometryFromProfileResult = z.infer<\n typeof PluginSpaceUpdateGeometryFromProfileResult\n>\n\n/**\n * Available properties that can be queried on a space.\n *\n * **Basic:**\n * | Value | Return Type | Description |\n * |---|---|---|\n * | `\"id\"` | `string` | Unique space identifier |\n * | `\"type\"` | `string` | Component type |\n * | `\"room_type\"` | `string` | Room type classification |\n * | `\"name\"` | `string` | Display name of the space |\n *\n * **Derived from parametric data:**\n * | Value | Return Type | Description |\n * |---|---|---|\n * | `\"area\"` | `number` | Bottom face area (from `areas_bottomFace`) |\n * | `\"breadth\"` | `number` | Breadth dimension |\n * | `\"depth\"` | `number` | Depth dimension |\n * | `\"height\"` | `number` | Height dimension |\n * | `\"massType\"` | `string \\| null` | Mass type classification |\n * | `\"spaceType\"` | `string \\| null` | Space type classification (Room, Balcony, etc.) |\n * | `\"storey\"` | `number \\| null` | Story the space belongs to |\n * | `\"departmentId\"` | `string \\| null` | Assigned department ID |\n * | `\"departmentName\"` | `string` | Assigned department name |\n * | `\"departmentColor\"` | `string` | Assigned department color (hex/CSS) |\n *\n * **Derived from mesh:**\n * | Value | Return Type | Description |\n * |---|---|---|\n * | `\"position\"` | {@linkcode Vec3Components} | Local position relative to parent |\n * | `\"absolutePosition\"` | {@linkcode Vec3Components} | Absolute position in the scene |\n * | `\"rotation\"` | {@linkcode Vec3Components} | Euler rotation angles (radians) |\n * | `\"rotationQuaternion\"` | {@linkcode QuatComponents} \\| `null` | Quaternion rotation, or `null` if unset |\n *\n * **Derived from brep:**\n * | Value | Return Type | Description |\n * |---|---|---|\n * | `\"planPoints\"` | {@linkcode Vec3Components}`[]` | Bottom outer profile points in world space with `y = 0` (2D plan). No closing duplicate point. |\n * | `\"profile\"` | {@linkcode CurveHandle}`[]` | Bottom outer profile curves in world space, as opaque curve handles. Read coordinates via the `core.geom.curve` read API. |\n * | `\"innerProfiles\"` | {@linkcode CurveHandle}`[][]` | Inner (hole) profile curve loops in world space — one loop per hole, `[]` when the space has none. Each curve is an opaque handle. |\n * | `\"innerPlanPoints\"` | {@linkcode Vec3Components}`[][]` | Inner (hole) profile points in world space with `y = 0` — one loop per hole, no closing duplicate, `[]` when none. |\n *\n * **Derived from adjacency manager:**\n * | Value | Return Type | Description |\n * |---|---|---|\n * | `\"adjacency\"` | `{ spaceId: string; value: number }[]` | Adjacency relationships to other spaces. `value`: `2` = Direct (required), `1` = Indirect or physical contact, `-1` = Restricted; `0` (no relationship) entries are filtered out. Empty when adjacency has never been computed — see `program.adjacency.compute`. |\n */\nexport const PluginSpaceGetProperty = z.enum([\n // Basic\n \"id\",\n \"type\",\n \"room_type\",\n \"name\",\n // Derived from parametric data\n \"area\",\n \"breadth\",\n \"depth\",\n \"height\",\n \"massType\",\n \"spaceType\",\n \"areaClass\",\n \"storey\",\n \"departmentId\",\n \"departmentName\",\n \"departmentColor\",\n // Derived from mesh\n \"position\",\n \"absolutePosition\",\n \"rotation\",\n \"rotationQuaternion\",\n // Derived from brep\n \"planPoints\",\n \"profile\",\n \"innerProfiles\",\n \"innerPlanPoints\",\n // Derived from adjacency manager\n \"adjacency\",\n])\n\n/**\n * Arguments for `get`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaceId` | `string` | The space ID to query |\n * | `properties` | {@linkcode PluginSpaceGetProperty}`[]` | Properties to retrieve |\n */\nexport const PluginSpaceGetArgs = z.object({\n spaceId: z.string(),\n properties: z.array(PluginSpaceGetProperty),\n})\n\nexport type PluginSpaceGetArgs = z.infer<typeof PluginSpaceGetArgs>\n\n/**\n * Supported space type values.\n *\n * Mirrors the internal `SpaceType` enum from `spaceType.types.ts`.\n *\n * | Value | Description |\n * |---|---|\n * | `\"Room\"` | Standard room |\n * | `\"Program Block\"` | Program/department block |\n * | `\"Balcony\"` | Balcony space |\n * | `\"Road\"` | Road |\n * | `\"Garden\"` | Garden area |\n * | `\"Deck\"` | Deck |\n * | `\"Pool\"` | Pool |\n * | `\"Walkway\"` | Walkway |\n * | `\"Envelope\"` | Envelope |\n * | `\"Parking\"` | Parking area |\n */\nexport const PluginSpaceType = z.enum([\n \"Room\",\n \"Program Block\",\n \"Balcony\",\n \"Road\",\n \"Garden\",\n \"Deck\",\n \"Pool\",\n \"Walkway\",\n \"Envelope\",\n \"Parking\",\n])\n/** The space-type value union (same-name type alias for positional signatures). */\nexport type PluginSpaceType = z.infer<typeof PluginSpaceType>\n\n/**\n * Area-class classification of a space (mirrors the internal `AreaClass` enum).\n *\n * | Value | Description |\n * |---|---|\n * | `\"NET\"` | Net (usable) area |\n * | `\"GROSS\"` | Gross area |\n * | `\"EXCLUDED\"` | Excluded from area totals |\n */\nexport const PluginAreaClass = z.enum([\"NET\", \"GROSS\", \"EXCLUDED\"])\n\n/**\n * Result of `get`.\n *\n * A partial object — only the properties that were requested in\n * {@linkcode PluginSpaceGetArgs.properties} will be present.\n */\nexport const PluginSpaceGetResult = z\n .object({\n // Basic\n id: z.string(),\n type: z.string(),\n room_type: z.string(),\n name: z.string(),\n // Derived from parametric data\n area: z.number(),\n breadth: z.number(),\n depth: z.number(),\n height: z.number(),\n massType: z.string().nullable(),\n spaceType: PluginSpaceType.nullable(),\n areaClass: PluginAreaClass,\n storey: z.number().nullable(),\n departmentId: z.string().nullable(),\n departmentName: z.string(),\n departmentColor: z.string(),\n // Derived from mesh\n position: Vec3Components,\n absolutePosition: Vec3Components,\n rotation: Vec3Components,\n rotationQuaternion: QuatComponents.nullable(),\n // Derived from brep\n planPoints: z.array(Vec3Components),\n profile: z.array(CurveHandle),\n innerProfiles: z.array(z.array(CurveHandle)),\n innerPlanPoints: z.array(z.array(Vec3Components)),\n // Derived from adjacency manager.\n // value: 2 = Direct (required), 1 = Indirect or physical contact, -1 = Restricted;\n // 0 (no relationship) entries are filtered out. Empty array when adjacency has\n // never been computed — run program.adjacency.compute() first for fresh data.\n adjacency: z.array(z.object({\n spaceId: z.string(),\n value: z.number(),\n })),\n })\n .partial()\n\nexport type PluginSpaceGetResult = z.infer<typeof PluginSpaceGetResult>\n\n/**\n * Result of `getAll`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spacesIds` | `string[]` | IDs of all spaces in the project |\n */\nexport const PluginSpaceGetAllResult = z.object({\n spacesIds: z.array(z.string()),\n})\n\nexport type PluginSpaceGetAllResult = z.infer<typeof PluginSpaceGetAllResult>\n\n/**\n * Arguments for `delete`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaceId` | `string` | The space ID to delete |\n */\nexport const PluginSpaceDeleteArgs = z.object({\n spaceId: z.string(),\n})\n\nexport type PluginSpaceDeleteArgs = z.infer<typeof PluginSpaceDeleteArgs>\n\n/** Result type for `delete` — returns nothing on success. */\nexport type PluginSpaceDeleteResult = void\n\n/**\n * Supported mass type values for {@linkcode PluginSpaceUpdateArgs.properties.massType}.\n *\n * Mirrors the internal `MASS_TYPES` enum.\n *\n * | Value | Description |\n * |---|---|\n * | `\"Plinth\"` | Plinth mass |\n * | `\"Void\"` | Void/cut-out |\n * | `\"Pergola\"` | Pergola structure |\n * | `\"Furniture\"` | Furniture element |\n * | `\"Facade element\"` | Facade element |\n * | `\"Generic mass\"` | Generic mass |\n * | `\"Room\"` | Room (default for spaces) |\n * | `\"Department\"` | Department block |\n * | `\"Building\"` | Building envelope |\n * | `\"Revit Import\"` | Imported from Revit |\n * | `\"Mass\"` | Generic mass type |\n * | `\"Site\"` | Site object |\n */\nexport const PluginMassType = z.enum([\n \"Plinth\",\n \"Void\",\n \"Pergola\",\n \"Furniture\",\n \"Facade element\",\n \"Generic mass\",\n \"Room\",\n \"Department\",\n \"Building\",\n \"Revit Import\",\n \"Mass\",\n \"Site\",\n])\n/** The mass-type value union (same-name type alias for positional signatures). */\nexport type PluginMassType = z.infer<typeof PluginMassType>\n\n/**\n * Well-known (built-in) department IDs.\n *\n * | Value | Department |\n * |---|---|\n * | `\"DEFAULT\"` | Default department |\n * | `\"SITE\"` | Site department |\n * | `\"ENVELOPE\"` | Envelope department |\n * | `\"CORE\"` | Core department |\n */\nexport const PluginWellKnownDepartmentId = z.enum([\n \"DEFAULT\",\n \"SITE\",\n \"ENVELOPE\",\n \"CORE\",\n])\n\n/**\n * Accepted department ID values — either a {@linkcode PluginWellKnownDepartmentId}\n * or a UUID string for custom (user-created) departments.\n */\nexport const PluginDepartmentId = z.union([\n PluginWellKnownDepartmentId,\n z.uuid(),\n])\n/** The department-id value union (same-name type alias for positional signatures). */\nexport type PluginDepartmentId = z.infer<typeof PluginDepartmentId>\n\n/**\n * Arguments for `update`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaceId` | `string` | The space ID to update |\n * | `properties` | `object` | Key/value pairs of properties to update (all optional) |\n * | `properties.room_type` | `string?` | New room type label |\n * | `properties.massType` | {@linkcode PluginMassType}`?` | New mass type |\n * | `properties.departmentId` | {@linkcode PluginDepartmentId}`?` | New department ID |\n */\nexport const PluginSpaceUpdateArgs = z.object({\n spaceId: z.string(),\n properties: z.object({\n room_type: z.string().optional(),\n massType: PluginMassType.optional(),\n spaceType: PluginSpaceType.optional(),\n areaClass: PluginAreaClass.optional(),\n departmentId: PluginDepartmentId.optional(),\n }),\n})\n\nexport type PluginSpaceUpdateArgs = z.infer<typeof PluginSpaceUpdateArgs>\n\n/**\n * Result of `update`.\n *\n * Echoes back the `spaceId` and the values of properties that were updated.\n * Properties that were not included in the update request will be `undefined`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaceId` | `string` | The space ID that was updated |\n * | `room_type` | `string?` | Updated room type (if changed) |\n * | `massType` | `string?` | Updated mass type (if changed) |\n * | `spaceType` | `string?` | Updated space type (if changed) |\n * | `departmentId` | `string \\| null?` | Updated department ID (if changed) |\n */\nexport const PluginSpaceUpdateResult = z.object({\n spaceId: z.string(),\n room_type: z.string().optional(),\n massType: z.string().optional(),\n spaceType: z.string().optional(),\n areaClass: PluginAreaClass.optional(),\n departmentId: z.string().nullable().optional(),\n})\n\nexport type PluginSpaceUpdateResult = z.infer<typeof PluginSpaceUpdateResult>\n\n// ---------------------------------------------------------------------------\n// Bulk operations\n// ---------------------------------------------------------------------------\n\n/**\n * A single item for `bulkCreate`.\n *\n * Discriminated on `kind`:\n * - `\"rectangular\"` — a box defined by `position` + `dimensions` (mirrors\n * {@linkcode PluginSpaceCreateRectangularArgs})\n * - `\"profile\"` — an extruded `profile` (with optional `innerProfiles`) by\n * `extrudeHeight` at `position` (mirrors {@linkcode PluginSpaceCreateFromProfileArgs})\n *\n * Every item may optionally set `room_type`, `spaceType`, `massType`, and\n * `departmentId`; these are applied within the same single undo step as the\n * creation.\n */\nexport const PluginSpaceBulkCreateItem = z.discriminatedUnion(\"kind\", [\n z.object({\n kind: z.literal(\"rectangular\"),\n position: Vec3Handle,\n dimensions: z.object({\n width: z.number(),\n height: z.number(),\n depth: z.number(),\n }),\n room_type: z.string().optional(),\n spaceType: PluginSpaceType.optional(),\n massType: PluginMassType.optional(),\n departmentId: PluginDepartmentId.optional(),\n }),\n z.object({\n kind: z.literal(\"profile\"),\n profile: ProfileHandle,\n innerProfiles: z.array(ProfileHandle).optional(),\n extrudeHeight: z.number(),\n position: Vec3Handle,\n room_type: z.string().optional(),\n spaceType: PluginSpaceType.optional(),\n massType: PluginMassType.optional(),\n departmentId: PluginDepartmentId.optional(),\n }),\n])\n\nexport type PluginSpaceBulkCreateItem = z.infer<typeof PluginSpaceBulkCreateItem>\n\n/**\n * Arguments for `bulkCreate`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `items` | {@linkcode PluginSpaceBulkCreateItem}`[]` | Spaces to create |\n */\nexport const PluginSpaceBulkCreateArgs = z.object({\n items: z.array(PluginSpaceBulkCreateItem),\n})\n\nexport type PluginSpaceBulkCreateArgs = z.infer<typeof PluginSpaceBulkCreateArgs>\n\n/**\n * Result of `bulkCreate`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaceIds` | `string[]` | IDs of the created spaces, in input order |\n */\nexport const PluginSpaceBulkCreateResult = z.object({\n spaceIds: z.array(z.string()),\n})\n\nexport type PluginSpaceBulkCreateResult = z.infer<\n typeof PluginSpaceBulkCreateResult\n>\n\n/**\n * A single item for `bulkUpdate`.\n *\n * Targets an existing space by `spaceId`. Geometry fields `profile` and\n * `extrudeHeight` must be supplied **together** (mirrors\n * {@linkcode PluginSpaceUpdateGeometryFromProfileArgs}). Property changes are\n * nested under `properties`, mirroring {@linkcode PluginSpaceUpdateArgs}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaceId` | `string` | The space to update |\n * | `profile` | {@linkcode ProfileHandle}`?` | New floor shape handle (requires `extrudeHeight`) |\n * | `extrudeHeight` | `number?` | New extrusion height (requires `profile`) |\n * | `properties` | `object?` | Property changes (same shape as {@linkcode PluginSpaceUpdateArgs.properties}) |\n */\nexport const PluginSpaceBulkUpdateItem = z.object({\n spaceId: z.string(),\n profile: ProfileHandle.optional(),\n extrudeHeight: z.number().optional(),\n properties: z\n .object({\n room_type: z.string().optional(),\n massType: PluginMassType.optional(),\n spaceType: PluginSpaceType.optional(),\n areaClass: PluginAreaClass.optional(),\n departmentId: PluginDepartmentId.optional(),\n })\n .optional(),\n})\n\nexport type PluginSpaceBulkUpdateItem = z.infer<typeof PluginSpaceBulkUpdateItem>\n\n/**\n * Arguments for `bulkUpdate`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `items` | {@linkcode PluginSpaceBulkUpdateItem}`[]` | Spaces to update |\n */\nexport const PluginSpaceBulkUpdateArgs = z.object({\n items: z.array(PluginSpaceBulkUpdateItem),\n})\n\nexport type PluginSpaceBulkUpdateArgs = z.infer<typeof PluginSpaceBulkUpdateArgs>\n\n/**\n * Result of `bulkUpdate`.\n *\n * Echoes one {@linkcode PluginSpaceUpdateResult} per item, in input order —\n * the same per-space shape returned by `update`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaces` | {@linkcode PluginSpaceUpdateResult}`[]` | Per-item updated values, in input order |\n */\nexport const PluginSpaceBulkUpdateResult = z.object({\n spaces: z.array(PluginSpaceUpdateResult),\n})\n\nexport type PluginSpaceBulkUpdateResult = z.infer<\n typeof PluginSpaceBulkUpdateResult\n>\n\n/**\n * Arguments for `bulkDelete`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaceIds` | `string[]` | The space IDs to delete |\n */\nexport const PluginSpaceBulkDeleteArgs = z.object({\n spaceIds: z.array(z.string()),\n})\n\nexport type PluginSpaceBulkDeleteArgs = z.infer<typeof PluginSpaceBulkDeleteArgs>\n\n/**\n * Result of `bulkDelete`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `deletedSpaceIds` | `string[]` | The space IDs that were removed |\n */\nexport const PluginSpaceBulkDeleteResult = z.object({\n deletedSpaceIds: z.array(z.string()),\n})\n\nexport type PluginSpaceBulkDeleteResult = z.infer<\n typeof PluginSpaceBulkDeleteResult\n>\n","import * as z from \"zod\"\nimport { ComponentHandle, Vec3Handle } from \"../../../handles\"\n\n/**\n * The shared field bag every hosted opening (door / window) carries — used by\n * {@linkcode PluginDesignCreateOpeningOptions} and by the bulk item schemas in\n * `bulk-items.ts`, so singular and plural openings validate identically.\n *\n * `facing` and `hinge` are WORLD POINTS, not directions: `facing` sits on the\n * side of the wall the leaf swings open to (where the plan symbol draws the\n * swing arc); `hinge` sits near the jamb the leaf is hinged on — the item is\n * reflected along the wall so its hinged jamb is the one nearer that point.\n * Either may be omitted (engine-chosen side / the catalog item's authored\n * hinge side). Neither has a visible effect on symmetric items such as\n * sliders or fixed windows.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `catalogId` | `string` | Library id: team `_id` or general `fullName` |\n * | `hostWall` | {@linkcode ComponentHandle} | The wall to host the opening |\n * | `position` | {@linkcode Vec3Handle} | World point projected onto the wall |\n * | `facing` | {@linkcode Vec3Handle}? | World point on the side of the wall the opening swings open to (default engine-chosen) |\n * | `hinge` | {@linkcode Vec3Handle}? | World point near the jamb the opening is hinged on (default the catalog item's authored side) |\n * | `label` | `string`? | Instance name (optional) |\n * | `width` | `number`? | Width override (Snaptrude units, > 0; default the catalog item's) |\n * | `height` | `number`? | Height override (Snaptrude units, > 0; default the catalog item's) |\n */\nexport const PluginOpeningBaseOptions = {\n catalogId: z.string().min(1),\n hostWall: ComponentHandle,\n position: Vec3Handle,\n facing: Vec3Handle.optional(),\n hinge: Vec3Handle.optional(),\n label: z.string().optional(),\n width: z.number().finite().positive().optional(),\n height: z.number().finite().positive().optional(),\n}\n","import * as z from \"zod\"\nimport { ContourHandle, ProfileHandle, Vec3Handle } from \"../../../handles\"\nimport { PluginOpeningBaseOptions } from \"./opening-fields\"\n\n/**\n * Maximum items per bulk `design.create.*` call. A bounded request keeps\n * validation, command capture and rollback work finite; split a larger import\n * into separate awaited batches, each with its own undo entry.\n */\nexport const PLUGIN_CREATE_BATCH_LIMIT = 1000\n\n// ---------------------------------------------------------------------------\n// wallRuns\n// ---------------------------------------------------------------------------\n\n/**\n * One wall run to create via {@linkcode PluginDesignCreateApi.wallRuns} — the\n * same inputs as {@linkcode PluginDesignCreateApi.walls}, one run per item.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | Ordered curve chain (wall centerlines) |\n * | `height` | `number`? | Wall height (Snaptrude units, > 0; default engine default) |\n * | `thickness` | `number`? | Wall thickness (Snaptrude units, > 0; default: `wallType`'s total layer thickness when given, else engine default) |\n * | `wallType` | `string`? | Wall type name from `design.types.list(\"wall\")` — layers/material/thickness defaults (default generic) |\n * | `storey` | `number`? (int) | Target storey (must exist) — the run's walls are assigned + lifted to its base in the same undo entry (default: geometry-derived) |\n */\nexport const PluginCreateWallRunItem = z.object({\n profile: ProfileHandle,\n height: z.number().optional(),\n thickness: z.number().optional(),\n wallType: z.string().min(1).optional(),\n storey: z.number().int().optional(),\n})\nexport type PluginCreateWallRunItem = z.infer<typeof PluginCreateWallRunItem>\n\n/** The `items` array of {@linkcode PluginDesignCreateApi.wallRuns} (1…1000). */\nexport const PluginCreateWallRunItems = z\n .array(PluginCreateWallRunItem)\n .min(1)\n .max(PLUGIN_CREATE_BATCH_LIMIT)\nexport type PluginCreateWallRunItems = z.infer<typeof PluginCreateWallRunItems>\n\n// ---------------------------------------------------------------------------\n// floors\n// ---------------------------------------------------------------------------\n\n/**\n * One floor to create via {@linkcode PluginDesignCreateApi.floors} — the same\n * footprint-extrude inputs as {@linkcode PluginDesignCreateApi.floor}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `contour` | {@linkcode ContourHandle} | Footprint (outer profile + holes) |\n * | `thickness` | `number` | Floor thickness (Snaptrude units, > 0) |\n * | `position` | {@linkcode Vec3Handle}? | Offset from origin (default origin) |\n */\nexport const PluginCreateFloorItem = z.object({\n contour: ContourHandle,\n thickness: z.number(),\n position: Vec3Handle.optional(),\n})\nexport type PluginCreateFloorItem = z.infer<typeof PluginCreateFloorItem>\n\n/** The `items` array of {@linkcode PluginDesignCreateApi.floors} (1…1000). */\nexport const PluginCreateFloorItems = z\n .array(PluginCreateFloorItem)\n .min(1)\n .max(PLUGIN_CREATE_BATCH_LIMIT)\nexport type PluginCreateFloorItems = z.infer<typeof PluginCreateFloorItems>\n\n// ---------------------------------------------------------------------------\n// doors / windows\n// ---------------------------------------------------------------------------\n\n/**\n * One door to place via {@linkcode PluginDesignCreateApi.doors} — the same\n * fields as a `\"door\"` {@linkcode PluginDesignCreateOpeningOptions}, so the\n * size overrides singular {@linkcode PluginDesignCreateApi.door} lacks are\n * available per item. `facing` and `hinge` are WORLD POINTS, not directions:\n * `facing` on the side of the wall the door swings open to, `hinge` near the\n * jamb it is hinged on. Rejects unknown fields.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `catalogId` | `string` | Library id: team `_id` or general `fullName` |\n * | `hostWall` | {@linkcode ComponentHandle} | The wall to host the door |\n * | `position` | {@linkcode Vec3Handle} | World point projected onto the wall |\n * | `facing` | {@linkcode Vec3Handle}? | World point on the side the door swings open to (default engine-chosen) |\n * | `hinge` | {@linkcode Vec3Handle}? | World point near the jamb the door is hinged on (default the catalog item's authored side) |\n * | `label` | `string`? | Instance name (optional) |\n * | `width` | `number`? | Width override (Snaptrude units, > 0; default the catalog item's) |\n * | `height` | `number`? | Height override (Snaptrude units, > 0; default the catalog item's) |\n */\nexport const PluginCreateDoorItem = z\n .object({ ...PluginOpeningBaseOptions })\n .strict()\nexport type PluginCreateDoorItem = z.infer<typeof PluginCreateDoorItem>\n\n/** The `items` array of {@linkcode PluginDesignCreateApi.doors} (1…1000). */\nexport const PluginCreateDoorItems = z\n .array(PluginCreateDoorItem)\n .min(1)\n .max(PLUGIN_CREATE_BATCH_LIMIT)\nexport type PluginCreateDoorItems = z.infer<typeof PluginCreateDoorItems>\n\n/**\n * One window to place via {@linkcode PluginDesignCreateApi.windows} — the same\n * fields as a `\"window\"` {@linkcode PluginDesignCreateOpeningOptions}.\n * `sillHeight` is a nonnegative Snaptrude-unit distance from the host wall's\n * base to the BOTTOM of the window, not to its center. `facing` and `hinge`\n * are WORLD POINTS, not directions: `facing` on the side of the wall the\n * window opens to, `hinge` near the jamb it is hinged on. Rejects unknown\n * fields.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `catalogId` | `string` | Library id: team `_id` or general `fullName` |\n * | `hostWall` | {@linkcode ComponentHandle} | The wall to host the window |\n * | `position` | {@linkcode Vec3Handle} | World point projected onto the wall |\n * | `facing` | {@linkcode Vec3Handle}? | World point on the side the window opens to (default engine-chosen) |\n * | `hinge` | {@linkcode Vec3Handle}? | World point near the jamb the window is hinged on (default the catalog item's authored side) |\n * | `label` | `string`? | Instance name (optional) |\n * | `width` | `number`? | Width override (Snaptrude units, > 0; default the catalog item's) |\n * | `height` | `number`? | Height override (Snaptrude units, > 0; default the catalog item's) |\n * | `sillHeight` | `number`? | Wall base to window bottom (Snaptrude units, ≥ 0; default the catalog item's) |\n */\nexport const PluginCreateWindowItem = z\n .object({\n ...PluginOpeningBaseOptions,\n sillHeight: z.number().finite().nonnegative().optional(),\n })\n .strict()\nexport type PluginCreateWindowItem = z.infer<typeof PluginCreateWindowItem>\n\n/** The `items` array of {@linkcode PluginDesignCreateApi.windows} (1…1000). */\nexport const PluginCreateWindowItems = z\n .array(PluginCreateWindowItem)\n .min(1)\n .max(PLUGIN_CREATE_BATCH_LIMIT)\nexport type PluginCreateWindowItems = z.infer<typeof PluginCreateWindowItems>\n\n// ---------------------------------------------------------------------------\n// furnitureItems\n// ---------------------------------------------------------------------------\n\n/**\n * One furniture instance to place via\n * {@linkcode PluginDesignCreateApi.furnitureItems} — the same inputs as\n * {@linkcode PluginDesignCreateApi.furniture} minus `createNewSourceMesh`: in a\n * batch the host owns source-mesh persistence (a source is recorded once per\n * call, however many items reuse it).\n *\n * `position.y` is the REST elevation: the item is grounded so its bounding-box\n * base sits exactly at `position.y` (the same surface-flush contract as\n * interactive drag-drop) — pass the floor/storey elevation to stand furniture\n * on it; never add half the item's height yourself.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `catalogId` | `string` | Library id: team `_id` or general `fullName` |\n * | `position` | {@linkcode Vec3Handle} | Absolute world placement point (`y` = rest elevation) |\n * | `label` | `string`? | Instance name and readable label (default auto `${name}Ins${n}`) |\n * | `angleInDegrees` | `number`? | Rotation about the vertical axis, in degrees (default unrotated) |\n */\nexport const PluginCreateFurnitureItem = z.object({\n catalogId: z.string().min(1),\n position: Vec3Handle,\n label: z.string().optional(),\n angleInDegrees: z.number().optional(),\n})\nexport type PluginCreateFurnitureItem = z.infer<\n typeof PluginCreateFurnitureItem\n>\n\n/**\n * The `items` array of {@linkcode PluginDesignCreateApi.furnitureItems}\n * (1…1000).\n */\nexport const PluginCreateFurnitureItems = z\n .array(PluginCreateFurnitureItem)\n .min(1)\n .max(PLUGIN_CREATE_BATCH_LIMIT)\nexport type PluginCreateFurnitureItems = z.infer<\n typeof PluginCreateFurnitureItems\n>\n","import * as z from \"zod\"\nimport { ComponentHandle, CurveHandle, Vec3Handle } from \"../../../handles\"\n\n// ---------------------------------------------------------------------------\n// Shared grid style vocabulary\n// ---------------------------------------------------------------------------\n\n/**\n * Grid line style (mirrors the engine `LineMode` values verbatim — the same\n * vocabulary as {@linkcode PluginReferenceLineStyle}).\n */\nexport const PluginGridLineStyle = z.enum([\"SOLID\", \"DASHED\", \"DOT_DASH\"])\nexport type PluginGridLineStyle = z.infer<typeof PluginGridLineStyle>\n\n/**\n * Optional visual styling shared by every grid creator (and reused as the\n * patch shape by `design.update.grid`). All fields optional; engine defaults\n * apply when omitted.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `style` | {@linkcode PluginGridLineStyle}? | Line style |\n * | `weight` | `number`? | Line weight (> 0) |\n * | `color` | `string`? | Line color as a `#rrggbb` hex string |\n * | `showStart` | `boolean`? | Show the start-end tag bubble |\n * | `showEnd` | `boolean`? | Show the end-end tag bubble |\n */\nexport const PluginGridStyleOptions = z.object({\n style: PluginGridLineStyle.optional(),\n weight: z.number().positive().optional(),\n color: z\n .string()\n .regex(/^#[0-9a-fA-F]{6}$/, \"color must be a #rrggbb hex string\")\n .optional(),\n showStart: z.boolean().optional(),\n showEnd: z.boolean().optional(),\n})\nexport type PluginGridStyleOptions = z.infer<typeof PluginGridStyleOptions>\n\n// ---------------------------------------------------------------------------\n// create.grid — free rectangular grid\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.grid}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `linesX` | `number` | Line count of the first family (integer ≥ 1) |\n * | `linesY` | `number` | Line count of the second family (integer ≥ 1) |\n * | `spacingX` | `number` | Spacing of the first family (Snaptrude units, > 0) |\n * | `spacingY` | `number` | Spacing of the second family (Snaptrude units, > 0) |\n * | `angle` | `number`? | Rotation in degrees, CCW about +Y (default 0) |\n * | `center` | {@linkcode Vec3Handle}? | Placement centre; defaults to the native viewport-centre behavior |\n * | …style | {@linkcode PluginGridStyleOptions} | Optional line styling |\n */\nexport const PluginDesignCreateGridArgs = z\n .object({\n linesX: z.number().int().min(1),\n linesY: z.number().int().min(1),\n spacingX: z.number().positive().finite(),\n spacingY: z.number().positive().finite(),\n angle: z.number().finite().optional(),\n center: Vec3Handle.optional(),\n })\n .merge(PluginGridStyleOptions)\nexport type PluginDesignCreateGridArgs = z.infer<typeof PluginDesignCreateGridArgs>\n\n// ---------------------------------------------------------------------------\n// create.gridFromCurve — single-curve grid\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.gridFromCurve}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The grid line's geometry — a line or arc (circles are rejected) |\n * | `tag` | `string`? | The line's grid tag (defaults to the draw tool's next tag) |\n * | …style | {@linkcode PluginGridStyleOptions} | Optional line styling |\n */\nexport const PluginDesignCreateGridFromCurveArgs = z\n .object({\n curve: CurveHandle,\n tag: z.string().min(1).optional(),\n })\n .merge(PluginGridStyleOptions)\nexport type PluginDesignCreateGridFromCurveArgs = z.infer<\n typeof PluginDesignCreateGridFromCurveArgs\n>\n\n// ---------------------------------------------------------------------------\n// create.gridsFromMasses — fitted grids\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.gridsFromMasses}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `masses` | {@linkcode ComponentHandle}`[]` | Masses whose footprints seat the grids |\n * | `spacingX` | `number` | First-family spacing (Snaptrude units, > 0) |\n * | `spacingY` | `number` | Second-family spacing (Snaptrude units, > 0) |\n * | …style | {@linkcode PluginGridStyleOptions} | Optional line styling |\n */\nexport const PluginDesignCreateGridsFromMassesArgs = z\n .object({\n masses: z.array(ComponentHandle).min(1),\n spacingX: z.number().positive().finite(),\n spacingY: z.number().positive().finite(),\n })\n .merge(PluginGridStyleOptions)\nexport type PluginDesignCreateGridsFromMassesArgs = z.infer<\n typeof PluginDesignCreateGridsFromMassesArgs\n>\n\n// ---------------------------------------------------------------------------\n// create.columnsFromGrids — columns at line intersections\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginDesignCreateApi.columnsFromGrids}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `grids` | {@linkcode ComponentHandle}`[]` | Grid groups whose line intersections seat the columns |\n * | `width` | `number` | Column width (Snaptrude units, > 0) |\n * | `depth` | `number` | Column depth (Snaptrude units, > 0) |\n */\nexport const PluginDesignCreateColumnsFromGridsArgs = z.object({\n grids: z.array(ComponentHandle).min(1),\n width: z.number().positive().finite(),\n depth: z.number().positive().finite(),\n})\nexport type PluginDesignCreateColumnsFromGridsArgs = z.infer<\n typeof PluginDesignCreateColumnsFromGridsArgs\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { ComponentHandle, BBoxComponents } from \"../../../handles\"\nimport {\n PluginSpaceType,\n PluginMassType,\n PluginDepartmentId,\n PluginAreaClass,\n} from \"../../entity/space\"\nimport { PluginStaircasePreset } from \"../create\"\nimport { PluginDesignQueryGeometryApi } from \"./geometry\"\nimport { PluginDesignQuerySpacesApi } from \"./spaces\"\nimport { PluginDesignQueryReferenceLinesApi } from \"./referenceLines\"\nimport { PluginDesignQueryGridsApi } from \"./grids\"\n\n// ---------------------------------------------------------------------------\n// Entity type vocabulary\n// ---------------------------------------------------------------------------\n\n/**\n * The kind of a BIM entity — the discovery/filter vocabulary for the whole\n * `design.query.*` surface. Values are clean `lowerCamel` tokens; the host maps\n * each to the engine's internal type (whose raw strings are case-inconsistent —\n * `\"roof\"`/`\"slab\"` lowercase, `\"Wall\"`/`\"Door\"` PascalCase — never surfaced here).\n *\n * | Value | Entity |\n * |---|---|\n * | `\"wall\"` | Wall |\n * | `\"space\"` | Space (a room mass) |\n * | `\"mass\"` | Generic mass (non-room) |\n * | `\"door\"` | Door |\n * | `\"window\"` | Window |\n * | `\"floor\"` | Floor |\n * | `\"slab\"` | Slab |\n * | `\"roof\"` | Roof |\n * | `\"column\"` | Column |\n * | `\"beam\"` | Beam |\n * | `\"ceiling\"` | Ceiling |\n * | `\"staircase\"` | Staircase |\n * | `\"furniture\"` | Furniture |\n * | `\"referenceLine\"` | Reference line |\n * | `\"curtainWall\"` | Parametric curtain wall |\n * | `\"mullion\"` | Curtain-wall mullion |\n * | `\"panel\"` | Curtain-wall panel |\n * | `\"parametricComponent\"` | A placed parametric family instance (native or Revit-imported). Its geometry is rebuilt from `design.family` parameters, so read and drive it through `design.family.getParameters` / `setParameters` rather than editing its meshes. |\n * | `\"grid\"` | Structural grid group |\n */\nexport const PluginEntityType = z.enum([\n \"wall\",\n \"space\",\n \"mass\",\n \"door\",\n \"window\",\n \"floor\",\n \"slab\",\n \"roof\",\n \"column\",\n \"beam\",\n \"ceiling\",\n \"staircase\",\n \"furniture\",\n \"referenceLine\",\n \"curtainWall\",\n \"mullion\",\n \"panel\",\n \"parametricComponent\",\n \"grid\",\n])\nexport type PluginEntityType = z.infer<typeof PluginEntityType>\n\n// ---------------------------------------------------------------------------\n// The shared filter — one options object, every list* method\n// ---------------------------------------------------------------------------\n\n/**\n * `EntityFilterOptions` — the **single filter** shared by every `design.query.list*`\n * method, so filtering is identical across all BIM object types. Every field is\n * optional and **AND-combined**: a component must satisfy all provided fields.\n * Plural fields are arrays (match any of); singular fields are scalars. A field\n * that names a type-specific dimension (`massTypes`/`spaceTypes`/`departmentId`)\n * excludes components that lack that dimension.\n *\n * | Field | Type | Keeps components where |\n * |---|---|---|\n * | `types` | {@linkcode PluginEntityType}`[]` | entity kind ∈ `types` (ignored by per-type `list*` — the method fixes the kind) |\n * | `storeys` | `number[]` | storey/floor level ∈ `storeys` (negative = basement) |\n * | `buildingIds` | `string[]` | building ∈ `buildingIds` |\n * | `structureIds` | `string[]` | structure ∈ `structureIds` |\n * | `label` | `string` | label (`room_type` ?? mesh label) === `label` |\n * | `massTypes` | {@linkcode PluginMassType}`[]` | mass-type ∈ (masses only) |\n * | `spaceTypes` | {@linkcode PluginSpaceType}`[]` | space-type ∈ (spaces only) |\n * | `departmentId` | {@linkcode PluginDepartmentId} | department === |\n * | `isSelected` | `boolean` | current selection state === |\n * | `isLocked` | `boolean` | lock flag === |\n * | `isHidden` | `boolean` | user-hidden flag === |\n * | `within` | {@linkcode BBoxComponents} | the entity's bounding box intersects the region |\n * | `overlaps` | {@linkcode ComponentHandle} | the entity's plan footprint overlaps the given space's bottom-face footprint — non-zero intersection area, so fully-inside, boundary-nested, and partially-overhanging entities all match; edge-touching with zero interior overlap does not. Exact polygon intersection (holes respected). Tested in plan — storey height is ignored; compose with `storeys` to scope a floor. Hidden entities are excluded by default — pass `isHidden` explicitly to override. The reference space itself is never returned. Throws on an unknown id. Entities without a brep footprint use their bounding-box plan rectangle. |\n * | `ids` | {@linkcode ComponentHandle}`[]` | id ∈ `ids` (restrict to a known set) |\n */\nexport const PluginEntityFilter = z.object({\n types: z.array(PluginEntityType).optional(),\n storeys: z.array(z.number()).optional(),\n buildingIds: z.array(z.string()).optional(),\n structureIds: z.array(z.string()).optional(),\n label: z.string().optional(),\n massTypes: z.array(PluginMassType).optional(),\n spaceTypes: z.array(PluginSpaceType).optional(),\n departmentId: PluginDepartmentId.optional(),\n isSelected: z.boolean().optional(),\n isLocked: z.boolean().optional(),\n isHidden: z.boolean().optional(),\n within: BBoxComponents.optional(),\n overlaps: ComponentHandle.optional(),\n ids: z.array(ComponentHandle).optional(),\n})\nexport type PluginEntityFilter = z.infer<typeof PluginEntityFilter>\n\n/**\n * Arguments for {@linkcode PluginDesignQueryApi.listByType} — a required entity\n * `type` plus every {@linkcode PluginEntityFilter} field.\n */\nexport const PluginDesignQueryListByTypeArgs = PluginEntityFilter.extend({\n type: PluginEntityType,\n})\nexport type PluginDesignQueryListByTypeArgs = z.infer<\n typeof PluginDesignQueryListByTypeArgs\n>\n\n// ---------------------------------------------------------------------------\n// Single-entity argument shapes\n// ---------------------------------------------------------------------------\n\n/** Arguments for methods that read one entity by its {@linkcode ComponentHandle}. */\nexport const PluginDesignQueryComponentArgs = z.object({\n component: ComponentHandle,\n})\nexport type PluginDesignQueryComponentArgs = z.infer<\n typeof PluginDesignQueryComponentArgs\n>\n\n/** Arguments for methods that look an entity up by id. */\nexport const PluginDesignQueryIdArgs = z.object({\n id: ComponentHandle,\n})\nexport type PluginDesignQueryIdArgs = z.infer<typeof PluginDesignQueryIdArgs>\n\n/** Arguments for methods over a set of entities (e.g. the union bounding box). */\nexport const PluginDesignQueryComponentsArgs = z.object({\n components: z.array(ComponentHandle),\n})\nexport type PluginDesignQueryComponentsArgs = z.infer<\n typeof PluginDesignQueryComponentsArgs\n>\n\n// ---------------------------------------------------------------------------\n// Read records\n// ---------------------------------------------------------------------------\n\n/**\n * The common property record returned by {@linkcode PluginDesignQueryApi.getProperties} —\n * a **partial** object (a field is present only when it applies to the entity).\n * Type-specific and geometry reads live on {@linkcode PluginDesignQueryApi.measure}\n * and `design.query.geometry.*`, keeping this record generic across every type.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | The entity id |\n * | `type` | {@linkcode PluginEntityType} | Entity kind |\n * | `label` | `string` | `room_type` ?? mesh label ?? `\"\"` |\n * | `storey` | `number` | Storey/floor level |\n * | `buildingId` | `string` | Owning building |\n * | `structureId` | `string` | Owning structure |\n * | `massType` | `string` | Mass-type (masses only) |\n * | `spaceType` | {@linkcode PluginSpaceType} | Space-type (spaces only) |\n * | `areaClass` | {@linkcode PluginAreaClass} | Area classification (spaces only) |\n * | `departmentId` | `string` | Department (masses only) |\n * | `buildingType` | `string` | Assigned building type NAME (walls / slabs / floors / roofs / ceilings only) — the Object Properties panel's Wall/Slab/Floor/Roof/Ceiling Type dropdown value, matching the names from `design.types.list(kind)` |\n * | `isLocked` | `boolean` | Lock flag |\n * | `isHidden` | `boolean` | User-hidden flag |\n * | `isSelected` | `boolean` | Current selection state |\n * | `boundingBox` | {@linkcode BBoxComponents} | World-space AABB |\n * | `adjacency` | `{ spaceId: string; value: number }[]` | Adjacency relationships (spaces/masses only). `value`: `2` = Direct (required), `1` = Indirect or physical contact, `-1` = Restricted; `0` entries are filtered out. `[]` when adjacency has never been computed — see `program.adjacency.compute`. |\n */\nexport const PluginEntityProperties = z\n .object({\n id: z.string(),\n type: PluginEntityType,\n label: z.string(),\n storey: z.number(),\n buildingId: z.string(),\n structureId: z.string(),\n massType: z.string(),\n spaceType: PluginSpaceType,\n areaClass: PluginAreaClass,\n departmentId: z.string(),\n buildingType: z.string(),\n isLocked: z.boolean(),\n isHidden: z.boolean(),\n isSelected: z.boolean(),\n boundingBox: BBoxComponents,\n adjacency: z.array(z.object({ spaceId: z.string(), value: z.number() })),\n })\n .partial()\nexport type PluginEntityProperties = z.infer<typeof PluginEntityProperties>\n\n/**\n * The multi-metric bundle returned by {@linkcode PluginDesignQueryApi.measure}.\n * `boundingBox` is always present; the numeric metrics appear only when the\n * entity's type computes them (area: floors/masses/slabs/roofs/beams/columns;\n * volume: all; length: walls/beams).\n *\n * `thickness` and `height` are the Object Properties panel's dimension reads\n * (the same engine source `design.update.wall` / `design.update.slab` etc.\n * write through), in engine units. `thickness`: walls (the plan thickness —\n * present only for straight orthogonal walls, exactly when the panel shows it)\n * and slabs/floors/roofs/ceilings (the vertical extent the panel calls\n * Thickness). `height`: walls only.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `area` | `number`? | Bottom-face area |\n * | `volume` | `number`? | Solid volume |\n * | `length` | `number`? | Running length (linear elements) |\n * | `thickness` | `number`? | Panel Thickness — walls (straight orthogonal only) and the slab family |\n * | `height` | `number`? | Panel Height — walls only |\n * | `boundingBox` | {@linkcode BBoxComponents} | World-space AABB (always present) |\n */\nexport const PluginEntityMeasurements = z.object({\n area: z.number().optional(),\n volume: z.number().optional(),\n length: z.number().optional(),\n thickness: z.number().optional(),\n height: z.number().optional(),\n boundingBox: BBoxComponents,\n})\nexport type PluginEntityMeasurements = z.infer<typeof PluginEntityMeasurements>\n\n/**\n * The parametric properties of a staircase — the same editable fields the\n * staircase properties panel exposes. Returned by\n * {@linkcode PluginDesignQueryApi.getStaircaseParams}; the write side is\n * `design.update.staircase`. Engine internals (block schema, min well size,\n * square-well padding) are deliberately excluded.\n *\n * All dimensions are in **engine units** (the same convention as\n * `design.doors.getWidth`); `steps` is a unitless count. The invariant\n * `staircaseHeight = steps × riser` is governed by the two lock booleans — see\n * `design.update.staircase`. Type-specific fields (`wellSize`, `landingWidth`,\n * `flightStartDistAfterTurn`) are present only when the preset uses them.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `staircaseType` | {@linkcode PluginStaircasePreset} | Base staircase type |\n * | `staircasePreset` | {@linkcode PluginStaircasePreset} | Named parametric preset |\n * | `staircaseHeight` | `number` | Total rise (engine units) |\n * | `steps` | `number` | Step count (unitless) |\n * | `riser` | `number` | Riser height (engine units) |\n * | `tread` | `number` | Tread depth (engine units) |\n * | `width` | `number` | Flight width (engine units) |\n * | `depth` | `number` | Slab/waist thickness (engine units) |\n * | `wellSize` | `number`? | Stairwell gap (dogLegged / openWell / square) |\n * | `landingWidth` | `number`? | Landing depth (straight / dogLegged) |\n * | `flightStartDistAfterTurn` | `number`? | Flight offset after a turn (dogLegged / lShaped / square) |\n * | `baseOffset` | `number` | Vertical offset from the storey base (engine units) |\n * | `isStoreyHeightUnlocked` | `boolean` | When `false`, height is tied to the storey height |\n * | `isStaircaseHeightUnlocked` | `boolean` | When `false`, height recomputes from steps × riser |\n * | `storey` | `number` | Storey/floor level the staircase sits on |\n */\nexport const PluginStaircaseParams = z.object({\n staircaseType: PluginStaircasePreset,\n staircasePreset: PluginStaircasePreset,\n staircaseHeight: z.number(),\n steps: z.number(),\n riser: z.number(),\n tread: z.number(),\n width: z.number(),\n depth: z.number(),\n wellSize: z.number().optional(),\n landingWidth: z.number().optional(),\n flightStartDistAfterTurn: z.number().optional(),\n baseOffset: z.number(),\n isStoreyHeightUnlocked: z.boolean(),\n isStaircaseHeightUnlocked: z.boolean(),\n storey: z.number(),\n})\nexport type PluginStaircaseParams = z.infer<typeof PluginStaircaseParams>\n\n// ---------------------------------------------------------------------------\n// The query API\n// ---------------------------------------------------------------------------\n\n/**\n * `design.query.*` — read-only enumeration + inspection of scene-committed BIM\n * entities (all-handle model, §11). No query mutates the scene.\n *\n * Every `list*` returns a snapshot {@linkcode ComponentHandle}`[]` (`[]`, never\n * `null`) filtered by the shared {@linkcode PluginEntityFilter}; per-entity reads\n * take a {@linkcode ComponentHandle} and return primitive records / child handles\n * or `null` when the entity is gone. Resolve details of a returned handle with\n * {@linkcode PluginDesignQueryApi.getProperties} / {@linkcode PluginDesignQueryApi.measure}\n * / `design.query.geometry.*`.\n *\n * Accessed via `snaptrude.design.query`.\n */\nexport abstract class PluginDesignQueryApi {\n /** Entity-geometry reads (brep, bottom contour). See {@linkcode PluginDesignQueryGeometryApi}. */\n public abstract geometry: PluginDesignQueryGeometryApi\n /** Space-specific reads (footprint, …). See {@linkcode PluginDesignQuerySpacesApi}. */\n public abstract spaces: PluginDesignQuerySpacesApi\n /** Reference-line-specific reads. See {@linkcode PluginDesignQueryReferenceLinesApi}. */\n public abstract referenceLines: PluginDesignQueryReferenceLinesApi\n /** Structural-grid-specific reads. See {@linkcode PluginDesignQueryGridsApi}. */\n public abstract grids: PluginDesignQueryGridsApi\n\n constructor() {}\n\n // ── enumeration ───────────────────────────────────────────────────────────\n\n /**\n * List **all** BIM entities in the project matching the filter.\n *\n * @param filter - an optional {@linkcode PluginEntityFilter}\n * @returns matching entities as {@linkcode ComponentHandle}`[]` (`[]` if none)\n *\n * @examplePrompt Show me everything in the model\n * @examplePrompt How many objects are on the ground floor?\n * @examplePrompt List all the doors and windows on level 2\n * @examplePrompt What elements are currently selected?\n * @examplePrompt Find every hidden object in the project\n *\n * # Example\n * ```ts\n * // Doors and windows on the ground storey (fields AND-combine)\n * const doorsAndWindows = await snaptrude.design.query.listEntities({\n * types: [\"door\", \"window\"],\n * storeys: [1],\n * })\n *\n * // Everything the user currently has selected\n * const selected = await snaptrude.design.query.listEntities({ isSelected: true })\n *\n * // Everything inside a world-space region (axis-aligned box, Snaptrude units)\n * const inRegion = await snaptrude.design.query.listEntities({\n * within: { min: { x: 0, y: 0, z: 0 }, max: { x: 10, y: 4, z: 8 } },\n * })\n * ```\n */\n public abstract listEntities(\n filter?: PluginEntityFilter,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * List all entities of one BIM object type — wall, door, slab, panel, and so\n * on — optionally narrowed by the shared filter.\n *\n * @param type - the {@linkcode PluginEntityType} to list\n * @param filter - an optional {@linkcode PluginEntityFilter}\n * @returns matching entities as {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt List all the curtain walls in the project\n * @examplePrompt Show every curtain wall panel on this storey\n * @examplePrompt Get all the elements of a single type, like columns\n * @examplePrompt Find all the locked slabs in the model\n *\n * # Example\n * ```ts\n * const lockedSlabs = await snaptrude.design.query.listByType(\"slab\", { isLocked: true })\n *\n * // Curtain-wall panels on storeys 2-3 of one building\n * const panels = await snaptrude.design.query.listByType(\"panel\", {\n * storeys: [2, 3],\n * buildingIds: [\"bld_1\"],\n * })\n * ```\n */\n public abstract listByType(\n type: PluginEntityType,\n filter?: PluginEntityFilter,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * List all the walls in the project, optionally filtered by storey, building,\n * label, selection state, or region.\n *\n * The `types` field of the filter is ignored (this method fixes the kind).\n *\n * @param filter - an optional {@linkcode PluginEntityFilter}\n * @returns wall {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt How many walls are in the project?\n * @examplePrompt List all the walls on the ground floor\n * @examplePrompt Which walls are currently selected?\n * @examplePrompt Find the walls on level 2 of building A\n * @examplePrompt Show me every hidden wall\n *\n * # Example\n * ```ts\n * const groundFloorWalls = await snaptrude.design.query.listWalls({ storeys: [1] })\n * ```\n */\n public abstract listWalls(\n filter?: PluginEntityFilter,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * List all the spaces (rooms) in the project, optionally filtered by storey,\n * department, space type, label, selection state, or region.\n *\n * @param filter - an optional {@linkcode PluginEntityFilter}\n * @returns space {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt How many rooms are in this project?\n * @examplePrompt List all the spaces on the second storey\n * @examplePrompt Which rooms are in the Bedrooms department?\n * @examplePrompt Find every room labelled Kitchen\n * @examplePrompt Show the spaces that are currently selected\n *\n * # Example\n * ```ts\n * // Balconies and decks on storey 2 (`label` is an exact match on room_type)\n * const outdoor = await snaptrude.design.query.listSpaces({\n * storeys: [2],\n * spaceTypes: [\"Balcony\", \"Deck\"],\n * })\n *\n * // Rooms in a department: resolve its id first, then filter and total the area\n * const { departments } = await snaptrude.program.departments.list()\n * const bedrooms = departments.find((d) => d.name === \"Bedrooms\")\n * if (bedrooms) {\n * const rooms = await snaptrude.design.query.listSpaces({ departmentId: bedrooms.id })\n * let total = 0\n * for (const room of rooms) {\n * const m = await snaptrude.design.query.measure(room)\n * total += m?.area ?? 0\n * }\n * }\n * ```\n */\n public abstract listSpaces(\n filter?: PluginEntityFilter,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * List all the generic masses (non-room massing volumes) in the project,\n * optionally filtered by storey, building, label, mass type, or region.\n *\n * @param filter - an optional {@linkcode PluginEntityFilter}\n * @returns mass {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt List all the masses in the model\n * @examplePrompt How many massing blocks are on the site?\n * @examplePrompt Show the generic masses on level 1\n * @examplePrompt Find every mass that isn't a room\n *\n * # Example\n * ```ts\n * // Only certain mass kinds — massTypes matches any of the given values\n * const massing = await snaptrude.design.query.listMasses({\n * massTypes: [\"Generic mass\", \"Plinth\"],\n * })\n * ```\n */\n public abstract listMasses(\n filter?: PluginEntityFilter,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * List all the doors in the project, optionally filtered by storey, building,\n * selection state, or region.\n *\n * @param filter - an optional {@linkcode PluginEntityFilter}\n * @returns door {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt How many doors are in the project?\n * @examplePrompt List all the doors on the ground floor\n * @examplePrompt Which doors are currently selected?\n * @examplePrompt Find every door in building B\n *\n * # Example\n * ```ts\n * // Ground-floor doors, then read each one's width via design.doors.*\n * const doors = await snaptrude.design.query.listDoors({ storeys: [1] })\n * for (const door of doors) {\n * const width = await snaptrude.design.doors.getWidth(door)\n * }\n * ```\n */\n public abstract listDoors(\n filter?: PluginEntityFilter,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * List all the windows in the project, optionally filtered by storey,\n * building, selection state, or region.\n *\n * @param filter - an optional {@linkcode PluginEntityFilter}\n * @returns window {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt How many windows does this building have?\n * @examplePrompt List every window on level 3\n * @examplePrompt Which windows are on the ground floor?\n * @examplePrompt Show all the windows that are currently selected\n *\n * # Example\n * ```ts\n * const level3Windows = await snaptrude.design.query.listWindows({ storeys: [3] })\n * ```\n */\n public abstract listWindows(\n filter?: PluginEntityFilter,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * List all the floor elements (floor plates / finishes) in the project,\n * optionally filtered by storey, building, lock state, or region.\n *\n * @param filter - an optional {@linkcode PluginEntityFilter}\n * @returns floor {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt List all the floor elements in the model\n * @examplePrompt How many floor plates are on the second storey?\n * @examplePrompt Which floors are locked?\n * @examplePrompt Show the floor finishes on level 0\n *\n * # Example\n * ```ts\n * const lockedFloors = await snaptrude.design.query.listFloors({ isLocked: true })\n * ```\n */\n public abstract listFloors(\n filter?: PluginEntityFilter,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * List all the slabs in the project, optionally filtered by storey, building,\n * lock state, or region.\n *\n * @param filter - an optional {@linkcode PluginEntityFilter}\n * @returns slab {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt List all the slabs in the project\n * @examplePrompt How many slabs are on level 2?\n * @examplePrompt Which slabs are locked?\n * @examplePrompt Show the slabs in the selected building\n *\n * # Example\n * ```ts\n * const storey2Slabs = await snaptrude.design.query.listSlabs({ storeys: [2] })\n * ```\n */\n public abstract listSlabs(\n filter?: PluginEntityFilter,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * List all the roofs in the project, optionally filtered by storey, building,\n * visibility, or region.\n *\n * @param filter - an optional {@linkcode PluginEntityFilter}\n * @returns roof {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt How many roofs does the model have?\n * @examplePrompt List all the roof elements\n * @examplePrompt Show the roofs on the top storey\n * @examplePrompt Find every hidden roof\n *\n * # Example\n * ```ts\n * const hiddenRoofs = await snaptrude.design.query.listRoofs({ isHidden: true })\n * ```\n */\n public abstract listRoofs(\n filter?: PluginEntityFilter,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * List all the columns in the project, optionally filtered by storey,\n * building, selection state, or region.\n *\n * @param filter - an optional {@linkcode PluginEntityFilter}\n * @returns column {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt How many columns are on the ground floor?\n * @examplePrompt List all the columns in building A\n * @examplePrompt Show every column on level 3\n * @examplePrompt Which columns are currently selected?\n *\n * # Example\n * ```ts\n * const groundFloorColumns = await snaptrude.design.query.listColumns({ storeys: [1] })\n * ```\n */\n public abstract listColumns(\n filter?: PluginEntityFilter,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * List all the beams in the project, optionally filtered by storey, building,\n * lock state, or region.\n *\n * @param filter - an optional {@linkcode PluginEntityFilter}\n * @returns beam {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt List all the beams in the project\n * @examplePrompt How many beams are on the second storey?\n * @examplePrompt Show the beams in this structure\n * @examplePrompt Find every locked beam\n *\n * # Example\n * ```ts\n * const storey2Beams = await snaptrude.design.query.listBeams({ storeys: [2] })\n * ```\n */\n public abstract listBeams(\n filter?: PluginEntityFilter,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * List all the ceilings in the project, optionally filtered by storey,\n * building, visibility, or region.\n *\n * @param filter - an optional {@linkcode PluginEntityFilter}\n * @returns ceiling {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt List all the ceilings in the model\n * @examplePrompt How many ceilings are on level 1?\n * @examplePrompt Which ceilings are hidden?\n * @examplePrompt Show the ceilings in building B\n *\n * # Example\n * ```ts\n * const hiddenCeilings = await snaptrude.design.query.listCeilings({ isHidden: true })\n * ```\n */\n public abstract listCeilings(\n filter?: PluginEntityFilter,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * List all the staircases in the project, optionally filtered by storey,\n * building, selection state, or region.\n *\n * @param filter - an optional {@linkcode PluginEntityFilter}\n * @returns staircase {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt How many staircases are in the building?\n * @examplePrompt List all the stairs in the project\n * @examplePrompt Show the staircases on the ground floor\n * @examplePrompt Which stairs are currently selected?\n *\n * # Example\n * ```ts\n * const groundFloorStairs = await snaptrude.design.query.listStaircases({ storeys: [1] })\n * ```\n */\n public abstract listStaircases(\n filter?: PluginEntityFilter,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * List all the furniture in the project, optionally filtered by storey,\n * building, label, selection state, or region.\n *\n * @param filter - an optional {@linkcode PluginEntityFilter}\n * @returns furniture {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt List all the furniture in the project\n * @examplePrompt How many furniture items are on this floor?\n * @examplePrompt Show the furniture inside the selected room\n * @examplePrompt Which furniture is on storey 2?\n *\n * # Example\n * ```ts\n * // Furniture whose plan footprint overlaps a room (`overlaps` is plan-only —\n * // scope the storey too, or furniture stacked on other floors matches)\n * const [kitchen] = await snaptrude.design.query.listSpaces({ label: \"Kitchen\" })\n * if (kitchen) {\n * const inKitchen = await snaptrude.design.query.listFurniture({\n * overlaps: kitchen,\n * storeys: [1],\n * })\n * }\n * ```\n */\n public abstract listFurniture(\n filter?: PluginEntityFilter,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * List all the reference lines in the project, optionally filtered by storey,\n * building, or region.\n *\n * @param filter - an optional {@linkcode PluginEntityFilter}\n * @returns reference-line {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt List all the reference lines in the model\n * @examplePrompt Show the guide lines on the ground floor\n * @examplePrompt How many reference lines are on this storey?\n * @examplePrompt Find the setting-out lines in building A\n *\n * # Example\n * ```ts\n * const groundFloorRefLines = await snaptrude.design.query.listReferenceLines({ storeys: [1] })\n * ```\n */\n public abstract listReferenceLines(\n filter?: PluginEntityFilter,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * List all the curtain-wall mullions in the project, optionally filtered by\n * storey, building, selection state, or region.\n *\n * @param filter - an optional {@linkcode PluginEntityFilter}\n * @returns mullion {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt List all the mullions in the curtain wall\n * @examplePrompt How many mullions are on the facade?\n * @examplePrompt Show every curtain wall mullion on level 5\n * @examplePrompt Which mullions are currently selected?\n *\n * # Example\n * ```ts\n * const level5Mullions = await snaptrude.design.query.listMullions({ storeys: [5] })\n * ```\n */\n public abstract listMullions(\n filter?: PluginEntityFilter,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * List all the structural grids (grid groups) in the project, optionally\n * filtered by storey, building, selection state, or region.\n *\n * @param filter - an optional {@linkcode PluginEntityFilter}\n * @returns grid-group {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt List all the structural grids in the model\n * @examplePrompt How many grids are on the ground floor?\n * @examplePrompt Show the grids on this storey\n * @examplePrompt Which grids are currently selected?\n *\n * # Example\n * ```ts\n * const groundFloorGrids = await snaptrude.design.query.listGrids({ storeys: [1] })\n * ```\n */\n public abstract listGrids(\n filter?: PluginEntityFilter,\n ): PluginApiReturn<ComponentHandle[]>\n\n // ── single-entity access & identity ─────────────────────────────────────────\n\n /**\n * Look an entity up by id — returns the same handle if it still resolves live,\n * else `null`. A cheap validity probe (distinct from {@linkcode PluginDesignQueryApi.exists}).\n *\n * @param id - the entity id ({@linkcode ComponentHandle}) to look up\n * @returns the {@linkcode ComponentHandle}, or `null` if it no longer resolves\n *\n * @examplePrompt Look up an object by its id\n * @examplePrompt Does id cmp_42 still point to something in the model?\n * @examplePrompt Fetch the element with this id\n * @examplePrompt Check if this saved element id is still valid\n *\n * # Example\n * ```ts\n * // Persistence round-trip: save a ref now, revalidate it in a later run\n * const [wall] = await snaptrude.design.query.listWalls()\n * const savedRef = await snaptrude.design.query.getEntityRef(wall)\n *\n * // ...later (savedRef restored from plugin storage)...\n * const stillThere = await snaptrude.design.query.getById(savedRef)\n * if (stillThere) {\n * const props = await snaptrude.design.query.getProperties(stillThere)\n * }\n * ```\n */\n public abstract getById(\n id: ComponentHandle,\n ): PluginApiReturn<ComponentHandle | null>\n\n /**\n * Test whether an entity id resolves to a live scene entity.\n * @param id - the entity id ({@linkcode ComponentHandle}) to look up\n * @returns `true` if it exists, otherwise `false`\n *\n * @examplePrompt Does this element still exist in the scene?\n * @examplePrompt Check whether the wall I stored earlier is still there\n * @examplePrompt Is id cmp_42 still valid?\n * @examplePrompt Verify the object wasn't deleted\n *\n * # Example\n * ```ts\n * // Guard before acting on a handle that may have been deleted or undone\n * if (await snaptrude.design.query.exists(wall)) {\n * const dx = await snaptrude.core.math.vec3.new(5, 0, 0)\n * await snaptrude.design.transform.move([wall], dx)\n * }\n * ```\n */\n public abstract exists(id: ComponentHandle): PluginApiReturn<boolean>\n\n /**\n * Get the kind of BIM entity a handle refers to — wall, door, space, slab,\n * and so on (its {@linkcode PluginEntityType}).\n * @param component - the entity's {@linkcode ComponentHandle}\n * @returns the kind, or `null` if the entity is gone / its type is unknown\n *\n * @examplePrompt What kind of element is this?\n * @examplePrompt Is the selected object a wall or a mass?\n * @examplePrompt Tell me the type of this component\n * @examplePrompt What am I looking at — a door, a window, or a panel?\n *\n * # Example\n * ```ts\n * // Dispatch on what the user selected\n * const selection = await snaptrude.design.selection.get()\n * for (const component of selection) {\n * const kind = await snaptrude.design.query.getEntityType(component)\n * if (kind === \"wall\") {\n * // wall-specific handling\n * } else if (kind === \"space\") {\n * // room-specific handling\n * }\n * }\n * ```\n */\n public abstract getEntityType(\n component: ComponentHandle,\n ): PluginApiReturn<PluginEntityType | null>\n\n /**\n * Get an entity's human label (`room_type` for spaces/masses, else the mesh\n * label; `\"\"` when unlabelled). The write side is `design.update` (`setLabel`).\n * @param component - the entity's {@linkcode ComponentHandle}\n * @returns the label string\n *\n * @examplePrompt What is this room called?\n * @examplePrompt Get the name of the selected object\n * @examplePrompt Show the label on this wall\n * @examplePrompt What's the room type of this space?\n *\n * # Example\n * ```ts\n * // Fuzzy label search (the `label` filter field is exact-match; for\n * // case-insensitive or partial matches, read labels and compare yourself)\n * const spaces = await snaptrude.design.query.listSpaces()\n * const kitchens: typeof spaces = []\n * for (const space of spaces) {\n * const label = await snaptrude.design.query.getLabel(space)\n * if (label.toLowerCase().includes(\"kitchen\")) kitchens.push(space)\n * }\n * ```\n */\n public abstract getLabel(component: ComponentHandle): PluginApiReturn<string>\n\n /**\n * Get a serializable, persistable reference to an entity. Under the all-handle\n * model a {@linkcode ComponentHandle} already IS the ref (raw `Component.id`),\n * so this is the identity passthrough — provided for API completeness.\n * @param component - the entity's {@linkcode ComponentHandle}\n * @returns the entity's {@linkcode ComponentHandle}\n *\n * @examplePrompt Give me a reference I can save for this element\n * @examplePrompt Get a persistent id for the selected wall\n * @examplePrompt Store a handle to this room so I can find it later\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const savedRef = await snaptrude.design.query.getEntityRef(wall)\n * // savedRef is a plain string — persist it, then revalidate in a later run\n * // with snaptrude.design.query.getById(savedRef)\n * ```\n */\n public abstract getEntityRef(\n component: ComponentHandle,\n ): PluginApiReturn<ComponentHandle>\n\n /**\n * List an entity's direct child entities — for example the doors and windows\n * hosted in a wall.\n * @param component - the entity's {@linkcode ComponentHandle}\n * @returns child {@linkcode ComponentHandle}`[]` (`[]` if none)\n *\n * @examplePrompt What's inside this wall — list its doors and windows\n * @examplePrompt Show the child elements of the selected object\n * @examplePrompt Which openings belong to this wall?\n * @examplePrompt List everything nested under this mass\n *\n * # Example\n * ```ts\n * // Count the openings hosted in a wall, split by kind\n * const children = await snaptrude.design.query.listChildren(wall)\n * let doors = 0\n * let windows = 0\n * for (const child of children) {\n * const kind = await snaptrude.design.query.getEntityType(child)\n * if (kind === \"door\") doors++\n * if (kind === \"window\") windows++\n * }\n * ```\n */\n public abstract listChildren(\n component: ComponentHandle,\n ): PluginApiReturn<ComponentHandle[]>\n\n /**\n * Get an entity's host/parent entity (e.g. the wall a door is cut into).\n * @param component - the entity's {@linkcode ComponentHandle}\n * @returns the host {@linkcode ComponentHandle}, or `null` if top-level\n *\n * @examplePrompt Which wall is this door in?\n * @examplePrompt Find the parent of the selected window\n * @examplePrompt What is this element attached to?\n * @examplePrompt Which wall hosts this window?\n *\n * # Example\n * ```ts\n * // From a door up to its host wall, then measure the wall\n * const [door] = await snaptrude.design.query.listDoors({ isSelected: true })\n * const hostWall = door ? await snaptrude.design.query.getHost(door) : null\n * if (hostWall) {\n * const m = await snaptrude.design.query.measure(hostWall)\n * console.log(\"wall length:\", m?.length)\n * }\n * ```\n */\n public abstract getHost(\n component: ComponentHandle,\n ): PluginApiReturn<ComponentHandle | null>\n\n // ── property & metric reads ─────────────────────────────────────────────────\n\n /**\n * Read an entity's common properties as a {@linkcode PluginEntityProperties}\n * record (only applicable fields are present). For spaces/masses this includes\n * `adjacency` — the per-entity adjacency read (bulk form:\n * `program.adjacency.getMatrix`). For walls and the slab family (slab / floor\n * / roof / ceiling) this includes `buildingType` — the assigned building type\n * NAME the panel's type dropdown shows, matching `design.types.list(kind)`.\n *\n * @param component - the entity's {@linkcode ComponentHandle}\n * @returns the property record\n *\n * @examplePrompt Show me all the details of the selected object\n * @examplePrompt What storey is this wall on?\n * @examplePrompt Which wall type is assigned to this wall?\n * @examplePrompt Which department does this room belong to?\n * @examplePrompt Is this element locked or hidden?\n * @examplePrompt Get the adjacencies of this space\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const props = await snaptrude.design.query.getProperties(wall)\n * console.log(props.type, props.storey, props.boundingBox)\n *\n * // The record is partial — type-specific fields exist only where they apply\n * const [room] = await snaptrude.design.query.listSpaces()\n * const p = await snaptrude.design.query.getProperties(room)\n * if (p.spaceType === \"Room\" && p.areaClass === \"NET\") {\n * for (const adj of p.adjacency ?? []) {\n * // adj.value: 2 = direct, 1 = indirect/contact, -1 = restricted\n * console.log(adj.spaceId, adj.value)\n * }\n * }\n * ```\n */\n public abstract getProperties(\n component: ComponentHandle,\n ): PluginApiReturn<PluginEntityProperties>\n\n /**\n * Measure an entity — read its area, volume, and/or running length (whichever\n * its type supports) together with its world-space bounding box in one call.\n *\n * For walls and the slab family this also carries the Object Properties\n * panel's dimension reads in engine units: `thickness` (walls — straight\n * orthogonal only, exactly when the panel shows it; slabs / floors / roofs /\n * ceilings — the vertical extent the panel calls Thickness) and `height`\n * (walls). These are the read side of `design.update.wall` /\n * `design.update.slab` / `.floor` / `.roof` / `.ceiling`.\n *\n * @param component - the entity's {@linkcode ComponentHandle}\n * @returns a {@linkcode PluginEntityMeasurements}, or `null` if the entity is gone\n *\n * @examplePrompt What's the area of this room?\n * @examplePrompt How long is the selected wall?\n * @examplePrompt How thick is this wall?\n * @examplePrompt Get the thickness of the slab on level 2\n * @examplePrompt Get the volume of this mass\n * @examplePrompt Give me the dimensions of the selected element\n * @examplePrompt What's the floor area of the kitchen?\n *\n * # Example\n * ```ts\n * const [space] = await snaptrude.design.query.listSpaces()\n * const m = await snaptrude.design.query.measure(space)\n * console.log(m?.area, m?.volume)\n *\n * // wall dimensions — the panel's Thickness / Height, in engine units\n * const [wall] = await snaptrude.design.query.listWalls()\n * const wm = await snaptrude.design.query.measure(wall)\n * console.log(wm?.thickness, wm?.height)\n * ```\n */\n public abstract measure(\n component: ComponentHandle,\n ): PluginApiReturn<PluginEntityMeasurements | null>\n\n /**\n * Read a staircase's parametric properties — its preset, dimensions, step\n * count, base offset, and height-lock state — as a\n * {@linkcode PluginStaircaseParams} record. The paired write is\n * `design.update.staircase`.\n *\n * All dimensions are in **engine units** (the same convention as\n * `design.doors.getWidth`); `steps` is a unitless count. A read — never\n * throws: returns `null` for a handle that is not a staircase or no longer\n * resolves.\n *\n * @param staircase - the staircase's {@linkcode ComponentHandle}\n * @returns the {@linkcode PluginStaircaseParams} record, or `null`\n *\n * @examplePrompt What's the riser height of this staircase?\n * @examplePrompt How many steps does the selected stair have?\n * @examplePrompt Show me the tread and width of this staircase\n * @examplePrompt Read all the parameters of the stairs on level 1\n *\n * # Example\n * ```ts\n * const [stair] = await snaptrude.design.query.listStaircases({ isSelected: true })\n * const params = stair ? await snaptrude.design.query.getStaircaseParams(stair) : null\n * if (params) console.log(params.steps, params.riser, params.tread)\n * ```\n */\n public abstract getStaircaseParams(\n staircase: ComponentHandle,\n ): PluginApiReturn<PluginStaircaseParams | null>\n\n /**\n * Get the **union** axis-aligned bounding box enclosing a set of entities.\n *\n * @param components - the set of entity {@linkcode ComponentHandle}s\n * @returns the union {@linkcode BBoxComponents}, or `null` if the set is empty /\n * none resolve\n *\n * @examplePrompt Get the bounding box around the selected objects\n * @examplePrompt What is the overall extent of these walls?\n * @examplePrompt How big is the region these elements cover together?\n * @examplePrompt Find the combined extents of everything on level 2\n *\n * # Example\n * ```ts\n * const walls = await snaptrude.design.query.listWalls()\n * const box = await snaptrude.design.query.getBoundingBox(walls)\n *\n * // Compose with the `within` filter: everything in the selection's extents\n * const selection = await snaptrude.design.selection.get()\n * const extents = await snaptrude.design.query.getBoundingBox(selection)\n * if (extents) {\n * const nearby = await snaptrude.design.query.listEntities({ within: extents })\n * const width = extents.max.x - extents.min.x\n * }\n * ```\n */\n public abstract getBoundingBox(\n components: ComponentHandle[],\n ): PluginApiReturn<BBoxComponents | null>\n\n /**\n * Compute the plan **outline of what is built on a storey** — the union of\n * the storey's wall (by default) footprints as polygons-with-holes. This is\n * the footprint-from-the-built-model query: by the time slabs are needed the\n * walls exist, and the engine's own solids (snapped, joined and mitred at\n * creation) are the cleanest wall network available — no CAD re-tracing.\n *\n * Semantics: the union of wall solids is the wall *material* — a ring-shaped\n * polygon. The **outer ring is the OUTSIDE wall face** (the slab boundary);\n * every enclosed region — rooms AND courtyards — appears as a hole. When the\n * largest hole is ~zero the walls don't enclose anything: the footprint\n * reports `enclosed: false` (an open wall network), never a garbage outline.\n * Detached buildings come back as separate footprints.\n *\n * A read — nothing is created. Feed a footprint's `outline` to\n * `core.geom.create.profileFromLinePoints` → `design.create.slab` to build\n * the floor/roof plate. Coordinates are world-space plan (x, z) in Snaptrude\n * internal units; `areaSq` values are in squared internal units.\n *\n * @param options - storey / included kinds / debris filter; see\n * {@linkcode PluginStoreyOutlineOptions}.\n * @returns the storey's footprints (possibly several for detached buildings)\n * plus pieces discarded by `minArea` (`footprints: []` when nothing matches).\n *\n * @examplePrompt What is the building footprint on the ground floor?\n * @examplePrompt Create a floor slab covering the whole storey\n * @examplePrompt Get the outline of the walls on storey 1\n * @examplePrompt How much area do the ground-floor walls enclose?\n *\n * # Example\n * ```ts\n * const r = await snaptrude.design.query.storeyOutline({ storey: 1 })\n * const main = r.footprints.find((f) => f.enclosed)\n * if (main) {\n * const profile = await snaptrude.core.geom.create.profileFromLinePoints(\n * main.outline.map((p) => ({ x: p.x, y: 0, z: p.z })),\n * )\n * // → design.create.slab with the profile's contour\n * }\n * ```\n */\n public abstract storeyOutline(\n options?: PluginStoreyOutlineOptions,\n ): PluginApiReturn<PluginStoreyOutlineResult>\n}\n\n\n/** A point in the world-space plan (x, z) projection, Snaptrude internal units. */\nexport const PluginOutlinePlanPoint = z.object({ x: z.number(), z: z.number() })\nexport type PluginOutlinePlanPoint = z.infer<typeof PluginOutlinePlanPoint>\n\n/**\n * Options for {@link PluginDesignQueryApi.storeyOutline}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `storey` | `number`? | Only components on this storey (default: all storeys) |\n * | `include` | {@link PluginEntityType}`[]`? | Component kinds to union (default `[\"wall\"]`) |\n * | `minArea` | `number`? | Drop union pieces below this plan area, squared internal units (default `1`) |\n */\nexport const PluginStoreyOutlineOptions = z.object({\n storey: z.number().int().optional(),\n include: z.array(PluginEntityType).nonempty().default([\"wall\"]),\n minArea: z.number().nonnegative().default(1),\n})\nexport type PluginStoreyOutlineOptions = z.input<\n typeof PluginStoreyOutlineOptions\n>\n\n/**\n * One connected footprint from {@link PluginDesignQueryApi.storeyOutline}.\n * `outline` traces the OUTSIDE wall face; `holes` are the enclosed regions\n * (rooms and courtyards). `enclosed` is `false` when the wall network does not\n * close around any region (open C-shape) — don't slab an unenclosed outline\n * without checking it.\n */\nexport const PluginStoreyFootprint = z.object({\n outline: z.array(PluginOutlinePlanPoint),\n holes: z.array(\n z.object({\n ring: z.array(PluginOutlinePlanPoint),\n areaSq: z.number().nonnegative(),\n }),\n ),\n areaSq: z.number().nonnegative(),\n enclosed: z.boolean(),\n})\nexport type PluginStoreyFootprint = z.infer<typeof PluginStoreyFootprint>\n\n/** Result of {@link PluginDesignQueryApi.storeyOutline}. */\nexport const PluginStoreyOutlineResult = z.object({\n footprints: z.array(PluginStoreyFootprint),\n discarded: z.array(\n z.object({ areaSq: z.number().nonnegative(), reason: z.string() }),\n ),\n})\nexport type PluginStoreyOutlineResult = z.infer<\n typeof PluginStoreyOutlineResult\n>\n\nexport * from \"./geometry\"\nexport * from \"./spaces\"\nexport * from \"./referenceLines\"\nexport * from \"./grids\"\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../../types\"\nimport {\n ComponentHandle,\n BrepHandle,\n ContourHandle,\n CurveHandle,\n} from \"../../../../handles\"\n\n/**\n * `snaptrude.design.query.geometry` — read the geometry of scene BIM entities.\n *\n * This is the bridge from the BIM/scene layer (`design.*`, keyed by\n * {@linkcode ComponentHandle}) into the geometry kernel (`core.geom.query.*`,\n * keyed by value/topology handles). {@linkcode PluginDesignQueryGeometryApi.getBrep}\n * mints a {@linkcode BrepHandle} for a component's boundary-representation mesh;\n * {@linkcode PluginDesignQueryGeometryApi.getBottomContour} mints a\n * {@linkcode ContourHandle} for its footprint outline;\n * {@linkcode PluginDesignQueryGeometryApi.getCenterline} mints a\n * {@linkcode CurveHandle} for a wall's core curve.\n * {@linkcode PluginDesignQueryGeometryApi.getTriangulatedMeshes} is the\n * handle-free exception: it returns the triangulated render meshes as plain\n * world-space arrays.\n *\n * Returned handles are session-ephemeral and go stale on undo/redo/re-topologize\n * — re-fetch a fresh one after edits.\n */\nexport abstract class PluginDesignQueryGeometryApi {\n constructor() {}\n\n /**\n * Get the boundary representation (B-rep) of a scene component — the solid\n * geometry (faces, edges, vertices) behind a wall, slab, room or any other\n * scene entity, as a reusable {@linkcode BrepHandle} for the\n * `core.geom.query.brep.*` reads.\n *\n * @param component - The scene component to read\n * @returns The brep as a {@linkcode BrepHandle}, or `null` if the component has no brep geometry\n *\n * @examplePrompt Get the 3D solid geometry of the selected wall\n * @examplePrompt How many faces does this mass have?\n * @examplePrompt Inspect the mesh of this slab so I can look at its faces and edges\n * @examplePrompt Read the vertices of the selected column's geometry\n *\n * @example\n * ```ts\n * const brep = await snaptrude.design.query.geometry.getBrep(component)\n * if (brep) {\n * const faces = await snaptrude.core.geom.query.brep.listFaces(brep)\n * }\n * ```\n */\n public abstract getBrep(\n component: ComponentHandle,\n ): PluginApiReturn<BrepHandle | null>\n\n /**\n * Get the **bottom-face contour** of a component — the footprint outline (outer\n * profile + holes) of its lowest horizontal face, as a reusable\n * {@linkcode ContourHandle}.\n *\n * Extracted from the component's B-rep; robust to inverted face normals. Read\n * its geometry via `snaptrude.core.geom.query.contour.*`, or feed it back into\n * `design.create.*` / `core.geom.*`.\n *\n * @param component - The scene component to read\n * @returns the footprint {@linkcode ContourHandle}, or `null` if the component\n * has no extractable bottom face (e.g. it carries no B-rep)\n *\n * @examplePrompt Get the footprint of the selected mass\n * @examplePrompt What is the plan outline of this building volume?\n * @examplePrompt Trace the base profile of this wall\n * @examplePrompt Find the ground-level outline of the slab, including any openings\n * @examplePrompt Work out the footprint area of the selected component\n *\n * @example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const contour = await snaptrude.design.query.geometry.getBottomContour(wall)\n * if (contour) {\n * const area = await snaptrude.core.geom.query.contour.getArea(contour)\n * }\n * ```\n */\n public abstract getBottomContour(\n component: ComponentHandle,\n ): PluginApiReturn<ContourHandle | null>\n\n /**\n * Get the **centerline** (core curve) of a wall — the single curve running\n * down the middle of the wall, as a reusable {@linkcode CurveHandle}\n * (a `Line` for a straight wall, an `Arc` for a curved wall).\n *\n * For a parametric wall this is its stored core curve; otherwise it is\n * derived from the wall's B-rep bottom face. Read its geometry via\n * `snaptrude.core.geom.query.curve.*`, or feed it into `design.create.*`.\n *\n * Only walls have a centerline. As with all `get*` reads it never throws:\n * a non-wall component, or a wall with no derivable centerline, yields `null`.\n *\n * @param component - The scene component to read (only walls yield a centerline)\n * @returns the centerline {@linkcode CurveHandle}, or `null` if the component\n * is not a wall or the wall has no derivable centerline (e.g. it carries no\n * B-rep or has an unsupported profile)\n *\n * @examplePrompt Get the centerline of the selected wall\n * @examplePrompt How long is this wall along its centre line?\n * @examplePrompt Find the axis running down the middle of this wall\n * @examplePrompt Give me the core curve of that curved wall\n * @examplePrompt Where does the wall's centreline start and end?\n *\n * @example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const centerline = await snaptrude.design.query.geometry.getCenterline(wall)\n * if (centerline) {\n * const length = await snaptrude.core.geom.query.curve.getLength(centerline)\n * }\n * ```\n */\n public abstract getCenterline(\n component: ComponentHandle,\n ): PluginApiReturn<CurveHandle | null>\n\n /**\n * Get the **triangulated render meshes** of scene components as plain,\n * serializable arrays — flat world-space vertex `positions` (`[x0, y0, z0,\n * x1, …]`) and triangle `indices` (three per triangle), one record per\n * requested component.\n *\n * Unlike the other `geometry.*` reads this does NOT mint kernel handles — the\n * raw arrays cross the boundary directly, ready for export, custom analysis,\n * or feeding an external renderer. Positions are **always world-space**\n * (instance/transform baked in; there is no local-coordinates mode) in plan\n * units — no conversion needed. Raw doubles, unrounded.\n *\n * With `includeMaterialIds: true` each record also carries `materialIds`: one\n * entry **per triangle** (`indices.length / 3` entries), the material *name*\n * painted on that triangle, or `null` for unpainted/default faces.\n *\n * With `includeFaceIndices: true` each record also carries `faceIds`: one\n * entry **per triangle** (input order), the B-rep face index owning that\n * triangle (same ids as {@linkcode getBrep} faces), or `null` where there is\n * no provenance. Non-B-rep meshes (Revit imports, generic models, furniture)\n * yield all-`null` rather than an error.\n *\n * A resolvable component with no readable mesh geometry yields\n * `{ id, positions: [], indices: [] }` — nothing is silently omitted.\n *\n * The call is capped at **500 000 triangles total**; over the cap it throws a\n * `VALIDATION` error whose message includes the actual total — split the\n * component list into smaller batches and call again.\n *\n * @param components - The scene components to triangulate\n * @param options - `includeMaterialIds` adds the per-triangle material names;\n * `includeFaceIndices` adds the per-triangle B-rep face indices\n * @returns `{ meshes }` — one {@linkcode PluginTriangulatedMesh} per requested\n * component, in input order\n * @throws `HANDLE_INVALID` if any component handle is gone/forged;\n * `VALIDATION` if the combined mesh exceeds 500 000 triangles\n *\n * @examplePrompt Export the selected masses as a triangle mesh\n * @examplePrompt Get the raw vertices and triangles of this wall in world coordinates\n * @examplePrompt Give me the triangulated geometry of every space with its materials\n * @examplePrompt Compute the surface area of this roof from its triangles\n *\n * @example\n * ```ts\n * const spaces = await snaptrude.design.query.listSpaces()\n * const { meshes } = await snaptrude.design.query.geometry.getTriangulatedMeshes(spaces, {\n * includeMaterialIds: true,\n * })\n * for (const mesh of meshes) {\n * const triangleCount = mesh.indices.length / 3\n * console.log(mesh.id, triangleCount, mesh.materialIds?.[0])\n * }\n * ```\n */\n public abstract getTriangulatedMeshes(\n components: ComponentHandle[],\n options?: PluginDesignQueryGeometryGetTriangulatedMeshesArgs[\"options\"],\n ): PluginApiReturn<PluginDesignQueryGeometryGetTriangulatedMeshesResult>\n}\n\n/**\n * Arguments for {@linkcode PluginDesignQueryGeometryApi.getBrep}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `component` | {@linkcode ComponentHandle} | The scene component to read |\n */\nexport const PluginDesignQueryGeometryGetBrepArgs = z.object({\n component: ComponentHandle,\n})\n\nexport type PluginDesignQueryGeometryGetBrepArgs = z.infer<\n typeof PluginDesignQueryGeometryGetBrepArgs\n>\n\n/**\n * Arguments for {@linkcode PluginDesignQueryGeometryApi.getBottomContour}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `component` | {@linkcode ComponentHandle} | The scene component to read |\n */\nexport const PluginDesignQueryGeometryGetBottomContourArgs = z.object({\n component: ComponentHandle,\n})\n\nexport type PluginDesignQueryGeometryGetBottomContourArgs = z.infer<\n typeof PluginDesignQueryGeometryGetBottomContourArgs\n>\n\n/**\n * Arguments for {@linkcode PluginDesignQueryGeometryApi.getCenterline}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `component` | {@linkcode ComponentHandle} | The scene component to read |\n */\nexport const PluginDesignQueryGeometryGetCenterlineArgs = z.object({\n component: ComponentHandle,\n})\n\nexport type PluginDesignQueryGeometryGetCenterlineArgs = z.infer<\n typeof PluginDesignQueryGeometryGetCenterlineArgs\n>\n\n/**\n * One component's triangulated world-space mesh, as plain serializable arrays\n * (no handles).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | The component's id (its {@linkcode ComponentHandle} token) |\n * | `positions` | `number[]` | Flat world-space vertex coordinates `[x0, y0, z0, x1, …]`, plan units, raw doubles |\n * | `indices` | `number[]` | Triangle vertex indices, three per triangle, into `positions / 3` |\n * | `materialIds` | `(string \\| null)[]` _(optional)_ | One entry per triangle: the painted material's name, `null` for unpainted/default faces. Present only when requested via `includeMaterialIds` |\n * | `faceIds` | `(number \\| null)[]` _(optional)_ | One entry per triangle: the B-rep face index owning it (same ids as {@linkcode PluginDesignQueryGeometryApi.getBrep} faces), `null` where there is no provenance (non-B-rep mesh). Present only when requested via `includeFaceIndices` |\n */\nexport const PluginTriangulatedMesh = z.object({\n id: z.string(),\n positions: z.array(z.number()),\n indices: z.array(z.number()),\n materialIds: z.array(z.string().nullable()).optional(),\n faceIds: z.array(z.number().nullable()).optional(),\n})\n\nexport type PluginTriangulatedMesh = z.infer<typeof PluginTriangulatedMesh>\n\n/**\n * Arguments for {@linkcode PluginDesignQueryGeometryApi.getTriangulatedMeshes}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `components` | {@linkcode ComponentHandle}`[]` | The scene components to triangulate |\n * | `options.includeMaterialIds` | `boolean` _(optional)_ | Also return the per-triangle material names |\n * | `options.includeFaceIndices` | `boolean` _(optional)_ | Also return the per-triangle B-rep face indices |\n */\nexport const PluginDesignQueryGeometryGetTriangulatedMeshesArgs = z.object({\n components: z.array(ComponentHandle),\n options: z\n .object({\n includeMaterialIds: z.boolean().optional(),\n includeFaceIndices: z.boolean().optional(),\n })\n .optional(),\n})\n\nexport type PluginDesignQueryGeometryGetTriangulatedMeshesArgs = z.infer<\n typeof PluginDesignQueryGeometryGetTriangulatedMeshesArgs\n>\n\n/**\n * Result of {@linkcode PluginDesignQueryGeometryApi.getTriangulatedMeshes}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `meshes` | {@linkcode PluginTriangulatedMesh}`[]` | One record per requested component, in input order |\n */\nexport const PluginDesignQueryGeometryGetTriangulatedMeshesResult = z.object({\n meshes: z.array(PluginTriangulatedMesh),\n})\n\nexport type PluginDesignQueryGeometryGetTriangulatedMeshesResult = z.infer<\n typeof PluginDesignQueryGeometryGetTriangulatedMeshesResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { ComponentHandle, ContourHandle } from \"../../../handles\"\nimport { PluginSpaceGetProperty, PluginSpaceGetResult } from \"../../entity/space\"\n\n/**\n * `design.query.spaces.*` — space-specific reads (a space is a room mass).\n *\n * Only the space queries feasible against the engine today are exposed here.\n * `getFloor` / `listBoundingWalls` (space↔wall topology) are deferred — they need\n * engine work that does not exist yet. `listAdjacent` stays deferred because the\n * shipped surfaces already answer it with value semantics: per-space via the\n * `adjacency` field of `design.query.getProperties`, in bulk via\n * `program.adjacency.getMatrix`.\n *\n * Accessed via `snaptrude.design.query.spaces`.\n */\nexport abstract class PluginDesignQuerySpacesApi {\n constructor() {}\n\n /**\n * Get selected properties of a space — the selective multi-property read.\n *\n * Only the properties listed in `properties` are returned — unlisted\n * properties will be `undefined` in the result. This is the canonical home of\n * the read formerly at `entity.space.get` (now deprecated). Adjacency is not\n * part of this read — it is an entity property: read it via the `adjacency`\n * field of {@linkcode PluginDesignQueryApi.getProperties}.\n *\n * @param space - The space (room mass) to read\n * @param properties - Array of property names to retrieve. See\n * {@linkcode PluginDesignQuerySpacesGetProperty} for available values.\n * @returns A partial {@linkcode PluginDesignQuerySpacesGetResult} containing only\n * the requested properties\n * @throws If the space does not exist or the component is not a space/mass\n *\n * @examplePrompt What is the area of this room?\n * @examplePrompt Get the name, storey and height of the selected space\n * @examplePrompt Which department does this room belong to?\n * @examplePrompt Where is this space positioned on the plan?\n * @examplePrompt Read the room type and dimensions of the selected room\n *\n * # Example\n * ```ts\n * const [space] = await snaptrude.design.query.listSpaces()\n * const info = await snaptrude.design.query.spaces.get(space, [\"name\", \"area\", \"position\"])\n * console.log(info.name, info.area, info.position)\n * ```\n */\n public abstract get(\n space: ComponentHandle,\n properties: PluginDesignQuerySpacesGetArgs[\"properties\"],\n ): PluginApiReturn<PluginDesignQuerySpacesGetResult>\n\n /**\n * Get the **footprint** of a space — its bottom-face contour (outer profile +\n * holes) as a reusable {@linkcode ContourHandle}. Equivalent to\n * {@linkcode PluginDesignQueryGeometryApi.getBottomContour} narrowed to spaces.\n *\n * @param space - The space (room mass) to read\n * @returns the footprint {@linkcode ContourHandle}, or `null` if the space has\n * no extractable footprint\n *\n * @examplePrompt Get the footprint of this room\n * @examplePrompt Trace the floor outline of the selected space\n * @examplePrompt What is the plan boundary of this room, including any holes?\n * @examplePrompt Give me the base profile of the selected room mass\n *\n * # Example\n * ```ts\n * const [space] = await snaptrude.design.query.listSpaces()\n * const footprint = await snaptrude.design.query.spaces.getFootprint(space)\n * ```\n */\n public abstract getFootprint(\n space: ComponentHandle,\n ): PluginApiReturn<ContourHandle | null>\n\n /**\n * Get the **enclosure** of a space — the floor and ceiling that cap it, the\n * bounding walls around it (with each wall's door/window openings), and the\n * neighbouring spaces it shares a boundary with.\n *\n * **Geometric v1 — best effort.** The engine has no persisted space→surface\n * topology, so this read is derived purely from *plan geometry*: the space\n * footprint is matched against the plan footprints/baselines of the slabs and\n * walls on the same storey. Consequences a caller must account for:\n *\n * - `faceIndices` is **always `[]`**. The triangle→B-rep-face provenance that\n * would populate it is a separate engine capability that does not exist yet;\n * read a surface's full geometry via\n * {@linkcode PluginDesignQueryGeometryApi.getBrep} on its `component` instead.\n * - After heavy edits (move / stretch / boolean) or on imported models the plan\n * match can miss or mis-attribute a surface — treat the result as advisory,\n * not authoritative.\n * - `adjacentSpaces` comes from the same adjacency data as\n * {@linkcode PluginProgramAdjacencyApi.getMatrix} (so the two agree on which\n * spaces neighbour). `sharedEdgeLength` is `0` because that data exposes no\n * edge length, and `sharedSurface` is the common boundary wall only when one\n * is found geometrically (else `null`).\n *\n * Read-only; never mutates the model.\n *\n * @param space - The space (room mass) to read\n * @returns the {@linkcode PluginSpaceEnclosure}, or `null` when `space` is not a\n * space (room mass) or has no extractable plan footprint\n *\n * @examplePrompt What walls, floor and ceiling enclose this room?\n * @examplePrompt Which of this room's walls are external?\n * @examplePrompt List the doors and windows around the selected space\n * @examplePrompt Which spaces are adjacent to this room, and through which wall?\n *\n * # Example\n * ```ts\n * const [space] = await snaptrude.design.query.listSpaces()\n * const enclosure = await snaptrude.design.query.spaces.getEnclosure(space)\n * if (enclosure) {\n * const walls = enclosure.surfaces.filter((s) => s.role === \"wall\")\n * const external = walls.filter((s) => s.isExternal)\n * console.log(`${walls.length} walls, ${external.length} external`)\n * }\n * ```\n */\n public abstract getEnclosure(\n space: ComponentHandle,\n ): PluginApiReturn<PluginSpaceEnclosure | null>\n}\n\n/**\n * Arguments for {@linkcode PluginDesignQuerySpacesApi.getFootprint}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `space` | {@linkcode ComponentHandle} | The space (room mass) to read |\n */\nexport const PluginDesignQueryGetFootprintArgs = z.object({\n space: ComponentHandle,\n})\nexport type PluginDesignQueryGetFootprintArgs = z.infer<\n typeof PluginDesignQueryGetFootprintArgs\n>\n\n/**\n * Property names readable via {@linkcode PluginDesignQuerySpacesApi.get} — the\n * legacy {@linkcode PluginSpaceGetProperty} set minus `\"adjacency\"` (adjacency is\n * an entity property: see the `adjacency` field of\n * {@linkcode PluginDesignQueryApi.getProperties}).\n */\nexport const PluginDesignQuerySpacesGetProperty =\n PluginSpaceGetProperty.exclude([\"adjacency\"])\nexport type PluginDesignQuerySpacesGetProperty = z.infer<\n typeof PluginDesignQuerySpacesGetProperty\n>\n\n/**\n * Arguments for {@linkcode PluginDesignQuerySpacesApi.get}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `space` | {@linkcode ComponentHandle} | The space (room mass) to read |\n * | `properties` | {@linkcode PluginDesignQuerySpacesGetProperty}`[]` | Properties to retrieve |\n */\nexport const PluginDesignQuerySpacesGetArgs = z.object({\n space: ComponentHandle,\n properties: z.array(PluginDesignQuerySpacesGetProperty),\n})\nexport type PluginDesignQuerySpacesGetArgs = z.infer<\n typeof PluginDesignQuerySpacesGetArgs\n>\n\n/**\n * Result of {@linkcode PluginDesignQuerySpacesApi.get} — the legacy\n * {@linkcode PluginSpaceGetResult} record minus `adjacency`. Partial: only the\n * requested properties are present.\n */\nexport const PluginDesignQuerySpacesGetResult = PluginSpaceGetResult.omit({\n adjacency: true,\n})\nexport type PluginDesignQuerySpacesGetResult = z.infer<\n typeof PluginDesignQuerySpacesGetResult\n>\n\n/**\n * Arguments for {@linkcode PluginDesignQuerySpacesApi.getEnclosure}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `space` | {@linkcode ComponentHandle} | The space (room mass) to read |\n */\nexport const PluginDesignQuerySpacesGetEnclosureArgs = z.object({\n space: ComponentHandle,\n})\nexport type PluginDesignQuerySpacesGetEnclosureArgs = z.infer<\n typeof PluginDesignQuerySpacesGetEnclosureArgs\n>\n\n/**\n * The role a bounding surface plays in a {@linkcode PluginSpaceEnclosure}:\n * `\"floor\"` (slab below), `\"ceiling\"` (slab/roof above), or `\"wall\"`.\n */\nexport const PluginEnclosureRole = z.enum([\"floor\", \"ceiling\", \"wall\"])\nexport type PluginEnclosureRole = z.infer<typeof PluginEnclosureRole>\n\n/** The kind of opening hosted in a bounding wall. */\nexport const PluginEnclosureOpeningKind = z.enum([\"door\", \"window\", \"void\"])\nexport type PluginEnclosureOpeningKind = z.infer<\n typeof PluginEnclosureOpeningKind\n>\n\n/**\n * One opening (door / window / void) in a bounding wall.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `kind` | {@linkcode PluginEnclosureOpeningKind} | `\"door\"`, `\"window\"`, or `\"void\"` |\n * | `component` | {@linkcode ComponentHandle} | The opening entity |\n */\nexport const PluginEnclosureOpening = z.object({\n kind: PluginEnclosureOpeningKind,\n component: ComponentHandle,\n})\nexport type PluginEnclosureOpening = z.infer<typeof PluginEnclosureOpening>\n\n/**\n * One bounding surface of a space's enclosure.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `role` | {@linkcode PluginEnclosureRole} | `\"floor\"`, `\"ceiling\"`, or `\"wall\"` |\n * | `component` | {@linkcode ComponentHandle} | The wall / slab / roof entity |\n * | `faceIndices` | `number[]` | B-rep face indices facing the space. **Always `[]` in geometric v1** (no triangle→face provenance yet) |\n * | `isExternal` | `boolean` | No space on the other side — for a wall, no other room bounds it; for a floor/ceiling, no room caps the storey below/above |\n * | `openings` | {@linkcode PluginEnclosureOpening}`[]` | Doors/windows/voids in the surface (walls only; `[]` for floor/ceiling in v1) |\n */\nexport const PluginEnclosureSurface = z.object({\n role: PluginEnclosureRole,\n component: ComponentHandle,\n faceIndices: z.array(z.number()),\n isExternal: z.boolean(),\n openings: z.array(PluginEnclosureOpening),\n})\nexport type PluginEnclosureSurface = z.infer<typeof PluginEnclosureSurface>\n\n/**\n * A space that neighbours the queried space.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `space` | {@linkcode ComponentHandle} | The neighbouring space |\n * | `sharedSurface` | {@linkcode ComponentHandle}` \\| null` | The common boundary wall when one is found geometrically, else `null` (e.g. an open-plan or non-physical adjacency) |\n * | `sharedEdgeLength` | `number` | Plan-unit length of the shared boundary — **always `0` in geometric v1** (the adjacency data exposes no edge length) |\n */\nexport const PluginAdjacentSpace = z.object({\n space: ComponentHandle,\n sharedSurface: ComponentHandle.nullable(),\n sharedEdgeLength: z.number(),\n})\nexport type PluginAdjacentSpace = z.infer<typeof PluginAdjacentSpace>\n\n/**\n * Result of {@linkcode PluginDesignQuerySpacesApi.getEnclosure} — the geometric\n * v1 enclosure of a space.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `space` | {@linkcode ComponentHandle} | The queried space |\n * | `surfaces` | {@linkcode PluginEnclosureSurface}`[]` | Floor + ceiling + bounding walls, as a flat list (any of the three may be absent when no plan match is found) |\n * | `adjacentSpaces` | {@linkcode PluginAdjacentSpace}`[]` | Neighbouring spaces from the adjacency data (`[]` when adjacency was never computed) |\n */\nexport const PluginSpaceEnclosure = z.object({\n space: ComponentHandle,\n surfaces: z.array(PluginEnclosureSurface),\n adjacentSpaces: z.array(PluginAdjacentSpace),\n})\nexport type PluginSpaceEnclosure = z.infer<typeof PluginSpaceEnclosure>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport {\n PluginReferenceLineGetProperty,\n PluginReferenceLineGetResult,\n} from \"../../entity/referenceLine\"\n\n/**\n * `design.query.referenceLines.*` — reference-line-specific reads.\n *\n * A **reference line** is a 2D guide line (or arc) in the scene, typically used\n * for grid lines and alignment guides. Enumerate reference lines with\n * `design.query.listReferenceLines`; read an individual line's properties here.\n * This is the canonical home of the read formerly at `entity.referenceLine.get`\n * (now deprecated).\n *\n * Accessed via `snaptrude.design.query.referenceLines`.\n */\nexport abstract class PluginDesignQueryReferenceLinesApi {\n constructor() {}\n\n /**\n * Get properties of a reference line by its ID.\n *\n * Only the properties listed in `properties` are returned — unlisted\n * properties will be `undefined` in the result.\n *\n * @param referenceLineId - The unique reference line ID\n * @param properties - Array of property names to retrieve. See\n * {@linkcode PluginReferenceLineGetProperty}.\n * @returns A partial {@linkcode PluginReferenceLineGetResult} containing only the\n * requested properties\n * @throws If the reference line does not exist\n *\n * @examplePrompt Get the curve geometry of this reference line\n * @examplePrompt Where does this grid line run?\n * @examplePrompt Read the geometry of a guide line by its id\n * @examplePrompt Look up the curve of the selected reference line\n *\n * # Example\n * ```ts\n * const [refLine] = await snaptrude.design.query.listReferenceLines()\n * const result = await snaptrude.design.query.referenceLines.get(refLine, [\"curve\"])\n * // result.curve is an opaque CurveHandle; read its coordinates via\n * // `snaptrude.core.geom.curve`.\n * ```\n */\n public abstract get(\n referenceLineId: string,\n properties: z.infer<typeof PluginReferenceLineGetProperty>[],\n ): PluginApiReturn<PluginReferenceLineGetResult>\n}\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { CurveHandle, BBoxComponents } from \"../../../handles\"\nimport { PluginReferenceLineStyle } from \"../create\"\n\n// ---------------------------------------------------------------------------\n// Records\n// ---------------------------------------------------------------------------\n\n/**\n * Visual properties of grid lines — the group's defaults and any per-line\n * overrides share this shape (values mirror the engine's `GridLineProps`).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `style` | {@linkcode PluginReferenceLineStyle} | Line style (`\"SOLID\"` \\| `\"DASHED\"` \\| `\"DOT_DASH\"`) |\n * | `weight` | `number` | Line weight |\n * | `color` | `string` | Line color as a `#rrggbb` hex string |\n * | `showStart` | `boolean` | Whether the start-end tag bubble is shown |\n * | `showEnd` | `boolean` | Whether the end-end tag bubble is shown |\n */\nexport const PluginGridLineProps = z.object({\n style: PluginReferenceLineStyle,\n weight: z.number(),\n color: z.string(),\n showStart: z.boolean(),\n showEnd: z.boolean(),\n})\nexport type PluginGridLineProps = z.infer<typeof PluginGridLineProps>\n\n/**\n * One grid line within a wing.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `curve` | {@linkcode CurveHandle} | The line's geometry in **world space** (a line or arc; read coordinates via `core.geom.curve`) |\n * | `tag` | `string` | The line's grid tag (e.g. `\"A\"`, `\"3\"`) |\n * | `visible` | `boolean` | Persisted per-line visibility |\n * | `props` | {@linkcode PluginGridLineProps} | **Effective** visual properties (group defaults merged with this line's overrides) |\n * | `overrides` | `Partial<`{@linkcode PluginGridLineProps}`>`? | This line's raw overrides, when any exist |\n */\nexport const PluginGridLineRecord = z.object({\n curve: CurveHandle,\n tag: z.string(),\n visible: z.boolean(),\n props: PluginGridLineProps,\n overrides: PluginGridLineProps.partial().optional(),\n})\nexport type PluginGridLineRecord = z.infer<typeof PluginGridLineRecord>\n\n/**\n * One wing of a grid — a family of parallel lines sharing a local frame.\n * `spacing` and `span` are measured along the wing's local axes in\n * Snaptrude units; line addresses used by mutating APIs are\n * `{ wing, line }` index pairs into these arrays.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spacing` | `number` | Nominal spacing between adjacent lines (Snaptrude units) |\n * | `tagStart` | `string` | First tag of the wing's deterministic tag sequence |\n * | `span` | `{ min: number; max: number }`? | Explicit family length along the wing's line axis, when baked |\n * | `lines` | {@linkcode PluginGridLineRecord}`[]` | The wing's lines, in wing order (index = the `line` address) |\n */\nexport const PluginGridWingRecord = z.object({\n spacing: z.number(),\n tagStart: z.string(),\n span: z.object({ min: z.number(), max: z.number() }).optional(),\n lines: z.array(PluginGridLineRecord),\n})\nexport type PluginGridWingRecord = z.infer<typeof PluginGridWingRecord>\n\n/**\n * Per-storey 2D-plan visibility of a grid.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `activeStorey` | `number` | The storey the grid belongs to |\n * | `disabledOnStoreys` | `number[]` | Storeys on whose 2D plan the grid is hidden |\n */\nexport const PluginGridStoreyVisibility = z.object({\n activeStorey: z.number(),\n disabledOnStoreys: z.array(z.number()),\n})\nexport type PluginGridStoreyVisibility = z.infer<typeof PluginGridStoreyVisibility>\n\n// ---------------------------------------------------------------------------\n// get — args & result\n// ---------------------------------------------------------------------------\n\n/**\n * Property names readable via {@linkcode PluginDesignQueryGridsApi.get}.\n *\n * | Value | Description |\n * |---|---|\n * | `\"angle\"` | World rotation about +Y, in **degrees** |\n * | `\"props\"` | Group-level default line properties |\n * | `\"storeyVisibility\"` | Active storey + storeys where the grid is hidden in 2D |\n * | `\"wings\"` | Full wing/line structure with world-space curve handles |\n * | `\"bounds\"` | World-space axis-aligned bounding box of the grid geometry |\n */\nexport const PluginGridGetProperty = z.enum([\n \"angle\",\n \"props\",\n \"storeyVisibility\",\n \"wings\",\n \"bounds\",\n])\nexport type PluginGridGetProperty = z.infer<typeof PluginGridGetProperty>\n\n/**\n * Arguments for {@linkcode PluginDesignQueryGridsApi.get}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `gridId` | `string` | The grid group ID to query |\n * | `properties` | {@linkcode PluginGridGetProperty}`[]` | Properties to retrieve |\n */\nexport const PluginGridGetArgs = z.object({\n gridId: z.string(),\n properties: z.array(PluginGridGetProperty),\n})\nexport type PluginGridGetArgs = z.infer<typeof PluginGridGetArgs>\n\n/**\n * Result of {@linkcode PluginDesignQueryGridsApi.get} — only the requested\n * properties are present.\n */\nexport const PluginGridGetResult = z\n .object({\n angle: z.number(),\n props: PluginGridLineProps,\n storeyVisibility: PluginGridStoreyVisibility,\n wings: z.array(PluginGridWingRecord),\n bounds: BBoxComponents,\n })\n .partial()\nexport type PluginGridGetResult = z.infer<typeof PluginGridGetResult>\n\n// ---------------------------------------------------------------------------\n// Abstract API\n// ---------------------------------------------------------------------------\n\n/**\n * `design.query.grids.*` — structural-grid-specific reads.\n *\n * A **grid** (grid group) is a structural/documentation grid: one or more\n * *wings* (families of parallel lines with a shared local frame), each holding\n * tagged grid *lines* (straight lines, or a lone arc). Enumerate grids with\n * `design.query.listGrids`; read an individual grid's structure here.\n *\n * Lines have no stable identity of their own — mutating APIs address them\n * positionally as `{ wing, line }` index pairs into the `wings` arrays\n * returned by {@linkcode get}. Structural edits (adding/removing lines,\n * count changes) re-index lines; re-query after mutating.\n *\n * Accessed via `snaptrude.design.query.grids`.\n */\nexport abstract class PluginDesignQueryGridsApi {\n constructor() {}\n\n /**\n * Get properties of a grid by its ID.\n *\n * Only the properties listed in `properties` are returned — unlisted\n * properties will be `undefined` in the result. `wings` is the heavy\n * read (it registers a world-space {@linkcode CurveHandle} per line);\n * request it only when the geometry is needed.\n *\n * Units: `angle` is in **degrees** (positive = CCW about +Y); `spacing`,\n * `span`, `bounds`, and curve coordinates are in **Snaptrude units**,\n * world space.\n *\n * @param gridId - The unique grid group ID\n * @param properties - Array of property names to retrieve. See\n * {@linkcode PluginGridGetProperty}.\n * @returns A partial {@linkcode PluginGridGetResult} containing only the\n * requested properties\n * @throws If the grid does not exist\n *\n * @examplePrompt What is the spacing of this grid?\n * @examplePrompt Read the grid line tags on this structural grid\n * @examplePrompt Get the geometry of every line in this grid\n * @examplePrompt Which storeys is this grid hidden on?\n *\n * # Example\n * ```ts\n * const [grid] = await snaptrude.design.query.listGrids()\n * const { wings } = await snaptrude.design.query.grids.get(grid, [\"wings\"])\n * // wings[0].lines[0].curve is an opaque CurveHandle in world space;\n * // read its coordinates via `snaptrude.core.geom.curve`.\n * ```\n */\n public abstract get(\n gridId: string,\n properties: z.infer<typeof PluginGridGetProperty>[],\n ): PluginApiReturn<PluginGridGetResult>\n}\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { ComponentHandle } from \"../../../handles\"\nimport { PluginDesignChangeResult } from \"../lock\"\n\n/**\n * The entity kinds {@linkcode PluginDesignSelectionApi.setByFilter} can select —\n * the union of the editor's \"Filter selection\" menus (Design tab + BIM tab).\n * Tokens mirror {@linkcode PluginEntityType} where a kind exists on the query\n * surface; the rest are selection-only kinds the query surface cannot reach\n * (layer-backed and annotation objects).\n *\n * | Value | Entity (menu label) |\n * |---|---|\n * | `\"wall\"` | Wall |\n * | `\"slab\"` | Floor slab |\n * | `\"floor\"` | Flooring |\n * | `\"door\"` | Door |\n * | `\"window\"` | Window |\n * | `\"beam\"` | Beam |\n * | `\"column\"` | Column |\n * | `\"ceiling\"` | Ceiling |\n * | `\"roof\"` | Roof |\n * | `\"staircase\"` | Staircase |\n * | `\"furniture\"` | Furniture |\n * | `\"pergola\"` | Pergola |\n * | `\"sunshade\"` | Sunshade |\n * | `\"referenceLine\"` | Reference line |\n * | `\"space\"` | Space (room mass) |\n * | `\"site\"` | Site |\n * | `\"terrain\"` | Terrain |\n * | `\"cad\"` | CAD import |\n * | `\"pdf\"` | PDF import |\n * | `\"image\"` | Image import |\n * | `\"dimensionLine\"` | Dimension line |\n * | `\"model3d\"` | Imported 3D model |\n * | `\"neighborhoodBuilding\"` | Neighborhood building |\n * | `\"programBlock\"` | Program block (department mass) |\n */\nexport const PluginSelectionEntityType = z.enum([\n \"wall\",\n \"slab\",\n \"floor\",\n \"door\",\n \"window\",\n \"beam\",\n \"column\",\n \"ceiling\",\n \"roof\",\n \"staircase\",\n \"furniture\",\n \"pergola\",\n \"sunshade\",\n \"referenceLine\",\n \"space\",\n \"site\",\n \"terrain\",\n \"cad\",\n \"pdf\",\n \"image\",\n \"dimensionLine\",\n \"model3d\",\n \"neighborhoodBuilding\",\n \"programBlock\",\n])\nexport type PluginSelectionEntityType = z.infer<typeof PluginSelectionEntityType>\n\n/**\n * The filter for {@linkcode PluginDesignSelectionApi.setByFilter}. At least one\n * field must be non-empty (to empty the selection use\n * {@linkcode PluginDesignSelectionApi.clear}). Fields **AND**-combine; values\n * within a field **OR**-combine — `{ storeys: [2], types: [\"furniture\"] }`\n * selects only the furniture on storey 2.\n *\n * | Field | Type | Selects entities where |\n * |---|---|---|\n * | `storeys` | `number[]` | storey/floor level ∈ `storeys` (negative = basement) |\n * | `types` | {@linkcode PluginSelectionEntityType}`[]` | entity kind ∈ `types` |\n */\nexport const PluginSelectionFilter = z\n .object({\n storeys: z.array(z.number()).optional(),\n types: z.array(PluginSelectionEntityType).optional(),\n })\n .refine((f) => (f.storeys?.length ?? 0) + (f.types?.length ?? 0) > 0, {\n message:\n \"PluginSelectionFilter requires at least one non-empty field (storeys or types); use design.selection.clear() to deselect everything\",\n })\nexport type PluginSelectionFilter = z.infer<typeof PluginSelectionFilter>\n\n/**\n * `snaptrude.design.selection` — read and mutate the current scene selection.\n *\n * Selection targets are {@linkcode ComponentHandle}s. `get` is the one sanctioned\n * `get`-returns-array accessor (§4.1(3)). The mutators (`set`/`add`/`remove`/`clear`)\n * change the live selection and return the resulting selection snapshot — they are\n * **not undoable** (the engine fires a selection listener, no command), so the\n * returned {@linkcode PluginDesignChangeResult} is a committed-state snapshot.\n */\nexport abstract class PluginDesignSelectionApi {\n constructor() {}\n\n /**\n * Read the current scene selection — the entities the user (or a previous\n * call) has selected, returned as handles you can pass straight into other\n * API calls.\n *\n * @returns The selection as {@linkcode ComponentHandle}`[]` (`[]` when nothing is selected)\n *\n * @examplePrompt What do I have selected right now?\n * @examplePrompt List the currently selected objects\n * @examplePrompt Which rooms are selected in the scene?\n * @examplePrompt Get the ids of everything I've selected\n * @examplePrompt Is anything selected?\n *\n * # Example\n * ```ts\n * // Selection handles feed straight into other API calls\n * const selection = await snaptrude.design.selection.get()\n * if (selection.length > 0) {\n * const props = await snaptrude.design.query.getProperties(selection[0])\n * }\n * ```\n */\n public abstract get(): PluginApiReturn<ComponentHandle[]>\n\n /**\n * Replace the selection with exactly the given entities — anything\n * previously selected is deselected first, so afterwards only these\n * entities are selected.\n *\n * @param components The entities to select\n * @returns The resulting selection snapshot\n *\n * @examplePrompt Select these three walls\n * @examplePrompt Select only the kitchen and deselect everything else\n * @examplePrompt Change the selection to just these rooms\n * @examplePrompt Highlight the spaces on the second storey\n *\n * # Example\n * ```ts\n * // Select only the ground-storey walls (replaces the previous selection)\n * const walls = await snaptrude.design.query.listWalls({ storeys: [1] })\n * const { affected } = await snaptrude.design.selection.set(walls)\n * ```\n */\n public abstract set(components: ComponentHandle[]): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Add entities to the current selection without deselecting anything — the\n * selection grows to include the given entities alongside whatever was\n * already selected.\n *\n * @param components The entities to add to the selection\n * @returns The resulting selection snapshot\n *\n * @examplePrompt Add the corridor to my current selection\n * @examplePrompt Select these columns too, keeping what's already selected\n * @examplePrompt Also select the roof\n * @examplePrompt Extend the selection to include all the doors on this floor\n *\n * # Example\n * ```ts\n * // Grow the selection to include every roof, keeping what's already selected\n * const roofs = await snaptrude.design.query.listRoofs()\n * await snaptrude.design.selection.add(roofs)\n * ```\n */\n public abstract add(components: ComponentHandle[]): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Remove entities from the current selection — the given entities are\n * deselected while everything else stays selected.\n *\n * @param components The entities to remove from the selection\n * @returns The resulting selection snapshot\n *\n * @examplePrompt Deselect the stair core but keep the rest selected\n * @examplePrompt Remove these walls from my selection\n * @examplePrompt Take the furniture out of the current selection\n * @examplePrompt Unselect the two smaller rooms\n *\n * # Example\n * ```ts\n * // Deselect the furniture while keeping everything else selected\n * const furniture = await snaptrude.design.query.listFurniture({ isSelected: true })\n * await snaptrude.design.selection.remove(furniture)\n * ```\n */\n public abstract remove(\n components: ComponentHandle[]\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Clear the selection so nothing is selected (equivalent to `set([])`).\n *\n * @returns The resulting (empty) selection snapshot\n *\n * @examplePrompt Deselect everything\n * @examplePrompt Clear my current selection\n * @examplePrompt Unselect all objects in the scene\n * @examplePrompt Make sure nothing is selected before we start\n *\n * # Example\n * ```ts\n * const { affected } = await snaptrude.design.selection.clear()\n * // affected is the resulting selection — always [] here\n * ```\n */\n public abstract clear(): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Replace the selection with every visible entity of the active building that\n * matches the filter — the programmatic equivalent of the editor's\n * **Story selection** (`storeys`) and **Filter selection** (`types`) menus\n * under the select tool, including their combination (fields AND, values\n * within a field OR). Reaches selection-only kinds the query surface cannot\n * (sites, terrain, CAD/PDF/image imports, dimension lines, 3D models,\n * neighborhood buildings) and selects whole groups the way the editor does.\n *\n * This is a one-shot bulk select, not a persistent mode: the user's next\n * canvas click resets the underlying filter state (the selection it made\n * stays until changed). Results honor the ambient view — active building,\n * hidden objects/buckets, and in 2D the active storey.\n *\n * @param filter Which entities to select — {@linkcode PluginSelectionFilter};\n * at least one field must be non-empty\n * @returns The resulting selection snapshot\n *\n * @examplePrompt Select everything on storey 2\n * @examplePrompt Select all the walls and doors\n * @examplePrompt Story selection: select the ground floor and first floor\n * @examplePrompt Filter selection: select all furniture in the model\n * @examplePrompt Select only the furniture on the second storey\n * @examplePrompt Select all the spaces so I can recolor them\n *\n * # Example\n * ```ts\n * // \"Story selection\": everything on storeys 1 and 2\n * await snaptrude.design.selection.setByFilter({ storeys: [1, 2] })\n *\n * // \"Filter selection\": all walls and doors\n * await snaptrude.design.selection.setByFilter({ types: [\"wall\", \"door\"] })\n *\n * // Combined: only the furniture on storey 2\n * const { affected } = await snaptrude.design.selection.setByFilter({\n * storeys: [2],\n * types: [\"furniture\"],\n * })\n * ```\n */\n public abstract setByFilter(\n filter: PluginSelectionFilter\n ): PluginApiReturn<PluginDesignChangeResult>\n}\n\n/**\n * Arguments for {@linkcode PluginDesignSelectionApi.set}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `components` | {@linkcode ComponentHandle}`[]` | The entities to select |\n */\nexport const PluginDesignSelectionSetArgs = z.object({\n components: z.array(ComponentHandle),\n})\nexport type PluginDesignSelectionSetArgs = z.infer<typeof PluginDesignSelectionSetArgs>\n\nexport const PluginDesignSelectionAddArgs = PluginDesignSelectionSetArgs\nexport type PluginDesignSelectionAddArgs = z.infer<typeof PluginDesignSelectionAddArgs>\n\nexport const PluginDesignSelectionRemoveArgs = PluginDesignSelectionSetArgs\nexport type PluginDesignSelectionRemoveArgs = z.infer<typeof PluginDesignSelectionRemoveArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { ComponentHandle } from \"../../../handles\"\nimport { PluginDesignChangeResult } from \"../lock\"\n\n/**\n * `snaptrude.design.doors` — door-specific reads + edits. Creation lives at\n * `design.create.door`; generic reads (`listDoors`/`getHost`/`getProperties`) live at\n * `design.query.*`. Door targets are {@linkcode ComponentHandle}s.\n *\n * `getSwingDirection` is a DERIVED read (no persisted field) — computed from the placed\n * mesh's orientation: stand on the side the door swings open to, facing it; `'left'`\n * when the hinged jamb is on your left. `mirror` reflects the door about one of its own\n * axes (undoable, one command): `'x'` flips the swing side, `'z'` swaps the hinged jamb;\n * `setType` is intentionally absent (the engine has no in-place re-type —\n * it would require delete+recreate).\n *\n * The **catalog** reads (`listCatalogGroups`/`listCatalog`/`getCatalogItem`/`exists`)\n * browse the placeable door library — plain value records keyed by a library id\n * (team `_id` or general `fullName`), NOT handles — and feed `design.create.door`.\n * They are network-backed but carry no `Async` suffix (§6.6) and never throw\n * (`[]`/`null`). The {@linkcode PluginObjectCatalogGroup} / {@linkcode PluginObjectCatalogItem}\n * DTOs are shared with `design.windows`.\n */\nexport abstract class PluginDesignDoorsApi {\n constructor() {}\n\n /**\n * Get the type name of a door — the label that identifies which kind of door\n * the instance is, as shown in its properties.\n *\n * @param door The door to query\n * @returns The type as a `string`, or `null`\n *\n * @examplePrompt What type of door is this?\n * @examplePrompt Tell me the door type of the selected door\n * @examplePrompt Is this a single or a double door?\n * @examplePrompt Check what kind of door is on this wall\n *\n * # Example\n * ```ts\n * const [door] = await snaptrude.design.query.listDoors({ isSelected: true })\n * const type = await snaptrude.design.doors.getType(door)\n * if (type) console.log(\"door type:\", type)\n * ```\n */\n public abstract getType(door: ComponentHandle): PluginApiReturn<string | null>\n\n /**\n * Get the family name of a door — the Revit or native family the door\n * instance comes from.\n *\n * @param door The door to query\n * @returns The family as a `string`, or `null`\n *\n * @examplePrompt Which family does this door belong to?\n * @examplePrompt What's the Revit family of the selected door?\n * @examplePrompt Show the family name for this door\n * @examplePrompt Find out which door family the imported door uses\n *\n * # Example\n * ```ts\n * const [door] = await snaptrude.design.query.listDoors()\n * const family = await snaptrude.design.doors.getFamily(door)\n * if (family) console.log(\"family:\", family)\n * ```\n */\n public abstract getFamily(door: ComponentHandle): PluginApiReturn<string | null>\n\n /**\n * Get the width of a door — how wide its opening is along the host wall.\n *\n * @param door The door to query\n * @returns The width as a `number`, or `null`\n *\n * @examplePrompt How wide is this door?\n * @examplePrompt What's the width of the selected door?\n * @examplePrompt Measure the width of the entrance door\n * @examplePrompt Check whether this door is 900mm wide\n *\n * # Example\n * ```ts\n * const [door] = await snaptrude.design.query.listDoors({ isSelected: true })\n * const width = await snaptrude.design.doors.getWidth(door)\n * if (width !== null) console.log(\"door width:\", width)\n * ```\n */\n public abstract getWidth(door: ComponentHandle): PluginApiReturn<number | null>\n\n /**\n * Get the height of a door — how tall its opening is from base to head.\n *\n * @param door The door to query\n * @returns The height as a `number`, or `null`\n *\n * @examplePrompt How tall is this door?\n * @examplePrompt What's the height of the selected door?\n * @examplePrompt Check the door height for this opening\n * @examplePrompt Is the front door 2100mm high?\n *\n * # Example\n * ```ts\n * const [door] = await snaptrude.design.query.listDoors()\n * const height = await snaptrude.design.doors.getHeight(door)\n * if (height !== null) console.log(\"door height:\", height)\n * ```\n */\n public abstract getHeight(door: ComponentHandle): PluginApiReturn<number | null>\n\n /**\n * Get a door's full dimensions in one call — width along the wall, vertical\n * height, and thickness through the wall — measured in the door's local\n * oriented space. Parity with {@linkcode PluginDesignWindowsApi.getDimensions}.\n *\n * @param door The door to query\n * @returns The dimensions, or `null`\n *\n * @examplePrompt What are the dimensions of this door?\n * @examplePrompt How big is the selected door?\n * @examplePrompt Give me the width, height and depth of this door\n * @examplePrompt Measure the entrance door including its thickness through the wall\n *\n * # Example\n * ```ts\n * const [door] = await snaptrude.design.query.listDoors({ isSelected: true })\n * const dims = await snaptrude.design.doors.getDimensions(door)\n * if (dims) console.log(dims.width, dims.height, dims.thickness)\n * ```\n */\n public abstract getDimensions(\n door: ComponentHandle\n ): PluginApiReturn<PluginDoorDimensions | null>\n\n /**\n * Get the floor a door is hosted on — the storey/level component that\n * supports the door.\n *\n * @param door The door to query\n * @returns The support floor {@linkcode ComponentHandle}, or `null`\n *\n * @examplePrompt Which floor is this door on?\n * @examplePrompt What storey does the selected door belong to?\n * @examplePrompt Find the level that hosts this door\n * @examplePrompt Tell me which floor the entrance door sits on\n *\n * # Example\n * ```ts\n * const [door] = await snaptrude.design.query.listDoors({ isSelected: true })\n * const floor = await snaptrude.design.doors.getSupportFloor(door)\n * if (floor) {\n * const props = await snaptrude.design.query.getProperties(floor)\n * console.log(\"hosted on storey:\", props.storey)\n * }\n * ```\n */\n public abstract getSupportFloor(door: ComponentHandle): PluginApiReturn<ComponentHandle | null>\n\n /**\n * Get a door's swing (hinge) handedness. Stand on the side the door swings\n * open to, facing the door: `'left'` when the hinged jamb is on your left,\n * `'right'` when it is on your right. Derived from the placed mesh's\n * orientation (the same reflection `mirror` and the `hinge` placement option\n * apply), not a persisted field.\n *\n * @param door The door to query\n * @returns `'left'` / `'right'`, or `null` if indeterminate\n *\n * @examplePrompt Which way does this door swing?\n * @examplePrompt Is the selected door left-handed or right-handed?\n * @examplePrompt Check the hinge side of this door\n * @examplePrompt Does the entrance door open to the left or the right?\n *\n * # Example\n * ```ts\n * const [door] = await snaptrude.design.query.listDoors({ isSelected: true })\n * const swing = await snaptrude.design.doors.getSwingDirection(door)\n * if (swing) console.log(\"opens as a\", swing, \"hand door\")\n * ```\n */\n public abstract getSwingDirection(\n door: ComponentHandle\n ): PluginApiReturn<\"left\" | \"right\" | null>\n\n /**\n * Mirror a door about its own axes. `'x'` (default) flips the swing side —\n * the door opens to the other side of the wall; `'z'` swaps the hinged jamb\n * (left-hand ↔ right-hand, same swing side); `'y'` turns it upside down.\n * Undoable as a single command.\n *\n * @param door The door to mirror\n * @param axis Reflection axis (optional; default `'x'` — the swing-flip axis; `'z'` — the hinge-jamb axis)\n * @returns The affected door(s)\n *\n * @examplePrompt Flip this door's swing\n * @examplePrompt Mirror the selected door\n * @examplePrompt Make this door open the other way\n * @examplePrompt Reverse the hinge side of the entrance door\n * @examplePrompt Change the door to swing from the other side\n *\n * # Example\n * ```ts\n * // Flip the selected door's swing side — axis defaults to \"x\"\n * const [door] = await snaptrude.design.query.listDoors({ isSelected: true })\n * const { affected } = await snaptrude.design.doors.mirror(door)\n * // …or swap the hinged jamb instead (keeps the swing side)\n * await snaptrude.design.doors.mirror(door, \"z\")\n * ```\n */\n public abstract mirror(\n door: ComponentHandle,\n axis?: PluginMirrorAxis\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Set the width of one or more doors, resizing each opening and re-cutting\n * the host wall. Width is in engine units; the change is undoable.\n *\n * @param doors The doors to resize\n * @param width New width (engine units)\n * @returns The affected doors\n *\n * @examplePrompt Make this door 900mm wide\n * @examplePrompt Widen the selected doors to 1 metre\n * @examplePrompt Change the door width to 3 feet\n * @examplePrompt Resize all the bathroom doors to 750 wide\n * @examplePrompt Narrow the entrance door a bit\n *\n * # Example\n * ```ts\n * // Resize every ground-floor door to a common width (engine units)\n * const doors = await snaptrude.design.query.listDoors({ storeys: [1] })\n * const { affected } = await snaptrude.design.doors.setWidth(doors, 900)\n * console.log(\"resized\", affected.length, \"doors\")\n * ```\n */\n public abstract setWidth(\n doors: ComponentHandle[],\n width: number\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Set the height of one or more doors, resizing each opening and re-cutting\n * the host wall. Height is in engine units; the change is undoable.\n *\n * @param doors The doors to resize\n * @param height New height (engine units)\n * @returns The affected doors\n *\n * @examplePrompt Make this door 2100mm tall\n * @examplePrompt Change the door height to 7 feet\n * @examplePrompt Raise the selected doors to 2.4m high\n * @examplePrompt Set all the doors on this storey to 2100 high\n *\n * # Example\n * ```ts\n * // setHeight takes an array — wrap a single door\n * const [door] = await snaptrude.design.query.listDoors({ isSelected: true })\n * await snaptrude.design.doors.setHeight([door], 2100)\n * ```\n */\n public abstract setHeight(\n doors: ComponentHandle[],\n height: number\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * List the door catalog **groups** — the placement families (Swing, Sliding,\n * Folding, Pivot, …), built-in groups merged with any team-custom types.\n *\n * @returns The groups as {@linkcode PluginObjectCatalogGroup}`[]` (`[]` when empty)\n *\n * @examplePrompt What kinds of doors can I place?\n * @examplePrompt List the door groups in the catalog\n * @examplePrompt Show me the door families available in this project\n * @examplePrompt Which door types does this project support?\n *\n * # Example\n * ```ts\n * const groups = await snaptrude.design.doors.listCatalogGroups()\n * for (const g of groups) console.log(g.dbType, g.label, g.source)\n * ```\n */\n public abstract listCatalogGroups(): PluginApiReturn<PluginObjectCatalogGroup[]>\n\n /**\n * List the placeable door catalog (team + general libraries), optionally\n * restricted to one group by its `dbType` token.\n *\n * @param group Restrict to one group's `dbType` (optional; e.g. `\"singleDoor\"`)\n * @returns The catalog as {@linkcode PluginObjectCatalogItem}`[]` (`[]` when empty)\n *\n * @examplePrompt What doors can I place in this project?\n * @examplePrompt List every door in the library\n * @examplePrompt Show me the sliding doors I can add\n * @examplePrompt Browse the door catalog\n *\n * # Example\n * ```ts\n * const [group] = await snaptrude.design.doors.listCatalogGroups()\n * const items = await snaptrude.design.doors.listCatalog(group?.dbType)\n * for (const item of items) console.log(item.id, item.name, item.thumbnailUrl)\n * ```\n */\n public abstract listCatalog(\n group?: string\n ): PluginApiReturn<PluginObjectCatalogItem[]>\n\n /**\n * Get a single door catalog item by its library id.\n *\n * @param id The catalog library id\n * @returns The item, or `null` if no catalog item matches\n *\n * @examplePrompt Get the details of this door from the library\n * @examplePrompt Look up the catalog entry for this door id\n * @examplePrompt What is the cost of this door item?\n * @examplePrompt Show me the family name of this door in the catalog\n *\n * # Example\n * ```ts\n * const [entry] = await snaptrude.design.doors.listCatalog()\n * const item = await snaptrude.design.doors.getCatalogItem(entry.id)\n * if (item) console.log(item.name, item.source, item.dbType)\n * ```\n */\n public abstract getCatalogItem(\n id: string\n ): PluginApiReturn<PluginObjectCatalogItem | null>\n\n /**\n * Test whether a door catalog item exists for the given library id.\n *\n * @param id The catalog library id\n * @returns `true` if it exists, otherwise `false`\n *\n * @examplePrompt Is this door still in the library?\n * @examplePrompt Check whether a door catalog item with this id exists\n * @examplePrompt Does my team library have this door?\n * @examplePrompt Verify the door is available before placing it\n *\n * # Example\n * ```ts\n * const [entry] = await snaptrude.design.doors.listCatalog()\n * const stillAvailable = await snaptrude.design.doors.exists(entry.id)\n * console.log(stillAvailable ? \"safe to place\" : \"removed from library\")\n * ```\n */\n public abstract exists(id: string): PluginApiReturn<boolean>\n}\n\n/**\n * Arguments for the single-door read methods.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `door` | {@linkcode ComponentHandle} | The door to query |\n */\nexport const PluginDesignDoorArgs = z.object({\n door: ComponentHandle,\n})\nexport type PluginDesignDoorArgs = z.infer<typeof PluginDesignDoorArgs>\n\n/**\n * A door's dimensions (engine units), symmetric with {@linkcode PluginWindowDimensions}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `width` | `number` | Along the wall |\n * | `height` | `number` | Vertical |\n * | `thickness` | `number` | Through the wall (engine `length` axis) |\n */\nexport const PluginDoorDimensions = z.object({\n width: z.number(),\n height: z.number(),\n thickness: z.number(),\n})\nexport type PluginDoorDimensions = z.infer<typeof PluginDoorDimensions>\n\n/** Mirror axis — tokens mirror the engine `FlipDirection` verbatim: `'x'` flips the swing side, `'z'` swaps the hinged jamb, `'y'` turns the door upside down. */\nexport const PluginMirrorAxis = z.enum([\"x\", \"y\", \"z\"])\nexport type PluginMirrorAxis = z.infer<typeof PluginMirrorAxis>\n\n/**\n * Arguments for {@linkcode PluginDesignDoorsApi.mirror}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `door` | {@linkcode ComponentHandle} | The door to mirror |\n * | `axis` | {@linkcode PluginMirrorAxis} | Reflection axis (optional; default `'x'` — the swing-flip axis; `'z'` — the hinge-jamb axis) |\n */\nexport const PluginDesignDoorMirrorArgs = z.object({\n door: ComponentHandle,\n axis: PluginMirrorAxis.optional(),\n})\nexport type PluginDesignDoorMirrorArgs = z.infer<typeof PluginDesignDoorMirrorArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignDoorsApi.setWidth}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `doors` | {@linkcode ComponentHandle}`[]` | The doors to resize |\n * | `width` | `number` | New width (engine units) |\n */\nexport const PluginDesignDoorSetWidthArgs = z.object({\n doors: z.array(ComponentHandle),\n width: z.number(),\n})\nexport type PluginDesignDoorSetWidthArgs = z.infer<typeof PluginDesignDoorSetWidthArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignDoorsApi.setHeight}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `doors` | {@linkcode ComponentHandle}`[]` | The doors to resize |\n * | `height` | `number` | New height (engine units) |\n */\nexport const PluginDesignDoorSetHeightArgs = z.object({\n doors: z.array(ComponentHandle),\n height: z.number(),\n})\nexport type PluginDesignDoorSetHeightArgs = z.infer<typeof PluginDesignDoorSetHeightArgs>\n\n// ---------------------------------------------------------------------------\n// catalog (shared by doors + windows)\n// ---------------------------------------------------------------------------\n\n/**\n * A door/window catalog **group** — a placement family (e.g. Swing, Sliding,\n * Casement). Shared by `design.doors` and `design.windows`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `dbType` | `string` | Stable engine token (e.g. `singleDoor`, `casementDouble`) |\n * | `label` | `string` | Human-readable group label (e.g. `\"Swing - 1 Shutter\"`) |\n * | `source` | `'default' \\| 'team'` | Built-in group or a team-custom type |\n */\nexport const PluginObjectCatalogGroup = z.object({\n dbType: z.string(),\n label: z.string(),\n source: z.enum([\"default\", \"team\"]),\n})\nexport type PluginObjectCatalogGroup = z.infer<typeof PluginObjectCatalogGroup>\n\n/**\n * A placeable door/window catalog **item** (value record — not a scene\n * entity/handle). Shared by `design.doors` and `design.windows`; symmetric with\n * {@linkcode PluginFurnitureCatalogItem}. The raw mesh (`.babylon`) URL is never\n * exposed — only `thumbnailUrl`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Library id (team `_id` or general `fullName`) — pass to `design.create.door`/`window` |\n * | `name` | `string` | Display name |\n * | `source` | `'team' \\| 'general'` | Which library it came from |\n * | `dbType` | `string` | The group token it belongs to (e.g. `singleDoor`) |\n * | `subType` | `string`? | Engine sub-type (optional) |\n * | `thumbnailUrl` | `string`? | Preview thumbnail URL (optional) |\n * | `cost` | `number`? | Cost (optional; team items) |\n * | `familyName` | `string`? | Family name (optional) |\n */\nexport const PluginObjectCatalogItem = z.object({\n id: z.string(),\n name: z.string(),\n source: z.enum([\"team\", \"general\"]),\n dbType: z.string(),\n subType: z.string().optional(),\n thumbnailUrl: z.string().optional(),\n cost: z.number().optional(),\n familyName: z.string().optional(),\n})\nexport type PluginObjectCatalogItem = z.infer<typeof PluginObjectCatalogItem>\n\n/**\n * Arguments for {@linkcode PluginDesignDoorsApi.listCatalog}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `group` | `string`? | Restrict to one group's `dbType` token (optional) |\n */\nexport const PluginDesignDoorListCatalogArgs = z.object({\n group: z.string().optional(),\n})\nexport type PluginDesignDoorListCatalogArgs = z.infer<typeof PluginDesignDoorListCatalogArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignDoorsApi.getCatalogItem} / {@linkcode PluginDesignDoorsApi.exists}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | The catalog library id |\n */\nexport const PluginDesignDoorGetCatalogItemArgs = z.object({\n id: z.string(),\n})\nexport type PluginDesignDoorGetCatalogItemArgs = z.infer<typeof PluginDesignDoorGetCatalogItemArgs>\n\nexport const PluginDesignDoorExistsArgs = PluginDesignDoorGetCatalogItemArgs\nexport type PluginDesignDoorExistsArgs = z.infer<typeof PluginDesignDoorExistsArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { ComponentHandle } from \"../../../handles\"\nimport { PluginDesignChangeResult } from \"../lock\"\nimport { PluginObjectCatalogGroup, PluginObjectCatalogItem } from \"../doors\"\n\n/**\n * `snaptrude.design.windows` — window-specific reads. Creation lives at\n * `design.create.window`; generic reads (`listWindows`/`getHost`/`getProperties`) live\n * at `design.query.*`. Window targets are {@linkcode ComponentHandle}s.\n *\n * The **catalog** reads (`listCatalogGroups`/`listCatalog`/`getCatalogItem`/`exists`)\n * browse the placeable window library — plain value records keyed by a library id\n * (team `_id` or general `fullName`), NOT handles — and feed `design.create.window`.\n * They are network-backed but carry no `Async` suffix (§6.6) and never throw\n * (`[]`/`null`). The {@linkcode PluginObjectCatalogGroup} / {@linkcode PluginObjectCatalogItem}\n * DTOs are shared with `design.doors` (defined there, re-exported here).\n *\n * `getSillHeight` (bounds-derived) and the command-wired `mirror` land in a later pass.\n */\nexport abstract class PluginDesignWindowsApi {\n constructor() {}\n\n /**\n * Get the type name of a window — the label that identifies which kind of\n * window the instance is, as shown in its properties.\n *\n * @param window The window to query\n * @returns The type as a `string`, or `null`\n *\n * @examplePrompt What type of window is this?\n * @examplePrompt Is the selected window a casement or a slider?\n * @examplePrompt Tell me the window type used on this wall\n * @examplePrompt Check what kind of window the living room has\n *\n * # Example\n * ```ts\n * const [win] = await snaptrude.design.query.listWindows({ isSelected: true })\n * const type = await snaptrude.design.windows.getType(win)\n * if (type) console.log(\"window type:\", type)\n * ```\n */\n public abstract getType(window: ComponentHandle): PluginApiReturn<string | null>\n\n /**\n * Get the width of a window — how wide its opening is along the host wall.\n *\n * @param window The window to query\n * @returns The width as a `number`, or `null`\n *\n * @examplePrompt How wide is this window?\n * @examplePrompt What's the width of the selected window?\n * @examplePrompt Measure the width of the kitchen window\n * @examplePrompt Check whether this window is 1200mm wide\n *\n * # Example\n * ```ts\n * const [win] = await snaptrude.design.query.listWindows()\n * const width = await snaptrude.design.windows.getWidth(win)\n * if (width !== null) console.log(\"window width:\", width)\n * ```\n */\n public abstract getWidth(window: ComponentHandle): PluginApiReturn<number | null>\n\n /**\n * Get the height of a window — how tall its opening is on the host wall.\n *\n * @param window The window to query\n * @returns The height as a `number`, or `null`\n *\n * @examplePrompt How tall is this window?\n * @examplePrompt What's the height of the selected window?\n * @examplePrompt Check the window height on this wall\n * @examplePrompt Is the bedroom window 1.5m high?\n *\n * # Example\n * ```ts\n * const [win] = await snaptrude.design.query.listWindows()\n * const height = await snaptrude.design.windows.getHeight(win)\n * if (height !== null) console.log(\"window height:\", height)\n * ```\n */\n public abstract getHeight(window: ComponentHandle): PluginApiReturn<number | null>\n\n /**\n * Get a window's full dimensions in one call — width along the wall, vertical\n * height, and thickness through the wall — measured in the window's local\n * oriented space.\n *\n * @param window The window to query\n * @returns The dimensions, or `null`\n *\n * @examplePrompt What are the dimensions of this window?\n * @examplePrompt How big is the selected window?\n * @examplePrompt Give me the width, height and depth of this window\n * @examplePrompt Measure the living room window\n * @examplePrompt Get the window size including its thickness through the wall\n *\n * # Example\n * ```ts\n * const [win] = await snaptrude.design.query.listWindows({ isSelected: true })\n * const dims = await snaptrude.design.windows.getDimensions(win)\n * if (dims) console.log(dims.width, dims.height, dims.thickness)\n * ```\n */\n public abstract getDimensions(\n window: ComponentHandle\n ): PluginApiReturn<PluginWindowDimensions | null>\n\n /**\n * Set the width of a window, resizing its opening and re-cutting the host\n * wall. Width is in engine units; the change is undoable.\n *\n * @param window The window to resize\n * @param width New width (engine units)\n * @returns The affected window\n *\n * @examplePrompt Make this window 1200mm wide\n * @examplePrompt Widen the selected window to 4 feet\n * @examplePrompt Change the window width to 1.5m\n * @examplePrompt Resize the kitchen window to 900 wide\n *\n * # Example\n * ```ts\n * // Takes a single window (unlike design.doors.setWidth, which takes an array)\n * const [win] = await snaptrude.design.query.listWindows({ isSelected: true })\n * const { affected } = await snaptrude.design.windows.setWidth(win, 1200)\n * ```\n */\n public abstract setWidth(\n window: ComponentHandle,\n width: number\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Set the height of a window, resizing its opening and re-cutting the host\n * wall. Height is in engine units; the change is undoable.\n *\n * @param window The window to resize\n * @param height New height (engine units)\n * @returns The affected window\n *\n * @examplePrompt Make this window 1.5m tall\n * @examplePrompt Change the window height to 4 feet\n * @examplePrompt Shorten the selected window to 900mm high\n * @examplePrompt Set the bedroom window height to 1200\n *\n * # Example\n * ```ts\n * const [win] = await snaptrude.design.query.listWindows({ isSelected: true })\n * await snaptrude.design.windows.setHeight(win, 1500)\n * ```\n */\n public abstract setHeight(\n window: ComponentHandle,\n height: number\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * List the window catalog **groups** — the placement families (Fixed,\n * Casement, Sliding, Folding, …), built-in groups merged with any\n * team-custom types.\n *\n * @returns The groups as {@linkcode PluginObjectCatalogGroup}`[]` (`[]` when empty)\n *\n * @examplePrompt What kinds of windows can I place?\n * @examplePrompt List the window groups in the catalog\n * @examplePrompt Show me the window families available in this project\n * @examplePrompt Which window types does this project support?\n *\n * # Example\n * ```ts\n * const groups = await snaptrude.design.windows.listCatalogGroups()\n * for (const g of groups) console.log(g.dbType, g.label, g.source)\n * ```\n */\n public abstract listCatalogGroups(): PluginApiReturn<PluginObjectCatalogGroup[]>\n\n /**\n * List the placeable window catalog (team + general libraries), optionally\n * restricted to one group by its `dbType` token.\n *\n * @param group Restrict to one group's `dbType` (optional; e.g. `\"casementDouble\"`)\n * @returns The catalog as {@linkcode PluginObjectCatalogItem}`[]` (`[]` when empty)\n *\n * @examplePrompt What windows can I place in this project?\n * @examplePrompt List every window in the library\n * @examplePrompt Show me the casement windows I can add\n * @examplePrompt Browse the window catalog\n *\n * # Example\n * ```ts\n * const [group] = await snaptrude.design.windows.listCatalogGroups()\n * const items = await snaptrude.design.windows.listCatalog(group?.dbType)\n * for (const item of items) console.log(item.id, item.name, item.thumbnailUrl)\n * ```\n */\n public abstract listCatalog(\n group?: string\n ): PluginApiReturn<PluginObjectCatalogItem[]>\n\n /**\n * Get a single window catalog item by its library id.\n *\n * @param id The catalog library id\n * @returns The item, or `null` if no catalog item matches\n *\n * @examplePrompt Get the details of this window from the library\n * @examplePrompt Look up the catalog entry for this window id\n * @examplePrompt What is the cost of this window item?\n * @examplePrompt Show me the family name of this window in the catalog\n *\n * # Example\n * ```ts\n * const [entry] = await snaptrude.design.windows.listCatalog()\n * const item = await snaptrude.design.windows.getCatalogItem(entry.id)\n * if (item) console.log(item.name, item.source, item.dbType)\n * ```\n */\n public abstract getCatalogItem(\n id: string\n ): PluginApiReturn<PluginObjectCatalogItem | null>\n\n /**\n * Test whether a window catalog item exists for the given library id.\n *\n * @param id The catalog library id\n * @returns `true` if it exists, otherwise `false`\n *\n * @examplePrompt Is this window still in the library?\n * @examplePrompt Check whether a window catalog item with this id exists\n * @examplePrompt Does my team library have this window?\n * @examplePrompt Verify the window is available before placing it\n *\n * # Example\n * ```ts\n * const [entry] = await snaptrude.design.windows.listCatalog()\n * const stillAvailable = await snaptrude.design.windows.exists(entry.id)\n * console.log(stillAvailable ? \"safe to place\" : \"removed from library\")\n * ```\n */\n public abstract exists(id: string): PluginApiReturn<boolean>\n}\n\n/**\n * Arguments for the single-window read methods.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `window` | {@linkcode ComponentHandle} | The window to query |\n */\nexport const PluginDesignWindowArgs = z.object({\n window: ComponentHandle,\n})\nexport type PluginDesignWindowArgs = z.infer<typeof PluginDesignWindowArgs>\n\n/**\n * A window's dimensions.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `width` | `number` | Along the wall |\n * | `height` | `number` | Vertical |\n * | `thickness` | `number` | Through the wall (engine `length` axis) |\n */\nexport const PluginWindowDimensions = z.object({\n width: z.number(),\n height: z.number(),\n thickness: z.number(),\n})\nexport type PluginWindowDimensions = z.infer<typeof PluginWindowDimensions>\n\n/**\n * Arguments for {@linkcode PluginDesignWindowsApi.setWidth}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `window` | {@linkcode ComponentHandle} | The window to resize |\n * | `width` | `number` | New width (engine units) |\n */\nexport const PluginDesignWindowSetWidthArgs = z.object({\n window: ComponentHandle,\n width: z.number(),\n})\nexport type PluginDesignWindowSetWidthArgs = z.infer<typeof PluginDesignWindowSetWidthArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignWindowsApi.setHeight}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `window` | {@linkcode ComponentHandle} | The window to resize |\n * | `height` | `number` | New height (engine units) |\n */\nexport const PluginDesignWindowSetHeightArgs = z.object({\n window: ComponentHandle,\n height: z.number(),\n})\nexport type PluginDesignWindowSetHeightArgs = z.infer<typeof PluginDesignWindowSetHeightArgs>\n\n// ---------------------------------------------------------------------------\n// catalog (shared DTOs defined in ../doors, re-exported here for symmetry)\n// ---------------------------------------------------------------------------\n\n// Re-export the shared catalog DTOs imported at the top of this file.\nexport { PluginObjectCatalogGroup, PluginObjectCatalogItem }\n\n/**\n * Arguments for {@linkcode PluginDesignWindowsApi.listCatalog}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `group` | `string`? | Restrict to one group's `dbType` token (optional) |\n */\nexport const PluginDesignWindowListCatalogArgs = z.object({\n group: z.string().optional(),\n})\nexport type PluginDesignWindowListCatalogArgs = z.infer<typeof PluginDesignWindowListCatalogArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignWindowsApi.getCatalogItem} / {@linkcode PluginDesignWindowsApi.exists}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | The catalog library id |\n */\nexport const PluginDesignWindowGetCatalogItemArgs = z.object({\n id: z.string(),\n})\nexport type PluginDesignWindowGetCatalogItemArgs = z.infer<typeof PluginDesignWindowGetCatalogItemArgs>\n\nexport const PluginDesignWindowExistsArgs = PluginDesignWindowGetCatalogItemArgs\nexport type PluginDesignWindowExistsArgs = z.infer<typeof PluginDesignWindowExistsArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { ComponentHandle } from \"../../../handles\"\nimport { PluginDesignChangeResult } from \"../lock\"\nimport { PluginObjectCatalogGroup } from \"../doors\"\n\n/**\n * `snaptrude.design.furniture` — the placeable furniture **catalog** (a library of\n * source items, NOT scene entities) plus dimension reads/edits of PLACED furniture.\n * Placement lives at `design.create.furniture` (returns a {@linkcode ComponentHandle});\n * generic reads of placed furniture live at `design.query.listFurniture`/`getProperties`.\n *\n * Catalog items are plain value records ({@linkcode PluginFurnitureCatalogItem}) keyed\n * by a library id (team `_id` or general `fullName`) — not handles. Reads are async\n * (network-backed) but carry no `Async` suffix (§6.6).\n *\n * {@linkcode PluginDesignFurnitureApi.getDimensions} / {@linkcode PluginDesignFurnitureApi.setDimensions}\n * target a PLACED furniture {@linkcode ComponentHandle} (not a catalog id) — `length`\n * along the object, `width` across it, `height` up. Values are engine units, matching\n * the length/width/height fields the furniture properties panel shows.\n */\nexport abstract class PluginDesignFurnitureApi {\n constructor() {}\n\n /**\n * Get the dimensions of a PLACED furniture item — `length` (along the object),\n * `width` (across it) and `height` (vertical), in engine units. Mirrors the\n * Length/Width/Height fields of the furniture properties panel.\n *\n * @param item The placed furniture to query\n * @returns The dimensions, or `null` if the geometry is degenerate\n *\n * @examplePrompt How big is this sofa?\n * @examplePrompt What are the dimensions of the selected furniture?\n * @examplePrompt Measure the length, width and height of this table\n * @examplePrompt Get the size of the chair I just placed\n *\n * # Example\n * ```ts\n * const [item] = await snaptrude.design.query.listFurniture({ isSelected: true })\n * const dims = await snaptrude.design.furniture.getDimensions(item)\n * if (dims) console.log(dims.length, dims.width, dims.height)\n * ```\n */\n public abstract getDimensions(\n item: ComponentHandle\n ): PluginApiReturn<PluginFurnitureDimensions | null>\n\n /**\n * Resize a PLACED furniture item along one or more axes — `length`, `width`\n * and/or `height` (engine units). Omitted axes are left unchanged. Each supplied\n * axis is an undoable command, mirroring editing the furniture properties panel.\n *\n * @param item The placed furniture to resize\n * @param dimensions New `length` / `width` / `height` (each optional, engine units)\n * @returns The affected furniture\n *\n * @examplePrompt Make this table 1.8m long\n * @examplePrompt Resize the selected sofa to 2000 wide\n * @examplePrompt Set this furniture to 800mm tall\n * @examplePrompt Change the desk to 1500 long and 700 deep\n *\n * # Example\n * ```ts\n * const [item] = await snaptrude.design.query.listFurniture({ isSelected: true })\n * const { affected } = await snaptrude.design.furniture.setDimensions(item, {\n * length: 1800,\n * height: 750\n * })\n * ```\n */\n public abstract setDimensions(\n item: ComponentHandle,\n dimensions: PluginFurnitureDimensionUpdates\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * List the categories (Living, Bedroom, Kitchen, …) that organize the\n * furniture catalog — the picker taxonomy, merged from built-in categories,\n * team types, and backend user types. Pass a value to\n * {@linkcode PluginDesignFurnitureApi.listCatalog}'s `category` filter.\n * @returns The category names (`[]` when empty)\n *\n * @examplePrompt What furniture categories are available?\n * @examplePrompt List the furniture categories\n * @examplePrompt Show me the furniture groups like living and bedroom\n * @examplePrompt Which furniture categories can I filter by?\n *\n * # Example\n * ```ts\n * const categories = await snaptrude.design.furniture.listCategories()\n * const [first] = categories\n * const items = await snaptrude.design.furniture.listCatalog(undefined, first)\n * console.log(first, \"→\", items.length, \"items\")\n * ```\n *\n * @deprecated Use `design.furniture.listCatalogGroups` — the same name\n * `design.doors` and `design.windows` use. Still supported.\n */\n public abstract listCategories(): PluginApiReturn<string[]>\n\n /**\n * List the furniture catalog's groups. Matches\n * {@linkcode PluginDesignDoorsApi.listCatalogGroups} and\n * {@linkcode PluginDesignWindowsApi.listCatalogGroups}, so all three catalog\n * surfaces are named alike.\n *\n * `source` is `\"default\"` for the built-in picker groups and `\"team\"` for the\n * sub-types of THIS project's team library — exactly the split\n * {@linkcode PluginDesignFurnitureApi.listCatalog}'s `source` filter uses, so\n * every `\"team\"` group is guaranteed to match at least one team item here.\n * For furniture the group token and its label are the same string (the\n * category), so `dbType === label`; `design.doors`/`design.windows` have a\n * distinct engine `dbType`.\n *\n * @returns The catalog groups as {@linkcode PluginObjectCatalogGroup}`[]`\n * (`[]` when empty). Pass a group's `dbType` (=== `label`) to\n * {@linkcode PluginDesignFurnitureApi.listCatalog}'s `category` filter to\n * list its items.\n *\n * @examplePrompt What furniture groups are available?\n * @examplePrompt List the furniture categories in this project\n *\n * # Example\n * ```ts\n * const groups = await snaptrude.design.furniture.listCatalogGroups()\n * for (const g of groups) console.log(g.dbType, g.label, g.source)\n * ```\n */\n public abstract listCatalogGroups(): PluginApiReturn<PluginObjectCatalogGroup[]>\n\n /**\n * List the placeable furniture catalog (team + general libraries),\n * optionally filtered by library `source` and/or `category`.\n * @param source Restrict to one library (optional)\n * @param category Restrict to one category (optional; see {@linkcode PluginDesignFurnitureApi.listCategories})\n * @returns The catalog as {@linkcode PluginFurnitureCatalogItem}`[]` (`[]` when empty)\n *\n * @examplePrompt What furniture can I place in this project?\n * @examplePrompt Show me all the furniture available in the library\n * @examplePrompt List the chairs and tables I can add to the model\n * @examplePrompt Which furniture items are in my team's library?\n * @examplePrompt Show me the bedroom furniture I can place\n *\n * # Example\n * ```ts\n * const bedroom = await snaptrude.design.furniture.listCatalog(\"team\", \"bedroom\")\n * for (const item of bedroom) console.log(item.id, item.name, item.thumbnailUrl)\n * ```\n */\n public abstract listCatalog(\n source?: \"team\" | \"general\",\n category?: string\n ): PluginApiReturn<PluginFurnitureCatalogItem[]>\n\n /**\n * Get a single catalog item by its library id.\n * @param id The catalog library id\n * @returns The item, or `null` if no catalog item matches\n *\n * @examplePrompt Get the details of this furniture item from the library\n * @examplePrompt Look up the catalog entry for this chair id\n * @examplePrompt What is the cost of this furniture item?\n * @examplePrompt Show me the family name of the sofa in the catalog\n *\n * # Example\n * ```ts\n * const [entry] = await snaptrude.design.furniture.listCatalog()\n * const item = await snaptrude.design.furniture.getCatalogItem(entry.id)\n * if (item) console.log(item.name, item.source, item.familyName)\n * ```\n */\n public abstract getCatalogItem(\n id: string\n ): PluginApiReturn<PluginFurnitureCatalogItem | null>\n\n /**\n * Test whether a catalog item exists for the given library id.\n * @param id The catalog library id\n * @returns `true` if it exists, otherwise `false`\n *\n * @examplePrompt Is this furniture item still in the library?\n * @examplePrompt Check whether a catalog item with this id exists\n * @examplePrompt Does my team library have this chair?\n * @examplePrompt Verify the sofa is available in the catalog before placing it\n *\n * # Example\n * ```ts\n * const [entry] = await snaptrude.design.furniture.listCatalog()\n * const stillAvailable = await snaptrude.design.furniture.exists(entry.id)\n * console.log(stillAvailable ? \"safe to place\" : \"removed from library\")\n * ```\n */\n public abstract exists(id: string): PluginApiReturn<boolean>\n}\n\n/**\n * A placeable furniture catalog item (value record — not a scene entity/handle).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Library id (team `_id` or general `fullName`) — pass to `design.create.furniture` |\n * | `name` | `string` | Display name |\n * | `source` | `'team' \\| 'general'` | Which library it came from |\n * | `subType` | `string` | Sub-type (optional) |\n * | `category` | `string` | Category the item belongs to (optional) |\n * | `thumbnailUrl` | `string` | Preview thumbnail URL (optional; never the mesh URL) |\n * | `cost` | `number` | Cost (optional; team items) |\n * | `familyName` | `string` | Family name (optional) |\n */\nexport const PluginFurnitureCatalogItem = z.object({\n id: z.string(),\n name: z.string(),\n source: z.enum([\"team\", \"general\"]),\n subType: z.string().optional(),\n category: z.string().optional(),\n thumbnailUrl: z.string().optional(),\n cost: z.number().optional(),\n familyName: z.string().optional(),\n})\nexport type PluginFurnitureCatalogItem = z.infer<typeof PluginFurnitureCatalogItem>\n\n/**\n * Arguments for {@linkcode PluginDesignFurnitureApi.listCatalog}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `source` | `'team' \\| 'general'` | Restrict to one library (optional) |\n * | `category` | `string` | Restrict to one category (optional) |\n */\nexport const PluginDesignFurnitureListCatalogArgs = z.object({\n source: z.enum([\"team\", \"general\"]).optional(),\n category: z.string().optional(),\n})\nexport type PluginDesignFurnitureListCatalogArgs = z.infer<\n typeof PluginDesignFurnitureListCatalogArgs\n>\n\n/**\n * Arguments for {@linkcode PluginDesignFurnitureApi.getCatalogItem} / {@linkcode PluginDesignFurnitureApi.exists}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | The catalog library id |\n */\nexport const PluginDesignFurnitureGetCatalogItemArgs = z.object({\n id: z.string(),\n})\nexport type PluginDesignFurnitureGetCatalogItemArgs = z.infer<\n typeof PluginDesignFurnitureGetCatalogItemArgs\n>\n\nexport const PluginDesignFurnitureExistsArgs = PluginDesignFurnitureGetCatalogItemArgs\nexport type PluginDesignFurnitureExistsArgs = z.infer<typeof PluginDesignFurnitureExistsArgs>\n\n// ---------------------------------------------------------------------------\n// placed-furniture dimensions\n// ---------------------------------------------------------------------------\n\n/**\n * A placed furniture item's dimensions (engine units).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `length` | `number` | Along the object (panel \"Length\") |\n * | `width` | `number` | Across the object (panel \"Width\") |\n * | `height` | `number` | Vertical (panel \"Height\") |\n */\nexport const PluginFurnitureDimensions = z.object({\n length: z.number(),\n width: z.number(),\n height: z.number(),\n})\nexport type PluginFurnitureDimensions = z.infer<typeof PluginFurnitureDimensions>\n\n/**\n * Arguments for {@linkcode PluginDesignFurnitureApi.getDimensions}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `item` | {@linkcode ComponentHandle} | The placed furniture to query |\n */\nexport const PluginDesignFurnitureItemArgs = z.object({\n item: ComponentHandle,\n})\nexport type PluginDesignFurnitureItemArgs = z.infer<typeof PluginDesignFurnitureItemArgs>\n\n/**\n * A partial dimension patch — each axis is optional; omitted axes are unchanged.\n * Every supplied axis must be a positive engine-unit length.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `length` | `number`? | New length along the object (engine units, > 0) |\n * | `width` | `number`? | New width across the object (engine units, > 0) |\n * | `height` | `number`? | New height (engine units, > 0) |\n */\nexport const PluginFurnitureDimensionUpdates = z.object({\n length: z.number().positive().optional(),\n width: z.number().positive().optional(),\n height: z.number().positive().optional(),\n})\nexport type PluginFurnitureDimensionUpdates = z.infer<typeof PluginFurnitureDimensionUpdates>\n\n/**\n * Arguments for {@linkcode PluginDesignFurnitureApi.setDimensions}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `item` | {@linkcode ComponentHandle} | The placed furniture to resize |\n * | `dimensions` | {@linkcode PluginFurnitureDimensionUpdates} | The axes to change (each optional) |\n */\nexport const PluginDesignFurnitureSetDimensionsArgs = z.object({\n item: ComponentHandle,\n dimensions: PluginFurnitureDimensionUpdates,\n})\nexport type PluginDesignFurnitureSetDimensionsArgs = z.infer<\n typeof PluginDesignFurnitureSetDimensionsArgs\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { ComponentHandle, MaterialHandle } from \"../../../handles\"\n\n/**\n * `snaptrude.design.materials` — the project material library + material assignment.\n *\n * Materials are {@linkcode MaterialHandle}s (entity-style, name-addressed). Reads return\n * a {@linkcode MaterialHandle}/{@linkcode PluginMaterialInfo}; `apply`/`reset` mutate the\n * scene (undoable via `save`, default `true`). `create` mints a material from a\n * {@linkcode PluginMaterialSpec} (idempotent by name). `list` reads the materials loaded\n * in the project; `listPresets` reads the browsable preset/library catalog (handle-less\n * {@linkcode PluginPresetMaterial} records that become materials via `create`/`apply`).\n *\n * Per-face assignment — {@linkcode PluginDesignMaterialsApi.applyToFaces} /\n * {@linkcode PluginDesignMaterialsApi.resetFaces} plus per-face reads\n * ({@linkcode PluginDesignMaterialsApi.getByFace} / {@linkcode PluginDesignMaterialsApi.listByFace})\n * — targets **BREP faces by durable face index** and is **BREP-only** (furniture,\n * doors/windows, and Revit imports are rejected) and **visual-only** in v1 (the\n * per-face finish is not carried into BIM schedules / cost layers). A face index\n * is durable only while the component's BREP topology (face count/order) is unchanged.\n *\n * `copy` (needs an undo wrapper) lands in a later pass.\n */\nexport abstract class PluginDesignMaterialsApi {\n constructor() {}\n\n /**\n * List the materials loaded in the project — every named material in the live\n * scene (applied finishes, plugin-created materials, and type defaults in use).\n * For the browsable preset/library catalog (finishes not yet in the project),\n * use {@linkcode PluginDesignMaterialsApi.listPresets}.\n *\n * @returns The materials as {@linkcode MaterialHandle}`[]`\n *\n * @examplePrompt List all the materials in this project\n * @examplePrompt What materials do I have available to apply?\n * @examplePrompt Which finishes exist in this model?\n * @examplePrompt Get every material in the project palette\n *\n * # Example\n * ```ts\n * const materials = await snaptrude.design.materials.list()\n * console.log(materials.length, \"materials in the project\")\n * ```\n */\n public abstract list(): PluginApiReturn<MaterialHandle[]>\n\n /**\n * List the preset material library — the catalog the app's material browser\n * shows (the built-in finishes plus the workspace's uploaded materials),\n * fetched from the material library service. Library entries are **not**\n * scene materials and carry no {@linkcode MaterialHandle}; each is a named\n * texture record. To use one, feed it to\n * {@linkcode PluginDesignMaterialsApi.create} /\n * {@linkcode PluginDesignMaterialsApi.apply} as a\n * {@linkcode PluginMaterialSpec}: `{ name, textureUrl, materialType: category }`.\n *\n * @returns The library as {@linkcode PluginPresetMaterial}`[]` (`[]` when the\n * library service is unreachable)\n *\n * @examplePrompt Show me the preset materials library\n * @examplePrompt What wood finishes are available in the material library?\n * @examplePrompt Browse the material catalog and apply a brick texture\n * @examplePrompt Which preset materials can I choose from?\n *\n * # Example\n * ```ts\n * const presets = await snaptrude.design.materials.listPresets()\n * const brick = presets.find((p) => p.category.toLowerCase().includes(\"brick\"))\n * if (brick) {\n * const walls = await snaptrude.design.query.listWalls()\n * await snaptrude.design.materials.apply(walls, {\n * name: brick.name,\n * textureUrl: brick.textureUrl,\n * materialType: brick.category,\n * })\n * }\n * ```\n */\n public abstract listPresets(): PluginApiReturn<PluginPresetMaterial[]>\n\n /**\n * Read a material's descriptor — its name, color, opacity, and texture\n * (when it has one).\n *\n * @param material The material to read\n * @returns The {@linkcode PluginMaterialInfo}, or `null` if the material is gone\n *\n * @examplePrompt What colour is the Brick material?\n * @examplePrompt Show me the details of this material\n * @examplePrompt Get the opacity of the glass finish\n * @examplePrompt Which texture image does the wood material use?\n *\n * # Example\n * ```ts\n * const [material] = await snaptrude.design.materials.list()\n * const info = await snaptrude.design.materials.getInfo(material)\n * if (info) console.log(info.name, info.colorHex, info.alpha)\n * ```\n */\n public abstract getInfo(\n material: MaterialHandle,\n ): PluginApiReturn<PluginMaterialInfo | null>\n\n /**\n * Read the material applied to a component **as a whole** — the eyedropper. For a\n * uniformly-painted component this is that single material; for a per-face-painted\n * component it is the material on the component's base/first face. Use\n * {@linkcode PluginDesignMaterialsApi.getByFace} /\n * {@linkcode PluginDesignMaterialsApi.listByFace} for the per-face breakdown.\n *\n * @param component The component to sample\n * @returns The whole-object material as a {@linkcode PluginComponentMaterial} — its\n * handle (chain it into `apply` / `getInfo`), engine id, name, and whether the\n * component carries its type `default` or an explicitly `applied` finish — or `null`\n * if the component carries no material\n *\n * @examplePrompt What material is on this wall?\n * @examplePrompt Eyedrop the finish of the selected object\n * @examplePrompt Which material does this slab use?\n * @examplePrompt Read the material on this column so I can paint others with it\n * @examplePrompt Is this component painted, and with what?\n *\n * # Example\n * ```ts\n * const [component] = await snaptrude.design.selection.get()\n * const sampled = await snaptrude.design.materials.get(component)\n * if (sampled) {\n * console.log(sampled.name, sampled.source) // e.g. \"Brick Red\" \"applied\"\n * const info = await snaptrude.design.materials.getInfo(sampled.material)\n * console.log(info?.colorHex)\n * }\n * ```\n */\n public abstract get(\n component: ComponentHandle,\n ): PluginApiReturn<PluginComponentMaterial | null>\n\n /**\n * Apply a material to one or more components — an existing project material by\n * handle, or a new one described inline as a {@linkcode PluginMaterialSpec}.\n * Undoable when `save` is true (the default).\n *\n * @param targets Components to paint\n * @param material Material handle, or an inline {@linkcode PluginMaterialSpec}\n * @param save Commit as an undoable command (default `true`)\n * @returns The number of targets the material was applied to\n *\n * @examplePrompt Paint the selected walls red\n * @examplePrompt Apply the brick material to these walls\n * @examplePrompt Put a wood finish on the floor\n * @examplePrompt Change the colour of this room's walls to #ff8800\n * @examplePrompt Add a concrete texture to the facade\n *\n * # Example\n * ```ts\n * const walls = await snaptrude.design.query.listWalls({ storeys: [1] })\n * // inline spec — creates (or reuses by name) a project material, then applies it\n * const { count } = await snaptrude.design.materials.apply(walls, { name: \"Accent Red\", color: \"#b04a3a\" })\n * console.log(\"painted\", count, \"walls\")\n * ```\n */\n public abstract apply(\n targets: ComponentHandle[],\n material: MaterialHandle | PluginMaterialSpec,\n save?: boolean,\n ): PluginApiReturn<PluginMaterialResult>\n\n /**\n * Reset one or more components back to the default material for their type,\n * removing any applied finish. The inverse of `apply`; undoable when `save`\n * is true (the default).\n *\n * @param components Components to reset\n * @param save Commit as an undoable command (default `true`)\n * @returns The number of components reset\n *\n * @examplePrompt Remove the paint from these walls\n * @examplePrompt Reset the selected objects to their default material\n * @examplePrompt Clear the custom finish on the floor\n * @examplePrompt Restore the original colour of this roof\n *\n * # Example\n * ```ts\n * const selected = await snaptrude.design.selection.get()\n * const { count } = await snaptrude.design.materials.reset(selected)\n * console.log(\"reset\", count, \"components to their default material\")\n * ```\n */\n public abstract reset(\n components: ComponentHandle[],\n save?: boolean,\n ): PluginApiReturn<PluginMaterialResult>\n\n /**\n * Paint specific **BREP faces** of a component with a material — an existing\n * project material by handle, or a new one described inline as a\n * {@linkcode PluginMaterialSpec}. Faces are addressed by **durable BREP face\n * index** (their 0-based position in the component's BREP face list).\n * Undoable when `save` is true (the default).\n *\n * Per-face materials are **BREP-only** (walls, floors, slabs, roofs, masses,\n * columns, beams; furniture, doors/windows, and Revit imports are rejected) and\n * **visual-only in v1** — the finish is not carried into BIM schedules / cost\n * layers. A face index is durable only while the BREP topology (face count/order)\n * is unchanged; a face index at or beyond the face count is rejected.\n *\n * @param component The (BREP) component to paint\n * @param faces BREP face indices to paint (nonnegative integers)\n * @param material Material handle, or an inline {@linkcode PluginMaterialSpec}\n * @param save Commit as an undoable command (default `true`)\n * @returns The number of faces painted\n *\n * @examplePrompt Paint the top face of this slab red\n * @examplePrompt Apply the brick material to faces 0 and 2 of this wall\n * @examplePrompt Put a wood finish on just the front face of this column\n * @examplePrompt Colour face 4 of the selected mass with #ff8800\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls({ storeys: [1] })\n * // faces are addressed by durable 0-based BREP face index\n * const { count } = await snaptrude.design.materials.applyToFaces(wall, [0, 1], { name: \"Accent\", color: \"#b04a3a\" })\n * console.log(\"painted\", count, \"faces\")\n * ```\n */\n public abstract applyToFaces(\n component: ComponentHandle,\n faces: number[],\n material: MaterialHandle | PluginMaterialSpec,\n save?: boolean,\n ): PluginApiReturn<PluginMaterialResult>\n\n /**\n * Reset specific BREP faces of a component back to the component/type default,\n * removing any per-face finish. The inverse of\n * {@linkcode PluginDesignMaterialsApi.applyToFaces}; undoable when `save` is true\n * (the default). BREP-only (see `applyToFaces`).\n *\n * @param component The (BREP) component\n * @param faces BREP face indices to reset (nonnegative integers)\n * @param save Commit as an undoable command (default `true`)\n * @returns The number of faces reset\n *\n * @examplePrompt Remove the paint from the top face of this slab\n * @examplePrompt Reset faces 0 and 2 of this wall to their default material\n * @examplePrompt Clear the custom finish on the front face of this column\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls({ storeys: [1] })\n * const { count } = await snaptrude.design.materials.resetFaces(wall, [0, 1])\n * console.log(\"reset\", count, \"faces\")\n * ```\n */\n public abstract resetFaces(\n component: ComponentHandle,\n faces: number[],\n save?: boolean,\n ): PluginApiReturn<PluginMaterialResult>\n\n /**\n * Read the material applied to a single BREP face.\n *\n * @param component The (BREP) component\n * @param face The BREP face index (a nonnegative integer)\n * @returns The face's material as a {@linkcode MaterialHandle}, or `null` if the\n * face carries no explicit per-face material (or the component has no BREP)\n *\n * @examplePrompt What material is on the top face of this slab?\n * @examplePrompt Get the finish applied to face 2 of this wall\n * @examplePrompt Which material is painted on the front face of this column?\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const material = await snaptrude.design.materials.getByFace(wall, 0)\n * if (material) console.log(await snaptrude.design.materials.getInfo(material))\n * ```\n */\n public abstract getByFace(\n component: ComponentHandle,\n face: number,\n ): PluginApiReturn<MaterialHandle | null>\n\n /**\n * List the per-face materials of a component — one entry per BREP face, in face\n * index order. A face carrying no explicit per-face material reports `null`.\n *\n * @param component The (BREP) component\n * @returns The per-face materials as {@linkcode PluginFaceMaterial}`[]` (`[]` for a\n * component with no BREP)\n *\n * @examplePrompt List the material on every face of this wall\n * @examplePrompt Show me which faces of this slab are painted and with what\n * @examplePrompt Break down the per-face finishes of the selected mass\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const faces = await snaptrude.design.materials.listByFace(wall)\n * for (const f of faces) console.log(f.faceIndex, f.material ?? \"(default)\")\n * ```\n */\n public abstract listByFace(\n component: ComponentHandle,\n ): PluginApiReturn<PluginFaceMaterial[]>\n\n /**\n * Create a new material in the project from a spec — a color, texture URL,\n * opacity, and/or name. Idempotent by name: creating with the name of an\n * existing material returns that material instead of duplicating it.\n *\n * @param spec The material construction spec\n * @returns The new material as a {@linkcode MaterialHandle}\n *\n * @examplePrompt Create a new red material called Accent\n * @examplePrompt Make a material from this texture image URL\n * @examplePrompt Add a semi-transparent glass material to the project\n * @examplePrompt Define a dark grey concrete finish I can reuse\n *\n * # Example\n * ```ts\n * const glass = await snaptrude.design.materials.create({ name: \"Frosted Glass\", color: \"#cfe4e8\", alpha: 0.4 })\n * const walls = await snaptrude.design.query.listWalls({ storeys: [1] })\n * await snaptrude.design.materials.apply(walls, glass)\n * ```\n */\n public abstract create(\n spec: PluginMaterialSpec,\n ): PluginApiReturn<MaterialHandle>\n\n /**\n * Get the default material for a component's type.\n * @param component The component to look up\n * @returns The default material as a {@linkcode MaterialHandle}, or `null`\n *\n * @examplePrompt What is the default material for walls?\n * @examplePrompt Get the standard material this floor type uses\n * @examplePrompt Which material would this roof have out of the box?\n * @examplePrompt Find the default finish for the selected column\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const defaultMaterial = await snaptrude.design.materials.getDefault(wall)\n * if (defaultMaterial) console.log(await snaptrude.design.materials.getInfo(defaultMaterial))\n * ```\n */\n public abstract getDefault(\n component: ComponentHandle,\n ): PluginApiReturn<MaterialHandle | null>\n\n /**\n * Test whether a component carries its (type) default material.\n * @param component The component to test\n * @returns `true` if default, otherwise `false`\n *\n * @examplePrompt Is this wall still using its default material?\n * @examplePrompt Has the material on this floor been changed?\n * @examplePrompt Check if the selected object still has its original finish\n * @examplePrompt Tell me whether this roof has been painted or is still standard\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const untouched = await snaptrude.design.materials.isDefault(wall)\n * console.log(untouched ? \"still default\" : \"has been painted\")\n * ```\n */\n public abstract isDefault(\n component: ComponentHandle,\n ): PluginApiReturn<boolean>\n\n /**\n * Test whether a component has a single (uniform) material across all faces.\n * @param component The component to test\n * @returns `true` if uniform, otherwise `false`\n *\n * @examplePrompt Does this wall have the same material on every face?\n * @examplePrompt Is the floor finished uniformly or face by face?\n * @examplePrompt Check if this column is a single material all over\n * @examplePrompt Are any faces of the selected object painted differently?\n *\n * # Example\n * ```ts\n * const [selected] = await snaptrude.design.selection.get()\n * const uniform = await snaptrude.design.materials.isUniform(selected)\n * console.log(uniform ? \"one material all over\" : \"painted per face\")\n * ```\n */\n public abstract isUniform(\n component: ComponentHandle,\n ): PluginApiReturn<boolean>\n\n /**\n * Test whether a material has a texture.\n * @param material The material to test\n * @returns `true` if it has a diffuse texture, otherwise `false`\n *\n * @examplePrompt Does this material have a texture image?\n * @examplePrompt Is the brick material textured or just a flat colour?\n * @examplePrompt Check whether the wood finish uses an image map\n * @examplePrompt Tell me if this material is image-based or a plain colour\n *\n * # Example\n * ```ts\n * const [material] = await snaptrude.design.materials.list()\n * const textured = await snaptrude.design.materials.hasTexture(material)\n * console.log(textured ? \"image-based\" : \"flat colour\")\n * ```\n */\n public abstract hasTexture(material: MaterialHandle): PluginApiReturn<boolean>\n}\n\n/**\n * A material's descriptor.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | Material name (== the handle token) |\n * | `id` | `string` | Engine material id |\n * | `colorHex` | `string` | Diffuse color as `#rrggbb` |\n * | `alpha` | `number` | Opacity `0..1` |\n * | `materialType` | `string` | Material type tag (optional) |\n * | `textureUrl` | `string` | Diffuse texture URL (optional) |\n */\nexport const PluginMaterialInfo = z.object({\n name: z.string(),\n id: z.string(),\n colorHex: z.string(),\n alpha: z.number(),\n materialType: z.string().optional(),\n textureUrl: z.string().optional(),\n})\nexport type PluginMaterialInfo = z.infer<typeof PluginMaterialInfo>\n\n/**\n * A material construction spec (input DTO — NOT a handle).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | Name (optional; generated if omitted) |\n * | `color` | `string` | Diffuse color `#rrggbb` (optional) |\n * | `textureUrl` | `string` | Diffuse texture URL (optional; wins over color) |\n * | `alpha` | `number` | Opacity `0..1` (optional) |\n * | `materialType` | `string` | Material type tag (optional) |\n */\nexport const PluginMaterialSpec = z.object({\n name: z.string().optional(),\n color: z.string().optional(),\n textureUrl: z.string().optional(),\n alpha: z.number().optional(),\n materialType: z.string().optional(),\n})\nexport type PluginMaterialSpec = z.infer<typeof PluginMaterialSpec>\n\n/** Result of a material mutation — the count of targets affected. */\nexport const PluginMaterialResult = z.object({ count: z.number() })\nexport type PluginMaterialResult = z.infer<typeof PluginMaterialResult>\n\n/**\n * A single component face's material assignment (value record — not a handle).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `faceIndex` | `number` | Durable BREP face index (0-based position in the face list) |\n * | `material` | {@linkcode MaterialHandle} \\| `null` | The applied material, or `null` for the default |\n */\nexport const PluginFaceMaterial = z.object({\n faceIndex: z.number().int().nonnegative(),\n material: MaterialHandle.nullable(),\n})\nexport type PluginFaceMaterial = z.infer<typeof PluginFaceMaterial>\n\n/**\n * The material on a component **as a whole** — the eyedropper read returned by\n * {@linkcode PluginDesignMaterialsApi.get} (value record, plus a reusable handle).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `material` | {@linkcode MaterialHandle} | The whole-object material — chain into `apply` / `getInfo` |\n * | `id` | `string` | Engine material id |\n * | `name` | `string` | Material name (== the handle token) |\n * | `source` | `\"default\" \\| \"applied\"` | Whether it is the component's type default or an explicitly-applied finish |\n */\nexport const PluginComponentMaterial = z.object({\n material: MaterialHandle,\n id: z.string(),\n name: z.string(),\n source: z.enum([\"default\", \"applied\"]),\n})\nexport type PluginComponentMaterial = z.infer<typeof PluginComponentMaterial>\n\n/**\n * A preset material library entry (value record — NOT a handle), returned by\n * {@linkcode PluginDesignMaterialsApi.listPresets}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | Display name of the library entry |\n * | `category` | `string` | Library category (camelCase, e.g. `\"wood\"`, `\"colors\"`, `\"brick\"`) |\n * | `textureUrl` | `string` | Texture/swatch image URL — feed to {@linkcode PluginMaterialSpec}`.textureUrl` |\n */\nexport const PluginPresetMaterial = z.object({\n name: z.string(),\n category: z.string(),\n textureUrl: z.string(),\n})\nexport type PluginPresetMaterial = z.infer<typeof PluginPresetMaterial>\n\n/** Arguments for {@linkcode PluginDesignMaterialsApi.getInfo} / `hasTexture`. */\nexport const PluginDesignMaterialsGetInfoArgs = z.object({\n material: MaterialHandle,\n})\nexport type PluginDesignMaterialsGetInfoArgs = z.infer<\n typeof PluginDesignMaterialsGetInfoArgs\n>\n\n/** Arguments for component-scoped predicates (`getDefault`/`isDefault`/`isUniform`). */\nexport const PluginDesignMaterialsComponentArgs = z.object({\n component: ComponentHandle,\n})\nexport type PluginDesignMaterialsComponentArgs = z.infer<\n typeof PluginDesignMaterialsComponentArgs\n>\n\n/**\n * Arguments for {@linkcode PluginDesignMaterialsApi.apply}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `targets` | {@linkcode ComponentHandle}`[]` | Components to paint |\n * | `material` | {@linkcode MaterialHandle} \\| {@linkcode PluginMaterialSpec} | Material handle, or an inline spec |\n * | `save` | `boolean` | Commit as an undoable command (default `true`) |\n */\nexport const PluginDesignMaterialsApplyArgs = z.object({\n targets: z.array(ComponentHandle),\n material: z.union([MaterialHandle, PluginMaterialSpec]),\n save: z.boolean().optional(),\n})\nexport type PluginDesignMaterialsApplyArgs = z.infer<\n typeof PluginDesignMaterialsApplyArgs\n>\n\n/** Arguments for {@linkcode PluginDesignMaterialsApi.reset}. */\nexport const PluginDesignMaterialsResetArgs = z.object({\n components: z.array(ComponentHandle),\n save: z.boolean().optional(),\n})\nexport type PluginDesignMaterialsResetArgs = z.infer<\n typeof PluginDesignMaterialsResetArgs\n>\n\n/** Arguments for {@linkcode PluginDesignMaterialsApi.create}. */\nexport const PluginDesignMaterialsCreateArgs = z.object({\n spec: PluginMaterialSpec,\n})\nexport type PluginDesignMaterialsCreateArgs = z.infer<\n typeof PluginDesignMaterialsCreateArgs\n>\n\n/**\n * Arguments for {@linkcode PluginDesignMaterialsApi.applyToFaces}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `component` | {@linkcode ComponentHandle} | The (BREP) component to paint |\n * | `faces` | `number[]` | BREP face indices to paint (nonnegative integers) |\n * | `material` | {@linkcode MaterialHandle} \\| {@linkcode PluginMaterialSpec} | Material handle, or an inline spec |\n * | `save` | `boolean` | Commit as an undoable command (default `true`) |\n */\nexport const PluginDesignMaterialsApplyToFacesArgs = z.object({\n component: ComponentHandle,\n faces: z.array(z.number().int().nonnegative()),\n material: z.union([MaterialHandle, PluginMaterialSpec]),\n save: z.boolean().optional(),\n})\nexport type PluginDesignMaterialsApplyToFacesArgs = z.infer<\n typeof PluginDesignMaterialsApplyToFacesArgs\n>\n\n/** Arguments for {@linkcode PluginDesignMaterialsApi.resetFaces}. */\nexport const PluginDesignMaterialsResetFacesArgs = z.object({\n component: ComponentHandle,\n faces: z.array(z.number().int().nonnegative()),\n save: z.boolean().optional(),\n})\nexport type PluginDesignMaterialsResetFacesArgs = z.infer<\n typeof PluginDesignMaterialsResetFacesArgs\n>\n\n/** Arguments for {@linkcode PluginDesignMaterialsApi.getByFace}. */\nexport const PluginDesignMaterialsGetByFaceArgs = z.object({\n component: ComponentHandle,\n face: z.number().int().nonnegative(),\n})\nexport type PluginDesignMaterialsGetByFaceArgs = z.infer<\n typeof PluginDesignMaterialsGetByFaceArgs\n>\n\n/** Arguments for {@linkcode PluginDesignMaterialsApi.listByFace} (component only). */\nexport const PluginDesignMaterialsListByFaceArgs =\n PluginDesignMaterialsComponentArgs\nexport type PluginDesignMaterialsListByFaceArgs = z.infer<\n typeof PluginDesignMaterialsListByFaceArgs\n>\n","import * as z from \"zod\"\nimport { ComponentHandle } from \"../../handles\"\n\n/**\n * Shared result for scene-state change mutations (lock/unlock, selection, visibility)\n * — echoes the handles actually affected (post-cascade for lock). Errors throw\n * (RPC rejects); there is no `Result<>` monad in the SDK (consistent with\n * `design.create.*` returning a handle directly).\n */\nexport const PluginDesignChangeResult = z.object({\n affected: z.array(ComponentHandle),\n})\nexport type PluginDesignChangeResult = z.infer<typeof PluginDesignChangeResult>\n\n/**\n * Arguments for {@linkcode PluginDesignApi.lock} / {@linkcode PluginDesignApi.unlock}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `components` | {@linkcode ComponentHandle}`[]` | Entities to lock / unlock |\n */\nexport const PluginDesignLockArgs = z.object({\n components: z.array(ComponentHandle),\n})\nexport type PluginDesignLockArgs = z.infer<typeof PluginDesignLockArgs>\n\nexport const PluginDesignUnlockArgs = PluginDesignLockArgs\nexport type PluginDesignUnlockArgs = z.infer<typeof PluginDesignUnlockArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignApi.isLocked}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `component` | {@linkcode ComponentHandle} | The entity to test |\n * | `includeInherited` | `boolean` | Fold group + bucket lock (default `true`); `false` reads the own flag only |\n */\nexport const PluginDesignIsLockedArgs = z.object({\n component: ComponentHandle,\n includeInherited: z.boolean().optional(),\n})\nexport type PluginDesignIsLockedArgs = z.infer<typeof PluginDesignIsLockedArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignApi.lockArea} / {@linkcode PluginDesignApi.unlockArea}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaces` | {@linkcode ComponentHandle}`[]` | Room / Department mass spaces to area-lock / unlock |\n */\nexport const PluginDesignLockAreaArgs = z.object({\n spaces: z.array(ComponentHandle),\n})\nexport type PluginDesignLockAreaArgs = z.infer<typeof PluginDesignLockAreaArgs>\n\nexport const PluginDesignUnlockAreaArgs = PluginDesignLockAreaArgs\nexport type PluginDesignUnlockAreaArgs = z.infer<typeof PluginDesignUnlockAreaArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignApi.isAreaLocked}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `space` | {@linkcode ComponentHandle} | The space to test |\n */\nexport const PluginDesignIsAreaLockedArgs = z.object({\n space: ComponentHandle,\n})\nexport type PluginDesignIsAreaLockedArgs = z.infer<typeof PluginDesignIsAreaLockedArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { ComponentHandle, Vec3Handle } from \"../../../handles\"\n\n/**\n * The erase strategies exposed to plugins. `SplitAndUnion` is *enumerable* but\n * NOT committable via {@linkcode PluginDesignEraseApi.edge} (it needs an\n * interactive screen-space re-pick); `SplitAndDelete` is reserved in the engine\n * and intentionally omitted. Derived host-side by the engine's `decideStrategy`.\n */\nexport const EraseStrategy = z.enum([\"DirectDelete\", \"DirectUnion\", \"SplitAndUnion\"])\nexport type EraseStrategy = z.infer<typeof EraseStrategy>\n\n/** Default enumerated strategies — the two the scripted `edge()` can commit. */\nexport const DEFAULT_ERASE_STRATEGIES: readonly EraseStrategy[] = [\"DirectDelete\", \"DirectUnion\"]\n\n/**\n * A **transient** reference to one erasable edge in the current plan's\n * bottom-adjacency graph.\n *\n * ⚠️ **Lifetime**: valid ONLY until the next geometry-modifying operation. Any\n * mutation (erase, boolean, transform, create, delete, …) rebuilds the adjacency\n * graph and invalidates every previously-returned ref. Passing a stale ref to\n * {@linkcode PluginDesignEraseApi.edge} throws `NO_STRATEGY_FOR_PICK_INFO`.\n * Always re-list after a mutation.\n *\n * This is a by-value record (round-trip it back to `edge()` unmodified), NOT a\n * registry handle. `components` and `snapPoint` are the marshalled public forms\n * of the engine's raw `mesh.uniqueId` numbers / by-value point.\n */\nexport const ErasableEdgeRef = z.object({\n /** Opaque bottom-graph edge id (engine form `\"e-<n>\"`). Do not mutate. Pass verbatim to {@linkcode PluginDesignEraseApi.edge}. */\n id: z.string().min(1),\n /** What erasing this edge would do: `DirectDelete` drops a mass; `DirectUnion` merges two adjacent masses. */\n strategy: EraseStrategy,\n /** Bottom-graph edge weight: `1` = external boundary edge, `>=2` = internal edge shared between components. */\n edgeWeight: z.number(),\n /** The components this edge is incident to — one for external (DirectDelete); two or more for internal (DirectUnion; 3+ at T-junctions where several masses share the edge). */\n components: z.array(ComponentHandle),\n /** A representative point on the edge (its head endpoint). Diagnostic only. */\n snapPoint: Vec3Handle,\n})\nexport type ErasableEdgeRef = z.infer<typeof ErasableEdgeRef>\n\n/**\n * Options for {@linkcode PluginDesignEraseApi.listErasableEdges}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `components` | {@linkcode ComponentHandle}`[]?` | Restrict to edges adjacent to at least one of these entities |\n * | `strategies` | {@linkcode EraseStrategy}`[]?` | Restrict to these strategies. Default `[\"DirectDelete\",\"DirectUnion\"]` (hides interactive-only `SplitAndUnion`) |\n *\n * TRANSPORT: positional now — `listErasableEdges(options?)`; this schema is\n * retained for host-side validation.\n */\nexport const PluginDesignListErasableEdgesArgs = z.object({\n components: z.array(ComponentHandle).optional(),\n strategies: z.array(EraseStrategy).optional(),\n})\nexport type PluginDesignListErasableEdgesArgs = z.infer<typeof PluginDesignListErasableEdgesArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignEraseApi.edge}.\n *\n * TRANSPORT: positional now — `edge(edge: ErasableEdgeRef)`; this schema is\n * retained for host-side validation.\n */\nexport const PluginDesignEraseEdgeArgs = z.object({\n edge: ErasableEdgeRef,\n})\nexport type PluginDesignEraseEdgeArgs = z.infer<typeof PluginDesignEraseEdgeArgs>\n\n/** Result of {@linkcode PluginDesignEraseApi.edge} — success sentinel; the engine yields no survivor id. */\nexport const PluginDesignEraseEdgeResult = z.object({ ok: z.literal(true) })\nexport type PluginDesignEraseEdgeResult = z.infer<typeof PluginDesignEraseEdgeResult>\n\n/**\n * `snaptrude.design.erase` — **adjacency-edge erase** (plan-level topology op).\n *\n * Erase is NOT delete. The erase tool operates on an **edge**, and the effect is\n * derived from that edge: erasing an internal edge between two masses **unions**\n * them (`DirectUnion`); erasing an external boundary edge of a single mass\n * **drops** it behind the edge (`DirectDelete`). For hard entity removal use\n * `design.delete.entities`; for CSG geometry delete use `design.boolean.subtract`.\n *\n * Two-step: {@linkcode listErasableEdges} to enumerate, then {@linkcode edge} to\n * commit one ref (single undo entry). Scoped to **2D plan adjacency edges** in v1.\n *\n * TRANSPORT: positional args per method.\n */\nexport abstract class PluginDesignEraseApi {\n constructor() {}\n\n /**\n * Enumerate every erasable edge in the current plan's bottom-adjacency graph\n * (one {@linkcode ErasableEdgeRef} per edge). Pure read — no scene mutation, no\n * command, not undoable. Erase-ineligible components (locked, hidden, off-storey)\n * are filtered out.\n *\n * ⚠️ Returned refs are **transient** — see {@linkcode ErasableEdgeRef}. Re-list\n * after any geometry mutation.\n *\n * @param options - {@linkcode PluginDesignListErasableEdgesArgs} — optional filters (`components`, `strategies`)\n * @returns The erasable edges (`[]` when none) — never `null`.\n *\n * @examplePrompt Which edges in my floor plan can be erased?\n * @examplePrompt List the erasable edges on this storey\n * @examplePrompt Show me the shared edges I could erase to merge rooms\n * @examplePrompt What edges between these two spaces can be removed?\n *\n * # Example\n * ```ts\n * const edges = await snaptrude.design.erase.listErasableEdges()\n * const union = edges.find((e) => e.strategy === \"DirectUnion\")\n * if (union) await snaptrude.design.erase.edge(union)\n * ```\n */\n public abstract listErasableEdges(\n options?: PluginDesignListErasableEdgesArgs,\n ): PluginApiReturn<ErasableEdgeRef[]>\n\n /**\n * Commit the erase for one edge. `DirectUnion` merges the two adjacent masses;\n * `DirectDelete` drops the single mass behind the boundary edge. Single undo entry.\n *\n * Pass an {@linkcode ErasableEdgeRef} from {@linkcode listErasableEdges}\n * unmodified. A stale ref (graph rebuilt by a prior mutation) or a `SplitAndUnion`\n * edge throws.\n *\n * @param edge - The {@linkcode ErasableEdgeRef} to erase, from {@linkcode listErasableEdges} unmodified\n * @returns `{ ok: true }` on success (the engine yields no survivor id today).\n * @throws when the ref is stale, its components are all erase-ineligible, or the\n * strategy is not committable via the scripted path.\n *\n * @examplePrompt Erase the edge between these two rooms so they merge\n * @examplePrompt Remove the shared boundary between the kitchen and the dining space\n * @examplePrompt Merge the two adjacent rooms by erasing their common edge\n * @examplePrompt Knock through the partition line between these spaces\n * @examplePrompt Erase this boundary edge from the plan\n *\n * # Example\n * ```ts\n * // Merge two adjacent rooms by erasing an internal edge they share\n * const edges = await snaptrude.design.erase.listErasableEdges({ strategies: [\"DirectUnion\"] })\n * if (edges.length > 0) {\n * const { ok } = await snaptrude.design.erase.edge(edges[0])\n * }\n * ```\n */\n public abstract edge(edge: ErasableEdgeRef): PluginApiReturn<PluginDesignEraseEdgeResult>\n}\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { ComponentHandle } from \"../../../handles\"\nimport { PluginDesignChangeResult } from \"../lock\"\n\n/**\n * Arguments for {@linkcode PluginDesignDeleteApi.entities}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `components` | {@linkcode ComponentHandle}`[]` | Entities to delete. Unknown/forged handles reject the whole call (fail-fast). |\n *\n * `.min(1)` is deliberately stricter than the shipped `PluginDesignLockArgs`\n * (which allows an empty array) — an empty delete is a caller error, not a no-op.\n *\n * TRANSPORT: positional args — validated host-side as `{ components }`.\n */\nexport const PluginDesignDeleteEntitiesArgs = z.object({\n components: z.array(ComponentHandle).min(1),\n})\nexport type PluginDesignDeleteEntitiesArgs = z.infer<typeof PluginDesignDeleteEntitiesArgs>\n\n/**\n * `snaptrude.design.delete.*` — **hard removal** of scene BIM entities.\n *\n * This is hard entity delete — distinct from `design.erase.edge` (a plan-level\n * adjacency-edge topology op) and `design.boolean.subtract` (a CSG geometry\n * delete). The constitution forbids `erase`/`clear`/`remove` as synonyms for\n * `delete`; these are three separate operations.\n *\n * TRANSPORT: positional args per method.\n */\nexport abstract class PluginDesignDeleteApi {\n constructor() {}\n\n /**\n * Delete one or more entities. Generic hard delete (backed by the same batched,\n * undoable path as `entity.space.delete`). Cascade is opinionated by the engine:\n * children are removed and linked-list neighbours are fixed up — not caller-tunable\n * in v1. Undoable — commits as a **single** undo entry.\n *\n * Dimension lines anchored to a deleted entity are removed with it, in the same undo\n * entry, exactly as the editor's Delete key does — see `design.dimensions`. They are\n * NOT listed in `affected`, which reports component handles only.\n *\n * @param components - Entities to delete. Unknown/forged handles reject the whole call (fail-fast).\n * @returns The entities that were deleted, as {@linkcode ComponentHandle}`[]`\n * (echoed host-side from the resolved+deleted set — the engine returns no ids).\n * Deleted handles are **stale** afterwards: re-resolving one throws `HANDLE_INVALID`.\n * @throws If any supplied handle is unknown/forged (`HANDLE_INVALID`) — the whole\n * call rejects before anything is deleted (all-or-nothing resolution).\n *\n * @examplePrompt Delete the selected objects\n * @examplePrompt Remove these walls from the model\n * @examplePrompt Get rid of this mass entirely\n * @examplePrompt Delete all the furniture I picked\n * @examplePrompt Remove the door and window I just created\n *\n * @performance Array delete — pass every target in one call (one host round-trip). There is\n * no per-item delete; collect the whole set and never loop this per entity.\n *\n * # Example\n * ```ts\n * // Hard-delete every furniture item on storey 2 (a single undo entry)\n * const furniture = await snaptrude.design.query.listFurniture({ storeys: [2] })\n * if (furniture.length > 0) {\n * const { affected } = await snaptrude.design.delete.entities(furniture)\n * }\n * ```\n */\n public abstract entities(\n components: ComponentHandle[],\n ): PluginApiReturn<PluginDesignChangeResult>\n}\n","import * as z from \"zod\"\nimport { ComponentHandle } from \"../../handles\"\n\n/**\n * Shared `design.*` operation return records (LLD Part A.3).\n *\n * `PluginDesignChangeResult` (`{ affected }`) is NOT here — it already lives in\n * `./lock` and is reused verbatim by the transform/delete mutators.\n *\n * NOTE: `PluginMirrorAxis` currently lives in `./doors`; its relocation into this\n * shared module is deferred to the transform wave (avoids a doors edit + a\n * double-export while `design.transform` is unwired).\n */\n\n/**\n * Result of a CSG boolean op (`design.boolean.union/subtract/intersect`). Grounded\n * in the engine `Boolean3dOperation.resultComponents { toCreate; toDelete }`:\n * `created` are the result masses, `deleted` is every consumed input (for\n * `subtract` this includes BOTH the target AND the tools). Errors throw (RPC\n * rejects); no `Result<>` crosses the boundary.\n */\nexport const BooleanOutcome = z.object({\n created: z.array(ComponentHandle),\n deleted: z.array(ComponentHandle),\n})\nexport type BooleanOutcome = z.infer<typeof BooleanOutcome>\n\n/**\n * Result of `design.edit.offsetSplit` (migrated from `tools.offset`). `createdIds`\n * are the components the offset produced; `deletedIds` are those a split-style\n * offset removed. `offsetValue` is the engine's POST-clamp / post-sign applied\n * distance — the only way a caller learns the actually-applied clamped distance\n * (the shipped `tools.offset` result dropped it).\n */\nexport const OffsetSplitOutcome = z.object({\n createdIds: z.array(ComponentHandle),\n deletedIds: z.array(ComponentHandle),\n offsetValue: z.number(),\n})\nexport type OffsetSplitOutcome = z.infer<typeof OffsetSplitOutcome>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { ComponentHandle, Vec3Handle } from \"../../../handles\"\nimport { BooleanOutcome } from \"../shared\"\n\n/**\n * `snaptrude.design.boolean` — OpenCascade CSG on scene masses/spaces.\n *\n * Every method **commits** the operation as one undoable step (engine `run()`\n * queues create/delete/group/dimension commands, `execute()` runs them through\n * `CommandManager`) and returns the committed {@linkcode BooleanOutcome}. Failures\n * **throw** (RPC rejects) — no `Result<>` monad crosses the boundary.\n *\n * Eligibility (all inputs): a `Mass` that is a room OR a DEPARTMENT, has a brep, is\n * unlocked (mesh + bucket), visible, and NOT in a parametric-copy group; the whole\n * set must be all-planar OR all-non-planar; **≥2** components; all inputs share one\n * group or are ungrouped; inputs must be adjacent/overlapping.\n *\n * `design.boolean.subtract` is a **CSG geometry delete** — distinct from\n * `design.delete.entities` (hard entity removal) and `design.erase.edge`\n * (adjacency-edge topology op).\n *\n * TRANSPORT: positional args per method.\n */\nexport abstract class PluginDesignBooleanApi {\n constructor() {}\n\n /**\n * Fuse two or more masses into a single combined mass (`A ∪ B ∪ …`) — a boolean\n * (CSG) union that merges the adjacent or overlapping inputs into one solid,\n * committed as a single undoable step. Order-independent.\n * @param components - The ≥2 entities to fuse (order-independent)\n * @param options - Optional output offset — {@linkcode BooleanOptions}\n * @returns The committed CSG delta — {@linkcode BooleanOutcome}\n * @throws if inputs are ineligible, disjoint, non-manifold when joined, span\n * different groups, or the CSG yields no / invalid geometry.\n *\n * @examplePrompt Merge these two masses into one\n * @examplePrompt Union the selected volumes into a single mass\n * @examplePrompt Combine the overlapping rooms into one shape\n * @examplePrompt Join the tower and podium blocks together\n * @examplePrompt Fuse all the selected massing blocks into a single solid\n *\n * # Example\n * ```ts\n * // Fuse the overlapping massing blocks on the ground storey into one solid\n * const masses = await snaptrude.design.query.listMasses({ storeys: [1] })\n * const { created, deleted } = await snaptrude.design.boolean.union(masses)\n * console.log(\"created\", created.length, \"mass; consumed\", deleted.length)\n * ```\n */\n public abstract union(\n components: ComponentHandle[],\n options?: BooleanOptions,\n ): PluginApiReturn<BooleanOutcome>\n\n /**\n * Cut one or more tool masses out of a target mass (`target − ⋃tools`) — a\n * boolean (CSG) subtraction that carves the tools' volume out of the target and\n * commits the remainder as a single undoable step.\n *\n * ORDER-SENSITIVE: the host seeds `[target, ...tools]`; index 0 is the arg brep,\n * the rest are tool breps. `deleted` includes the target AND every tool;\n * `created` is the remainder mass(es).\n * @param target - The entity subtracted FROM (arg brep)\n * @param tools - The ≥1 entities subtracted (tool breps)\n * @param options - Optional output offset — {@linkcode BooleanOptions}\n * @returns The committed CSG delta — {@linkcode BooleanOutcome}\n * @throws as {@linkcode union}; a `NoResult` here carries the \"select the target\n * object first\" hint from the engine.\n *\n * @examplePrompt Cut this shape out of the bigger mass\n * @examplePrompt Subtract the cylinder from the building volume\n * @examplePrompt Punch an opening through this block using the small box\n * @examplePrompt Carve the courtyard volume out of the main massing\n * @examplePrompt Remove the overlapping part of mass B from mass A\n *\n * # Example\n * ```ts\n * // Order matters: the first arg is the target, the array holds the tools\n * const [building, courtyard] = await snaptrude.design.selection.get()\n * const { created, deleted } = await snaptrude.design.boolean.subtract(building, [courtyard])\n * // deleted includes the target AND every tool; created is the remainder\n * ```\n */\n public abstract subtract(\n target: ComponentHandle,\n tools: ComponentHandle[],\n options?: BooleanOptions,\n ): PluginApiReturn<BooleanOutcome>\n\n /**\n * Keep only the overlapping volume of exactly two masses (`A ∩ B`) — a boolean\n * (CSG) intersection that discards everything outside the shared region and\n * commits the overlap as a single undoable step. Order-independent.\n *\n * HARD constraint: **EXACTLY 2** components (schema `.length(2)`; the engine\n * also throws `MoreThan2ComponentsForIntersect` on >2).\n * @param components - **EXACTLY 2** entities to intersect (order-independent)\n * @param options - Optional output offset — {@linkcode BooleanOptions}\n * @returns The committed CSG delta — {@linkcode BooleanOutcome}\n * @throws as {@linkcode union}, plus `MoreThan2ComponentsForIntersect` on >2.\n *\n * @examplePrompt Keep only the part where these two masses overlap\n * @examplePrompt Intersect the two selected volumes\n * @examplePrompt Give me the common volume between the tower and the zoning envelope\n * @examplePrompt Trim this mass down to just its overlap with the other one\n *\n * # Example\n * ```ts\n * // Keep only the overlap of the tower and the zoning envelope (EXACTLY 2 inputs),\n * // shifting the result mass 10 units in x via the optional offset\n * const [tower, envelope] = await snaptrude.design.query.listMasses()\n * const offset = await snaptrude.core.math.vec3.new(10, 0, 0)\n * const { created } = await snaptrude.design.boolean.intersect([tower, envelope], { offset })\n * ```\n */\n public abstract intersect(\n components: ComponentHandle[],\n options?: BooleanOptions,\n ): PluginApiReturn<BooleanOutcome>\n}\n\n/**\n * Optional knobs shared by all boolean ops. The only safe one is a world-space\n * translation applied to the output mass(es) — maps to `Boolean3dOperation.offset`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `offset` | {@linkcode Vec3Handle} | Translation added to the created result mass(es) (default: none) |\n */\nexport const BooleanOptions = z.object({\n offset: Vec3Handle.optional(),\n})\nexport type BooleanOptions = z.infer<typeof BooleanOptions>\n\n/**\n * Arguments for {@linkcode PluginDesignBooleanApi.union}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `components` | {@linkcode ComponentHandle}`[]` | The ≥2 entities to fuse (order-independent) |\n * | `options` | {@linkcode BooleanOptions} | Optional output offset |\n */\nexport const PluginDesignBooleanUnionArgs = z.object({\n components: z.array(ComponentHandle).min(2),\n options: BooleanOptions.optional(),\n})\nexport type PluginDesignBooleanUnionArgs = z.infer<typeof PluginDesignBooleanUnionArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignBooleanApi.subtract}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `target` | {@linkcode ComponentHandle} | The entity subtracted FROM (arg brep) |\n * | `tools` | {@linkcode ComponentHandle}`[]` | The ≥1 entities subtracted (tool breps) |\n * | `options` | {@linkcode BooleanOptions} | Optional output offset |\n */\nexport const PluginDesignBooleanSubtractArgs = z.object({\n target: ComponentHandle,\n tools: z.array(ComponentHandle).min(1),\n options: BooleanOptions.optional(),\n})\nexport type PluginDesignBooleanSubtractArgs = z.infer<typeof PluginDesignBooleanSubtractArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignBooleanApi.intersect}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `components` | {@linkcode ComponentHandle}`[]` | **EXACTLY 2** entities to intersect (order-independent) |\n * | `options` | {@linkcode BooleanOptions} | Optional output offset |\n */\nexport const PluginDesignBooleanIntersectArgs = z.object({\n components: z.array(ComponentHandle).length(2),\n options: BooleanOptions.optional(),\n})\nexport type PluginDesignBooleanIntersectArgs = z.infer<typeof PluginDesignBooleanIntersectArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { ComponentHandle, Vec3Handle } from \"../../../handles\"\nimport { PluginDesignChangeResult } from \"../lock\"\n\n/**\n * `snaptrude.design.transform` — rigid transforms on scene entities.\n *\n * `move` / `rotate` migrated from the removed `tools.transform.*`; `align` snaps a\n * set of components' bounding-box edges to each other (or to a reference); `mirror`\n * flips components in place about a horizontal axis. All ops are undoable and return\n * {@linkcode PluginDesignChangeResult} `{ affected }`; failures throw (RPC rejects).\n *\n * `getPosition` / `setPosition` (and `scale`, an engine gap) are NOT in this surface\n * yet — they are a later pass.\n */\nexport abstract class PluginDesignTransformApi {\n constructor() {}\n\n /**\n * Translate entities by a **relative** displacement vector (added to the first\n * component's absolute position as the anchor). Undoable.\n *\n * @param components - Entities to move\n * @param displacement - Relative translation as a {@linkcode Vec3Handle}\n * @param options - `is2D`, `trackParentChange`\n * @returns {@linkcode PluginDesignChangeResult}\n *\n * @examplePrompt Move the selected room 5 metres to the right\n * @examplePrompt Shift these walls 3m along the x axis\n * @examplePrompt Nudge this space 500mm north\n * @examplePrompt Translate the furniture block by (10, 0, 5)\n * @examplePrompt Move these masses up one storey\n *\n * @performance Array API — one host round-trip for the whole set. Pass every target in a\n * single call; never loop `design.transform.move` per entity.\n *\n * # Example\n * ```ts\n * const { vec3 } = snaptrude.core.math\n * await snaptrude.design.transform.move([\"space-id\"], vec3.new(10, 0, 0))\n * ```\n */\n public abstract move(\n components: ComponentHandle[],\n displacement: Vec3Handle,\n options?: { is2D?: boolean; trackParentChange?: boolean },\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Rotate entities by a **signed angle in DEGREES** (positive = CCW about +Y).\n * The pivot defaults to the combined bounding-box centre; pass `options.pivot`\n * to rotate about an explicit world-space point instead. NOTE: unlike the\n * removed `tools.transform.rotate` (radians), this canonical form takes\n * DEGREES. Undoable.\n *\n * @param components - Entities to rotate\n * @param angleInDegrees - Signed rotation angle in degrees\n * @param options - `axis` (defaults to world +Y), `pivot` (world-space rotation\n * centre as a {@linkcode Vec3Handle}; defaults to the combined bbox centre),\n * `is2D`\n * @returns {@linkcode PluginDesignChangeResult}\n *\n * @examplePrompt Rotate the selected building 90 degrees\n * @examplePrompt Turn this room 45 degrees clockwise\n * @examplePrompt Spin the furniture block 180 degrees about its centre\n * @examplePrompt Rotate these masses 30 degrees around the vertical axis\n * @examplePrompt Rotate these walls 90 degrees about the origin\n *\n * @performance Array API — one host round-trip for the whole set. Pass every target in a\n * single call; never loop `design.transform.rotate` per entity.\n *\n * # Example\n * ```ts\n * await snaptrude.design.transform.rotate([\"space-id\"], 90)\n * // …or rotate about an explicit pivot point\n * const { vec3 } = snaptrude.core.math\n * await snaptrude.design.transform.rotate([\"space-id\"], 90, { pivot: vec3.new(0, 0, 0) })\n * ```\n */\n public abstract rotate(\n components: ComponentHandle[],\n angleInDegrees: number,\n options?: { axis?: Vec3Handle; pivot?: Vec3Handle; is2D?: boolean },\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Align a set of components by snapping one bounding-box edge to a common value\n * — the classic Left/Center/Right/Top/Middle/Bottom align. Purely **world-axis**\n * (deterministic and headless — the UI's camera-relative remap is bypassed):\n *\n * - `left` / `right` / `center` → world **X** min / max / mid\n * - `top` / `bottom` / `middle` → world **Z** max / min / mid\n *\n * There is **no vertical (Y) alignment** in v1 (the UI only reaches it via a\n * side-elevation camera remap, which this API does not replicate). Doors and\n * windows are **rejected** in v1 — if any component in `components` (or the\n * `reference`) is a door or window, the whole call **throws**; nothing is\n * silently dropped. A group aligns as **one rigid body** (its whole-group\n * bounding box moves together). Undoable.\n *\n * Requires **at least 2 components** unless a `reference` is supplied; with a\n * reference, every component aligns to that reference's edge (the reference does\n * not move) and a single component is allowed. Y (height) is preserved.\n *\n * @param components - Entities to align (≥2 without a `reference`)\n * @param edge - Which bounding-box edge to snap ({@linkcode PluginAlignEdge})\n * @param options - `reference`: a component whose edge is the fixed target\n * @returns {@linkcode PluginDesignChangeResult}\n *\n * @examplePrompt Align the selected rooms to the left\n * @examplePrompt Line up these masses along their top edges\n * @examplePrompt Center these columns horizontally\n * @examplePrompt Align all the walls to the left edge of this reference wall\n * @examplePrompt Snap the bottom edges of these spaces together\n *\n * @performance Array API — one host round-trip for the whole set. Pass every target in a\n * single call; never loop `design.transform.align` per entity.\n *\n * # Example\n * ```ts\n * const rooms = await snaptrude.design.selection.get()\n * // align every room's left (world -X) edge to a common minimum\n * await snaptrude.design.transform.align(rooms, \"left\")\n * // …or align them to a fixed reference component's left edge\n * const [anchor] = await snaptrude.design.query.listWalls({ storeys: [1] })\n * await snaptrude.design.transform.align(rooms, \"left\", { reference: anchor })\n * ```\n */\n public abstract align(\n components: ComponentHandle[],\n edge: PluginAlignEdge,\n options?: { reference?: ComponentHandle },\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Mirror components **in place** about a horizontal axis, flipping their geometry\n * across that axis through the combined bounding-box centre (the mesh's\n * world-matrix flip + brep inversion + CAD/dimension-line/parametric handling\n * commit atomically). Nothing moves off-centre — this is the toolbar \"flip\", not\n * a translate-and-reflect. Undoable.\n *\n * `axis` names the axis the geometry mirrors **across** (world axes, Y is up):\n * - `'x'` — mirror across world X (a front↔back flip in plan; reflects the Z coord)\n * - `'z'` — mirror across world Z (a left↔right flip in plan; reflects the X coord)\n *\n * Vertical (`'y'`) mirroring is intentionally not exposed. Locked, bucket-locked\n * and ineligible entries (terrain, plinths, reference/dimension lines,\n * neighbourhood buildings) are **silently dropped**; the call only throws if\n * nothing mirrorable remains. `affected` echoes the supplied handles.\n *\n * @param components - Entities to mirror (≥1)\n * @param axis - Axis to mirror across ({@linkcode PluginTransformMirrorAxis})\n * @returns {@linkcode PluginDesignChangeResult}\n *\n * @examplePrompt Mirror the selected building across the x axis\n * @examplePrompt Flip these rooms left to right\n * @examplePrompt Mirror this furniture block horizontally\n * @examplePrompt Flip the selected walls front to back\n * @examplePrompt Create a mirror image of these masses about the z axis\n *\n * @performance Array API — one host round-trip for the whole set. Pass every target in a\n * single call; never loop `design.transform.mirror` per entity.\n *\n * # Example\n * ```ts\n * const rooms = await snaptrude.design.selection.get()\n * await snaptrude.design.transform.mirror(rooms, \"x\")\n * ```\n */\n public abstract mirror(\n components: ComponentHandle[],\n axis: PluginTransformMirrorAxis,\n ): PluginApiReturn<PluginDesignChangeResult>\n}\n\n/**\n * The horizontal axis a {@linkcode PluginDesignTransformApi.mirror} flips **across**\n * (world axes, Y up): `'x'` mirrors across world X (front↔back in plan; reflects Z),\n * `'z'` mirrors across world Z (left↔right in plan; reflects X). Vertical (`'y'`)\n * mirroring is not exposed.\n */\nexport const PluginTransformMirrorAxis = z.enum([\"x\", \"z\"])\nexport type PluginTransformMirrorAxis = z.infer<\n typeof PluginTransformMirrorAxis\n>\n\n/**\n * The bounding-box edge an {@linkcode PluginDesignTransformApi.align} snaps to.\n * Mirrors the engine's align `DirectionType`. World-axis mapping: `left`/`right`/`center`\n * → world X min/max/mid; `top`/`bottom`/`middle` → world Z max/min/mid.\n */\nexport const PluginAlignEdge = z.enum([\n \"left\",\n \"right\",\n \"center\",\n \"top\",\n \"bottom\",\n \"middle\",\n])\nexport type PluginAlignEdge = z.infer<typeof PluginAlignEdge>\n\n/**\n * Arguments for {@linkcode PluginDesignTransformApi.move} (options flattened).\n */\nexport const PluginDesignMoveArgs = z.object({\n components: z.array(ComponentHandle).min(1),\n displacement: Vec3Handle,\n is2D: z.boolean().optional(),\n trackParentChange: z.boolean().optional(),\n})\nexport type PluginDesignMoveArgs = z.infer<typeof PluginDesignMoveArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignTransformApi.rotate} (options flattened).\n */\nexport const PluginDesignRotateArgs = z.object({\n components: z.array(ComponentHandle).min(1),\n angleInDegrees: z.number(),\n axis: Vec3Handle.optional(),\n is2D: z.boolean().optional(),\n pivot: Vec3Handle.optional(),\n})\nexport type PluginDesignRotateArgs = z.infer<typeof PluginDesignRotateArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignTransformApi.align} (options flattened).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `components` | {@linkcode ComponentHandle}`[]` | Entities to align (≥2 unless `reference` is set — host-enforced) |\n * | `edge` | {@linkcode PluginAlignEdge} | Which bounding-box edge to snap |\n * | `reference` | {@linkcode ComponentHandle} | A fixed component whose edge is the target (optional) |\n */\nexport const PluginDesignAlignArgs = z.object({\n components: z.array(ComponentHandle).min(1),\n edge: PluginAlignEdge,\n reference: ComponentHandle.optional(),\n})\nexport type PluginDesignAlignArgs = z.infer<typeof PluginDesignAlignArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignTransformApi.mirror}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `components` | {@linkcode ComponentHandle}`[]` | Entities to mirror (≥1) |\n * | `axis` | {@linkcode PluginTransformMirrorAxis} | Horizontal axis to mirror across (`'x'` \\| `'z'`) |\n */\nexport const PluginDesignMirrorArgs = z.object({\n components: z.array(ComponentHandle).min(1),\n axis: PluginTransformMirrorAxis,\n})\nexport type PluginDesignMirrorArgs = z.infer<typeof PluginDesignMirrorArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { ComponentHandle, Vec3Handle } from \"../../../handles\"\nimport { OffsetSplitOutcome } from \"../shared\"\n\n/**\n * STAGED (LLD Wave 2) — abstract shell only. NOT yet wired into `PluginDesignApi`\n * and has no host implementation. `offsetSplit` migrates off `tools.offset` (adding\n * the previously-dropped `offsetValue` to the result).\n *\n * `snaptrude.design.edit` — non-create geometry edits (offset-split, and later\n * push-pull / topology edits). Undoable.\n *\n * TRANSPORT: positional args per method.\n */\nexport abstract class PluginDesignEditApi {\n constructor() {}\n\n /**\n * Offset the top profile of a component by a distance and split the face along\n * the offset curve. Positive distances offset outward, negative inward.\n * Single-component only (the engine rejects batches).\n * @param component - The component whose top profile is offset-split.\n * @param offsetDistance - Offset distance; positive = outward, negative = inward.\n * @param options - Optional settings: `profilePickPoint` picks the profile when ambiguous.\n * @returns {@linkcode OffsetSplitOutcome} `{ createdIds, deletedIds, offsetValue }`\n *\n * @examplePrompt Offset the top face of this mass inward by 2 metres and split it\n * @examplePrompt Create a 1m setback border on top of the selected mass\n * @examplePrompt Split the roof profile with a 500mm inward offset\n * @examplePrompt Offset-split the slab's top profile outward by 3 feet\n *\n * # Example\n * ```ts\n * // Offset the top profile inward by 2 units and split the face along it\n * const [mass] = await snaptrude.design.query.listMasses()\n * const { createdIds, offsetValue } = await snaptrude.design.edit.offsetSplit(mass, -2)\n * // offsetValue is the post-clamp distance the engine actually applied\n * ```\n */\n public abstract offsetSplit(\n component: ComponentHandle,\n offsetDistance: number,\n options?: { profilePickPoint?: Vec3Handle },\n ): PluginApiReturn<OffsetSplitOutcome>\n}\n\n// Positional target: offsetSplit(component, offsetDistance, options?)\nexport const PluginDesignOffsetSplitArgs = z.object({\n component: ComponentHandle,\n offsetDistance: z.number(),\n profilePickPoint: Vec3Handle.optional(),\n})\nexport type PluginDesignOffsetSplitArgs = z.infer<typeof PluginDesignOffsetSplitArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\nimport { ComponentHandle, ProfileHandle } from \"../../../handles\"\nimport {\n PluginSpaceType,\n PluginMassType,\n PluginDepartmentId,\n PluginAreaClass,\n PluginSpaceUpdateResult,\n} from \"../../entity/space\"\nimport { PluginDesignChangeResult } from \"../lock\"\nimport {\n PluginBuildableEnvelopePolygonVertex,\n PluginBuildableEnvelopeSetbackTier,\n PluginBuildableEnvelopeVerticalCap,\n PluginBuildableEnvelopeUpdateResult,\n} from \"../../entity/buildableEnvelope\"\nimport { PluginGridStyleOptions } from \"../create/grids\"\n\n/**\n * Sparse property updates for a space. Only provided fields change.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `room_type` | `string`? | Room label |\n * | `massType` | `PluginMassType`? | Mass type |\n * | `spaceType` | `PluginSpaceType`? | Space type |\n * | `areaClass` | `PluginAreaClass`? | Area class (NET/GROSS/EXCLUDED) |\n * | `departmentId` | `PluginDepartmentId`? | Department |\n */\nexport const PluginSpacePropertyUpdates = z.object({\n room_type: z.string().optional(),\n massType: PluginMassType.optional(),\n spaceType: PluginSpaceType.optional(),\n areaClass: PluginAreaClass.optional(),\n departmentId: PluginDepartmentId.optional(),\n})\nexport type PluginSpacePropertyUpdates = z.infer<\n typeof PluginSpacePropertyUpdates\n>\n\n/** Geometry replacement for a space — re-extrude a profile. */\nexport const PluginSpaceGeometryUpdate = z.object({\n profile: ProfileHandle,\n extrudeHeight: z.number(),\n})\nexport type PluginSpaceGeometryUpdate = z.infer<\n typeof PluginSpaceGeometryUpdate\n>\n\n/** One space to update via {@linkcode PluginDesignUpdateApi.spaces}. */\nexport const PluginUpdateSpaceItem = z.object({\n space: ComponentHandle,\n properties: PluginSpacePropertyUpdates.optional(),\n geometry: PluginSpaceGeometryUpdate.optional(),\n})\nexport type PluginUpdateSpaceItem = z.infer<typeof PluginUpdateSpaceItem>\n\n/**\n * Wire schema for {@linkcode PluginDesignUpdateApi.setLabel}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `component` | `ComponentHandle` | The component to relabel |\n * | `label` | `string` | New label (non-empty after trimming) |\n */\nexport const PluginDesignSetLabelArgs = z.object({\n component: ComponentHandle,\n label: z.string().trim().min(1),\n})\nexport type PluginDesignSetLabelArgs = z.infer<typeof PluginDesignSetLabelArgs>\n\n/**\n * Sparse parametric updates for a staircase (all fields optional; at least one\n * required). Only the fields you provide change. Consumed by\n * {@linkcode PluginDesignUpdateApi.staircase}; the paired read is\n * `design.query.getStaircaseParams`.\n *\n * All dimensions are in **engine units** (the same convention as\n * `design.doors.setWidth`); `steps` is a unitless integer count. Height, riser,\n * and step count are **coupled** (`staircaseHeight = steps × riser`); the two\n * lock booleans govern which value recomputes — see the method JSDoc.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `isStoreyHeightUnlocked` | `boolean`? | Unlock the staircase height from the storey height |\n * | `isStaircaseHeightUnlocked` | `boolean`? | Unlock the height from `steps × riser` |\n * | `storeyHeight` | `number`? | Storey height the stair sits within (engine units) |\n * | `staircaseHeight` | `number`? | Total rise (engine units) |\n * | `steps` | `number`? | Step count (unitless integer) |\n * | `riser` | `number`? | Riser height (engine units) |\n * | `tread` | `number`? | Tread depth (engine units) |\n * | `width` | `number`? | Flight width (engine units) |\n * | `depth` | `number`? | Slab/waist thickness (engine units) |\n * | `wellSize` | `number`? | Stairwell gap (dogLegged / openWell / square) |\n * | `flightStartDistAfterTurn` | `number`? | Flight offset after a turn (dogLegged / lShaped / square) |\n * | `landingWidth` | `number`? | Landing depth (straight / dogLegged) |\n * | `baseOffset` | `number`? | Vertical offset from the storey base (engine units) |\n */\nexport const PluginStaircaseParamUpdates = z\n .object({\n isStoreyHeightUnlocked: z.boolean().optional(),\n isStaircaseHeightUnlocked: z.boolean().optional(),\n storeyHeight: z.number().optional(),\n staircaseHeight: z.number().optional(),\n steps: z.number().int().optional(),\n riser: z.number().optional(),\n tread: z.number().optional(),\n width: z.number().optional(),\n depth: z.number().optional(),\n wellSize: z.number().optional(),\n flightStartDistAfterTurn: z.number().optional(),\n landingWidth: z.number().optional(),\n baseOffset: z.number().optional(),\n })\n .refine((p) => Object.keys(p).length > 0, {\n message:\n \"PluginStaircaseParamUpdates requires at least one field to change\",\n })\nexport type PluginStaircaseParamUpdates = z.infer<\n typeof PluginStaircaseParamUpdates\n>\n\n/**\n * Arguments for {@linkcode PluginDesignUpdateApi.staircase}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `staircases` | {@linkcode ComponentHandle}`[]` | The staircases to update |\n * | `params` | {@linkcode PluginStaircaseParamUpdates} | Sparse parametric changes (≥ 1 field) |\n */\nexport const PluginDesignUpdateStaircaseArgs = z.object({\n staircases: z.array(ComponentHandle),\n params: PluginStaircaseParamUpdates,\n})\nexport type PluginDesignUpdateStaircaseArgs = z.infer<\n typeof PluginDesignUpdateStaircaseArgs\n>\n\n/**\n * Sparse edits for a wall (all optional; at least one required). Only the\n * fields you provide change. Dimensions are in **engine units** (the same\n * convention as `design.doors.setWidth`). Consumed by\n * {@linkcode PluginDesignUpdateApi.wall}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `thickness` | `number`? | Wall thickness, engine units (> 0) |\n * | `height` | `number`? | Wall height, engine units (> 0) |\n * | `type` | `string`? | Convert the wall to another wall type — a name (or `wall:`-prefixed id) from `design.types.list(\"wall\")` |\n */\nexport const PluginWallUpdates = z\n .object({\n thickness: z.number().positive().optional(),\n height: z.number().positive().optional(),\n type: z.string().trim().min(1).optional(),\n })\n .refine((p) => Object.keys(p).length > 0, {\n message: \"PluginWallUpdates requires at least one field to change\",\n })\nexport type PluginWallUpdates = z.infer<typeof PluginWallUpdates>\n\n/**\n * Sparse dimension edits for a slab-family element (slab / floor / roof /\n * ceiling). All values are engine units, the same convention as\n * `design.doors.setWidth`. Consumed by {@linkcode PluginDesignUpdateApi.slab},\n * `floor`, `roof`, and `ceiling`.\n *\n * `overhang` is only available on **slabs and roofs** (the panel's Overhang\n * slider) — floors and ceilings reject it with `PRECONDITION_FAILED`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `thickness` | `number`? | Element thickness, engine units (> 0) |\n * | `overhang` | `number`? | Absolute overhang offset, engine units (slab / roof only) |\n */\nexport const PluginSlabUpdates = z\n .object({\n thickness: z.number().positive().optional(),\n overhang: z.number().optional(),\n })\n .refine((p) => Object.keys(p).length > 0, {\n message: \"PluginSlabUpdates requires at least one field to change\",\n })\nexport type PluginSlabUpdates = z.infer<typeof PluginSlabUpdates>\n\n/** Arguments for {@linkcode PluginDesignUpdateApi.wall}. */\nexport const PluginDesignUpdateWallArgs = z.object({\n component: ComponentHandle,\n updates: PluginWallUpdates,\n})\nexport type PluginDesignUpdateWallArgs = z.infer<\n typeof PluginDesignUpdateWallArgs\n>\n\n/**\n * Arguments for the slab-family updaters —\n * {@linkcode PluginDesignUpdateApi.slab}, `floor`, `roof`, `ceiling`.\n */\nexport const PluginDesignUpdateSlabArgs = z.object({\n component: ComponentHandle,\n updates: PluginSlabUpdates,\n})\nexport type PluginDesignUpdateSlabArgs = z.infer<\n typeof PluginDesignUpdateSlabArgs\n>\n\n/** Arguments for {@linkcode PluginDesignUpdateApi.setElevation}. */\nexport const PluginDesignSetElevationArgs = z.object({\n component: ComponentHandle,\n elevation: z.number(),\n})\nexport type PluginDesignSetElevationArgs = z.infer<\n typeof PluginDesignSetElevationArgs\n>\n\n/** Arguments for {@linkcode PluginDesignUpdateApi.setBaseOffset}. */\nexport const PluginDesignSetBaseOffsetArgs = z.object({\n component: ComponentHandle,\n offset: z.number(),\n})\nexport type PluginDesignSetBaseOffsetArgs = z.infer<\n typeof PluginDesignSetBaseOffsetArgs\n>\n\n/**\n * Arguments for {@linkcode PluginDesignUpdateApi.setStorey}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `components` | {@linkcode ComponentHandle}`[]` | The components to reassign |\n * | `storey` | `number` | Target integer storey number (`entity.story` convention; must exist) |\n */\nexport const PluginDesignSetStoreyArgs = z.object({\n components: z.array(ComponentHandle),\n storey: z.number().int(),\n})\nexport type PluginDesignSetStoreyArgs = z.infer<\n typeof PluginDesignSetStoreyArgs\n>\n\n/**\n * The sparse GROUP-LEVEL patch accepted by {@linkcode PluginDesignUpdateApi.grid}.\n * Only provided fields change. Wing- and line-level updates live on the grids\n * entity family: `design.grids.updateWings` / `design.grids.updateLines`.\n */\nexport const PluginGridPatch = z.object({\n angle: z.number().finite().optional(),\n props: PluginGridStyleOptions.optional(),\n storeyVisibility: z\n .array(z.object({ storey: z.number().int(), visible: z.boolean() }))\n .optional(),\n})\nexport type PluginGridPatch = z.infer<typeof PluginGridPatch>\n\n/** Arguments for {@linkcode PluginDesignUpdateApi.grid}. */\nexport const PluginDesignUpdateGridArgs = z.object({\n grid: ComponentHandle,\n patch: PluginGridPatch,\n})\nexport type PluginDesignUpdateGridArgs = z.infer<typeof PluginDesignUpdateGridArgs>\n\n/**\n * `snaptrude.design.update` — edit existing scene entities (properties + geometry).\n *\n * Migrated from the removed `entity.space.{update, updateGeometryFromProfile, bulkUpdate}`.\n * Only provided fields change; each call is undoable. Failures throw (RPC rejects).\n */\nexport abstract class PluginDesignUpdateApi {\n constructor() {}\n\n /**\n * Set a component's **Label** — the same editable Label field the properties\n * panel shows (doors, windows, furniture, spaces, and every other labelled\n * component). The paired read is `design.query.getLabel`. Undoable; the\n * canvas label text updates immediately.\n *\n * Strictly **per component**: labelling one door of a placed family never\n * touches its sibling doors (the panel's Label edit, by contrast, applies to\n * the whole family). On **legacy masses** without a space type, panel parity\n * applies — a label matching a room-type preset with a height can also\n * adjust the mass's height and material, exactly like typing it in the panel.\n *\n * Throws when the component is locked, the handle is gone/forged, the label\n * is empty, or the component has no mesh to label. Re-applying the current\n * label is a no-op that returns `true` without touching the undo stack.\n *\n * @param component - The component to relabel\n * @param label - New label text (non-empty; leading/trailing whitespace trimmed)\n * @returns `true` once the label is applied\n *\n * @examplePrompt Rename this door to D-101\n * @examplePrompt Number all doors on storey 2 with a D-2-XX tag\n * @examplePrompt Set the selected window's label to W-04\n * @examplePrompt Tag every door with its fire rating\n *\n * # Example\n * ```ts\n * // Door tag numbering: label every door as D-<storey>-<n>, numbered per storey\n * await snaptrude.design.selection.setByFilter({ types: [\"door\"] })\n * const doors = await snaptrude.design.selection.get()\n * const counts = {}\n * for (const door of doors) {\n * const { storey = 0 } = await snaptrude.design.query.getProperties(door)\n * counts[storey] = (counts[storey] ?? 0) + 1\n * await snaptrude.design.update.setLabel(door, `D-${storey}-${String(counts[storey]).padStart(2, \"0\")}`)\n * }\n * ```\n */\n public abstract setLabel(\n component: ComponentHandle,\n label: string,\n ): PluginApiReturn<boolean>\n\n /**\n * Update a structural grid's **group-level** fields with a sparse patch —\n * default line styling, absolute angle, and per-storey 2D visibility. Only\n * provided fields change. Wing- and line-level updates live on the grids\n * entity family: {@linkcode PluginDesignGridsApi.updateWings} /\n * {@linkcode PluginDesignGridsApi.updateLines}.\n *\n * Every change routes through the exact setters the native panel uses —\n * each changed field commits its own undoable command, identical to editing\n * that field in the panel. `angle` is an absolute target in degrees (CCW\n * about +Y).\n *\n * @param grid - The grid group to update\n * @param patch - The sparse {@linkcode PluginGridPatch}\n * @returns `true` once applied\n *\n * @examplePrompt Rotate the grid to 45 degrees\n * @examplePrompt Make this grid's lines dashed and grey\n * @examplePrompt Hide this grid on storey 3's plan\n *\n * # Example\n * ```ts\n * const [grid] = await snaptrude.design.query.listGrids()\n * await snaptrude.design.update.grid(grid, { angle: 45, props: { style: \"DASHED\" } })\n * ```\n */\n public abstract grid(\n grid: ComponentHandle,\n patch: PluginGridPatch,\n ): PluginApiReturn<boolean>\n\n /**\n * Update a single space (room) — change its properties (room label, space\n * type, mass type, area class, department) and/or replace its geometry by\n * re-extruding a profile to a new height. Sparse and undoable: only the\n * fields you provide change.\n *\n * @param space - The space to update\n * @param options - `properties` (sparse) and/or `geometry` ({ profile, extrudeHeight })\n * @returns the updated space's echoed {@linkcode PluginSpaceUpdateResult}\n *\n * @examplePrompt Rename this room to Office\n * @examplePrompt Change this space's room type to Meeting Room\n * @examplePrompt Set the height of this room to 3 metres\n * @examplePrompt Move this space into the Circulation department\n * @examplePrompt Exclude this room from the area calculations\n *\n * @performance For MORE THAN ONE space, call `design.update.spaces(items[])` — the whole\n * batch is one host round-trip. Looping this single-space updater is N round-trips.\n *\n * # Example\n * ```ts\n * await snaptrude.design.update.space(\"space-id\", { properties: { room_type: \"Office\" } })\n * ```\n */\n public abstract space(\n space: ComponentHandle,\n options: {\n properties?: PluginSpacePropertyUpdates\n geometry?: PluginSpaceGeometryUpdate\n },\n ): PluginApiReturn<PluginSpaceUpdateResult>\n\n /**\n * Update many spaces (rooms) in one undoable operation — the bulk form of\n * {@linkcode space}. Each item pairs a space with the sparse property\n * changes and/or geometry re-extrude to apply; every item is validated up\n * front, then all changes are applied as a single command.\n *\n * Migrated from `entity.space.bulkUpdate`.\n *\n * @param items - One {@linkcode PluginUpdateSpaceItem} per space\n * @returns one {@linkcode PluginSpaceUpdateResult} per item, in input order\n *\n * @examplePrompt Label all the selected rooms as Bedrooms\n * @examplePrompt Change the room type of these five spaces at once\n * @examplePrompt Set every room on this floor to a 3m height in one go\n * @examplePrompt Bulk-assign these spaces to the Service department\n * @examplePrompt Mark all of these rooms as excluded from area in one update\n *\n * @performance Bulk update — the whole batch is ONE host round-trip. Prefer over looping\n * `design.update.space`: build the `items[]` array first, then make one call.\n *\n * # Example\n * ```ts\n * // Re-label every selected room and mark it NET area — one undoable bulk update\n * const rooms = await snaptrude.design.query.listSpaces({ isSelected: true })\n * const results = await snaptrude.design.update.spaces(\n * rooms.map((space) => ({\n * space,\n * properties: { room_type: \"Bedroom\", areaClass: \"NET\" },\n * })),\n * )\n * // One result per item, in input order\n * console.log(results.map((r) => r.spaceId))\n * ```\n */\n public abstract spaces(\n items: PluginUpdateSpaceItem[],\n ): PluginApiReturn<PluginSpaceUpdateResult[]>\n\n /**\n * Update the parameters of one or more staircases — riser, tread, width,\n * depth, step count, total height, well size, landing depth, base offset,\n * and the two height locks — in one undoable operation. Sparse: only the\n * fields you provide change. The paired read is\n * `design.query.getStaircaseParams`.\n *\n * All dimensions are in **engine units** (the same convention as\n * `design.doors.setWidth`); `steps` is a unitless integer count. The height,\n * riser, and step count are **coupled** — `staircaseHeight = steps × riser` —\n * and the two lock booleans govern which value recomputes when another\n * changes. With `isStoreyHeightUnlocked` false the staircase height follows\n * the storey height; unlocking it also unlocks the staircase height. Setting\n * `steps` recomputes `riser` (or the height, once the height is unlocked),\n * and setting `riser` recomputes `steps`/height in turn.\n *\n * Presets-only: this edits the parametric fields of a preset staircase; it\n * does not change the staircase's type/preset (there is no in-place re-type).\n * Every edit rebuilds the staircase geometry, so the call is async.\n *\n * Throws when a handle is not a staircase, when a staircase is locked, or\n * when a value falls outside the engine's accepted range (RPC rejects).\n *\n * @param staircases - the staircases to update\n * @param params - sparse {@linkcode PluginStaircaseParamUpdates} (at least one field)\n * @returns the affected staircases\n *\n * @examplePrompt Set the riser of this staircase to 165mm\n * @examplePrompt Change all the stairs to 14 steps\n * @examplePrompt Make the selected staircase 1200 wide with a 280 tread\n * @examplePrompt Widen every staircase on level 1 to 1 metre\n * @examplePrompt Unlock the height and set this stair to 3200 tall\n *\n * # Example\n * ```ts\n * // Give every ground-floor staircase a common riser + tread (engine units)\n * const stairs = await snaptrude.design.query.listStaircases({ storeys: [1] })\n * const { affected } = await snaptrude.design.update.staircase(stairs, {\n * riser: 165,\n * tread: 280,\n * })\n * console.log(\"updated\", affected.length, \"staircases\")\n * ```\n */\n public abstract staircase(\n staircases: ComponentHandle[],\n params: PluginStaircaseParamUpdates,\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Edit a **wall** — its thickness and/or height (the same command the\n * properties panel's Thickness/Height fields drive) and/or its **wall type**\n * (the panel's Wall Types dropdown: converts the wall to another type,\n * rebuilding its layer stack and materials). Sparse: only the fields you\n * provide change. Undoable; the geometry rebuilds and any dimension lines\n * refresh. Stacked-wall companions and hosted fenestration are handled\n * exactly as the panel does.\n *\n * Thickness/height are in **engine units** (the same convention as\n * `design.doors.setWidth`) and must be `> 0`. Shrinking a wall past its\n * openings is rejected by the engine (the wall is skipped with no change).\n * `type` accepts a type name from `design.types.list(\"wall\")` (or its\n * `wall:`-prefixed id); when combined with dimensions, the type conversion\n * is applied first so an explicit thickness/height wins over the type's\n * defaults. Re-applying the wall's current type is a no-op.\n *\n * Throws when the handle is not a wall, when the wall is locked, when the\n * handle is gone/forged, when `type` names an unknown wall type, when the\n * engine rejects the conversion (e.g. a curved wall thinner than the new\n * type's minimum thickness), or when no field is provided.\n *\n * A type conversion (and a thickness edit on a parametric wall) REBUILDS the\n * wall, replacing it with a new component: the input handle is then dead\n * (further calls throw HANDLE_INVALID) and `affected[0]` carries the\n * replacement's handle — use it for any follow-up calls.\n *\n * @param component - The wall to edit\n * @param updates - Sparse {@linkcode PluginWallUpdates} (at least one field)\n * @returns the affected wall — after a rebuild, the REPLACEMENT wall's handle\n *\n * @examplePrompt Make this wall 230mm thick\n * @examplePrompt Set the selected wall's height to 3 metres\n * @examplePrompt Thicken every exterior wall to 300\n * @examplePrompt Change this wall to a Glass Wall\n * @examplePrompt Convert all the partitions on this floor to Brick Wall\n *\n * # Example\n * ```ts\n * // Convert every wall on storey 1 to the first listed wall type\n * const [wallType] = await snaptrude.design.types.list(\"wall\")\n * const walls = await snaptrude.design.query.listWalls({ storeys: [1] })\n * for (const wall of walls) {\n * await snaptrude.design.update.wall(wall, { type: wallType.id })\n * }\n * ```\n */\n public abstract wall(\n component: ComponentHandle,\n updates: PluginWallUpdates,\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Edit a **slab's** thickness and/or overhang — the same commands the\n * properties panel's Thickness field and Overhang slider drive. Sparse: only\n * the fields you provide change. Undoable; the slab rebuilds and dimension\n * lines refresh. Both values are in **engine units** (the same convention as\n * `design.doors.setWidth`); thickness must be `> 0`, overhang is an absolute\n * offset (negative insets the slab) and re-applying the current overhang is\n * a no-op.\n *\n * Throws when the handle is not a slab, when it is locked, when the handle is\n * gone/forged, when `overhang` is set on a slab that has no overhang (plinth /\n * basement slabs, or overhang disabled), or when no field is provided.\n *\n * @param component - The slab to edit\n * @param updates - Sparse {@linkcode PluginSlabUpdates} (at least one field)\n * @returns the affected slab\n *\n * @examplePrompt Set this slab's thickness to 250mm\n * @examplePrompt Make the floor slab 300 thick\n * @examplePrompt Give this slab a 200 overhang\n * @examplePrompt Remove the overhang on every slab on level 2\n *\n * # Example\n * ```ts\n * const [slab] = await snaptrude.design.query.listSlabs()\n * await snaptrude.design.update.slab(slab, { thickness: 250, overhang: 200 })\n * ```\n */\n public abstract slab(\n component: ComponentHandle,\n updates: PluginSlabUpdates,\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Edit a **floor's** thickness — the slab-family updater for floors (shares\n * the slab thickness command). Undoable. Thickness is in **engine units**\n * (the `design.doors.setWidth` convention) and must be `> 0`. Floors have no\n * Overhang control, so `overhang` is rejected with `PRECONDITION_FAILED`.\n *\n * Throws when the handle is not a floor, when it is locked, when the handle\n * is gone/forged, when `overhang` is provided, or when no field is provided.\n *\n * @param component - The floor to edit\n * @param updates - {@linkcode PluginSlabUpdates} (a `thickness`)\n * @returns the affected floor\n *\n * @examplePrompt Make this floor 200mm thick\n * @examplePrompt Set every floor to a 250 thickness\n * @examplePrompt Thicken the ground floor slab to 300\n *\n * # Example\n * ```ts\n * const [floor] = await snaptrude.design.query.listFloors()\n * await snaptrude.design.update.floor(floor, { thickness: 200 })\n * ```\n */\n public abstract floor(\n component: ComponentHandle,\n updates: PluginSlabUpdates,\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Edit a **roof's** thickness and/or overhang — the same commands the roof\n * panel's Thickness field and Overhang slider drive (shares the slab-family\n * machinery). Sparse: only the fields you provide change. Undoable. Both\n * values are in **engine units** (the `design.doors.setWidth` convention);\n * thickness must be `> 0`, overhang is an absolute offset (negative insets\n * the roof) and re-applying the current overhang is a no-op.\n *\n * Throws when the handle is not a roof, when it is locked, when the handle is\n * gone/forged, when `overhang` is set on a roof whose overhang is disabled,\n * or when no field is provided.\n *\n * @param component - The roof to edit\n * @param updates - Sparse {@linkcode PluginSlabUpdates} (at least one field)\n * @returns the affected roof\n *\n * @examplePrompt Make this roof 300mm thick\n * @examplePrompt Set the roof slab thickness to 250\n * @examplePrompt Give the roof a 600 overhang\n * @examplePrompt Pull the roof overhang back to 0\n *\n * # Example\n * ```ts\n * const [roof] = await snaptrude.design.query.listRoofs()\n * await snaptrude.design.update.roof(roof, { overhang: 600 })\n * ```\n */\n public abstract roof(\n component: ComponentHandle,\n updates: PluginSlabUpdates,\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Edit a **ceiling's** thickness — the slab-family updater for ceilings\n * (shares the slab thickness command). Undoable. Thickness is in **engine\n * units** (the `design.doors.setWidth` convention) and must be `> 0`.\n * Ceilings have no Overhang control, so `overhang` is rejected with\n * `PRECONDITION_FAILED`.\n *\n * Throws when the handle is not a ceiling, when it is locked, when the handle\n * is gone/forged, when `overhang` is provided, or when no field is provided.\n *\n * @param component - The ceiling to edit\n * @param updates - {@linkcode PluginSlabUpdates} (a `thickness`)\n * @returns the affected ceiling\n *\n * @examplePrompt Make this ceiling 150mm thick\n * @examplePrompt Set every ceiling to a 100 thickness\n * @examplePrompt Thicken the ceiling to 200\n *\n * # Example\n * ```ts\n * const [ceiling] = await snaptrude.design.query.listCeilings()\n * await snaptrude.design.update.ceiling(ceiling, { thickness: 150 })\n * ```\n */\n public abstract ceiling(\n component: ComponentHandle,\n updates: PluginSlabUpdates,\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Set a component's **absolute world elevation** — moves it vertically so its\n * base (the bounding-box bottom, world Y) lands at `elevation`. Composed over\n * the canonical command-backed move machinery, so it is undoable and persisted\n * exactly like dragging the element up or down; per-type invariants (e.g. a\n * staircase's base offset) stay in sync. `elevation` is in **engine units**\n * (the `design.doors.setWidth` convention). Re-applying the current elevation\n * is a no-op.\n *\n * Throws when the component is locked or the handle is gone/forged.\n *\n * @param component - The component to move vertically\n * @param elevation - Target world elevation of the component's base (engine units)\n * @returns the affected component\n *\n * @examplePrompt Set this element's elevation to 3000\n * @examplePrompt Raise this mass so its base sits at 3 metres\n * @examplePrompt Drop the selected slab to elevation 0\n * @examplePrompt Put this staircase's base at 3200\n *\n * # Example\n * ```ts\n * // Sit the roof slab exactly at the top of a 3m storey\n * const [roof] = await snaptrude.design.query.listRoofs()\n * await snaptrude.design.update.setElevation(roof, 3000)\n * ```\n */\n public abstract setElevation(\n component: ComponentHandle,\n elevation: number,\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Set a component's **base offset** — its vertical distance above its own\n * storey's base level. Moves the component so its base lands at\n * `storeyBase + offset`, composed over the same command-backed move machinery\n * as {@linkcode setElevation} (undoable, persisted, per-type invariants kept\n * in sync). Use this instead of {@linkcode setElevation} when you want an\n * offset relative to the storey rather than an absolute world height.\n * `offset` is in **engine units** (the `design.doors.setWidth` convention);\n * `0` sits the base flush with the storey base. Re-applying the current\n * offset is a no-op.\n *\n * Throws when the component is locked, when it has no storey to offset from,\n * or when the handle is gone/forged.\n *\n * @param component - The component to offset\n * @param offset - Distance of the component's base above its storey base (engine units)\n * @returns the affected component\n *\n * @examplePrompt Lift this element 300 above its floor\n * @examplePrompt Set this staircase's base offset to 0\n * @examplePrompt Raise the selected mass 500 off the storey base\n * @examplePrompt Offset this slab 150 above the floor level\n *\n * # Example\n * ```ts\n * const [stair] = await snaptrude.design.query.listStaircases()\n * await snaptrude.design.update.setBaseOffset(stair, 0)\n * ```\n */\n public abstract setBaseOffset(\n component: ComponentHandle,\n offset: number,\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Reassign one or more components to another **storey** — the same operation\n * as the properties panel's Story dropdown, and it works across element kinds\n * exactly like the panel (walls, slabs, masses, furniture, staircases, ...).\n * Each component is moved vertically to the target storey's level (per-kind\n * placement rules — e.g. a slab sits at the storey base, a wall on it) and\n * its storey assignment, children, and dimension lines update together as\n * **one undoable operation**.\n *\n * `storey` is an integer storey number in the `entity.story` convention\n * (`1` is the ground storey, `-1` the first basement; `0` does not exist)\n * and **must already exist** in each component's building — this call never\n * creates storeys (use `core.storeys.create` first). Components already on\n * the target storey are skipped (no-op) and are not echoed in `affected`.\n *\n * Throws when the target storey does not exist, when any component is\n * locked, or when a handle is gone/forged — all before anything moves\n * (all-or-nothing).\n *\n * @param components - The components to reassign\n * @param storey - Target storey number (integer, `entity.story` convention)\n * @returns the components actually reassigned\n *\n * @examplePrompt Move this sofa to storey 2\n * @examplePrompt Put the selected walls on the first basement level\n * @examplePrompt Send everything on this storey up one level\n * @examplePrompt Reassign these slabs to storey 3\n *\n * # Example\n * ```ts\n * // Move the current selection up to storey 2\n * const selection = await snaptrude.design.selection.get()\n * const { affected } = await snaptrude.design.update.setStorey(selection, 2)\n * console.log(\"moved\", affected.length, \"components\")\n * ```\n */\n public abstract setStorey(\n components: ComponentHandle[],\n storey: number,\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Update an existing parametric buildable envelope — regenerate the zoning\n * volume from a revised site polygon, setbacks, vertical cap, or FAR and\n * lot-coverage limits, keeping the same envelope id. Canonical home of the\n * update formerly at `entity.buildableEnvelope.update` (now deprecated).\n *\n * @param buildableEnvelopeId - Existing envelope ID to update; non-empty and\n * must match an existing envelope on the canvas.\n * @param sitePolygon - Site polygon vertices in `lengthUnit`; minimum 3 vertices.\n * @param lengthUnit - Unit used by all length fields (`\"ft\" | \"m\"`).\n * @param setbacks - Setback profile, ground tier first; minimum 1 tier.\n * @param verticalCap - Maximum height or floor count.\n * @param floorToFloor - Required floor-to-floor height in `lengthUnit`.\n * @param farRatio - Optional FAR value, positive when provided.\n * @param lotCoverageMaxPct - Optional lot coverage cap, `0..100`.\n * @returns A {@linkcode PluginBuildableEnvelopeUpdateResult} with the\n * `buildableEnvelopeId` of the updated envelope.\n * @throws If validation fails, the envelope does not exist, or generation\n * produces no renderable geometry.\n *\n * @examplePrompt Change the envelope's height limit to 175 feet\n * @examplePrompt Update the setbacks on the buildable envelope\n * @examplePrompt Regenerate the zoning envelope with a new site boundary\n * @examplePrompt Increase the FAR on the buildable volume\n *\n * # Example\n * ```ts\n * const { buildableEnvelopeId } = await snaptrude.design.update.buildableEnvelope(\n * existingId,\n * [\n * { x: 0, z: 0 },\n * { x: 100, z: 0 },\n * { x: 100, z: 80 },\n * { x: 0, z: 80 },\n * ],\n * \"ft\",\n * [{ aboveHeight: 0, front: 10, side: 5, rear: 10 }],\n * { kind: \"max_height\", maxHeight: 175 },\n * 12,\n * )\n * ```\n */\n public abstract buildableEnvelope(\n buildableEnvelopeId: string,\n sitePolygon: PluginBuildableEnvelopePolygonVertex[],\n lengthUnit: \"ft\" | \"m\",\n setbacks: PluginBuildableEnvelopeSetbackTier[],\n verticalCap: PluginBuildableEnvelopeVerticalCap,\n floorToFloor: number,\n farRatio?: number,\n lotCoverageMaxPct?: number,\n ): PluginApiReturn<PluginBuildableEnvelopeUpdateResult>\n}\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport { ComponentHandle } from \"../../handles\"\nimport { PluginDesignChangeResult } from \"./lock\"\n\n/**\n * `snaptrude.design.visibility.*` — hide, isolate, and reveal scene entities.\n *\n * Visibility is a per-component user flag (the same one the \"Hide\" / \"Isolate\" /\n * \"Show All\" toolbar and right-click actions toggle). Every method here commits\n * an undoable command and echoes the affected entities as\n * {@linkcode PluginDesignChangeResult}.\n *\n * Accessed via `snaptrude.design.visibility`.\n */\nexport abstract class PluginDesignVisibilityApi {\n constructor() {}\n\n /**\n * Hide entities from the viewport — the same as the \"Hide\" action. Undoable.\n * Hosted door/window/void children ride along with their wall. Already-hidden\n * entities stay hidden.\n *\n * @param components Entities to hide\n * @returns The affected entities (echoing the input handles)\n *\n * @examplePrompt Hide the selected walls\n * @examplePrompt Conceal these masses so I can see behind them\n * @examplePrompt Hide every column on this floor\n * @examplePrompt Make this furniture disappear from view\n *\n * # Example\n * ```ts\n * const walls = await snaptrude.design.query.listWalls({ storeys: [1] })\n * const { affected } = await snaptrude.design.visibility.hide(walls)\n * ```\n */\n public abstract hide(\n components: ComponentHandle[],\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Reveal specific hidden components — the inverse of\n * {@linkcode PluginDesignVisibilityApi.hide}. Use\n * {@linkcode PluginDesignVisibilityApi.showAll} to reveal everything.\n *\n * @param components - The components to reveal.\n * @returns A {@linkcode PluginDesignChangeResult} echoing the components shown.\n *\n * @examplePrompt Show these walls again\n * @examplePrompt Unhide the selected furniture\n *\n * # Example\n * ```ts\n * await snaptrude.design.visibility.show([wall])\n * ```\n */\n public abstract show(\n components: ComponentHandle[],\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Isolate entities — hide everything else so only the given entities remain\n * visible (the \"Isolate\" / solo action). Undoable. Reverse it with\n * {@linkcode PluginDesignVisibilityApi.showAll}.\n *\n * @param components Entities to keep visible; everything else is hidden\n * @returns The isolated entities (echoing the input handles)\n *\n * @examplePrompt Isolate the selected room so I can focus on it\n * @examplePrompt Show only these walls and hide the rest\n * @examplePrompt Solo this mass\n * @examplePrompt Hide everything except the staircase\n *\n * # Example\n * ```ts\n * const [room] = await snaptrude.design.selection.get()\n * await snaptrude.design.visibility.isolate([room])\n * ```\n */\n public abstract isolate(\n components: ComponentHandle[],\n ): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Reveal everything hidden on the active story — the \"Show All\" action, which\n * also exits an isolate. Undoable.\n *\n * @returns The entities that were made visible\n *\n * @examplePrompt Show everything again\n * @examplePrompt Unhide all the hidden objects\n * @examplePrompt Exit isolate mode\n * @examplePrompt Reveal everything I hid earlier\n *\n * # Example\n * ```ts\n * const { affected } = await snaptrude.design.visibility.showAll()\n * console.log(`Revealed ${affected.length} entities`)\n * ```\n */\n public abstract showAll(): PluginApiReturn<PluginDesignChangeResult>\n}\n\n/**\n * Arguments for {@linkcode PluginDesignVisibilityApi.hide}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `components` | {@linkcode ComponentHandle}`[]` | Entities to hide |\n */\nexport const PluginDesignVisibilityHideArgs = z.object({\n components: z.array(ComponentHandle),\n})\nexport type PluginDesignVisibilityHideArgs = z.infer<typeof PluginDesignVisibilityHideArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignVisibilityApi.show}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `components` | {@linkcode ComponentHandle}`[]` | Entities to reveal |\n */\nexport const PluginDesignVisibilityShowArgs = z.object({\n components: z.array(ComponentHandle),\n})\nexport type PluginDesignVisibilityShowArgs = z.infer<\n typeof PluginDesignVisibilityShowArgs\n>\n\n/**\n * Arguments for {@linkcode PluginDesignVisibilityApi.isolate}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `components` | {@linkcode ComponentHandle}`[]` | Entities to keep visible |\n */\nexport const PluginDesignVisibilityIsolateArgs = z.object({\n components: z.array(ComponentHandle),\n})\nexport type PluginDesignVisibilityIsolateArgs = z.infer<\n typeof PluginDesignVisibilityIsolateArgs\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport {\n ComponentHandle,\n DimensionHandle,\n Vec3Components,\n Vec3Handle,\n} from \"../../handles\"\n\n/**\n * Dimension lines (Measuring Tape) — the measurement annotations the Measuring\n * Tape tool leaves in the 3D scene, NOT an entity's width/height/depth\n * properties (for those see `design.windows.getDimensions`,\n * `design.create.staircase`'s `dimensions`, or `design.query.measure`).\n *\n * A dimension line is a scene object: two endpoints anchored to the geometry\n * they were measured on, an offset that pushes the drawn line clear of the\n * span, and a label showing the distance in the project's units. Because the\n * endpoints are anchored, the line follows its host when the host is moved,\n * edited, or resized, and it is removed with the host when the host is deleted.\n * Dimension lines are storey-scoped, render in both plan and 3D, and are drawn\n * into Present-mode sheets (style them with\n * `presentation.placedViews.updateStyles(shapeId, \"Dimension\", …)`).\n *\n * **Units.** Every length here — `offset` and the record's `length` — is in\n * **Babylon units** (BU), the engine's storage unit: 1 BU = 10 in = 0.254 m.\n * Convert with `core.units.convert(value, from, await core.units.getBabylonType())`.\n *\n * **Offset convention.** `options.offset` is a **signed** distance in BU:\n * positive pushes the drawn line to the **left** of the `from` → `to`\n * direction in plan (`cross(up, direction)`), negative to the right. It\n * defaults to `3.937` BU (1 m). The placement mode is always `\"across\"` (the\n * tape tool's perpendicular mode), which re-perpendicularises when the host\n * geometry is edited.\n *\n * Every mutator here (`create`, `delete`, `hide`, `show`) commits through the\n * engine's command manager, so each call is a single undo entry, is autosaved,\n * and replays to collaborators.\n *\n * Accessed via `snaptrude.design.dimensions`.\n */\nexport abstract class PluginDesignDimensionsApi {\n constructor() {}\n\n /**\n * Draw one dimension line between two world points, anchored to a component.\n *\n * `anchor` is **required**: the endpoints are stored relative to the anchor's\n * mesh, and only component-anchored dimension lines survive a reload. A\n * free-point dimension anchors to a scene helper mesh whose id is not stable\n * across reloads, so it is dropped or corrupted when the project is reopened\n * — the host therefore rejects a non-component anchor rather than writing a\n * record that silently disappears. Pass `options.anchorTo` to anchor the\n * second endpoint to a different component (it defaults to `anchor`).\n *\n * The dimension is created flat (never plan-projected) so the same call\n * produces the same record whatever the current camera; it still draws in\n * plan.\n *\n * @param from - World-space first endpoint.\n * @param to - World-space second endpoint.\n * @param anchor - The component the first endpoint is anchored to (required).\n * @param options - `anchorTo` (component for the second endpoint; defaults to\n * `anchor`), `offset` (signed perpendicular offset in Babylon units,\n * positive = left of the `from` → `to` direction in plan; default `3.937`\n * BU = 1 m).\n * @returns The new dimension line's {@linkcode DimensionHandle}.\n * @throws `VALIDATION` for malformed arguments or a degenerate span (`from`\n * and `to` closer than the engine's minimum); `HANDLE_INVALID` for an\n * unknown `anchor` / `anchorTo`; `PRECONDITION_FAILED` when the anchor is\n * outside the active proposal or the editor is not mounted;\n * `METHOD_NOT_PERMITTED` when plugin writes are disabled.\n *\n * @examplePrompt Dimension every wall on level 2\n * @examplePrompt Add a dimension line along this wall\n * @examplePrompt Measure the width of this room and label it on the plan\n *\n * # Example\n * ```ts\n * const { design, core } = snaptrude\n * const v = core.math.vec3\n * const curve = core.geom.query.curve\n * for (const wall of await design.query.listWalls({ storeys: [2] })) {\n * const cl = await design.query.geometry.getCenterline(wall)\n * if (!cl) continue\n * const a = await curve.getStartPoint(cl)\n * const b = await curve.getEndPoint(cl)\n * await design.dimensions.create(\n * await v.new(a.x, a.y, a.z),\n * await v.new(b.x, b.y, b.z),\n * wall,\n * )\n * }\n * ```\n */\n public abstract create(\n from: Vec3Handle,\n to: Vec3Handle,\n anchor: ComponentHandle,\n options?: { anchorTo?: ComponentHandle; offset?: number },\n ): PluginApiReturn<DimensionHandle>\n\n /**\n * List the dimension lines in the project as full records.\n *\n * Filters combine with AND. `storeys` keeps only the dimensions on those\n * storey numbers, `anchors` only the ones anchored to those components\n * (matching either endpoint, and matching an instanced anchor's source mesh\n * too), `isHidden` only the ones whose user Hide flag equals the value given.\n *\n * Dimension lines drawn by the Measuring Tape onto a scene helper mesh rather\n * than a component come back with `anchor: null` and are not proposal-scoped.\n *\n * @param options - `storeys`, `anchors`, `isHidden` filters (all optional,\n * ANDed).\n * @returns The matching {@linkcode PluginDimensionLine} records (`[]` when\n * nothing matches).\n * @throws `VALIDATION` for malformed filters; `HANDLE_INVALID` for an unknown\n * handle in `anchors`.\n *\n * @examplePrompt List all the dimension lines in this model\n * @examplePrompt Which dimensions are on level 3?\n * @examplePrompt Flag any dimension line shorter than 600 mm\n *\n * @performance Array read — one host round-trip returns every record. Filter\n * in one call rather than calling `get` per dimension.\n *\n * # Example\n * ```ts\n * const units = snaptrude.core.units\n * const min = await units.convert(600, \"millimeters\", await units.getBabylonType())\n * const short = (await snaptrude.design.dimensions.list()).filter(\n * (d) => d.length < min,\n * )\n * console.log(short.map((d) => `${d.label} on storey ${d.storey}`))\n * ```\n */\n public abstract list(options?: {\n storeys?: number[]\n anchors?: ComponentHandle[]\n isHidden?: boolean\n }): PluginApiReturn<PluginDimensionLine[]>\n\n /**\n * Read one dimension line by handle.\n *\n * Returns `null` — rather than throwing — when the dimension no longer\n * exists, so a handle kept across a delete or an undo can be polled safely.\n *\n * @param dimension - The dimension line to read.\n * @returns Its {@linkcode PluginDimensionLine} record, or `null` if it is\n * gone.\n * @throws `VALIDATION` if `dimension` is not a non-empty id string.\n *\n * @examplePrompt Read the dimension line I just created\n * @examplePrompt How long is this dimension and what does its label say?\n * @examplePrompt Check whether that dimension line still exists\n *\n * # Example\n * ```ts\n * const dim = await snaptrude.design.dimensions.get(handle)\n * if (dim) console.log(dim.label, dim.length, dim.storey)\n * ```\n */\n public abstract get(\n dimension: DimensionHandle,\n ): PluginApiReturn<PluginDimensionLine | null>\n\n /**\n * Delete dimension lines — the same hard removal as selecting them and\n * pressing Delete. Undoable as a **single** entry for the whole batch.\n *\n * Deleted handles are stale afterwards: {@linkcode\n * PluginDesignDimensionsApi.get} returns `null` for them and the other\n * mutators throw `HANDLE_INVALID`.\n *\n * @param dimensions - The dimension lines to delete (at least one — an empty\n * array is a caller error, not a no-op).\n * @returns The dimensions that were deleted, as\n * {@linkcode PluginDimensionsChangeResult}.\n * @throws `VALIDATION` for an empty or malformed array; `HANDLE_INVALID` if\n * any handle is unknown (the whole call rejects before anything is\n * deleted); `PRECONDITION_FAILED` if any dimension's anchor is outside the\n * active proposal; `METHOD_NOT_PERMITTED` when plugin writes are disabled.\n *\n * @examplePrompt Remove all dimension lines\n * @examplePrompt Delete the dimensions on this wall\n * @examplePrompt Clear the measurements I added to level 2\n *\n * @performance Array API — pass the whole set in one call (one host\n * round-trip, one undo entry). Never loop this per dimension.\n *\n * # Example\n * ```ts\n * const dims = snaptrude.design.dimensions\n * const onWall = await dims.list({ anchors: [wall] })\n * if (onWall.length > 0) await dims.delete(onWall.map((d) => d.id))\n * ```\n */\n public abstract delete(\n dimensions: DimensionHandle[],\n ): PluginApiReturn<PluginDimensionsChangeResult>\n\n /**\n * Hide dimension lines from the viewport — the same as the right-click \"Hide\"\n * action (it sets the user Hide flag, `isHidden`). Undoable. Already-hidden\n * dimensions are skipped and are not reported in `affected`.\n *\n * @param dimensions - The dimension lines to hide.\n * @returns The dimensions actually hidden, as\n * {@linkcode PluginDimensionsChangeResult} — already-hidden inputs are\n * omitted, so `affected` can be shorter than the input.\n * @throws `VALIDATION` for a malformed array; `HANDLE_INVALID` if any handle\n * is unknown; `PRECONDITION_FAILED` if any dimension's anchor is outside\n * the active proposal; `METHOD_NOT_PERMITTED` when plugin writes are\n * disabled.\n *\n * @examplePrompt Hide the dimensions on this storey\n * @examplePrompt Hide every dimension line while I present\n * @examplePrompt Temporarily hide the measurements on level 3\n *\n * @performance Array API — one host round-trip and one undo entry for the\n * whole set.\n *\n * # Example\n * ```ts\n * const dims = snaptrude.design.dimensions\n * const onLevel3 = await dims.list({ storeys: [3] })\n * const { affected } = await dims.hide(onLevel3.map((d) => d.id))\n * ```\n */\n public abstract hide(\n dimensions: DimensionHandle[],\n ): PluginApiReturn<PluginDimensionsChangeResult>\n\n /**\n * Reveal hidden dimension lines — clear the user Hide flag, the inverse of\n * {@linkcode PluginDesignDimensionsApi.hide}. Undoable. Already-visible\n * dimensions are skipped and are not reported in `affected`.\n *\n * Clearing the flag does not guarantee the dimension is on screen: a\n * dimension whose anchor component is itself hidden stays off screen with\n * `isHidden: false` and `isVisible: false`.\n *\n * @param dimensions - The dimension lines to reveal.\n * @returns The dimensions actually revealed, as\n * {@linkcode PluginDimensionsChangeResult} — already-visible inputs are\n * omitted, so `affected` can be shorter than the input.\n * @throws `VALIDATION` for a malformed array; `HANDLE_INVALID` if any handle\n * is unknown; `PRECONDITION_FAILED` if any dimension's anchor is outside\n * the active proposal; `METHOD_NOT_PERMITTED` when plugin writes are\n * disabled.\n *\n * @examplePrompt Show the dimension lines again\n * @examplePrompt Unhide all the hidden dimensions\n * @examplePrompt Bring back the measurements I hid on this storey\n *\n * @performance Array API — one host round-trip and one undo entry for the\n * whole set.\n *\n * # Example\n * ```ts\n * const dims = snaptrude.design.dimensions\n * const hidden = await dims.list({ isHidden: true })\n * await dims.show(hidden.map((d) => d.id))\n * ```\n */\n public abstract show(\n dimensions: DimensionHandle[],\n ): PluginApiReturn<PluginDimensionsChangeResult>\n}\n\n/**\n * How a dimension line's drawn offset is constrained.\n *\n * - `\"across\"` — perpendicular to the span (the tape tool's default, and what\n * every plugin-created dimension uses); re-perpendicularises when the host\n * geometry is edited.\n * - `\"x\"` / `\"y\"` / `\"z\"` — the offset is locked to that world axis.\n * - `\"none\"` — no placement mode recorded (a plain world-space offset).\n */\nexport const PluginDimensionPlacement = z.enum([\n \"across\",\n \"x\",\n \"y\",\n \"z\",\n \"none\",\n])\nexport type PluginDimensionPlacement = z.infer<typeof PluginDimensionPlacement>\n\n/**\n * One dimension line (Measuring Tape annotation). Lengths are in **Babylon\n * units** (1 BU = 0.254 m — convert with `core.units.convert`).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | {@linkcode DimensionHandle} | The dimension line's handle |\n * | `from` | {@linkcode Vec3Components} | Live world position of the first endpoint |\n * | `to` | {@linkcode Vec3Components} | Live world position of the second endpoint |\n * | `length` | `number` | Straight-line distance between `from` and `to`, in Babylon units |\n * | `label` | `string` | The text drawn on the canvas, in the project's units (a bare number for metric/inch projects, `27' 7\"` style for feet-inches). For a span whose x, y and z all differ the drawn line is the plan projection, so `label` reads the horizontal distance while `length` is the true 3D one |\n * | `offset` | {@linkcode Vec3Components} | World vector from the measured span to the drawn line |\n * | `placement` | {@linkcode PluginDimensionPlacement} | How that offset is constrained |\n * | `anchor` | {@linkcode ComponentHandle}` \\| null` | Component the first endpoint is anchored to; `null` for a free point or a non-component host mesh |\n * | `anchorTo` | {@linkcode ComponentHandle}` \\| null` | Component the second endpoint is anchored to; equals `anchor` when both endpoints share a host |\n * | `storey` | `number` | Storey the dimension belongs to |\n * | `buildingId` | `string \\| null` | Building it belongs to, when it has one |\n * | `isHidden` | `boolean` | The user Hide flag (what `hide` / `show` toggle) |\n * | `isVisible` | `boolean` | Whether it is actually drawn — `false` when hidden, and also when an anchor component is hidden |\n * | `isPlanProjected` | `boolean` | Whether it is drawn flattened onto the storey base (Measuring Tape dimensions drawn in 2D are; plugin-created ones never are) |\n */\nexport const PluginDimensionLine = z.object({\n id: DimensionHandle,\n from: Vec3Components,\n to: Vec3Components,\n length: z.number(),\n label: z.string(),\n offset: Vec3Components,\n placement: PluginDimensionPlacement,\n anchor: ComponentHandle.nullable(),\n anchorTo: ComponentHandle.nullable(),\n storey: z.number(),\n buildingId: z.string().nullable(),\n isHidden: z.boolean(),\n isVisible: z.boolean(),\n isPlanProjected: z.boolean(),\n})\nexport type PluginDimensionLine = z.infer<typeof PluginDimensionLine>\n\n/**\n * Result of every `design.dimensions` mutation that takes a batch\n * ({@linkcode PluginDesignDimensionsApi.delete} /\n * {@linkcode PluginDesignDimensionsApi.hide} /\n * {@linkcode PluginDesignDimensionsApi.show}) — the dimensions actually\n * affected. `hide` / `show` skip dimensions already in the target state, so\n * `affected` can be shorter than the input; `delete` echoes the whole batch.\n * Failures throw (the RPC rejects); there is no `Result<>` monad in the SDK.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `affected` | {@linkcode DimensionHandle}`[]` | The dimension lines the call changed |\n */\nexport const PluginDimensionsChangeResult = z.object({\n affected: z.array(DimensionHandle),\n})\nexport type PluginDimensionsChangeResult = z.infer<\n typeof PluginDimensionsChangeResult\n>\n\n/**\n * Arguments for {@linkcode PluginDesignDimensionsApi.create} (options\n * flattened).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `from` | {@linkcode Vec3Handle} | World-space first endpoint |\n * | `to` | {@linkcode Vec3Handle} | World-space second endpoint |\n * | `anchor` | {@linkcode ComponentHandle} | Component the first endpoint anchors to (required — free-point dimensions do not survive a reload) |\n * | `anchorTo` | {@linkcode ComponentHandle}? | Component for the second endpoint (default: `anchor`) |\n * | `offset` | `number`? | Signed perpendicular offset in Babylon units, positive = left of the `from` → `to` direction in plan (default `3.937` BU = 1 m) |\n *\n * TRANSPORT: positional args — validated host-side as this object.\n */\nexport const PluginDesignDimensionsCreateArgs = z.object({\n from: Vec3Handle,\n to: Vec3Handle,\n anchor: ComponentHandle,\n anchorTo: ComponentHandle.optional(),\n offset: z.number().finite().optional(),\n})\nexport type PluginDesignDimensionsCreateArgs = z.infer<\n typeof PluginDesignDimensionsCreateArgs\n>\n\n/**\n * Arguments for {@linkcode PluginDesignDimensionsApi.list} (options flattened).\n * Filters combine with AND; omitting all of them lists every dimension line.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `storeys` | `number[]`? | Only dimensions on these storey numbers |\n * | `anchors` | {@linkcode ComponentHandle}`[]`? | Only dimensions anchored to these components (either endpoint) |\n * | `isHidden` | `boolean`? | user-hidden flag === |\n */\nexport const PluginDesignDimensionsListArgs = z.object({\n storeys: z.array(z.number()).optional(),\n anchors: z.array(ComponentHandle).optional(),\n isHidden: z.boolean().optional(),\n})\nexport type PluginDesignDimensionsListArgs = z.infer<\n typeof PluginDesignDimensionsListArgs\n>\n\n/**\n * Arguments for {@linkcode PluginDesignDimensionsApi.get}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `dimension` | {@linkcode DimensionHandle} | The dimension line to read |\n */\nexport const PluginDesignDimensionsGetArgs = z.object({\n dimension: DimensionHandle,\n})\nexport type PluginDesignDimensionsGetArgs = z.infer<\n typeof PluginDesignDimensionsGetArgs\n>\n\n/**\n * Arguments for {@linkcode PluginDesignDimensionsApi.delete}.\n *\n * `.min(1)` mirrors {@linkcode PluginDesignDeleteEntitiesArgs}: an empty delete\n * is a caller error, not a no-op.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `dimensions` | {@linkcode DimensionHandle}`[]` | Dimension lines to delete. Unknown handles reject the whole call (fail-fast). |\n */\nexport const PluginDesignDimensionsDeleteArgs = z.object({\n dimensions: z.array(DimensionHandle).min(1),\n})\nexport type PluginDesignDimensionsDeleteArgs = z.infer<\n typeof PluginDesignDimensionsDeleteArgs\n>\n\n/**\n * Arguments for {@linkcode PluginDesignDimensionsApi.hide}. An empty array is\n * allowed and is a no-op (mirroring {@linkcode PluginDesignVisibilityHideArgs}).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `dimensions` | {@linkcode DimensionHandle}`[]` | Dimension lines to hide |\n */\nexport const PluginDesignDimensionsHideArgs = z.object({\n dimensions: z.array(DimensionHandle),\n})\nexport type PluginDesignDimensionsHideArgs = z.infer<\n typeof PluginDesignDimensionsHideArgs\n>\n\n/**\n * Arguments for {@linkcode PluginDesignDimensionsApi.show}. An empty array is\n * allowed and is a no-op.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `dimensions` | {@linkcode DimensionHandle}`[]` | Dimension lines to reveal |\n */\nexport const PluginDesignDimensionsShowArgs = z.object({\n dimensions: z.array(DimensionHandle),\n})\nexport type PluginDesignDimensionsShowArgs = z.infer<\n typeof PluginDesignDimensionsShowArgs\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../../types\"\n\n/**\n * `snaptrude.design.types` — READ-ONLY reference over the project's building\n * **type / assembly** system (the Project Properties layer stacks: External /\n * Internal walls, slabs, floors, ceilings, roofs). A type bundles a labelled\n * layer stack — each layer's role, thickness (engine units) and material — plus a\n * summed total thickness. Both the built-in defaults and any team/user-added types\n * are surfaced.\n *\n * Types are plain value records ({@linkcode PluginBuildingType}) — NOT scene\n * entities/handles. A type's `id` is a stable `\"<kind>:<name>\"` token\n * (e.g. `\"wall:brickWithPlasterAndPaint\"`) returned by\n * {@linkcode PluginDesignTypesApi.list} and consumed by\n * {@linkcode PluginDesignTypesApi.get}. Reads never throw — an unknown id yields\n * `null`; a kind with no types yields `[]`.\n *\n * These are reads only: there is no engine seam to author a type from a plugin\n * (types are created through the construction/project-properties UI).\n */\nexport abstract class PluginDesignTypesApi {\n constructor() {}\n\n /**\n * List the building types of one `kind` (wall / slab / floor / ceiling / roof)\n * — the built-in defaults plus any team/user-added types. Returns lightweight\n * summaries; call {@linkcode PluginDesignTypesApi.get} for the full layer stack.\n *\n * @param kind Which family of types to list\n * @returns The type summaries as {@linkcode PluginBuildingTypeSummary}`[]` (`[]` when none)\n *\n * @examplePrompt What wall types does this project have?\n * @examplePrompt List the available slab assemblies\n * @examplePrompt Show me every roof type in the project\n * @examplePrompt Which floor types can I use?\n *\n * # Example\n * ```ts\n * const wallTypes = await snaptrude.design.types.list(\"wall\")\n * for (const t of wallTypes) console.log(t.id, t.label, t.thickness)\n * ```\n */\n public abstract list(\n kind: PluginBuildingTypeKind\n ): PluginApiReturn<PluginBuildingTypeSummary[]>\n\n /**\n * Get one building type by its `\"<kind>:<name>\"` id — its labelled layer stack\n * (each layer's role, thickness and material) plus the summed total thickness.\n *\n * @param typeId The type id from {@linkcode PluginDesignTypesApi.list} (e.g. `\"wall:exposedConcrete\"`)\n * @returns The full type, or `null` if no type matches\n *\n * @examplePrompt What layers make up this wall type?\n * @examplePrompt Show the construction of the exposed-concrete wall\n * @examplePrompt What's the total thickness of this slab type?\n * @examplePrompt Which materials are in this roof assembly?\n *\n * # Example\n * ```ts\n * const [first] = await snaptrude.design.types.list(\"wall\")\n * const type = await snaptrude.design.types.get(first.id)\n * if (type) for (const layer of type.layers) console.log(layer.name, layer.thickness)\n * ```\n */\n public abstract get(typeId: string): PluginApiReturn<PluginBuildingType | null>\n}\n\n/** The building-type families exposed by {@linkcode PluginDesignTypesApi}. */\nexport const PluginBuildingTypeKind = z.enum([\"wall\", \"slab\", \"floor\", \"ceiling\", \"roof\"])\nexport type PluginBuildingTypeKind = z.infer<typeof PluginBuildingTypeKind>\n\n/**\n * One layer of a building type's assembly.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | Layer name (e.g. `\"RCC\"`, `\"Paint\"`) |\n * | `role` | `string` | Layer role (e.g. `\"Structure\"`, `\"Finish\"`) |\n * | `thickness` | `number` | Layer thickness in engine units (project's active unit mode) |\n * | `material` | `object`? | Layer material — `{ name, type }` (optional) |\n */\nexport const PluginTypeLayer = z.object({\n name: z.string(),\n role: z.string(),\n thickness: z.number(),\n material: z\n .object({\n name: z.string(),\n type: z.string(),\n })\n .optional(),\n})\nexport type PluginTypeLayer = z.infer<typeof PluginTypeLayer>\n\n/**\n * A lightweight building-type summary (no layer stack).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Stable `\"<kind>:<name>\"` token — pass to `design.types.get` |\n * | `kind` | {@linkcode PluginBuildingTypeKind} | Which family it belongs to |\n * | `label` | `string` | Human-readable label |\n * | `isDefault` | `boolean` | `true` for a built-in type, `false` for a team/user type |\n * | `thickness` | `number` | Summed layer thickness in engine units |\n */\nexport const PluginBuildingTypeSummary = z.object({\n id: z.string(),\n kind: PluginBuildingTypeKind,\n label: z.string(),\n isDefault: z.boolean(),\n thickness: z.number(),\n})\nexport type PluginBuildingTypeSummary = z.infer<typeof PluginBuildingTypeSummary>\n\n/**\n * A full building type — a {@linkcode PluginBuildingTypeSummary} plus its layer stack.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Stable `\"<kind>:<name>\"` token |\n * | `kind` | {@linkcode PluginBuildingTypeKind} | Which family it belongs to |\n * | `label` | `string` | Human-readable label |\n * | `isDefault` | `boolean` | `true` for a built-in type |\n * | `thickness` | `number` | Summed layer thickness in engine units |\n * | `layers` | {@linkcode PluginTypeLayer}`[]` | The ordered layer stack |\n */\nexport const PluginBuildingType = PluginBuildingTypeSummary.extend({\n layers: z.array(PluginTypeLayer),\n})\nexport type PluginBuildingType = z.infer<typeof PluginBuildingType>\n\n/**\n * Arguments for {@linkcode PluginDesignTypesApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `kind` | {@linkcode PluginBuildingTypeKind} | Which family of types to list |\n */\nexport const PluginDesignTypesListArgs = z.object({\n kind: PluginBuildingTypeKind,\n})\nexport type PluginDesignTypesListArgs = z.infer<typeof PluginDesignTypesListArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignTypesApi.get}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `typeId` | `string` | The `\"<kind>:<name>\"` id from `design.types.list` |\n */\nexport const PluginDesignTypesGetArgs = z.object({\n typeId: z.string(),\n})\nexport type PluginDesignTypesGetArgs = z.infer<typeof PluginDesignTypesGetArgs>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport { ComponentHandle, Vec3Components } from \"../../handles\"\n\n/**\n * `snaptrude.design.family.*` — author NATIVE parametric families from a JSON spec.\n *\n * A parametric family is a reusable, parameter-driven building component: a\n * curtain-wall panel, a louvre screen, a balustrade, a shading fin. Unlike the\n * rest of `design.*`, you do not build one out of geometry calls — you write a\n * JSON **spec** describing its parameters and its geometry rules, and the host\n * compiles that into a live parametric definition the user can place, flex and\n * save to their library.\n *\n * ## Workflow\n *\n * ```\n * getReference() read the spec DSL once — grammar, ops, expressions, example\n * prepare({ bundle }) validate + compile + show a live ghost; nothing committed\n * flex({ values }) re-run at new values; returns the geometry delta\n * place() arm placement — the USER clicks to drop it\n * create({ name }) commit it to the project's family library\n * ```\n *\n * ## Read getReference() first\n *\n * The spec DSL is a closed expression language with its own op set, coordinate\n * convention (**+Y up, Snaptrude units** — the scene's own unit, 1 SU = 254 mm;\n * literals may carry a unit suffix such as `600mm` or `2ft`)\n * and part-naming law. It is deliberately NOT duplicated in these types:\n * {@linkcode PluginDesignFamilyApi.getReference} returns the whole reference,\n * generated from a family that is actually registered in the host, so the\n * documentation cannot drift from what the compiler accepts.\n *\n * ## The repair contract\n *\n * {@linkcode PluginDesignFamilyApi.prepare} does **not** throw on an invalid\n * spec — it returns `{ ok: false, errors }` where each error carries a JSON\n * pointer into the bundle you sent (e.g. `\"/root/geometry/2/body/0/w\"`). Read\n * `errors[0].path`, fix that node, and call `prepare` again. A failed `prepare`\n * leaves the previous good draft standing, so it costs nothing but the node you\n * broke. Re-authoring from scratch throws away everything already correct.\n *\n * Accessed via `snaptrude.design.family`.\n */\nexport abstract class PluginDesignFamilyApi {\n constructor() {}\n\n /**\n * Read the family-spec DSL reference: grammar, the op table, the expression\n * whitelist, the coordinate convention, the repair contract, and a worked\n * example taken from a registered family.\n *\n * Read this before authoring a family for the first time in a session. The\n * DSL is a closed language — a guessed op or function is a rejected spec.\n *\n * @returns The reference text\n *\n * @examplePrompt How do I write a parametric family spec?\n * @examplePrompt Show me the family DSL\n *\n * # Example\n * ```ts\n * const { reference } = await snaptrude.design.family.getReference()\n * ```\n */\n public abstract getReference(): PluginApiReturn<PluginFamilyReference>\n\n /**\n * Validate, compile and preview a family spec. Nothing is committed to the\n * model or the library — call {@linkcode PluginDesignFamilyApi.place} or\n * {@linkcode PluginDesignFamilyApi.create} for that.\n *\n * Safe to call repeatedly while iterating. On success, check `partCount` and\n * `bbox` against what you intended before treating the family as correct — a\n * facade that built 3 parts, or a 3 m screen reporting 40 m of height, is a\n * visible bug the validator cannot catch.\n *\n * @param args The spec bundle, as `{ bundle }` or `{ spec }`\n * @returns Build report, or `{ ok: false, errors }` with JSON-pointer paths\n *\n * @examplePrompt Make a louvre screen with adjustable blade count and tilt\n * @examplePrompt Design a curtain wall panel family with 8 bays\n * @examplePrompt Create a railing family I can adjust\n *\n * # Example\n * ```ts\n * const r = await snaptrude.design.family.prepare({ bundle })\n * if (!r.ok) console.error(r.errors[0].path, r.errors[0].message)\n * ```\n */\n public abstract prepare(\n args: PluginDesignFamilyPrepareArgs,\n ): PluginApiReturn<PluginFamilyPrepareResult>\n\n /**\n * Re-run the prepared family's recipe at new parameter values.\n *\n * Values are merged over the current ones and clamped to the parameter\n * sheet's min/max, so a partial `{ bays: 6 }` is fine. The returned `changed`\n * buckets are the host's own diff of what the geometry did — flexing a\n * driving parameter and reporting them demonstrates the family is parametric.\n *\n * @param args The parameter values to change\n * @returns The new build plus the geometry delta\n *\n * @examplePrompt Change it to 6 bays\n * @examplePrompt Show me what happens with 20 blades\n *\n * # Example\n * ```ts\n * const { changed } = await snaptrude.design.family.flex({ values: { bays: 6 } })\n * console.log(`removed ${changed.removed}, rebuilt ${changed.rebuilt}`)\n * ```\n */\n public abstract flex(\n args: PluginDesignFamilyFlexArgs,\n ): PluginApiReturn<PluginFamilyFlexResult>\n\n /**\n * The prepared family's spec bundle, or `null` when nothing is prepared.\n *\n * @returns The draft bundle, or `null`\n *\n * # Example\n * ```ts\n * const bundle = await snaptrude.design.family.get()\n * ```\n */\n public abstract get(): PluginApiReturn<PluginFamilyBundle | null>\n\n /**\n * Every parametric family registered in this session — native and imported.\n *\n * @returns One summary per family\n *\n * @examplePrompt What families are available?\n *\n * # Example\n * ```ts\n * const families = await snaptrude.design.family.list()\n * ```\n */\n public abstract list(): PluginApiReturn<PluginFamilySummary[]>\n\n /**\n * Put a family into the model — either the prepared draft or any family from\n * {@linkcode PluginDesignFamilyApi.list}.\n *\n * Two modes, chosen by whether you pass `position`:\n *\n * **With `position`** the component is created there and then, undoably. Use\n * this when you know where it goes — you were given coordinates, or you are\n * copying an existing instance. The returned `component` handle works with\n * `design.transform.move` / `.rotate` and with\n * {@linkcode PluginDesignFamilyApi.setParameters}.\n *\n * **Without `position`** it arms placement mode with a ghost on the cursor\n * and returns immediately — no geometry yet. The USER clicks to drop it, so\n * tell them to. Prefer this when the location is a judgement call.\n *\n * To COPY a placed component, place its family again at the new position with\n * the same `values` — there is no separate copy call.\n *\n * @param args Which family, where, and at what parameter values\n * @returns What was placed; `component` is present only in positioned mode\n *\n * @examplePrompt Place it in the model\n * @examplePrompt Put a louvre screen at the origin\n * @examplePrompt Copy that table two metres to the right\n *\n * # Example\n * ```ts\n * // positioned — geometry exists when this resolves\n * const { component } = await snaptrude.design.family.place({\n * id: \"revit:gypsum-stud-partition~90ad72\",\n * position: { x: 0, y: 0, z: 0 },\n * rotation: 90,\n * })\n * await snaptrude.design.transform.move([component], { x: 2, y: 0, z: 0 })\n *\n * // interactive — the user clicks\n * await snaptrude.design.family.place()\n * ```\n */\n public abstract place(\n args?: PluginDesignFamilyPlaceArgs,\n ): PluginApiReturn<PluginFamilyPlaceResult>\n\n /**\n * Read a placed component's current parameter values and its sheet.\n *\n * Use before {@linkcode PluginDesignFamilyApi.setParameters} so you change\n * what is actually there: parameter KEYS are the family's own, not the\n * label the user says, and a Revit-imported family carries parameters that\n * do not drive geometry at all.\n *\n * @param args The component to read\n * @returns Its definition, current values, and the parameter sheet\n *\n * @examplePrompt What size is that table?\n * @examplePrompt Show me the parameters on this partition\n *\n * # Example\n * ```ts\n * const { values, parameters } = await snaptrude.design.family.getParameters({ component })\n * ```\n */\n public abstract getParameters(\n args: PluginDesignFamilyGetParametersArgs,\n ): PluginApiReturn<PluginFamilyInstanceParameters | null>\n\n /**\n * Change a placed component's parameters and rebuild its geometry in place.\n *\n * Partial: pass only what changes. The component keeps its position,\n * rotation and identity — this is an edit, not a replace, and it is undoable.\n *\n * Only parameters with `drivesGeometry` actually move the mesh. Setting one\n * that does not is accepted and stored (it round-trips to Revit) but nothing\n * visible happens — say so rather than claiming a change the user cannot see.\n *\n * @param args The component and the values to change\n * @returns The values now in effect\n *\n * @examplePrompt Make that table seat 8\n * @examplePrompt Change the stud spacing to 400\n *\n * # Example\n * ```ts\n * await snaptrude.design.family.setParameters({ component, values: { seats: 8 } })\n * ```\n */\n public abstract setParameters(\n args: PluginDesignFamilySetParametersArgs,\n ): PluginApiReturn<PluginFamilyInstanceParameters>\n\n /**\n * Commit the prepared bundle to the project's parametric-definition library,\n * so it survives the session and appears in the Families panel.\n *\n * @param args Optional library name and team scope\n * @returns The catalogued definition's id and version\n *\n * @examplePrompt Save this as a family called Vertical Louvre\n * @examplePrompt Add it to my library\n *\n * # Example\n * ```ts\n * const { definitionId } = await snaptrude.design.family.create({ name: \"Louvre Screen\" })\n * ```\n */\n public abstract create(\n args?: PluginDesignFamilyCreateArgs,\n ): PluginApiReturn<PluginFamilyCreateResult>\n\n /**\n * Remove a family from the library: its catalog row is archived, so it\n * leaves the Families panel. Placed instances are untouched — in this\n * project and every other — because archived pins keep resolving. Pass\n * `instances: true` to ALSO delete this project's instances in one undoable\n * command; do that only when the user asks for the instances to go.\n *\n * Omit `id` to target the prepared draft. `id` may be a draft id, a saved\n * catalog id, or any family from `list()`.\n *\n * @param args Optional family id, and whether to delete instances too\n * @returns Whether a library row was archived and how many instances were removed\n *\n * @examplePrompt Delete the Louvre Screen family from my library\n * @examplePrompt Get rid of that bollard and every copy of it in here\n *\n * # Example\n * ```ts\n * await snaptrude.design.family.delete({ id: \"revit:steel-bollard~bef047\" })\n * await snaptrude.design.family.delete({ id: \"revit:steel-bollard~bef047\", instances: true })\n * ```\n */\n public abstract delete(\n args?: PluginDesignFamilyDeleteArgs,\n ): PluginApiReturn<PluginFamilyDeleteResult>\n}\n\n// ---------------------------------------------------------------------------\n// Result types\n// ---------------------------------------------------------------------------\n\n/** The family-spec DSL reference returned by `getReference`. */\nexport interface PluginFamilyReference {\n /** Grammar, op table, expression whitelist, repair contract, worked example. */\n reference: string\n}\n\n/**\n * A family spec bundle. `root` is the family; `deps` carries every spec\n * reachable through an `instance` op, keyed by spec id, so a saved family is\n * self-contained and reloads anywhere.\n *\n * The internal shape of a spec is documented by\n * {@linkcode PluginDesignFamilyApi.getReference}, not by these types — it is a\n * data DSL whose grammar changes independently of this API surface.\n */\nexport interface PluginFamilyBundle {\n root: Record<string, unknown>\n deps?: Record<string, Record<string, unknown>>\n}\n\n/** Axis-aligned extents of a built family. Snaptrude units (1 SU = 254 mm), +Y up. */\nexport interface PluginFamilyBBox {\n min: [number, number, number]\n max: [number, number, number]\n /** `[width, height, depth]` in Snaptrude units — convert with `core.units.convert` for display. */\n size: [number, number, number]\n}\n\n/** One row of a family's parameter sheet. */\nexport interface PluginFamilyParameter {\n key: string\n label: string\n type: \"length\" | \"integer\" | \"enum\" | \"text\" | \"boolean\"\n /** Current value. `length`/`integer` are numbers; the rest are strings. */\n value: string | number\n default: string | number\n /**\n * Whether this parameter actually drives geometry — INFERRED by the host from\n * which names the spec's expressions reference. `false` means the value is\n * stored and round-tripped but moves nothing.\n */\n drivesGeometry: boolean\n min?: number\n max?: number\n options?: readonly string[]\n}\n\n/**\n * A validation diagnostic. `path` is a JSON pointer into the submitted bundle —\n * fix that node and re-`prepare`.\n */\nexport interface PluginFamilyDiagnostic {\n /** JSON pointer, e.g. `\"/root/geometry/2/body/0/w\"`. */\n path: string\n code:\n | \"schema\"\n | \"bad-expression\"\n | \"unknown-name\"\n | \"unknown-def\"\n | \"cycle\"\n | \"duplicate-name\"\n | \"unbounded-repeat\"\n | \"depth\"\n | \"bad-dimension\"\n | \"proportion\"\n message: string\n hint?: string\n}\n\n/** What `prepare` reports. */\nexport interface PluginFamilyPrepareResult {\n /** `false` means the spec was rejected — read `errors`, fix, re-prepare. */\n ok: boolean\n familyId?: string\n version?: number\n label?: string\n /** How many parts the family built. Sanity-check against intent. */\n partCount?: number\n /** Extents in Snaptrude units. Sanity-check against intent. */\n bbox?: PluginFamilyBBox\n evalMs?: number\n parameters?: PluginFamilyParameter[]\n /** Computed quantities, when the spec declares a `takeoff` block. */\n takeoff?: Record<string, number | string>\n /** The first few part names — enough to check the naming came out right. */\n sampleParts?: string[]\n /** Present when `snapshot` was asked for. */\n snapshot?: PluginFamilySnapshot\n errors: PluginFamilyDiagnostic[]\n warnings: PluginFamilyDiagnostic[]\n /** Set when the bundle validated but threw while building. */\n error?: string\n}\n\n/**\n * The host's diff of what a flex did to the geometry. This is the evidence that\n * a family is parametric.\n */\nexport interface PluginFamilyChangeBuckets {\n added: number\n removed: number\n moved: number\n rebuilt: number\n addedNames: string[]\n removedNames: string[]\n}\n\n/** What `flex` reports. */\nexport interface PluginFamilyFlexResult {\n ok: boolean\n /** The RESOLVED values after merge + clamping — not necessarily what was sent. */\n values?: Record<string, number | string>\n partCount?: number\n bbox?: PluginFamilyBBox\n evalMs?: number\n changed?: PluginFamilyChangeBuckets\n takeoff?: Record<string, number | string>\n /** Proportion warnings at the flexed values (code `proportion`): a part thinner than 0.5 mm, a part or family over 50 m, a family under 10 mm — the signature of a unit slip. */\n warnings?: PluginFamilyDiagnostic[]\n /** Present when `snapshot` was asked for. */\n snapshot?: PluginFamilySnapshot\n error?: string\n}\n\n/** One family available to place — from the session registry or the catalog. */\nexport interface PluginFamilySummary {\n id: string\n version: number\n label: string\n category?: string\n /** Number of rows on the family's parameter sheet. */\n parameters: number\n /**\n * Where the family came from, which decides how far you can edit it.\n *\n * `\"native\"` — authored from a JSON spec through this API. Its recipe is\n * data, so every parameter genuinely re-runs the geometry.\n *\n * `\"revit\"` — imported from a Revit family. Parameters exist and are\n * editable, but only those the importer could bind to geometry actually\n * change the mesh; the rest are carried for round-tripping. Check\n * `drivesGeometry` on the parameter before promising the user a change.\n */\n kind: \"native\" | \"revit\"\n}\n\n/** What `place` reports. */\nexport interface PluginFamilyPlaceResult {\n ok: boolean\n familyId?: string\n version?: number\n /**\n * The created component — present ONLY when `position` was given. In\n * interactive mode nothing exists yet, so there is nothing to hand back;\n * the user has not clicked.\n */\n component?: ComponentHandle\n error?: string\n}\n\n/** A placed component's parameter state, from `getParameters` / `setParameters`. */\nexport interface PluginFamilyInstanceParameters {\n /** The definition this instance is pinned to. */\n familyId: string\n version: number\n label: string\n /** Current values, keyed by parameter key. */\n values: Record<string, number | string>\n /** The sheet: what each key means, its bounds, and whether it moves geometry. */\n parameters: PluginFamilyParameter[]\n /** Present when `getParameters` was asked for a `snapshot`. */\n snapshot?: PluginFamilySnapshot\n}\n\n/** What `create` reports. */\nexport interface PluginFamilyCreateResult {\n ok: boolean\n definitionId?: string\n version?: number\n guid?: string\n /** True when the save also dropped one instance into the model (nothing was placed yet). */\n placed?: boolean\n /** Host guidance for the caller, e.g. that an instance was placed at the view centre. */\n hint?: string\n error?: string\n}\n\n/** What `delete` reports. */\nexport interface PluginFamilyDeleteResult {\n ok: boolean\n familyId?: string\n /** Instances deleted from the scene (only with `instances: true`), in one undoable command. */\n removedInstances?: number\n /** True when a library row was archived; false for an unsaved draft. */\n archived?: boolean\n error?: string\n}\n\n// ---------------------------------------------------------------------------\n// Argument schemas\n// ---------------------------------------------------------------------------\n\n/**\n * A spec bundle as it crosses the boundary: `root` plus optional `deps`.\n *\n * Loose by design — the spec grammar is a data DSL validated by the host's own\n * validator, which returns JSON-pointer diagnostics far more useful than a Zod\n * shape error. This schema only guarantees the envelope is an object.\n */\nexport const PluginFamilyBundleSchema = z.object({\n root: z.record(z.string(), z.unknown()),\n deps: z.record(z.string(), z.record(z.string(), z.unknown())).optional(),\n})\n\n/**\n * Arguments for {@linkcode PluginDesignFamilyApi.prepare}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `bundle` | {@linkcode PluginFamilyBundle} | The family spec bundle |\n * | `spec` | {@linkcode PluginFamilyBundle} | Alias for `bundle` |\n * | `snapshot` | {@linkcode PluginFamilySnapshotOption} | Also return a picture of the built family |\n *\n * Exactly one of `bundle` / `spec` is required. A bare `FamilySpec` (no `root`\n * wrapper) is also accepted by the host and wrapped for the caller.\n */\n/**\n * Ask for a picture with the result: `true` for the default sheet (iso, front,\n * right and top tiles with a dimension line), or an object to choose views and\n * tile size. The picture is an isolated offscreen render of the family alone —\n * the viewport is not moved — and comes back as a PNG data URL in the result's\n * `snapshot.image`. Through the agent's `run_worker`, return the result and the\n * image reaches the model as an image.\n */\nexport const PluginFamilySnapshotOption = z.union([\n z.boolean(),\n z.object({\n views: z.array(z.enum([\"iso\", \"front\", \"right\", \"top\"])).optional(),\n /** Pixels per tile (square), 64–2048; default 512. */\n tileSize: z.number().optional(),\n }),\n])\nexport type PluginFamilySnapshotOption = z.infer<typeof PluginFamilySnapshotOption>\n\n/** The picture block on prepare / flex / getParameters results when `snapshot` was asked for. */\nexport interface PluginFamilySnapshot {\n ok: boolean\n /** PNG data URL of the composed sheet. */\n image?: string\n views?: (\"iso\" | \"front\" | \"right\" | \"top\")[]\n /** [width, height, depth] in Snaptrude units. */\n size?: [number, number, number]\n ms?: number\n /** Set when the picture could not be taken (e.g. the legacy renderer); the call itself still succeeded. */\n error?: string\n}\n\nexport const PluginDesignFamilyPrepareArgs = z.object({\n bundle: PluginFamilyBundleSchema.optional(),\n spec: PluginFamilyBundleSchema.optional(),\n snapshot: PluginFamilySnapshotOption.optional(),\n})\nexport type PluginDesignFamilyPrepareArgs = z.infer<typeof PluginDesignFamilyPrepareArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignFamilyApi.flex}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `values` | `Record<string, number \\| string>` | Parameter values to change (partial) |\n * | `snapshot` | {@linkcode PluginFamilySnapshotOption} | Also return a picture of the flexed family |\n */\nexport const PluginDesignFamilyFlexArgs = z.object({\n values: z.record(z.string(), z.union([z.number(), z.string()])),\n snapshot: PluginFamilySnapshotOption.optional(),\n})\nexport type PluginDesignFamilyFlexArgs = z.infer<typeof PluginDesignFamilyFlexArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignFamilyApi.create}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | Library name; defaults to the spec's own label |\n * | `teamId` | `string` | Team library to save into; omit for personal |\n */\nexport const PluginDesignFamilyCreateArgs = z.object({\n name: z.string().optional(),\n teamId: z.string().optional(),\n})\nexport type PluginDesignFamilyCreateArgs = z.infer<typeof PluginDesignFamilyCreateArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignFamilyApi.delete}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Family to remove; omit for the prepared draft |\n * | `instances` | `boolean` | Also delete this project's placed instances; default false |\n */\nexport const PluginDesignFamilyDeleteArgs = z.object({\n id: z.string().optional(),\n instances: z.boolean().optional(),\n})\nexport type PluginDesignFamilyDeleteArgs = z.infer<typeof PluginDesignFamilyDeleteArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignFamilyApi.place}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Family to place, from {@linkcode PluginDesignFamilyApi.list}. Omit to place the prepared draft |\n * | `version` | `number` | Exact version; defaults to the newest known |\n * | `position` | `{ x, y, z }` | Place HERE, without user interaction. Omit to arm the cursor instead |\n * | `rotation` | `number` | Yaw in degrees about +Y. Only with `position` |\n * | `values` | `Record<string, number \\| string>` | Parameter values for THIS instance |\n * | `storey` | `number` | Storey to place on; defaults to the active one |\n */\nexport const PluginDesignFamilyPlaceArgs = z.object({\n id: z.string().optional(),\n version: z.number().int().positive().optional(),\n position: Vec3Components.optional(),\n rotation: z.number().optional(),\n values: z.record(z.string(), z.union([z.number(), z.string()])).optional(),\n storey: z.number().int().optional(),\n})\nexport type PluginDesignFamilyPlaceArgs = z.infer<typeof PluginDesignFamilyPlaceArgs>\n\n/**\n * Arguments for {@linkcode PluginDesignFamilyApi.getParameters}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `component` | {@linkcode ComponentHandle} | A placed parametric component |\n */\nexport const PluginDesignFamilyGetParametersArgs = z.object({\n component: ComponentHandle,\n /** Also return a picture of this instance (its recipe re-run at its values, with its paint). */\n snapshot: PluginFamilySnapshotOption.optional(),\n})\nexport type PluginDesignFamilyGetParametersArgs = z.infer<\n typeof PluginDesignFamilyGetParametersArgs\n>\n\n/**\n * Arguments for {@linkcode PluginDesignFamilyApi.setParameters}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `component` | {@linkcode ComponentHandle} | A placed parametric component |\n * | `values` | `Record<string, number \\| string>` | Values to change (partial) |\n */\nexport const PluginDesignFamilySetParametersArgs = z.object({\n component: ComponentHandle,\n values: z.record(z.string(), z.union([z.number(), z.string()])),\n})\nexport type PluginDesignFamilySetParametersArgs = z.infer<\n typeof PluginDesignFamilySetParametersArgs\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport { ComponentHandle } from \"../../handles\"\nimport { PluginGridStyleOptions } from \"./create/grids\"\n\n/**\n * Tag policy for structural line removal (mirrors the engine\n * `GridLineTagPolicy` values verbatim): `\"rewrite\"` renumbers each affected\n * wing's tag sequence after removal; `\"preserve\"` keeps the surviving lines'\n * tags unchanged.\n */\nexport const PluginGridLineTagPolicy = z.enum([\"rewrite\", \"preserve\"])\nexport type PluginGridLineTagPolicy = z.infer<typeof PluginGridLineTagPolicy>\n\n/** A positional grid-line address — indices into `design.query.grids.get`'s `wings` arrays. */\nexport const PluginGridLineAddress = z.object({\n wing: z.number().int().nonnegative(),\n line: z.number().int().nonnegative(),\n})\nexport type PluginGridLineAddress = z.infer<typeof PluginGridLineAddress>\n\n/**\n * One per-wing entry of {@linkcode PluginDesignGridsApi.updateWings} —\n * addressed by wing index. `spanMin`/`spanMax` move the family's length\n * handles (Snaptrude units).\n */\nexport const PluginGridWingEntry = z.object({\n wing: z.number().int().nonnegative(),\n spacing: z.number().positive().finite().optional(),\n count: z.number().int().min(1).optional(),\n spanMin: z.number().finite().optional(),\n spanMax: z.number().finite().optional(),\n})\nexport type PluginGridWingEntry = z.infer<typeof PluginGridWingEntry>\n\n/** Arguments for {@linkcode PluginDesignGridsApi.updateWings}. */\nexport const PluginDesignGridsUpdateWingsArgs = z.object({\n grid: ComponentHandle,\n wings: z.array(PluginGridWingEntry).min(1),\n})\nexport type PluginDesignGridsUpdateWingsArgs = z.infer<\n typeof PluginDesignGridsUpdateWingsArgs\n>\n\n/**\n * One per-line entry of {@linkcode PluginDesignGridsApi.updateLines} —\n * addressed positionally by `{ wing, line }` indices (invalidated by\n * structural edits; re-query after mutating). `distanceToPrevious` /\n * `distanceToNext` set the typed distance to the adjacent line on that side\n * and are mutually exclusive.\n *\n * `visible` is **hide-only** (`false`), mirroring the product: per-line show\n * does not exist — showing the GROUP (`design.visibility.show`) restores all\n * of its lines, exactly like the native Show All.\n */\nexport const PluginGridLineEntry = z\n .object({\n wing: z.number().int().nonnegative(),\n line: z.number().int().nonnegative(),\n tag: z.string().min(1).optional(),\n distanceToPrevious: z.number().positive().finite().optional(),\n distanceToNext: z.number().positive().finite().optional(),\n props: PluginGridStyleOptions.optional(),\n visible: z.literal(false).optional(),\n })\n .superRefine((entry, ctx) => {\n if (entry.distanceToPrevious !== undefined && entry.distanceToNext !== undefined) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message:\n \"distanceToPrevious and distanceToNext are mutually exclusive on one line entry\",\n })\n }\n })\nexport type PluginGridLineEntry = z.infer<typeof PluginGridLineEntry>\n\n/** Arguments for {@linkcode PluginDesignGridsApi.updateLines}. */\nexport const PluginDesignGridsUpdateLinesArgs = z.object({\n grid: ComponentHandle,\n lines: z.array(PluginGridLineEntry).min(1),\n})\nexport type PluginDesignGridsUpdateLinesArgs = z.infer<\n typeof PluginDesignGridsUpdateLinesArgs\n>\n\n/** Arguments for {@linkcode PluginDesignGridsApi.addLine}. */\nexport const PluginDesignGridsAddLineArgs = z.object({\n grid: ComponentHandle,\n wing: z.number().int().nonnegative(),\n position: z.number().finite(),\n})\nexport type PluginDesignGridsAddLineArgs = z.infer<typeof PluginDesignGridsAddLineArgs>\n\n/** Arguments for {@linkcode PluginDesignGridsApi.removeLines}. */\nexport const PluginDesignGridsRemoveLinesArgs = z.object({\n grid: ComponentHandle,\n lines: z.array(PluginGridLineAddress).min(1),\n tagPolicy: PluginGridLineTagPolicy.optional(),\n})\nexport type PluginDesignGridsRemoveLinesArgs = z.infer<\n typeof PluginDesignGridsRemoveLinesArgs\n>\n\n/**\n * `design.grids.*` — structural edits on a grid's line structure (the\n * entity-family home for grid-specific operations, like `design.doors`).\n *\n * Lines are addressed positionally as `{ wing, line }` index pairs into the\n * `wings` arrays returned by `design.query.grids.get`. Adding, removing, or\n * re-counting lines re-indexes the wing — re-query after every structural edit.\n *\n * Group-level edits (default styling, angle, storey visibility) live on\n * `design.update.grid`; creation lives on `design.create.grid*`.\n *\n * Accessed via `snaptrude.design.grids`.\n */\nexport abstract class PluginDesignGridsApi {\n constructor() {}\n\n /**\n * Update one or more **wings** of a grid with sparse per-wing entries —\n * `spacing` (uniform re-space), `count` (grow/truncate + tag renumber),\n * `spanMin`/`spanMax` (family length handles). Every change routes through\n * the exact setters the native panel uses — each changed field commits its\n * own undoable command, identical to editing that field in the panel.\n *\n * A `count` change re-indexes the wing's lines — positional `{ wing, line }`\n * addresses held from before the call are stale afterwards; re-query.\n *\n * @param grid - The grid group\n * @param wings - Sparse {@linkcode PluginGridWingEntry} entries (≥ 1)\n * @returns `true` once applied\n *\n * @examplePrompt Change this grid's spacing to 8 metres\n * @examplePrompt Make this grid 10 by 12 lines\n *\n * # Example\n * ```ts\n * await snaptrude.design.grids.updateWings(grid, [{ wing: 0, spacing: 8, count: 10 }])\n * ```\n */\n public abstract updateWings(\n grid: ComponentHandle,\n wings: PluginGridWingEntry[],\n ): PluginApiReturn<boolean>\n\n /**\n * Update one or more **lines** of a grid with sparse per-line entries —\n * `tag`, `distanceToPrevious`/`distanceToNext` (typed distance to the\n * neighbour on that side; mutually exclusive), `props` (per-line style\n * overrides), and `visible: false` (hide). Every change routes through the\n * exact setters the native panel uses — each changed field commits its own\n * undoable command, identical to editing that field in the panel.\n *\n * Visibility mirrors the product exactly: `visible` accepts only `false` —\n * per-line show does not exist. Hiding lines that would leave NO visible\n * line hides the whole GROUP instead (the native last-line rule; those line\n * flags stay untouched). To bring hidden lines back, show the group\n * (`design.visibility.show([grid])`) — it restores ALL of its lines, like\n * the native Show All.\n *\n * @param grid - The grid group\n * @param lines - Sparse {@linkcode PluginGridLineEntry} entries (≥ 1)\n * @returns `true` once applied\n *\n * @examplePrompt Rename grid line 2 to \"B\"\n * @examplePrompt Move this grid line 4 metres from its neighbour\n * @examplePrompt Hide grid line 3 on this grid\n *\n * # Example\n * ```ts\n * await snaptrude.design.grids.updateLines(grid, [\n * { wing: 0, line: 1, tag: \"B\", distanceToPrevious: 4 },\n * ])\n * ```\n */\n public abstract updateLines(\n grid: ComponentHandle,\n lines: PluginGridLineEntry[],\n ): PluginApiReturn<boolean>\n\n /**\n * Insert a parallel grid line into a wing at an explicit position along the\n * wing's spacing axis (Snaptrude units, wing-local — line 0's position can\n * be read from its curve). Sibling lines do not shift; the wing's tag\n * sequence is renumbered. Undoable.\n *\n * @param grid - The grid group\n * @param wing - Wing index\n * @param position - Position along the wing's spacing axis (Snaptrude units)\n * @returns `true` once inserted\n * @throws `VALIDATION` on a bad wing index or non-finite position\n *\n * @examplePrompt Add a grid line between lines 2 and 3\n * @examplePrompt Insert an extra grid line 4 metres from line A\n *\n * # Example\n * ```ts\n * await snaptrude.design.grids.addLine(grid, 0, 12.5)\n * ```\n */\n public abstract addLine(\n grid: ComponentHandle,\n wing: number,\n position: number,\n ): PluginApiReturn<boolean>\n\n /**\n * Remove grid lines by positional address, as ONE undoable step. With\n * `tagPolicy: \"rewrite\"` (default) each affected wing renumbers its tag\n * sequence; `\"preserve\"` keeps surviving tags unchanged. A wing emptied of\n * lines is dropped from the group.\n *\n * Removing EVERY line of the group is rejected — delete the group instead\n * (`design.delete.entities`).\n *\n * @param grid - The grid group\n * @param lines - Positional addresses to remove (see `design.query.grids.get`)\n * @param tagPolicy - `\"rewrite\"` (default) or `\"preserve\"`\n * @returns `true` once removed\n * @throws `VALIDATION` on out-of-range addresses; `OPERATION_FAILED` when the\n * removal would delete the group's last line\n *\n * @examplePrompt Delete grid line B\n * @examplePrompt Remove the last two lines of this grid without renumbering\n *\n * # Example\n * ```ts\n * await snaptrude.design.grids.removeLines(grid, [{ wing: 0, line: 2 }], \"preserve\")\n * ```\n */\n public abstract removeLines(\n grid: ComponentHandle,\n lines: PluginGridLineAddress[],\n tagPolicy?: PluginGridLineTagPolicy,\n ): PluginApiReturn<boolean>\n}\n","import { PluginApiReturn } from \"../../types\"\nimport { ComponentHandle } from \"../../handles\"\nimport { PluginDesignCreateApi } from \"./create\"\nimport { PluginDesignQueryApi } from \"./query\"\nimport { PluginDesignSelectionApi } from \"./selection\"\nimport { PluginDesignDoorsApi } from \"./doors\"\nimport { PluginDesignWindowsApi } from \"./windows\"\nimport { PluginDesignFurnitureApi } from \"./furniture\"\nimport { PluginDesignMaterialsApi } from \"./materials\"\nimport { PluginDesignEraseApi } from \"./erase\"\nimport { PluginDesignDeleteApi } from \"./delete\"\nimport { PluginDesignBooleanApi } from \"./boolean\"\nimport { PluginDesignTransformApi } from \"./transform\"\nimport { PluginDesignEditApi } from \"./edit\"\nimport { PluginDesignUpdateApi } from \"./update\"\nimport { PluginDesignVisibilityApi } from \"./visibility\"\nimport { PluginDesignDimensionsApi } from \"./dimensions\"\nimport { PluginDesignTypesApi } from \"./types\"\nimport { PluginDesignFamilyApi } from \"./family\"\nimport { PluginDesignGridsApi } from \"./grids\"\nimport { PluginDesignChangeResult } from \"./lock\"\n\n/**\n * `snaptrude.design.*` — author, query & edit BIM geometry.\n *\n * - {@linkcode PluginDesignApi.create} — new BIM entities\n * - {@linkcode PluginDesignApi.query} — read entities + their geometry\n * - {@linkcode PluginDesignApi.selection} — read/mutate the scene selection\n * - {@linkcode PluginDesignApi.doors} / {@linkcode PluginDesignApi.windows} — fenestration reads + edits\n * - {@linkcode PluginDesignApi.furniture} — furniture catalog reads\n * - {@linkcode PluginDesignApi.materials} — material library + assignment\n * - {@linkcode PluginDesignApi.boolean} — CSG boolean ops (union / subtract / intersect)\n * - {@linkcode PluginDesignApi.erase} — plan-level adjacency-edge erase (NOT hard delete)\n * - {@linkcode PluginDesignApi.delete} — hard entity removal\n * - {@linkcode PluginDesignApi.visibility} — hide / isolate / reveal entities\n * - {@linkcode PluginDesignApi.dimensions} — dimension lines (Measuring Tape): create / list / delete / hide\n * - {@linkcode PluginDesignApi.types} — read-only building type / assembly reference\n * - {@linkcode PluginDesignApi.family} — author parametric families from a JSON spec\n * - {@linkcode PluginDesignApi.lock} / {@linkcode PluginDesignApi.unlock} / {@linkcode PluginDesignApi.isLocked} / {@linkcode PluginDesignApi.listLocked} — lock state (top-level design verbs, §2A.1)\n * - {@linkcode PluginDesignApi.lockArea} / {@linkcode PluginDesignApi.unlockArea} / {@linkcode PluginDesignApi.isAreaLocked} / {@linkcode PluginDesignApi.listAreaLocked} — footprint-area lock for Room/Department spaces\n *\n * Transform and edit operation namespaces land here in later waves.\n */\nexport abstract class PluginDesignApi {\n /** Author new scene-committed BIM entities. See {@linkcode PluginDesignCreateApi}. */\n public abstract create: PluginDesignCreateApi\n /** Read scene entities and their geometry. See {@linkcode PluginDesignQueryApi}. */\n public abstract query: PluginDesignQueryApi\n /** Read/mutate the current selection. See {@linkcode PluginDesignSelectionApi}. */\n public abstract selection: PluginDesignSelectionApi\n /** Door-specific reads + edits. See {@linkcode PluginDesignDoorsApi}. */\n public abstract doors: PluginDesignDoorsApi\n /** Window-specific reads. See {@linkcode PluginDesignWindowsApi}. */\n public abstract windows: PluginDesignWindowsApi\n /** Furniture catalog reads. See {@linkcode PluginDesignFurnitureApi}. */\n public abstract furniture: PluginDesignFurnitureApi\n /** Material library + assignment. See {@linkcode PluginDesignMaterialsApi}. */\n public abstract materials: PluginDesignMaterialsApi\n /** Plan-level adjacency-edge erase (NOT hard delete). See {@linkcode PluginDesignEraseApi}. */\n public abstract erase: PluginDesignEraseApi\n /** Hard entity removal. See {@linkcode PluginDesignDeleteApi}. */\n public abstract delete: PluginDesignDeleteApi\n /** CSG boolean ops (union / subtract / intersect). See {@linkcode PluginDesignBooleanApi}. */\n public abstract boolean: PluginDesignBooleanApi\n /** Rigid transforms — move / rotate. See {@linkcode PluginDesignTransformApi}. */\n public abstract transform: PluginDesignTransformApi\n /** Non-create geometry edits — offset-split. See {@linkcode PluginDesignEditApi}. */\n public abstract edit: PluginDesignEditApi\n /** Update existing entities — properties + geometry. See {@linkcode PluginDesignUpdateApi}. */\n public abstract update: PluginDesignUpdateApi\n /** Hide / isolate / reveal entities. See {@linkcode PluginDesignVisibilityApi}. */\n public abstract visibility: PluginDesignVisibilityApi\n /** Dimension lines (Measuring Tape). See {@linkcode PluginDesignDimensionsApi}. */\n public abstract dimensions: PluginDesignDimensionsApi\n /** Read-only building type / assembly reference. See {@linkcode PluginDesignTypesApi}. */\n public abstract types: PluginDesignTypesApi\n /** Author parametric families from a JSON spec. See {@linkcode PluginDesignFamilyApi}. */\n public abstract family: PluginDesignFamilyApi\n\n /** Structural edits on a grid's line structure. See {@linkcode PluginDesignGridsApi}. */\n public abstract grids: PluginDesignGridsApi\n\n /**\n * Lock entities so they cannot be edited. Undoable. Cascades to void/door/window\n * children, stacked-wall companions, and terrain neighborhood; skips\n * active-group members.\n * @param components Entities to lock\n * @returns The affected entities (post-cascade)\n *\n * @examplePrompt Lock the selected walls so they can't be edited\n * @examplePrompt Freeze these masses so nobody moves them by accident\n * @examplePrompt Lock everything on the ground floor\n * @examplePrompt Protect this room from further changes\n *\n * # Example\n * ```ts\n * // Lock every wall on the ground storey\n * const walls = await snaptrude.design.query.listWalls({ storeys: [1] })\n * const { affected } = await snaptrude.design.lock(walls)\n * // affected is post-cascade: hosted doors/windows are included\n * ```\n */\n public abstract lock(components: ComponentHandle[]): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Unlock previously locked entities so they can be edited again. Undoable.\n * Paired with {@linkcode PluginDesignApi.lock}.\n * @param components Entities to unlock\n * @returns The affected entities\n *\n * @examplePrompt Unlock the selected objects\n * @examplePrompt Make this locked wall editable again\n * @examplePrompt Unfreeze the masses I locked earlier\n * @examplePrompt Remove the lock from these doors\n *\n * # Example\n * ```ts\n * // Unlock everything that is explicitly locked\n * const locked = await snaptrude.design.listLocked()\n * const { affected } = await snaptrude.design.unlock(locked)\n * ```\n */\n public abstract unlock(components: ComponentHandle[]): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Test whether an entity is locked (folds group + bucket lock unless `includeInherited: false`).\n * @param component The entity to test\n * @param includeInherited Fold group + bucket lock (default `true`); `false` reads the own flag only\n * @returns `true` if locked, otherwise `false`\n *\n * @examplePrompt Is this wall locked?\n * @examplePrompt Check whether the selected mass is locked or editable\n * @examplePrompt Why can't I move this object? Check if it is locked\n * @examplePrompt Tell me if this room is locked, counting locks inherited from its group\n *\n * # Example\n * ```ts\n * const [wall] = await snaptrude.design.query.listWalls()\n * const locked = await snaptrude.design.isLocked(wall)\n * // Own flag only — ignore locks inherited from the group / bucket\n * const ownLock = await snaptrude.design.isLocked(wall, false)\n * ```\n */\n public abstract isLocked(\n component: ComponentHandle,\n includeInherited?: boolean,\n ): PluginApiReturn<boolean>\n\n /**\n * List the explicitly-locked entities (excludes purely group/bucket-inherited locks).\n * @returns The locked entities as {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt Which objects in the model are locked?\n * @examplePrompt List all the locked entities in this project\n * @examplePrompt Show everything that has been locked\n * @examplePrompt Are there any locked walls or masses in the scene?\n *\n * # Example\n * ```ts\n * const locked = await snaptrude.design.listLocked()\n * console.log(locked.length, \"explicitly locked entities\")\n * ```\n */\n public abstract listLocked(): PluginApiReturn<ComponentHandle[]>\n\n /**\n * Lock the **footprint area** of one or more Room / Department mass spaces —\n * geometry edits that would change the bottom-face area are then refused or\n * auto-resized to preserve it. Distinct from {@linkcode PluginDesignApi.lock}\n * (which freezes the whole object): this locks only the area. Cascades to the\n * space's instances and its source mass. Undoable.\n *\n * Only Room / Department masses are area-lockable; any other handle throws.\n * Requires the `area_lock` feature to be enabled — when it is off this throws.\n *\n * @param spaces Room / Department mass spaces to area-lock\n * @returns The affected spaces (post-cascade, echoing the input handles)\n *\n * @examplePrompt Lock the floor area of the selected rooms\n * @examplePrompt Freeze the footprint area of this department so its size can't drift\n * @examplePrompt Area-lock every room on this storey\n * @examplePrompt Pin the area of this space while I edit its walls\n *\n * # Example\n * ```ts\n * const rooms = await snaptrude.design.selection.get()\n * const { affected } = await snaptrude.design.lockArea(rooms)\n * console.log(\"area-locked\", affected.length, \"spaces\")\n * ```\n */\n public abstract lockArea(spaces: ComponentHandle[]): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Unlock the footprint area of previously area-locked spaces so their area can\n * change again. Paired with {@linkcode PluginDesignApi.lockArea}; cascades to\n * instances + source; undoable. Requires the `area_lock` feature (throws if off).\n *\n * @param spaces Room / Department mass spaces to area-unlock\n * @returns The affected spaces (post-cascade)\n *\n * @examplePrompt Unlock the area of the selected rooms\n * @examplePrompt Remove the footprint-area lock from this department\n * @examplePrompt Let me resize this space again by unlocking its area\n * @examplePrompt Area-unlock everything I locked earlier\n *\n * # Example\n * ```ts\n * const locked = await snaptrude.design.listAreaLocked()\n * const { affected } = await snaptrude.design.unlockArea(locked)\n * ```\n */\n public abstract unlockArea(spaces: ComponentHandle[]): PluginApiReturn<PluginDesignChangeResult>\n\n /**\n * Test whether a space's footprint area is locked. A read — never throws;\n * returns `false` for non-space handles and when the `area_lock` feature is off.\n *\n * @param space The space to test\n * @returns `true` if the space's area is locked, otherwise `false`\n *\n * @examplePrompt Is the area of this room locked?\n * @examplePrompt Check whether this department's footprint is pinned\n * @examplePrompt Tell me if the selected space has its area locked\n *\n * # Example\n * ```ts\n * const [room] = await snaptrude.design.selection.get()\n * const locked = await snaptrude.design.isAreaLocked(room)\n * console.log(locked ? \"area is locked\" : \"area is free\")\n * ```\n */\n public abstract isAreaLocked(space: ComponentHandle): PluginApiReturn<boolean>\n\n /**\n * List the spaces whose footprint area is locked. A read — never throws; returns\n * `[]` when the `area_lock` feature is off.\n *\n * @returns The area-locked spaces as {@linkcode ComponentHandle}`[]`\n *\n * @examplePrompt Which spaces have their area locked?\n * @examplePrompt List every room with a locked footprint\n * @examplePrompt Show me all the area-locked departments\n *\n * # Example\n * ```ts\n * const locked = await snaptrude.design.listAreaLocked()\n * console.log(locked.length, \"area-locked spaces\")\n * ```\n */\n public abstract listAreaLocked(): PluginApiReturn<ComponentHandle[]>\n\n constructor() {}\n}\n\nexport * from \"./create\"\nexport * from \"./query\"\nexport * from \"./selection\"\nexport * from \"./doors\"\nexport * from \"./windows\"\nexport * from \"./furniture\"\nexport * from \"./materials\"\nexport * from \"./lock\"\nexport * from \"./erase\"\nexport * from \"./delete\"\nexport * from \"./shared\"\nexport * from \"./boolean\"\nexport * from \"./transform\"\nexport * from \"./edit\"\nexport * from \"./update\"\nexport * from \"./visibility\"\nexport * from \"./dimensions\"\nexport * from \"./types\"\nexport * from \"./family\"\nexport * from \"./grids\"\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * Buildable Envelope management — create and update parametric zoning envelopes.\n *\n * A **buildable envelope** is the regulated volume inside which a building may\n * be massed. The host owns envelope identity: {@linkcode PluginBuildableEnvelopeApi.create create}\n * mints a `buildableEnvelopeId` and returns it; {@linkcode PluginBuildableEnvelopeApi.update update}\n * requires that id to target the existing envelope.\n *\n * Accessed via `snaptrude.entity.buildableEnvelope`.\n */\nexport abstract class PluginBuildableEnvelopeApi {\n constructor() {}\n\n /**\n * Create a new parametric buildable envelope — the zoning-regulated volume a\n * building may occupy — generated from a site polygon, tiered setbacks, a\n * height or floor-count cap, and optional FAR and lot-coverage limits.\n *\n * @param sitePolygon - Site polygon vertices in `lengthUnit`; minimum 3 vertices.\n * @param lengthUnit - Unit used by all length fields (`\"ft\" | \"m\"`).\n * @param setbacks - Setback profile, ground tier first; minimum 1 tier.\n * @param verticalCap - Maximum height or floor count.\n * @param floorToFloor - Required floor-to-floor height in `lengthUnit`.\n * @param farRatio - Optional FAR value, positive when provided.\n * @param lotCoverageMaxPct - Optional lot coverage cap, `0..100`.\n * @returns A {@linkcode PluginBuildableEnvelopeCreateResult} with the\n * `buildableEnvelopeId` of the created envelope.\n * @throws If validation fails or generation produces no renderable geometry.\n *\n * @examplePrompt Create a buildable envelope for my site\n * @examplePrompt Generate the zoning envelope with 10ft front setbacks\n * @examplePrompt Show the maximum buildable volume with a 150ft height limit\n * @examplePrompt Model the allowable massing from the setback rules\n * @examplePrompt Create a zoning envelope capped at 12 floors with FAR 3\n *\n * # Example\n * ```ts\n * const { buildableEnvelopeId } = await snaptrude.entity.buildableEnvelope.create(\n * [\n * { x: 0, z: 0 },\n * { x: 100, z: 0 },\n * { x: 100, z: 80 },\n * { x: 0, z: 80 },\n * ],\n * \"ft\",\n * [\n * { aboveHeight: 0, front: 10, side: 5, rear: 10 },\n * { aboveHeight: 100, front: 20, side: 10, rear: 20 },\n * ],\n * { kind: \"max_height\", maxHeight: 150 },\n * 12,\n * )\n * ```\n *\n * @deprecated Use `design.create.buildableEnvelope`\n */\n public abstract create(\n sitePolygon: PluginBuildableEnvelopePolygonVertex[],\n lengthUnit: \"ft\" | \"m\",\n setbacks: PluginBuildableEnvelopeSetbackTier[],\n verticalCap: PluginBuildableEnvelopeVerticalCap,\n floorToFloor: number,\n farRatio?: number,\n lotCoverageMaxPct?: number\n ): PluginApiReturn<PluginBuildableEnvelopeCreateResult>\n\n /**\n * Update an existing parametric buildable envelope — regenerate the zoning\n * volume from a revised site polygon, setbacks, vertical cap, or FAR and\n * lot-coverage limits, keeping the same envelope id.\n *\n * @param buildableEnvelopeId - Existing envelope ID to update; non-empty and\n * must match an existing envelope on the canvas.\n * @param sitePolygon - Site polygon vertices in `lengthUnit`; minimum 3 vertices.\n * @param lengthUnit - Unit used by all length fields (`\"ft\" | \"m\"`).\n * @param setbacks - Setback profile, ground tier first; minimum 1 tier.\n * @param verticalCap - Maximum height or floor count.\n * @param floorToFloor - Required floor-to-floor height in `lengthUnit`.\n * @param farRatio - Optional FAR value, positive when provided.\n * @param lotCoverageMaxPct - Optional lot coverage cap, `0..100`.\n * @returns A {@linkcode PluginBuildableEnvelopeUpdateResult} with the\n * `buildableEnvelopeId` of the updated envelope.\n * @throws If validation fails, the envelope does not exist, or generation\n * produces no renderable geometry.\n *\n * @examplePrompt Change the envelope's height limit to 175 feet\n * @examplePrompt Update the setbacks on the buildable envelope\n * @examplePrompt Regenerate the zoning envelope with a new site boundary\n * @examplePrompt Increase the FAR on the buildable volume\n * @examplePrompt Modify the envelope to allow 15 storeys instead\n *\n * # Example\n * ```ts\n * const { buildableEnvelopeId } = await snaptrude.entity.buildableEnvelope.update(\n * existingId,\n * [\n * { x: 0, z: 0 },\n * { x: 100, z: 0 },\n * { x: 100, z: 80 },\n * { x: 0, z: 80 },\n * ],\n * \"ft\",\n * [{ aboveHeight: 0, front: 10, side: 5, rear: 10 }],\n * { kind: \"max_height\", maxHeight: 175 },\n * 12,\n * )\n * ```\n *\n * @deprecated Use `design.update.buildableEnvelope`\n */\n public abstract update(\n buildableEnvelopeId: string,\n sitePolygon: PluginBuildableEnvelopePolygonVertex[],\n lengthUnit: \"ft\" | \"m\",\n setbacks: PluginBuildableEnvelopeSetbackTier[],\n verticalCap: PluginBuildableEnvelopeVerticalCap,\n floorToFloor: number,\n farRatio?: number,\n lotCoverageMaxPct?: number\n ): PluginApiReturn<PluginBuildableEnvelopeUpdateResult>\n}\n\n/**\n * Site polygon vertex in the request `lengthUnit`.\n *\n * Vertices may be passed as `{x, z}` objects or `[x, z]` tuples. Numeric\n * values must be finite (no `NaN` or `±Infinity`).\n */\nexport const PluginBuildableEnvelopePolygonVertex = z.union([\n z.object({ x: z.number().finite(), z: z.number().finite() }).strict(),\n z.tuple([z.number().finite(), z.number().finite()]),\n])\n\nexport type PluginBuildableEnvelopePolygonVertex = z.infer<\n typeof PluginBuildableEnvelopePolygonVertex\n>\n\n/**\n * Vertical cap for a buildable envelope.\n *\n * Use exactly one shape:\n * - `{ kind: \"max_height\", maxHeight }`\n * - `{ kind: \"max_floors\", maxFloors }`\n *\n * Floor-to-floor height is not nested here; it is always supplied as the\n * top-level `floorToFloor` argument.\n */\nexport const PluginBuildableEnvelopeVerticalCap = z.discriminatedUnion(\"kind\", [\n z.object({\n kind: z.literal(\"max_height\"),\n maxHeight: z.number().finite().positive(),\n }).strict(),\n z.object({\n kind: z.literal(\"max_floors\"),\n maxFloors: z.number().finite().int().positive(),\n }).strict(),\n])\n\nexport type PluginBuildableEnvelopeVerticalCap = z.infer<\n typeof PluginBuildableEnvelopeVerticalCap\n>\n\n/**\n * One tier of a buildable envelope's setback profile.\n *\n * Tiers are ordered by `aboveHeight`. The first tier (index 0) must have\n * `aboveHeight: 0` and describes the ground footprint; each subsequent tier\n * describes the complete setback at a strictly greater height. Each tier\n * carries its own `front`, `side`, and `rear` values — there is no\n * inheritance from earlier tiers.\n *\n * All numeric values must be finite and nonnegative, in the request\n * `lengthUnit`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `aboveHeight` | `number` | Height at or above which this tier applies; the ground tier uses `0` |\n * | `front` | `number` | Front setback for this tier |\n * | `side` | `number` | Side setback for this tier |\n * | `rear` | `number` | Rear setback for this tier |\n */\nexport const PluginBuildableEnvelopeSetbackTier = z.object({\n aboveHeight: z.number().finite().nonnegative(),\n front: z.number().finite().nonnegative(),\n side: z.number().finite().nonnegative(),\n rear: z.number().finite().nonnegative(),\n}).strict()\n\nexport type PluginBuildableEnvelopeSetbackTier = z.infer<\n typeof PluginBuildableEnvelopeSetbackTier\n>\n\n// Enforces setback tier ordering invariants on the parsed args shape:\n// - setbacks[0].aboveHeight === 0\n// - aboveHeight strictly increasing thereafter\n// Length >= 1 is enforced earlier by z.array(...).min(1).\nfunction refineSetbackTiers(\n args: { setbacks: PluginBuildableEnvelopeSetbackTier[] },\n ctx: z.RefinementCtx\n): void {\n const tiers = args.setbacks\n if (tiers.length === 0) return\n if (tiers[0].aboveHeight !== 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: [\"setbacks\", 0, \"aboveHeight\"],\n message: \"Ground tier required: setbacks[0].aboveHeight must be 0.\",\n })\n }\n for (let i = 1; i < tiers.length; i++) {\n if (!(tiers[i].aboveHeight > tiers[i - 1].aboveHeight)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: [\"setbacks\", i, \"aboveHeight\"],\n message:\n \"Setback tiers must be ordered by strictly increasing aboveHeight.\",\n })\n }\n }\n}\n\n/**\n * Arguments for {@linkcode PluginBuildableEnvelopeApi.create}.\n *\n * Strict schema. The host owns identity — no `buildableEnvelopeId` is accepted\n * on create; the id is minted by the host and returned in\n * {@linkcode PluginBuildableEnvelopeCreateResult}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sitePolygon` | {@linkcode PluginBuildableEnvelopePolygonVertex}`[]` | Site polygon vertices in `lengthUnit`; minimum 3 vertices |\n * | `lengthUnit` | `\"ft\" \\| \"m\"` | Unit used by all length fields |\n * | `setbacks` | {@linkcode PluginBuildableEnvelopeSetbackTier}`[]` | Setback profile, ground tier first; minimum 1 tier |\n * | `verticalCap` | {@linkcode PluginBuildableEnvelopeVerticalCap} | Maximum height or floor count |\n * | `floorToFloor` | `number` | Required floor-to-floor height in `lengthUnit` |\n * | `farRatio` | `number?` | Optional FAR value, positive when provided |\n * | `lotCoverageMaxPct` | `number?` | Optional lot coverage cap, `0..100` |\n */\nexport const PluginBuildableEnvelopeCreateArgs = z\n .object({\n sitePolygon: z.array(PluginBuildableEnvelopePolygonVertex).min(3),\n lengthUnit: z.union([z.literal(\"ft\"), z.literal(\"m\")]),\n setbacks: z.array(PluginBuildableEnvelopeSetbackTier).min(1),\n verticalCap: PluginBuildableEnvelopeVerticalCap,\n floorToFloor: z.number().finite().positive(),\n farRatio: z.number().finite().positive().optional(),\n lotCoverageMaxPct: z.number().finite().min(0).max(100).optional(),\n })\n .strict()\n .superRefine(refineSetbackTiers)\n\nexport type PluginBuildableEnvelopeCreateArgs = z.infer<\n typeof PluginBuildableEnvelopeCreateArgs\n>\n\n/**\n * Result of {@linkcode PluginBuildableEnvelopeApi.create}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `buildableEnvelopeId` | `string` | Non-empty unique ID of the created buildable envelope |\n */\nexport const PluginBuildableEnvelopeCreateResult = z.object({\n buildableEnvelopeId: z.string().min(1),\n}).strict()\n\nexport type PluginBuildableEnvelopeCreateResult = z.infer<\n typeof PluginBuildableEnvelopeCreateResult\n>\n\n/**\n * Arguments for {@linkcode PluginBuildableEnvelopeApi.update}.\n *\n * Strict schema. `buildableEnvelopeId` is required and identifies the prior\n * envelope on the canvas to update.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `buildableEnvelopeId` | `string` | Existing envelope ID to update; non-empty |\n * | `sitePolygon` | {@linkcode PluginBuildableEnvelopePolygonVertex}`[]` | Site polygon vertices in `lengthUnit`; minimum 3 vertices |\n * | `lengthUnit` | `\"ft\" \\| \"m\"` | Unit used by all length fields |\n * | `setbacks` | {@linkcode PluginBuildableEnvelopeSetbackTier}`[]` | Setback profile, ground tier first; minimum 1 tier |\n * | `verticalCap` | {@linkcode PluginBuildableEnvelopeVerticalCap} | Maximum height or floor count |\n * | `floorToFloor` | `number` | Required floor-to-floor height in `lengthUnit` |\n * | `farRatio` | `number?` | Optional FAR value, positive when provided |\n * | `lotCoverageMaxPct` | `number?` | Optional lot coverage cap, `0..100` |\n */\nexport const PluginBuildableEnvelopeUpdateArgs = z\n .object({\n buildableEnvelopeId: z.string().min(1),\n sitePolygon: z.array(PluginBuildableEnvelopePolygonVertex).min(3),\n lengthUnit: z.union([z.literal(\"ft\"), z.literal(\"m\")]),\n setbacks: z.array(PluginBuildableEnvelopeSetbackTier).min(1),\n verticalCap: PluginBuildableEnvelopeVerticalCap,\n floorToFloor: z.number().finite().positive(),\n farRatio: z.number().finite().positive().optional(),\n lotCoverageMaxPct: z.number().finite().min(0).max(100).optional(),\n })\n .strict()\n .superRefine(refineSetbackTiers)\n\nexport type PluginBuildableEnvelopeUpdateArgs = z.infer<\n typeof PluginBuildableEnvelopeUpdateArgs\n>\n\n/**\n * Result of {@linkcode PluginBuildableEnvelopeApi.update}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `buildableEnvelopeId` | `string` | Non-empty unique ID of the updated buildable envelope |\n */\nexport const PluginBuildableEnvelopeUpdateResult = z.object({\n buildableEnvelopeId: z.string().min(1),\n}).strict()\n\nexport type PluginBuildableEnvelopeUpdateResult = z.infer<\n typeof PluginBuildableEnvelopeUpdateResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport { ProfileHandle, CurveHandle } from \"../../handles\"\n\n/**\n * Reference line management — create, query, and delete reference lines.\n *\n * A **reference line** is a 2D guide line (or arc) in the Snaptrude scene,\n * typically used for grid lines and alignment guides. Reference lines\n * carry properties such as grid tags, labels, and line styles.\n *\n * All methods are **host API calls** that return Promises and support\n * undo/redo via Snaptrude's command system.\n *\n * Accessed via `snaptrude.entity.referenceLine`.\n */\nexport abstract class PluginReferenceLineApi {\n constructor() {}\n\n /**\n * Create multiple reference lines from a profile.\n *\n * Each curve in the given {@linkcode ProfileHandle} becomes\n * a separate reference line. Uses `ReferenceLineConstructor.createFromProfile`\n * internally.\n *\n * @param profile - A {@linkcode ProfileHandle} whose curves define the\n * reference lines to create\n * @returns A {@linkcode PluginReferenceLineCreateMultiResult} with the\n * `referenceLineIds` of the created reference lines\n * @throws If reference line creation fails\n *\n * @examplePrompt Draw grid lines across the site\n * @examplePrompt Create reference lines from these curves\n * @examplePrompt Add alignment guides along the building edge\n * @examplePrompt Set up a structural grid using guide lines\n * @examplePrompt Turn this profile into reference lines\n *\n * # Example\n * ```ts\n * const { vec3 } = snaptrude.core.math\n *\n * const start = await vec3.new(0, 0, 0)\n * const mid = await vec3.new(10, 0, 0)\n * const end = await vec3.new(10, 0, 10)\n * const l1 = await snaptrude.core.geom.create.line(start, mid)\n * const l2 = await snaptrude.core.geom.create.line(mid, end)\n * const profile = await snaptrude.core.geom.create.profileFromCurves([l1, l2])\n *\n * const { referenceLineIds } = await snaptrude.entity.referenceLine.createMulti(profile)\n * ```\n *\n * @deprecated Use `design.create.referenceLines`\n */\n public abstract createMulti(\n profile: ProfileHandle\n ): PluginApiReturn<PluginReferenceLineCreateMultiResult>\n\n /**\n * Get properties of a reference line by its ID.\n *\n * Only the properties listed in `properties` are returned — unlisted\n * properties will be `undefined` in the result.\n *\n * @param referenceLineId - The unique reference line ID\n * @param properties - Array of property names to retrieve. See\n * {@linkcode PluginReferenceLineGetProperty}.\n * @returns A partial {@linkcode PluginReferenceLineGetResult} containing only the\n * requested properties\n * @throws If the reference line does not exist\n *\n * @examplePrompt Get the curve geometry of this reference line\n * @examplePrompt Where does this grid line run?\n * @examplePrompt Read the geometry of a guide line by its id\n * @examplePrompt Look up the curve of the selected reference line\n *\n * # Example\n * ```ts\n * const result = await snaptrude.entity.referenceLine.get(\"some-ref-line-id\", [\"curve\"])\n * // result.curve is an opaque CurveHandle; a dedicated curve read API for its\n * // coordinates is exposed separately via `snaptrude.core.geom.curve`.\n * ```\n *\n * @deprecated Use `design.query.referenceLines.get`\n */\n public abstract get(\n referenceLineId: string,\n properties: z.infer<typeof PluginReferenceLineGetProperty>[]\n ): PluginApiReturn<PluginReferenceLineGetResult>\n\n /**\n * Get the IDs of all reference lines in the current project.\n *\n * Returns every reference line across all stories.\n * Use the returned IDs with {@linkcode PluginReferenceLineApi.get} to query\n * individual reference line properties.\n *\n * @returns A {@linkcode PluginReferenceLineGetAllResult} with a\n * `referenceLineIds` array\n *\n * @examplePrompt How many grid lines are in the project?\n * @examplePrompt List all the reference lines in my model\n * @examplePrompt Show every guide line in the file\n * @examplePrompt Do I have any reference lines in this project?\n *\n * # Example\n * ```ts\n * const { referenceLineIds } = await snaptrude.entity.referenceLine.getAll()\n * console.log(`Project has ${referenceLineIds.length} reference lines`)\n * ```\n *\n * @deprecated Use `design.query.listReferenceLines`\n */\n public abstract getAll(): PluginApiReturn<PluginReferenceLineGetAllResult>\n\n /**\n * Delete a reference line by its ID.\n *\n * Permanently removes the reference line and its associated mesh from\n * the scene. This operation is undoable via Snaptrude's command system.\n *\n * @param referenceLineId - The unique reference line ID to delete\n * @throws If the reference line does not exist\n *\n * @examplePrompt Delete this reference line\n * @examplePrompt Remove the grid line I just drew\n * @examplePrompt Get rid of the alignment guide on the left\n * @examplePrompt Erase a reference line by its id\n *\n * # Example\n * ```ts\n * await snaptrude.entity.referenceLine.delete(\"some-ref-line-id\")\n * ```\n *\n * @deprecated Use `design.delete.entities`\n */\n public abstract delete(\n referenceLineId: string\n ): PluginApiReturn<PluginReferenceLineDeleteResult>\n}\n\n/**\n * Arguments for {@linkcode PluginReferenceLineApi.createMulti}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `profile` | {@linkcode ProfileHandle} | Profile handle whose curves define reference lines |\n */\nexport const PluginReferenceLineCreateMultiArgs = z.object({\n profile: ProfileHandle,\n})\n\nexport type PluginReferenceLineCreateMultiArgs = z.infer<typeof PluginReferenceLineCreateMultiArgs>\n\n/**\n * Result of {@linkcode PluginReferenceLineApi.createMulti}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `referenceLineIds` | `string[]` | IDs of the created reference lines |\n */\nexport const PluginReferenceLineCreateMultiResult = z.object({\n referenceLineIds: z.array(z.string()),\n})\n\nexport type PluginReferenceLineCreateMultiResult = z.infer<typeof PluginReferenceLineCreateMultiResult>\n\n/**\n * Available properties that can be queried on a reference line.\n *\n * | Value | Return Type | Description |\n * |---|---|---|\n * | `\"curve\"` | {@linkcode CurveHandle} | The curve geometry of the reference line, as an opaque handle |\n */\nexport const PluginReferenceLineGetProperty = z.enum([\n \"curve\",\n])\n\n/**\n * Arguments for {@linkcode PluginReferenceLineApi.get}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `referenceLineId` | `string` | The reference line ID to query |\n * | `properties` | {@linkcode PluginReferenceLineGetProperty}`[]` | Properties to retrieve |\n */\nexport const PluginReferenceLineGetArgs = z.object({\n referenceLineId: z.string(),\n properties: z.array(PluginReferenceLineGetProperty),\n})\n\nexport type PluginReferenceLineGetArgs = z.infer<typeof PluginReferenceLineGetArgs>\n\n/**\n * Result of {@linkcode PluginReferenceLineApi.get}.\n *\n * A partial object — only the properties that were requested in\n * {@linkcode PluginReferenceLineGetArgs.properties} will be present.\n */\nexport const PluginReferenceLineGetResult = z\n .object({\n curve: CurveHandle,\n })\n .partial()\n\nexport type PluginReferenceLineGetResult = z.infer<typeof PluginReferenceLineGetResult>\n\n/**\n * Result of {@linkcode PluginReferenceLineApi.getAll}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `referenceLineIds` | `string[]` | IDs of all reference lines in the project |\n */\nexport const PluginReferenceLineGetAllResult = z.object({\n referenceLineIds: z.array(z.string()),\n})\n\nexport type PluginReferenceLineGetAllResult = z.infer<typeof PluginReferenceLineGetAllResult>\n\n/**\n * Arguments for {@linkcode PluginReferenceLineApi.delete}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `referenceLineId` | `string` | The reference line ID to delete |\n */\nexport const PluginReferenceLineDeleteArgs = z.object({\n referenceLineId: z.string(),\n})\n\nexport type PluginReferenceLineDeleteArgs = z.infer<typeof PluginReferenceLineDeleteArgs>\n\n/** Result type for {@linkcode PluginReferenceLineApi.delete} — returns nothing on success. */\nexport type PluginReferenceLineDeleteResult = void\n","import { PluginBuildableEnvelopeApi } from \"./buildableEnvelope\"\nimport { PluginReferenceLineApi } from \"./referenceLine\"\nimport { PluginStoryApi } from \"./story\"\n\n/**\n * CRUD operations on Snaptrude building entities.\n *\n * - {@linkcode PluginEntityApi.story} — Create, query, and update stories (floors)\n * - {@linkcode PluginEntityApi.referenceLine} — Create, query, and delete reference lines\n * - {@linkcode PluginEntityApi.buildableEnvelope} — Create or update parametric buildable envelopes\n *\n * Space operations moved to `design.create.spaces` / `design.update.*` /\n * `design.query.*` / `design.delete.entities` (the `entity.space` API was removed);\n * `./space` still exports the shared space value types.\n */\nexport abstract class PluginEntityApi {\n /** Story (floor/storey) operations. See {@linkcode PluginStoryApi}. */\n public abstract story: PluginStoryApi\n /** Reference line operations. See {@linkcode PluginReferenceLineApi}. */\n public abstract referenceLine: PluginReferenceLineApi\n /** Buildable Envelope operations. See {@linkcode PluginBuildableEnvelopeApi}. */\n public abstract buildableEnvelope: PluginBuildableEnvelopeApi\n\n constructor() {}\n}\n\nexport * from \"./buildableEnvelope\"\nexport * from \"./referenceLine\"\nexport * from \"./space\"\nexport * from \"./story\"\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport { PluginAreaUnit } from \"./metrics\"\nimport { ComponentHandle } from \"../../handles\"\n\n/**\n * Program departments — read the department groupings of the active program.\n *\n * In program mode a **department** is a named, colored grouping of spaces with an\n * optional gross area **target** and a unit **count** target. This is the program-planning view of departments\n * (their identity, color, and target area); the underlying space geometry is read\n * and edited through the geometry namespaces, not here.\n *\n * All methods are reads: they return plain records and never throw for a missing\n * department (`get` returns `null`, `list` returns `[]`).\n *\n * Accessed via `snaptrude.program.departments`.\n */\nexport abstract class PluginProgramDepartmentsApi {\n constructor() {}\n\n /**\n * List the departments in the active program.\n *\n * Returns every department — well-known (Default, Site, …) and user-created —\n * with its identity, color, and area target (when one has been set).\n *\n * @returns A {@linkcode PluginProgramDepartmentsListResult} with a `departments`\n * array (empty when the project has none).\n *\n * @examplePrompt List all the departments in my program\n * @examplePrompt What program groups do I have and what are their target areas?\n * @examplePrompt Show every department with its colour\n * @examplePrompt Get the area target for each department\n * @examplePrompt How many departments are in this project?\n *\n * # Example\n * ```ts\n * const { units, departments } = await snaptrude.program.departments.list()\n * for (const d of departments) console.log(d.name, d.color, d.targetArea, units)\n * ```\n */\n public abstract list(): PluginApiReturn<PluginProgramDepartmentsListResult>\n\n /**\n * Get a single department by id.\n *\n * @param departmentId - The id of the department to read.\n * @returns The matching {@linkcode PluginProgramDepartment}, or `null` if no\n * department has that id.\n *\n * @examplePrompt Get the department with id dep_123\n * @examplePrompt Look up a single department by its id\n * @examplePrompt What is the target area of this department id?\n *\n * # Example\n * ```ts\n * const dept = await snaptrude.program.departments.get(\"dep_123\")\n * if (dept) console.log(dept.name, dept.targetArea)\n * ```\n */\n public abstract get(\n departmentId: string,\n ): PluginApiReturn<PluginProgramDepartmentsGetResult>\n\n /**\n * Create a new department in the active program.\n *\n * Adds a named, colored program grouping. The new department starts empty —\n * put spaces in it with {@linkcode PluginProgramDepartmentsApi.assign}, and set\n * its area goal with {@linkcode PluginProgramDepartmentsApi.setTargetArea}.\n *\n * @param name - Display name of the new department.\n * @param color - CSS hex color string (e.g. `\"#b5e1dc\"`).\n * @returns The new department's id (read the full record back with\n * {@linkcode PluginProgramDepartmentsApi.get}).\n * @throws If the department could not be created.\n *\n * @examplePrompt Create a department called Bedrooms in light blue\n * @examplePrompt Add a new program group named Circulation with color #cccccc\n * @examplePrompt Make a Classrooms department\n * @examplePrompt Set up a Lobby department in the program\n *\n * # Example\n * ```ts\n * const { departmentId } = await snaptrude.program.departments.create(\"Bedrooms\", \"#b5e1dc\")\n * ```\n */\n public abstract create(\n name: string,\n color: string,\n ): PluginApiReturn<PluginProgramDepartmentsCreateResult>\n\n /**\n * Update a department's name and/or color.\n *\n * A **sparse** update: omitted fields are left unchanged. Use\n * {@linkcode PluginProgramDepartmentsApi.setTargetArea} to change the area\n * target (not this method).\n *\n * @param departmentId - The id of the department to update.\n * @param options - Any of `name`, `color`; omitted fields are left unchanged.\n * @returns The updated {@linkcode PluginProgramDepartment} (with the response\n * `units` for its `targetArea`).\n * @throws If no department has the given id.\n *\n * @examplePrompt Rename the Bedrooms department to Suites\n * @examplePrompt Change the Circulation department colour to #ff8800\n * @examplePrompt Recolour department dep_123\n * @examplePrompt Update the Lobby department's display name\n *\n * # Example\n * ```ts\n * const dept = await snaptrude.program.departments.update(\"dep_123\", { name: \"Suites\" })\n * ```\n */\n public abstract update(\n departmentId: string,\n options?: { name?: string; color?: string },\n ): PluginApiReturn<PluginProgramDepartmentsUpdateResult>\n\n /**\n * Assign one or more spaces to a department.\n *\n * Moves each space into the given department — an undoable membership change.\n * A space belongs to exactly one department, so assigning re-homes it from any\n * previous one. Paired with {@linkcode PluginProgramDepartmentsApi.unassign}.\n *\n * @param departmentId - The department to assign the spaces to.\n * @param spaceIds - The ids of the spaces to move into the department (at\n * least one).\n * @returns The department and the space ids that were assigned.\n * @throws If the department does not exist or a space id is invalid.\n *\n * @examplePrompt Assign the selected rooms to the Bedrooms department\n * @examplePrompt Put spaces 12 and 13 in the Circulation department\n * @examplePrompt Move this space into the Lobby department\n * @examplePrompt Add the kitchen spaces to the Service department\n *\n * # Example\n * ```ts\n * await snaptrude.program.departments.assign(\"dep_123\", [\"sp_1\", \"sp_2\"])\n * ```\n */\n public abstract assign(\n departmentId: string,\n spaceIds: ComponentHandle[],\n ): PluginApiReturn<PluginProgramDepartmentsAssignResult>\n\n /**\n * Remove one or more spaces from their department.\n *\n * Returns each space to the **Default** department — there is no \"no\n * department\" state, so unassigning means reassigning to Default. Paired with\n * {@linkcode PluginProgramDepartmentsApi.assign}.\n *\n * @param spaceIds - The ids of the spaces to remove from their current\n * department (at least one).\n * @returns The space ids that were returned to Default.\n * @throws If a space id is invalid.\n *\n * @examplePrompt Remove the selected rooms from their department\n * @examplePrompt Unassign space sp_1 from any department\n * @examplePrompt Clear the department on these spaces\n * @examplePrompt Take the kitchen out of the Service department\n *\n * # Example\n * ```ts\n * await snaptrude.program.departments.unassign([\"sp_1\"])\n * ```\n */\n public abstract unassign(\n spaceIds: ComponentHandle[],\n ): PluginApiReturn<PluginProgramDepartmentsUnassignResult>\n\n /**\n * Set a department's target area.\n *\n * The goal the program metrics compare allocated area against (see\n * {@linkcode PluginProgramMetricsApi}). Pass `units` to say what unit\n * `targetArea` is in; it defaults to the project's area units. Read the target\n * back from {@linkcode PluginProgramDepartmentsApi.get} (`targetArea`).\n *\n * @param departmentId - The id of the department whose target to set.\n * @param targetArea - The target area, in `units`.\n * @param units - Unit of `targetArea` (defaults to the project's area units).\n * @returns The updated {@linkcode PluginProgramDepartment} with its `units`.\n * @throws If no department has the given id.\n *\n * @examplePrompt Set the Bedrooms department target area to 2000 sqft\n * @examplePrompt Give the Circulation department a 500 sqm area target\n * @examplePrompt Target 1500 square feet for the Classrooms department\n * @examplePrompt Update the area goal for department dep_123\n *\n * # Example\n * ```ts\n * await snaptrude.program.departments.setTargetArea(\"dep_123\", 2000, \"ft2\")\n * ```\n */\n public abstract setTargetArea(\n departmentId: string,\n targetArea: number,\n units?: PluginAreaUnit,\n ): PluginApiReturn<PluginProgramDepartmentsSetTargetAreaResult>\n\n /**\n * Set a department's target unit count.\n *\n * The number of units (program blocks) the department is planned for — the\n * count twin of {@linkcode PluginProgramDepartmentsApi.setTargetArea}, stored\n * beside the gross area target on the department. Read it back from\n * {@linkcode PluginProgramDepartmentsApi.get} (`targetCount`).\n *\n * @param departmentId - The id of the department whose count to set.\n * @param targetCount - The target number of units (a non-negative integer).\n * @returns The updated {@linkcode PluginProgramDepartment} with its `units`.\n * @throws If no department has the given id.\n *\n * @examplePrompt Set the Bedrooms department unit count to 12\n * @examplePrompt The program needs 4 clinic blocks — update the department count\n * @examplePrompt Update the target count for department dep_123\n *\n * # Example\n * ```ts\n * await snaptrude.program.departments.setTargetCount(\"dep_123\", 12)\n * ```\n */\n public abstract setTargetCount(\n departmentId: string,\n targetCount: number,\n ): PluginApiReturn<PluginProgramDepartmentsSetTargetCountResult>\n\n /**\n * Delete a department.\n *\n * Any spaces still assigned to it are returned to the **Default** department\n * (no space is left orphaned). The reserved departments (Default, Site,\n * Envelope, Core) cannot be deleted.\n *\n * @param departmentId - The id of the department to delete.\n * @returns The id of the deleted department and the spaces that were reassigned\n * to Default.\n * @throws If no department has the given id, or it is a reserved department.\n *\n * @examplePrompt Delete the Bedrooms department\n * @examplePrompt Remove the empty Circulation department\n * @examplePrompt Delete department dep_123 and move its rooms to Default\n *\n * # Example\n * ```ts\n * await snaptrude.program.departments.delete(\"dep_123\")\n * ```\n */\n public delete?: (\n departmentId: string,\n ) => PluginApiReturn<PluginProgramDepartmentsDeleteResult>\n}\n\n/**\n * A department in the active program. Its `targetArea` is in the enclosing\n * response's `units` (see the area-units convention in\n * {@linkcode PluginAreaUnit}).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Unique department id |\n * | `name` | `string` | Display name |\n * | `color` | `string` | CSS hex color string (e.g. `\"#b5e1dc\"`) |\n * | `targetArea` | `number \\| null` | Gross target area in the response's `units`, or `null` if no target is set |\n * | `targetCount` | `number` | Target number of units / program blocks (`0` when unset) |\n */\nexport const PluginProgramDepartment = z.object({\n id: z.string(),\n name: z.string(),\n color: z.string(),\n targetArea: z.number().nullable(),\n targetCount: z.number(),\n})\nexport type PluginProgramDepartment = z.infer<typeof PluginProgramDepartment>\n\n/**\n * Result of {@linkcode PluginProgramDepartmentsApi.list}. Per the area-units\n * convention, `units` sits on this response envelope (not on each record).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `units` | `\"ft2\" \\| \"m2\"` | The unit every `targetArea` below is reported in |\n * | `departments` | {@linkcode PluginProgramDepartment}`[]` | All departments in the active program |\n */\nexport const PluginProgramDepartmentsListResult = z.object({\n units: PluginAreaUnit,\n departments: z.array(PluginProgramDepartment),\n})\nexport type PluginProgramDepartmentsListResult = z.infer<\n typeof PluginProgramDepartmentsListResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramDepartmentsApi.get}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `departmentId` | `string` | The id of the department to read |\n */\nexport const PluginProgramDepartmentsGetArgs = z.object({\n departmentId: z.string(),\n})\nexport type PluginProgramDepartmentsGetArgs = z.infer<\n typeof PluginProgramDepartmentsGetArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramDepartmentsApi.get} — the department, or\n * `null` when no department has the given id. This is a bare `.get` record, so\n * per the area-units convention `units` is carried on the record itself.\n */\nexport const PluginProgramDepartmentsGetResult = PluginProgramDepartment.extend(\n {\n units: PluginAreaUnit,\n },\n).nullable()\nexport type PluginProgramDepartmentsGetResult = z.infer<\n typeof PluginProgramDepartmentsGetResult\n>\n\n/**\n * A department record with its area `units` — the shape returned by the\n * single-record department writes (`update`, `setTargetArea`). Per the\n * area-units convention, a bare record carries `units` on itself.\n */\nexport const PluginProgramDepartmentRecord = PluginProgramDepartment.extend({\n units: PluginAreaUnit,\n})\nexport type PluginProgramDepartmentRecord = z.infer<\n typeof PluginProgramDepartmentRecord\n>\n\n/**\n * Arguments for {@linkcode PluginProgramDepartmentsApi.create}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | Display name of the new department |\n * | `color` | `string` | CSS hex color string (e.g. `\"#b5e1dc\"`) |\n */\nexport const PluginProgramDepartmentsCreateArgs = z.object({\n name: z.string(),\n color: z.string(),\n})\nexport type PluginProgramDepartmentsCreateArgs = z.infer<\n typeof PluginProgramDepartmentsCreateArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramDepartmentsApi.create} — the id of the\n * newly created department.\n */\nexport const PluginProgramDepartmentsCreateResult = z.object({\n departmentId: z.string(),\n})\nexport type PluginProgramDepartmentsCreateResult = z.infer<\n typeof PluginProgramDepartmentsCreateResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramDepartmentsApi.update}. Sparse — only\n * the provided fields are changed.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `departmentId` | `string` | The id of the department to update |\n * | `name` | `string \\| undefined` | New display name (unchanged if omitted) |\n * | `color` | `string \\| undefined` | New CSS hex color (unchanged if omitted) |\n */\nexport const PluginProgramDepartmentsUpdateArgs = z.object({\n departmentId: z.string(),\n name: z.string().optional(),\n color: z.string().optional(),\n})\nexport type PluginProgramDepartmentsUpdateArgs = z.infer<\n typeof PluginProgramDepartmentsUpdateArgs\n>\n\n/** Result of {@linkcode PluginProgramDepartmentsApi.update} — the updated record. */\nexport const PluginProgramDepartmentsUpdateResult =\n PluginProgramDepartmentRecord\nexport type PluginProgramDepartmentsUpdateResult = z.infer<\n typeof PluginProgramDepartmentsUpdateResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramDepartmentsApi.assign}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `departmentId` | `string` | The department to assign the spaces to |\n * | `spaceIds` | `ComponentHandle[]` | The ids of the spaces to move into the department |\n */\nexport const PluginProgramDepartmentsAssignArgs = z.object({\n departmentId: z.string(),\n spaceIds: z.array(ComponentHandle).min(1),\n})\nexport type PluginProgramDepartmentsAssignArgs = z.infer<\n typeof PluginProgramDepartmentsAssignArgs\n>\n\n/** Result of {@linkcode PluginProgramDepartmentsApi.assign}. */\nexport const PluginProgramDepartmentsAssignResult = z.object({\n departmentId: z.string(),\n spaceIds: z.array(ComponentHandle),\n})\nexport type PluginProgramDepartmentsAssignResult = z.infer<\n typeof PluginProgramDepartmentsAssignResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramDepartmentsApi.unassign}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaceIds` | `ComponentHandle[]` | The ids of the spaces to return to the Default department |\n */\nexport const PluginProgramDepartmentsUnassignArgs = z.object({\n spaceIds: z.array(ComponentHandle).min(1),\n})\nexport type PluginProgramDepartmentsUnassignArgs = z.infer<\n typeof PluginProgramDepartmentsUnassignArgs\n>\n\n/** Result of {@linkcode PluginProgramDepartmentsApi.unassign}. */\nexport const PluginProgramDepartmentsUnassignResult = z.object({\n spaceIds: z.array(ComponentHandle),\n})\nexport type PluginProgramDepartmentsUnassignResult = z.infer<\n typeof PluginProgramDepartmentsUnassignResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramDepartmentsApi.setTargetArea}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `departmentId` | `string` | The id of the department whose target to set |\n * | `targetArea` | `number` | The target area, in `units` |\n * | `units` | `\"ft2\" \\| \"m2\" \\| undefined` | Unit of `targetArea` (defaults to the project's area units) |\n */\nexport const PluginProgramDepartmentsSetTargetAreaArgs = z.object({\n departmentId: z.string(),\n targetArea: z.number(),\n units: PluginAreaUnit.optional(),\n})\nexport type PluginProgramDepartmentsSetTargetAreaArgs = z.infer<\n typeof PluginProgramDepartmentsSetTargetAreaArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramDepartmentsApi.setTargetArea} — the updated\n * department record with its `units`.\n */\nexport const PluginProgramDepartmentsSetTargetAreaResult =\n PluginProgramDepartmentRecord\nexport type PluginProgramDepartmentsSetTargetAreaResult = z.infer<\n typeof PluginProgramDepartmentsSetTargetAreaResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramDepartmentsApi.setTargetCount}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `departmentId` | `string` | The id of the department whose count to set |\n * | `targetCount` | `number` | Target number of units (non-negative integer) |\n */\nexport const PluginProgramDepartmentsSetTargetCountArgs = z.object({\n departmentId: z.string(),\n targetCount: z.number().int().nonnegative(),\n})\nexport type PluginProgramDepartmentsSetTargetCountArgs = z.infer<\n typeof PluginProgramDepartmentsSetTargetCountArgs\n>\n\n/** Result of {@linkcode PluginProgramDepartmentsApi.setTargetCount} — the updated record with `units`. */\nexport const PluginProgramDepartmentsSetTargetCountResult =\n PluginProgramDepartmentRecord\nexport type PluginProgramDepartmentsSetTargetCountResult = z.infer<\n typeof PluginProgramDepartmentsSetTargetCountResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramDepartmentsApi.delete}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `departmentId` | `string` | The id of the department to delete |\n */\nexport const PluginProgramDepartmentsDeleteArgs = z.object({\n departmentId: z.string(),\n})\nexport type PluginProgramDepartmentsDeleteArgs = z.infer<\n typeof PluginProgramDepartmentsDeleteArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramDepartmentsApi.delete}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `departmentId` | `string` | The id of the deleted department |\n * | `reassignedSpaceIds` | `ComponentHandle[]` | Spaces that were returned to the Default department |\n */\nexport const PluginProgramDepartmentsDeleteResult = z.object({\n departmentId: z.string(),\n reassignedSpaceIds: z.array(ComponentHandle),\n})\nexport type PluginProgramDepartmentsDeleteResult = z.infer<\n typeof PluginProgramDepartmentsDeleteResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * Program metrics — the area-program summary of the active project.\n *\n * Aggregate planning numbers: total target versus allocated area, plus a\n * per-department breakdown. This is the program-mode \"am I hitting my area\n * targets?\" read; per-entity geometry measurements (a single space's area,\n * volume, bounding box) live under the geometry namespaces, not here.\n *\n * Every area is reported in the summary's `units` (ft² or m²). This is a read:\n * it never throws and returns an empty breakdown with zero totals when the\n * project has no program.\n *\n * Accessed via `snaptrude.program.metrics`.\n */\nexport abstract class PluginProgramMetricsApi {\n constructor() {}\n\n /**\n * Get the area-program summary for the active project.\n *\n * @returns A {@linkcode PluginProgramMetricsSummary} with project-level totals\n * and a per-department area breakdown.\n *\n * @examplePrompt How much area have I allocated versus my targets?\n * @examplePrompt Give me the program area summary\n * @examplePrompt Which departments are over or under their target area?\n * @examplePrompt What's the total built area in the program?\n * @examplePrompt Show the area breakdown per department\n * @examplePrompt Am I within my area program?\n *\n * # Example\n * ```ts\n * const m = await snaptrude.program.metrics.get()\n * console.log(`${m.totalAllocatedArea} / ${m.totalTargetArea} ${m.units}`)\n * console.log(\"unassigned:\", m.unassignedArea)\n * for (const d of m.departments) {\n * console.log(d.name, d.allocatedArea, d.targetArea)\n * }\n * ```\n */\n public abstract get(): PluginApiReturn<PluginProgramMetricsSummary>\n}\n\n/**\n * The unit areas are reported in: square feet (`\"ft2\"`) or square metres (`\"m2\"`).\n *\n * **Area-units convention (applies to every area-returning method).** A result\n * carries exactly one `units` field, at the **top level of the value the method\n * returns**: on the response **envelope** for `list`/multi/composite results, and\n * on the **record itself** only for a single `.get` that returns a bare record.\n * Inner/leaf records never repeat it.\n */\nexport const PluginAreaUnit = z.enum([\"ft2\", \"m2\"])\nexport type PluginAreaUnit = z.infer<typeof PluginAreaUnit>\n\n/**\n * Per-department area figures within the program summary. Areas are in the\n * enclosing {@linkcode PluginProgramMetricsSummary}'s `units`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `departmentId` | `string` | Department id |\n * | `name` | `string` | Department name |\n * | `targetArea` | `number \\| null` | Target area in project units, or `null` if no target is set |\n * | `allocatedArea` | `number` | Area currently allocated to this department, in project units |\n */\nexport const PluginProgramDepartmentMetric = z.object({\n departmentId: z.string(),\n name: z.string(),\n targetArea: z.number().nullable(),\n allocatedArea: z.number(),\n})\nexport type PluginProgramDepartmentMetric = z.infer<\n typeof PluginProgramDepartmentMetric\n>\n\n/**\n * Result of {@linkcode PluginProgramMetricsApi.get}.\n *\n * Reconciliation invariant: `totalAllocatedArea === unassignedArea` plus the sum\n * of every `departments[].allocatedArea`. `totalTargetArea` is the sum of every\n * `departments[].targetArea` (a `null` target counts as 0); unassigned area has\n * no target. Every area is in `units`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `units` | `\"ft2\" \\| \"m2\"` | The unit every area in this summary is reported in |\n * | `totalTargetArea` | `number` | Sum of all department target areas |\n * | `totalAllocatedArea` | `number` | Total allocated area = departments + `unassignedArea` |\n * | `unassignedArea` | `number` | Allocated area not attributed to any listed department (site/unassigned) — the delta that reconciles the total |\n * | `departments` | {@linkcode PluginProgramDepartmentMetric}`[]` | Per-department area breakdown |\n */\nexport const PluginProgramMetricsSummary = z\n .object({\n units: PluginAreaUnit,\n totalTargetArea: z.number(),\n totalAllocatedArea: z.number(),\n unassignedArea: z.number(),\n departments: z.array(PluginProgramDepartmentMetric),\n })\n .refine(\n (s) => {\n const summed =\n s.unassignedArea +\n s.departments.reduce((acc, d) => acc + d.allocatedArea, 0)\n // Reconciliation tolerance: 0.01 absolute or 0.01% relative, for float drift.\n const tolerance = Math.max(0.01, Math.abs(s.totalAllocatedArea) * 1e-4)\n return Math.abs(s.totalAllocatedArea - summed) <= tolerance\n },\n {\n message:\n \"totalAllocatedArea must equal unassignedArea + the sum of departments[].allocatedArea\",\n },\n )\nexport type PluginProgramMetricsSummary = z.infer<\n typeof PluginProgramMetricsSummary\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * Program adjacency — the proximity relationships between departments or\n * spaces.\n *\n * Adjacency is stage one of the product's space-planning workflow (compute →\n * arrange → pack): the AI service scores which program elements need to be\n * near each other, and the geometry contributes physical contact. Cell values:\n *\n * | Value | Meaning |\n * |---|---|\n * | `2` | Direct — adjacency required |\n * | `1` | Indirect — nearby preferred, or physical contact |\n * | `-1` | Restricted — must NOT be adjacent |\n * | `0` | No relationship — or adjacency was never computed |\n *\n * Zeros are ambiguous **by design** (the engine cannot distinguish \"no\n * relationship\" from \"never computed\"): call\n * {@linkcode PluginProgramAdjacencyApi.compute} first when you need fresh\n * data. Persisted matrices auto-hydrate on scene load, so warm reads are the\n * common case. The arrange/pack stages of the workflow live in\n * {@linkcode PluginProgramLayoutApi} (`program.layout`).\n *\n * Accessed via `snaptrude.program.adjacency`.\n */\nexport abstract class PluginProgramAdjacencyApi {\n constructor() {}\n\n /**\n * Read the adjacency matrix at the department or space level.\n *\n * Returns a square `matrix` whose rows/columns are aligned to `ids`\n * (component ids). Pass `ids` to read a sub-matrix in that order; omit it to\n * get every eligible element at that level. Cell values:\n *\n * | Value | Meaning |\n * |---|---|\n * | `2` | Direct — adjacency required |\n * | `1` | Indirect — nearby preferred, or physical contact |\n * | `-1` | Restricted — must NOT be adjacent |\n * | `0` | No relationship — or adjacency was never computed |\n *\n * Zeros are ambiguous by design — call\n * {@linkcode PluginProgramAdjacencyApi.compute} first for fresh data\n * (persisted matrices auto-hydrate on scene load, so warm reads are the\n * common case). Space-level relationships are computed within a storey:\n * cross-storey pairs always read `0`. At the `department` level the result\n * also carries `departmentIds`, a parallel array joining each row to the\n * department entity ids returned by `program.departments.list` (the row\n * `ids` are department-mass component ids, not department entity ids).\n *\n * @param level - Read the `\"department\"` or `\"space\"` matrix.\n * @param ids - Component ids to read a sub-matrix for, in this order\n * (defaults to every eligible element at the level).\n * @returns A {@linkcode PluginProgramAdjacencyGetMatrixResult} with `ids`,\n * the square `matrix`, and (at department level) `departmentIds`.\n * @throws If an id is unknown or does not match the requested level.\n *\n * @examplePrompt Get the space adjacency matrix\n * @examplePrompt Which departments must be adjacent?\n * @examplePrompt Show the adjacency relationships between departments\n * @examplePrompt Read the adjacency matrix for these spaces\n * @examplePrompt Which rooms are restricted from being next to each other?\n *\n * # Example\n * ```ts\n * const { ids, matrix } = await snaptrude.program.adjacency.getMatrix(\"department\")\n * ```\n */\n public abstract getMatrix(\n level: PluginAdjacencyLevel,\n ids?: string[],\n ): PluginApiReturn<PluginProgramAdjacencyGetMatrixResult>\n\n /**\n * Compute (or refresh) the adjacency data for the project.\n *\n * Runs the AI adjacency precompute and persists the result; a no-op when\n * the stored data is already fresh. A real run takes **~2s at minimum**\n * (engine pacing) plus the AI service round-trip. This is a write (it\n * mutates and persists project analysis data), so it is blocked when plugin\n * writes are disabled.\n *\n * @returns A {@linkcode PluginProgramAdjacencyComputeResult} —\n * `{ success: true }` when the data is fresh or the run completed;\n * `{ success: false, error }` when the AI service could not be reached.\n *\n * @examplePrompt Refresh adjacency analysis\n * @examplePrompt Compute the adjacency matrix for this project\n * @examplePrompt Recalculate which departments should be adjacent\n *\n * # Example\n * ```ts\n * const { success } = await snaptrude.program.adjacency.compute()\n * if (success) {\n * const data = await snaptrude.program.adjacency.getMatrix(\"space\")\n * }\n * ```\n */\n public abstract compute(): PluginApiReturn<PluginProgramAdjacencyComputeResult>\n}\n\n/**\n * The level an adjacency matrix is read at.\n *\n * | Value | Description |\n * |---|---|\n * | `\"department\"` | One row/column per department mass |\n * | `\"space\"` | One row/column per space (within a storey) |\n */\nexport const PluginAdjacencyLevel = z.enum([\"department\", \"space\"])\nexport type PluginAdjacencyLevel = z.infer<typeof PluginAdjacencyLevel>\n\n/**\n * Arguments for {@linkcode PluginProgramAdjacencyApi.getMatrix}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `level` | {@linkcode PluginAdjacencyLevel} | Read the `\"department\"` or `\"space\"` matrix |\n * | `ids` | `string[] \\| undefined` | Component ids to read a sub-matrix for, in this order (defaults to every eligible element at the level) |\n */\nexport const PluginProgramAdjacencyGetMatrixArgs = z.object({\n level: PluginAdjacencyLevel,\n ids: z.array(z.string()).optional(),\n})\nexport type PluginProgramAdjacencyGetMatrixArgs = z.infer<\n typeof PluginProgramAdjacencyGetMatrixArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramAdjacencyApi.getMatrix}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `ids` | `string[]` | Component ids, in matrix row/column order |\n * | `departmentIds` | `(string \\| null)[] \\| undefined` | Department level only: parallel to `ids`, the department entity id each row belongs to (joinable against `program.departments.list`), `null` when unassigned |\n * | `matrix` | `number[][]` | Square matrix aligned to `ids`; values in `{-1, 0, 1, 2}` |\n */\nexport const PluginProgramAdjacencyGetMatrixResult = z.object({\n ids: z.array(z.string()),\n departmentIds: z.array(z.string().nullable()).optional(),\n matrix: z.array(z.array(z.number())),\n})\nexport type PluginProgramAdjacencyGetMatrixResult = z.infer<\n typeof PluginProgramAdjacencyGetMatrixResult\n>\n\n/**\n * Result of {@linkcode PluginProgramAdjacencyApi.compute}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `success` | `boolean` | `true` when the data is fresh or the run completed |\n * | `error` | `string \\| undefined` | Failure reason when `success` is `false` |\n */\nexport const PluginProgramAdjacencyComputeResult = z.object({\n success: z.boolean(),\n error: z.string().optional(),\n})\nexport type PluginProgramAdjacencyComputeResult = z.infer<\n typeof PluginProgramAdjacencyComputeResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * Program layout — arrange and pack spaces inside the envelope.\n *\n * These wrap the product's **Arrange in envelope** / **Pack in envelope**\n * tools (engine: `Planner.pack_arrange` in the react host) — stages two and\n * three of the space-planning workflow that starts with\n * {@linkcode PluginProgramAdjacencyApi} (`program.adjacency`).\n *\n * Both {@linkcode PluginProgramLayoutApi.arrange} and\n * {@linkcode PluginProgramLayoutApi.pack} run as an **asynchronous backend\n * job** on the space-solver service:\n *\n * 1. `arrange` / `pack` start the job and **return immediately** — they do\n * not wait for the layout.\n * 2. Poll {@linkcode PluginProgramLayoutApi.getState} until `status` is no\n * longer `\"running\"` — a run can take up to 20 minutes.\n * 3. {@linkcode PluginProgramLayoutApi.cancel} aborts an in-flight run.\n *\n * `arrange` produces several candidate layouts. By default it **auto-commits\n * the first solution** as one undoable edit; pass `autoCommit: false` to hold\n * the candidates for review instead — `getState` then reports\n * `status: \"pendingReview\"` with `totalSolutions`, and\n * {@linkcode PluginProgramLayoutApi.applySolution} commits the one you pick\n * (the headless counterpart of the product's \"Solution N of M → Apply\" bar).\n * `pack` re-shapes and applies its single result directly. Both are\n * **Pro-plan-gated**, matching the product UI.\n *\n * {@linkcode PluginProgramLayoutApi.stack} is the **cross-storey** action —\n * the product's *Pack in envelope* / auto-stack. It splits the building\n * envelope by storey and packs each storey's departments into its slice as one\n * undoable edit. Unlike `arrange` / `pack`, it is **not** the async-job model:\n * it resolves inline when the whole multi-storey pack is done (no `getState`\n * polling) and returns the storeys it had to skip. It is Pro-gated too.\n *\n * Accessed via `snaptrude.program.layout`.\n */\nexport abstract class PluginProgramLayoutApi {\n constructor() {}\n\n /**\n * Arrange the spaces inside the envelope using the computed adjacency data.\n *\n * Starts the backend job and **returns immediately** — poll\n * {@linkcode PluginProgramLayoutApi.getState} until `status` leaves\n * `\"running\"` (a run can take up to 20 minutes). The solver produces\n * several candidate layouts; by default this **auto-commits the first\n * solution** as one undoable edit. Pass `autoCommit: false` to review them\n * instead: the run finishes with `status: \"pendingReview\"` and\n * `totalSolutions` in `getState`, and\n * {@linkcode PluginProgramLayoutApi.applySolution} commits the candidate you\n * pick ({@linkcode PluginProgramLayoutApi.cancel} discards them). An\n * arrange **without an envelope** applies directly (there is nothing to\n * review) regardless of `autoCommit`. Starting a run while one is in flight\n * replaces it; while solutions are pending review, new runs are refused —\n * apply or cancel first.\n *\n * With no `options`, operates on the eligible Room/Department masses on the\n * active storey and auto-detects the single buildable envelope there.\n *\n * @param options - Optional {@linkcode PluginProgramLayoutRunArgs} — the\n * space / department masses to arrange and the envelope to fit them in.\n * Omitted fields fall back to the active-storey defaults.\n * @returns A {@linkcode PluginProgramLayoutRunResult} — `{ success: true }`\n * when the job was started. Solver failures surface through\n * {@linkcode PluginProgramLayoutApi.getState} (`status: \"inactive\"`), not\n * as a rejected call.\n * @throws When the project is not on a Pro plan.\n * @throws When a `spaceId` / `departmentId` / `envelopeId` does not resolve.\n * @throws When more than one envelope is on the active storey and none was\n * given (ambiguous).\n * @throws When arrange solutions are pending review (apply or cancel them\n * first).\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Arrange the rooms in the envelope\n * @examplePrompt Lay out the departments inside the building envelope\n * @examplePrompt Auto-arrange the program spaces\n * @examplePrompt Arrange the rooms but let me pick the solution\n *\n * # Example\n * ```ts\n * const { success, error } = await snaptrude.program.layout.arrange()\n * if (!success) throw new Error(error)\n * // poll until the layout is applied\n * let job = await snaptrude.program.layout.getState()\n * while (job?.status === \"running\") {\n * await new Promise((r) => setTimeout(r, 5000))\n * job = await snaptrude.program.layout.getState()\n * }\n * ```\n */\n public abstract arrange(\n options?: PluginProgramLayoutRunArgs,\n ): PluginApiReturn<PluginProgramLayoutRunResult>\n\n /**\n * Pack the spaces into the envelope (a tighter fit than arrange).\n *\n * Like {@linkcode PluginProgramLayoutApi.arrange}, this starts the backend\n * job and **returns immediately** — poll\n * {@linkcode PluginProgramLayoutApi.getState} until `status` leaves\n * `\"running\"` (up to 20 minutes). Pack re-shapes mass geometry to fit and\n * **applies its single result directly** (no solution review). Starting a\n * run while one is in flight replaces it.\n *\n * With no `options`, operates on the eligible Room/Department masses on the\n * active storey and auto-detects the single buildable envelope there.\n *\n * @param options - Optional {@linkcode PluginProgramLayoutRunArgs} — same\n * shape as `arrange`; omitted fields fall back to the active-storey\n * defaults.\n * @returns A {@linkcode PluginProgramLayoutRunResult} — `{ success: true }`\n * when the job was started. Solver failures surface through\n * {@linkcode PluginProgramLayoutApi.getState} (`status: \"inactive\"`).\n * @throws When the project is not on a Pro plan.\n * @throws When a `spaceId` / `departmentId` / `envelopeId` does not resolve.\n * @throws When more than one envelope is on the active storey and none was\n * given (ambiguous).\n * @throws When arrange solutions are pending review (apply or cancel them\n * first).\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Pack the rooms into the envelope\n * @examplePrompt Fit the program spaces tightly into the building\n * @examplePrompt Run pack-in-envelope on this storey\n *\n * # Example\n * ```ts\n * const { success } = await snaptrude.program.layout.pack({ envelopeId: \"be_...\" })\n * let job = await snaptrude.program.layout.getState()\n * while (job?.status === \"running\") {\n * await new Promise((r) => setTimeout(r, 5000))\n * job = await snaptrude.program.layout.getState()\n * }\n * ```\n */\n public abstract pack(\n options?: PluginProgramLayoutRunArgs,\n ): PluginApiReturn<PluginProgramLayoutRunResult>\n\n /**\n * Apply one of the pending arrange solutions.\n *\n * The headless counterpart of the product's **\"Solution N of M → Apply\"**\n * review bar: after an {@linkcode PluginProgramLayoutApi.arrange} run\n * started with `autoCommit: false` finishes with `status: \"pendingReview\"`,\n * this commits the candidate at `index` (0-based,\n * `0 ≤ index < totalSolutions` from\n * {@linkcode PluginProgramLayoutApi.getState}) as **one undoable edit** and\n * discards the other candidates. `getState` then reports\n * `status: \"active\"`.\n *\n * @param index - 0-based index of the pending solution to apply.\n * @returns A {@linkcode PluginProgramLayoutApplyResult} —\n * `{ success: true }` when the solution was applied.\n * @throws When no arrange solutions are pending review (no\n * `arrange({ autoCommit: false })` run has finished, or its solutions were\n * already applied / cancelled).\n * @throws When `index` is out of range for the pending solutions.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Apply the second arrange solution\n * @examplePrompt Pick layout solution 3 and commit it\n * @examplePrompt Apply the arrange solution I chose\n *\n * # Example\n * ```ts\n * await snaptrude.program.layout.arrange({ autoCommit: false })\n * let job = await snaptrude.program.layout.getState()\n * while (job?.status === \"running\") {\n * await new Promise((r) => setTimeout(r, 5000))\n * job = await snaptrude.program.layout.getState()\n * }\n * if (job?.status === \"pendingReview\") {\n * console.log(`${job.totalSolutions} candidate layouts`)\n * await snaptrude.program.layout.applySolution(1) // commit the second one\n * }\n * ```\n */\n public abstract applySolution(\n index: number,\n ): PluginApiReturn<PluginProgramLayoutApplyResult>\n\n /**\n * Stack the program across **every storey** of the building — the product's\n * *Pack in envelope* / auto-stack.\n *\n * Splits the building envelope massing into per-storey envelope slices, then\n * packs each storey's departments into its slice, applying the whole\n * multi-storey result as **one undoable edit**.\n *\n * Unlike {@linkcode PluginProgramLayoutApi.arrange} /\n * {@linkcode PluginProgramLayoutApi.pack}, this **is not** the async-job\n * model: `await` it and it resolves when the stack is done — there is no\n * `getState` polling. It operates on the whole active model (no id / envelope\n * arguments): it auto-discovers the departments and envelope on each storey.\n *\n * Storeys that have departments but **no envelope** are reported in\n * `skippedStoreys` (nothing was packed there). A storey's departments must\n * already be on that storey — `stack` does not move departments between\n * floors, it lays out each floor's program into that floor's envelope.\n *\n * @returns A {@linkcode PluginProgramLayoutStackResult} — `success: true`\n * with the (possibly empty) `skippedStoreys` when the stack was applied, or\n * `success: false` with an `error` when the run failed (e.g. the adjacency\n * service was unreachable).\n * @throws When the project is not on a Pro plan.\n * @throws When plugin writes are disabled.\n * @throws When an `arrange` / `pack` run, or another `stack`, is already in\n * flight, or arrange solutions are pending review (finish, apply, or\n * `cancel` first).\n *\n * @examplePrompt Stack the program across all floors\n * @examplePrompt Auto-stack the departments into the envelope on every storey\n * @examplePrompt Pack the whole building's program into its envelope\n *\n * # Example\n * ```ts\n * const { success, skippedStoreys, error } = await snaptrude.program.layout.stack()\n * if (!success) throw new Error(error)\n * if (skippedStoreys.length)\n * console.warn(`No envelope on storeys: ${skippedStoreys.join(\", \")}`)\n * ```\n */\n public abstract stack(): PluginApiReturn<PluginProgramLayoutStackResult>\n\n /**\n * Get the state of the layout run.\n *\n * The polling read for the async job started by\n * {@linkcode PluginProgramLayoutApi.arrange} /\n * {@linkcode PluginProgramLayoutApi.pack}. `status` values:\n *\n * | Status | Meaning |\n * |---|---|\n * | `\"running\"` | A run is in flight — keep polling |\n * | `\"active\"` | The run finished and its layout is applied to the model |\n * | `\"inactive\"` | A run finished without applying a layout (the solver failed or found no solution) |\n * | `\"pendingReview\"` | An `arrange({ autoCommit: false })` run finished and its candidates await {@linkcode PluginProgramLayoutApi.applySolution} / {@linkcode PluginProgramLayoutApi.cancel} |\n *\n * While `status` is `\"pendingReview\"` the state also carries\n * `totalSolutions` — the number of candidate layouts to pick from.\n *\n * @returns A {@linkcode PluginProgramLayoutStateResult} with the run\n * `status`, or `null` when no run result is available (never ran or was\n * cancelled).\n *\n * @examplePrompt Is the arrange done?\n * @examplePrompt Check the status of the layout run\n * @examplePrompt Did the pack finish?\n *\n * # Example\n * ```ts\n * const job = await snaptrude.program.layout.getState()\n * if (job?.status === \"active\") console.log(\"layout applied\")\n * ```\n */\n public abstract getState(): PluginApiReturn<PluginProgramLayoutStateResult>\n\n /**\n * Cancel the in-flight layout run.\n *\n * Aborts the backend job and discards any pending solution. Also discards\n * a finished run's **pending-review** solutions (after\n * `arrange({ autoCommit: false })`) without applying one. A no-op (returns\n * `false`) when nothing is running and nothing is pending.\n *\n * @returns `true` when a run was cancelled or pending solutions were\n * discarded, `false` when there was nothing to cancel.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Cancel the arrange\n * @examplePrompt Stop the running layout job\n * @examplePrompt Abort the pack in envelope\n *\n * # Example\n * ```ts\n * await snaptrude.program.layout.cancel()\n * ```\n */\n public abstract cancel(): PluginApiReturn<boolean>\n}\n\n/**\n * Arguments for {@linkcode PluginProgramLayoutApi.arrange} /\n * {@linkcode PluginProgramLayoutApi.pack}. All fields are optional — omitted\n * fields fall back to the eligible masses and the single envelope on the\n * active storey.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaceIds` | `string[] \\| undefined` | Component ids of the Room masses to lay out |\n * | `departmentIds` | `string[] \\| undefined` | Component ids of the Department masses to lay out |\n * | `envelopeId` | `string \\| undefined` | The envelope to fit into — a mass component id or a buildable-envelope handle (`be_…`); omit to auto-detect the single envelope on the active storey |\n * | `autoCommit` | `boolean \\| undefined` | `arrange` only — `true` / omitted (default) auto-commits the first candidate solution; `false` holds the candidates for review via {@linkcode PluginProgramLayoutApi.applySolution}. Ignored by `pack` (it applies its single result directly) |\n */\nexport const PluginProgramLayoutRunArgs = z.object({\n spaceIds: z.array(z.string()).optional(),\n departmentIds: z.array(z.string()).optional(),\n envelopeId: z.string().optional(),\n autoCommit: z.boolean().optional(),\n})\nexport type PluginProgramLayoutRunArgs = z.infer<\n typeof PluginProgramLayoutRunArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramLayoutApi.arrange} /\n * {@linkcode PluginProgramLayoutApi.pack} — reports whether the run was\n * **started**, not whether the layout finished (poll\n * {@linkcode PluginProgramLayoutApi.getState} for completion).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `success` | `boolean` | Whether the layout run was started |\n * | `error` | `string \\| undefined` | Failure reason when `success` is `false` |\n */\nexport const PluginProgramLayoutRunResult = z.object({\n success: z.boolean(),\n error: z.string().optional(),\n})\nexport type PluginProgramLayoutRunResult = z.infer<\n typeof PluginProgramLayoutRunResult\n>\n\n/**\n * The status of a layout run.\n *\n * | Value | Meaning |\n * |---|---|\n * | `\"running\"` | A run is in flight — keep polling |\n * | `\"active\"` | The run finished and its layout is applied to the model |\n * | `\"inactive\"` | A run finished without applying a layout (the solver failed or found no solution) |\n * | `\"pendingReview\"` | An `arrange({ autoCommit: false })` run finished and its candidate solutions await {@linkcode PluginProgramLayoutApi.applySolution} / {@linkcode PluginProgramLayoutApi.cancel} |\n */\nexport const PluginProgramLayoutJobStatus = z.enum([\n \"running\",\n \"active\",\n \"inactive\",\n \"pendingReview\",\n])\nexport type PluginProgramLayoutJobStatus = z.infer<\n typeof PluginProgramLayoutJobStatus\n>\n\n/**\n * The state of a layout run.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `status` | {@linkcode PluginProgramLayoutJobStatus} | `\"running\"` \\| `\"active\"` \\| `\"inactive\"` \\| `\"pendingReview\"` |\n * | `totalSolutions` | `number \\| undefined` | Number of candidate solutions awaiting review — present only while `status` is `\"pendingReview\"` |\n */\nexport const PluginProgramLayoutJobState = z.object({\n status: PluginProgramLayoutJobStatus,\n totalSolutions: z.number().int().optional(),\n})\nexport type PluginProgramLayoutJobState = z.infer<\n typeof PluginProgramLayoutJobState\n>\n\n/**\n * Result of {@linkcode PluginProgramLayoutApi.getState} — the run state, or\n * `null` when no run result is available (never ran or was cancelled).\n */\nexport const PluginProgramLayoutStateResult =\n PluginProgramLayoutJobState.nullable()\nexport type PluginProgramLayoutStateResult = z.infer<\n typeof PluginProgramLayoutStateResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramLayoutApi.applySolution}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `index` | `number` | 0-based index of the pending solution to apply (`0 ≤ index < totalSolutions`) |\n */\nexport const PluginProgramLayoutApplySolutionArgs = z.object({\n index: z.number().int().min(0),\n})\nexport type PluginProgramLayoutApplySolutionArgs = z.infer<\n typeof PluginProgramLayoutApplySolutionArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramLayoutApi.applySolution}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `success` | `boolean` | Whether the chosen solution was applied |\n * | `error` | `string \\| undefined` | Failure reason when `success` is `false` |\n */\nexport const PluginProgramLayoutApplyResult = z.object({\n success: z.boolean(),\n error: z.string().optional(),\n})\nexport type PluginProgramLayoutApplyResult = z.infer<\n typeof PluginProgramLayoutApplyResult\n>\n\n/**\n * Result of {@linkcode PluginProgramLayoutApi.stack} — reports whether the\n * cross-storey stack was applied and which storeys were skipped.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `success` | `boolean` | Whether the stack was applied |\n * | `skippedStoreys` | `number[]` | Storey values that had departments but no envelope, so nothing was packed there |\n * | `error` | `string \\| undefined` | Failure reason when `success` is `false` |\n */\nexport const PluginProgramLayoutStackResult = z.object({\n success: z.boolean(),\n skippedStoreys: z.array(z.number()),\n error: z.string().optional(),\n})\nexport type PluginProgramLayoutStackResult = z.infer<\n typeof PluginProgramLayoutStackResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport { PluginAreaUnit } from \"./metrics\"\n\n/**\n * Program spreadsheet — read a building's program/takeoff data and render, build,\n * and export report sheets from it. The surface spans two levels:\n *\n * - **High-level renders** — {@linkcode PluginProgramSpreadsheetApi.render} (rows +\n * columns → a formatted, titled table) and its dataset specializations\n * {@linkcode PluginProgramSpreadsheetApi.renderTakeoff} /\n * {@linkcode PluginProgramSpreadsheetApi.renderProgram} (the host pulls the\n * dataset). Authors think \"render my takeoff to a sheet\".\n * - **Low-level construction** — for authors who need fine control:\n * {@linkcode PluginProgramSpreadsheetApi.createSheet},\n * {@linkcode PluginProgramSpreadsheetApi.setValues},\n * {@linkcode PluginProgramSpreadsheetApi.setFormulas},\n * {@linkcode PluginProgramSpreadsheetApi.setStyles},\n * {@linkcode PluginProgramSpreadsheetApi.mergeCells},\n * {@linkcode PluginProgramSpreadsheetApi.addConditionalFormatting},\n * {@linkcode PluginProgramSpreadsheetApi.recalculate}, and\n * {@linkcode PluginProgramSpreadsheetApi.transaction} (atomic, undoable batch).\n *\n * Plus dataset reads ({@linkcode PluginProgramSpreadsheetApi.getProgram} /\n * {@linkcode PluginProgramSpreadsheetApi.getTakeoff} — the data the render* methods\n * draw — and {@linkcode PluginProgramSpreadsheetApi.getMaterialTakeoff}, surface area\n * per material), sheet reads ({@linkcode PluginProgramSpreadsheetApi.listSheets} /\n * {@linkcode PluginProgramSpreadsheetApi.getSheetData}), charts + emphasis\n * ({@linkcode PluginProgramSpreadsheetApi.addChart} /\n * {@linkcode PluginProgramSpreadsheetApi.highlightCells}), workbook file export\n * ({@linkcode PluginProgramSpreadsheetApi.export}), reusable\n * {@linkcode PluginProgramSpreadsheetApi.templates}, live\n * {@linkcode PluginProgramSpreadsheetApi.bindings}, a sandboxed script escape hatch\n * ({@linkcode PluginProgramSpreadsheetApi.runScript}), an audit log\n * ({@linkcode PluginProgramSpreadsheetApi.getAuditLog}), and a liveness\n * {@linkcode PluginProgramSpreadsheetApi.ping}. `addTable` is intentionally not\n * exposed — `render` composes header + rows + table in one call.\n *\n * **Verb note (naming authority; rows pending merge into constitution §4 by its owner).**\n * Existing §4 verbs used as-is: `get`, `list`, `set` (bulk cell/format writes;\n * read back via {@linkcode PluginProgramSpreadsheetApi.getSheetData}), `create`,\n * `add` (charts, conditional formats), `apply` (templates), `export`, and\n * `transaction` (the sanctioned atomic-batch verb), plus the ratified `render` and\n * `highlight` program domain verbs ◆. New program-domain verbs ratified here ◆:\n * `recalculate` (recompute the workbook's formula results — distinct from\n * `refresh`), `merge` ({@linkcode PluginProgramSpreadsheetApi.mergeCells} spans a\n * cell range — a spreadsheet-domain verb, explicitly NOT the CSG-solid `union`\n * fold), `refresh` ({@linkcode PluginProgramSpreadsheetBindingsApi.refresh}\n * re-pulls a binding's live source — distinct from `recalculate`), `run`\n * ({@linkcode PluginProgramSpreadsheetApi.runScript} executes a sandboxed script),\n * and `ping` (a meta liveness probe). Renames for compliance: the host's `save` →\n * `templates.create`; `getRecentEntries` → `getAuditLog`.\n *\n * Accessed via `snaptrude.program.spreadsheet`.\n */\nexport abstract class PluginProgramSpreadsheetApi {\n constructor() {}\n\n /**\n * Reusable sheet templates — capture a sheet's layout as a named template and\n * re-apply it to another sheet. See {@linkcode PluginProgramSpreadsheetTemplatesApi}.\n */\n public abstract templates: PluginProgramSpreadsheetTemplatesApi\n\n /**\n * Live data bindings — bind a program dataset to a sheet range and refresh it\n * on demand. See {@linkcode PluginProgramSpreadsheetBindingsApi}.\n */\n public abstract bindings: PluginProgramSpreadsheetBindingsApi\n\n /**\n * Plugin datasets — named, plugin-supplied row sets that bindings can render\n * and auto-refresh on rerun. See {@linkcode PluginProgramSpreadsheetDatasetsApi}.\n */\n public abstract datasets: PluginProgramSpreadsheetDatasetsApi\n\n /**\n * Render a titled, formatted table onto a named sheet (create-or-replace).\n *\n * The intent-level replacement for the raw cell-write sequence: it writes the\n * header + rows, applies per-column format/alignment, optionally wraps the\n * block in a table, freezes the header, and appends a totals row. This is the\n * generic table render; the `render*` family is reserved to also carry\n * dataset-specific renders (`renderTakeoff`, `renderProgram`) once their\n * argument shapes are ratified.\n *\n * @param sheetName - Target sheet name.\n * @param columns - Column definitions, at least one (see\n * {@linkcode PluginSpreadsheetColumn}).\n * @param rows - Row objects keyed by column `key`.\n * @param options - Optional `title`, `startCell` (default `\"A1\"`), `whenExists`\n * (default `\"replace\"`), `asTable` (default `true`), `freezeHeader` (default\n * `true`), and `totals` row.\n * @returns A {@linkcode PluginProgramSpreadsheetRenderResult} with the written\n * ranges and counts.\n * @throws If `columns` is empty, the target sheet exists and `whenExists` is\n * `\"error\"`, or a cell address is invalid.\n *\n * @examplePrompt Render my takeoff as a table on a sheet\n * @examplePrompt Write the area schedule to a spreadsheet with totals\n * @examplePrompt Put the department breakdown on a sheet called \"Program\"\n * @examplePrompt Output this data as a formatted table with a header\n * @examplePrompt Create a report sheet from these rows and columns\n *\n * # Example\n * ```ts\n * await snaptrude.program.spreadsheet.render(\n * \"Program\",\n * [\n * { key: \"name\", header: \"Department\" },\n * { key: \"area\", header: \"Area\", format: \"0.00\", align: \"right\" },\n * ],\n * [{ name: \"FOH\", area: 120.5 }],\n * { totals: [{ column: \"area\", fn: \"sum\", label: \"Total\" }] },\n * )\n * ```\n */\n public abstract render(\n sheetName: string,\n columns: PluginSpreadsheetColumn[],\n rows: Record<string, PluginSpreadsheetCell>[],\n options?: {\n title?: string\n startCell?: string\n whenExists?: \"replace\" | \"error\"\n asTable?: boolean\n freezeHeader?: boolean\n totals?: PluginSpreadsheetTotal[]\n },\n ): PluginApiReturn<PluginProgramSpreadsheetRenderResult>\n\n /**\n * Render the quantity/material takeoff onto a sheet.\n *\n * A specialization of the render* family: the **host pulls and projects the\n * current takeoff dataset itself** — the plugin supplies only the target\n * `sheetName`, an optional `title`, and an optional `filter`; there is **no\n * data argument**. The `filter` is applied host-side to the projected rows\n * before rendering. Returns the same rendered-artifact record as\n * {@linkcode PluginProgramSpreadsheetApi.render}.\n * _(Authored ahead — optional via the `spreadsheet?` mount until the host\n * round-trip is live.)_\n *\n * @param options - Optional `sheetName`, `title`, and `filter` — a\n * case-insensitive substring kept when any of a row's cell values contains\n * it (a {@linkcode PluginProgramSpreadsheetRenderDatasetArgs}).\n * @returns A {@linkcode PluginProgramSpreadsheetRenderResult} with the written\n * ranges and counts.\n * @throws If the takeoff cannot be rendered.\n *\n * @examplePrompt Render the takeoff onto a sheet\n * @examplePrompt Put the quantity takeoff on a spreadsheet\n * @examplePrompt Generate the takeoff schedule sheet titled Takeoff\n * @examplePrompt Render the material takeoff filtered to the walls\n *\n * # Example\n * ```ts\n * await snaptrude.program.spreadsheet.renderTakeoff({ sheetName: \"Takeoff\" })\n * ```\n */\n public abstract renderTakeoff(options?: {\n sheetName?: string\n title?: string\n filter?: string\n }): PluginApiReturn<PluginProgramSpreadsheetRenderResult>\n\n /**\n * Render the area program onto a sheet.\n *\n * A specialization of the render* family: the **host pulls and projects the\n * current program dataset itself** (departments with area targets vs allocated)\n * — the plugin supplies only the target `sheetName`, an optional `title`, and an\n * optional `filter`; there is **no data argument**. The `filter` is applied\n * host-side to the projected rows before rendering. Returns the same\n * rendered-artifact record as {@linkcode PluginProgramSpreadsheetApi.render}.\n * _(Authored ahead — optional via the `spreadsheet?` mount until the host\n * round-trip is live.)_\n *\n * @param options - Optional `sheetName`, `title`, and `filter` — a\n * case-insensitive substring kept when any of a row's cell values contains\n * it (a {@linkcode PluginProgramSpreadsheetRenderDatasetArgs}).\n * @returns A {@linkcode PluginProgramSpreadsheetRenderResult} with the written\n * ranges and counts.\n * @throws If the program cannot be rendered.\n *\n * @examplePrompt Render the area program onto a sheet\n * @examplePrompt Put the department program on a spreadsheet\n * @examplePrompt Generate the program schedule sheet titled Program\n * @examplePrompt Render the program filtered to the residential departments\n *\n * # Example\n * ```ts\n * await snaptrude.program.spreadsheet.renderProgram({ sheetName: \"Program\" })\n * ```\n */\n public abstract renderProgram(options?: {\n sheetName?: string\n title?: string\n filter?: string\n }): PluginApiReturn<PluginProgramSpreadsheetRenderResult>\n\n /**\n * Export the report workbook to a file (xlsx, pdf, or csv).\n *\n * Returns the file content as base64 so the plugin can download, upload, or\n * attach it. This is the workbook **file** export; to read cell **values** back\n * as data, use {@linkcode PluginProgramSpreadsheetApi.getSheetData} instead.\n *\n * `xlsx` and `pdf` export the **whole** workbook. `csv` is inherently\n * single-sheet: it serializes one sheet's used range — pass `options.sheetName`\n * to choose which sheet, or omit it to serialize the active sheet.\n *\n * For a different export intent, see `core.io.export.model` (the 3D model\n * file) and `presentation.export` (Present-mode layout sheets to PDF/PNG/JPG).\n *\n * @param format - The file format to export to.\n * @param options - Optional `fileName` (without extension; a default is used\n * when omitted) and `sheetName` — the sheet to serialize for `csv`\n * (ignored by `xlsx`/`pdf`, which always export the whole workbook).\n * @returns A {@linkcode PluginProgramSpreadsheetExportResult} with the file's\n * `fileName`, `mimeType`, and base64 `dataBase64`.\n * @throws If the workbook cannot be serialized to the requested format.\n *\n * @examplePrompt Export the report as an Excel file\n * @examplePrompt Download the whole workbook as a PDF\n * @examplePrompt Export the program workbook to xlsx\n * @examplePrompt Save the report as a csv file\n * @examplePrompt Export the Takeoff sheet as CSV\n *\n * # Example\n * ```ts\n * const file = await snaptrude.program.spreadsheet.export(\"xlsx\")\n * // file.fileName, file.mimeType, file.dataBase64\n *\n * // CSV of a specific sheet:\n * const csv = await snaptrude.program.spreadsheet.export(\"csv\", { sheetName: \"Takeoff\" })\n * ```\n */\n public abstract export(\n format: PluginSpreadsheetExportFormat,\n options?: { fileName?: string; sheetName?: string },\n ): PluginApiReturn<PluginProgramSpreadsheetExportResult>\n\n /**\n * List the sheets in the report workbook.\n *\n * @returns A {@linkcode PluginProgramSpreadsheetListSheetsResult} with each sheet's\n * name/index/visibility/size, plus the active sheet name.\n *\n * @examplePrompt List the sheets in the spreadsheet\n * @examplePrompt What sheets are in the report workbook?\n * @examplePrompt Which sheet is currently active?\n * @examplePrompt How many sheets does the report have?\n *\n * # Example\n * ```ts\n * const { sheets, activeSheet } = await snaptrude.program.spreadsheet.listSheets()\n * ```\n */\n public abstract listSheets(): PluginApiReturn<PluginProgramSpreadsheetListSheetsResult>\n\n /**\n * Read a sheet's data back as a matrix (and optionally as header-keyed records).\n *\n * @param sheetName - Sheet to read.\n * @param options - Optional `range` (A1; omit to read the sheet's used range)\n * and `header` (`true` to also get records keyed by the header row).\n * @returns A {@linkcode PluginProgramSpreadsheetGetSheetDataResult} with the read\n * `range`, `values` matrix, and optional `records`.\n * @throws If the sheet does not exist or the range is invalid.\n *\n * @examplePrompt Read the data back from the Program sheet\n * @examplePrompt Get the values in range A1:D20 of a sheet\n * @examplePrompt Read a sheet as rows of records keyed by header\n * @examplePrompt What's in the takeoff sheet right now?\n *\n * # Example\n * ```ts\n * const { values, records } = await snaptrude.program.spreadsheet.getSheetData(\n * \"Program\",\n * { header: true },\n * )\n * ```\n */\n public abstract getSheetData(\n sheetName: string,\n options?: { range?: string; header?: boolean },\n ): PluginApiReturn<PluginProgramSpreadsheetGetSheetDataResult>\n\n /**\n * Add a chart to a sheet, driven by a cell range.\n *\n * @param sheetName - Sheet to add the chart to.\n * @param chartType - Chart type (see {@linkcode PluginSpreadsheetChartType}).\n * @param dataRange - A1 range driving the chart.\n * @param options - Optional `title`, `position` (sheet coords), and `size` (px).\n * @returns A {@linkcode PluginProgramSpreadsheetAddChartResult} with the new\n * chart's id.\n * @throws If the sheet or data range is invalid.\n *\n * @examplePrompt Add a bar chart of the department areas\n * @examplePrompt Insert a pie chart from range B2:C10\n * @examplePrompt Put a column chart on the Program sheet\n * @examplePrompt Chart the area breakdown by department\n *\n * # Example\n * ```ts\n * const { chartId } = await snaptrude.program.spreadsheet.addChart(\n * \"Program\",\n * \"column\",\n * \"A1:B10\",\n * { title: \"Area by department\" },\n * )\n * ```\n */\n public abstract addChart(\n sheetName: string,\n chartType: PluginSpreadsheetChartType,\n dataRange: string,\n options?: {\n title?: string\n position?: { x: number; y: number }\n size?: { width: number; height: number }\n },\n ): PluginApiReturn<PluginProgramSpreadsheetAddChartResult>\n\n /**\n * Place a floating image on a workbook sheet from a base64 data URI.\n *\n * The image is anchored at a cell's top-left corner (default `\"A1\"`) and\n * floats over the grid (a SpreadJS floating picture — it does not occupy\n * cells). Only base64 `data:image/png|jpeg|svg+xml` URIs are accepted —\n * never URLs — and the URI is capped at 2 MB. Passing a `name` gives the\n * picture a stable id: re-running with the same `name` replaces the previous\n * picture (rerun-friendly); when omitted a name is generated.\n *\n * For images on Present-mode sheets (the tldraw canvas), use\n * `presentation.shapes` instead — this verb places floating pictures in the\n * SpreadJS workbook.\n *\n * @param sheetName - Sheet to place the image on.\n * @param image - The image payload: a base64 `dataUri`\n * (`data:image/png|jpeg|svg+xml;base64,…`, max 2 MB).\n * @param options - Optional `anchorCell` (A1 notation, default `\"A1\"`),\n * `size` in px (intrinsic size when omitted), and stable `name`\n * (replace-on-rerun).\n * @returns A {@linkcode PluginProgramSpreadsheetAddImageResult} with the sheet\n * and the picture's name.\n * @throws If the sheet does not exist, or the data URI is not an accepted\n * image type or exceeds 2 MB.\n *\n * @examplePrompt Add my logo image to the report sheet\n * @examplePrompt Place a PNG at cell B2 on the Takeoff sheet\n * @examplePrompt Put an SVG diagram on a spreadsheet sheet\n * @examplePrompt Insert an image into the workbook\n *\n * # Example\n * ```ts\n * const { name } = await snaptrude.program.spreadsheet.addImage(\n * \"Report\",\n * { dataUri: \"data:image/png;base64,iVBORw0...\" },\n * { anchorCell: \"B2\", size: { width: 240, height: 120 }, name: \"logo\" },\n * )\n * ```\n */\n public abstract addImage(\n sheetName: string,\n image: { dataUri: string },\n options?: {\n anchorCell?: string\n size?: { width: number; height: number }\n name?: string\n },\n ): PluginApiReturn<PluginProgramSpreadsheetAddImageResult>\n\n /**\n * Highlight a range of cells (background color and optional emphasis).\n *\n * @param sheetName - Sheet containing the range.\n * @param range - A1 range to highlight.\n * @param options - Optional `color` (CSS hex; a default emphasis is used when\n * omitted) and `bold`.\n * @returns A {@linkcode PluginProgramSpreadsheetHighlightCellsResult} echoing the\n * highlighted sheet and range.\n * @throws If the sheet or range is invalid.\n *\n * @examplePrompt Highlight the cells that are over budget\n * @examplePrompt Color range A2:A10 red\n * @examplePrompt Highlight B2:D2 in yellow\n * @examplePrompt Emphasize the totals row on the Program sheet\n *\n * # Example\n * ```ts\n * await snaptrude.program.spreadsheet.highlightCells(\"Program\", \"A2:A10\", {\n * color: \"#ffe08a\",\n * })\n * ```\n */\n public abstract highlightCells(\n sheetName: string,\n range: string,\n options?: { color?: string; bold?: boolean },\n ): PluginApiReturn<PluginProgramSpreadsheetHighlightCellsResult>\n\n // --- Program / takeoff dataset reads (the data the render* methods draw) ---\n\n /**\n * Read the area-program dataset (departments with target vs allocated area).\n *\n * Returns the same figures {@linkcode PluginProgramSpreadsheetApi.renderProgram}\n * projects onto a sheet, as render-ready rows — so a plugin can inspect or\n * transform the program before rendering it (or render it itself via\n * {@linkcode PluginProgramSpreadsheetApi.render}). For the canonical\n * area-program summary read, see {@linkcode PluginProgramMetricsApi.get}.\n *\n * @returns A {@linkcode PluginProgramSpreadsheetGetProgramResult} with per-department\n * rows (`targetArea`/`allocatedArea`/`variance`), totals, and the report `units`.\n *\n * @examplePrompt Get the area program data\n * @examplePrompt Read the department program before I render it\n * @examplePrompt Give me the program rows with target vs allocated area\n * @examplePrompt What's the program dataset the report is built from?\n *\n * # Example\n * ```ts\n * const program = await snaptrude.program.spreadsheet.getProgram()\n * for (const row of program.rows) console.log(row.name, row.allocatedArea, row.targetArea)\n * ```\n */\n public abstract getProgram(): PluginApiReturn<PluginProgramSpreadsheetGetProgramResult>\n\n /**\n * Read the quantity/material takeoff dataset (per-element quantities).\n *\n * Returns the same rows {@linkcode PluginProgramSpreadsheetApi.renderTakeoff}\n * projects onto a sheet — one record per counted element (category, item,\n * level, quantity, unit) — so a plugin can inspect or transform the takeoff\n * before rendering it (or render it itself via\n * {@linkcode PluginProgramSpreadsheetApi.render}).\n *\n * @returns A {@linkcode PluginProgramSpreadsheetGetTakeoffResult} with the takeoff\n * `rows`, a `rowCount`, and the `generatedAt` timestamp.\n *\n * @examplePrompt Get the takeoff data\n * @examplePrompt Read the quantity takeoff rows\n * @examplePrompt Give me the per-element quantities before I render them\n * @examplePrompt What's in the material takeoff right now?\n *\n * # Example\n * ```ts\n * const takeoff = await snaptrude.program.spreadsheet.getTakeoff()\n * console.log(takeoff.rowCount, \"elements\")\n * ```\n */\n public abstract getTakeoff(): PluginApiReturn<PluginProgramSpreadsheetGetTakeoffResult>\n\n /**\n * Read the material take-off dataset (surface area per material, per element type).\n *\n * The native \"Material Take-Off\" quantities — the same figures the BIM Take-Off's\n * *Material Take-Off* sheet lists — as render-ready rows: for each material applied\n * to a wall/floor/ceiling/slab/roof family, its rolled-up **surface area** in the\n * project's display units. This is the material-centric complement to\n * {@linkcode PluginProgramSpreadsheetApi.getTakeoff} (which rolls up by element\n * family). A read only — nothing is written and no file is downloaded.\n *\n * When no materials are applied (or the model is empty) the result is simply an\n * empty `rows` array — not an error.\n *\n * @returns A {@linkcode PluginProgramSpreadsheetGetMaterialTakeoffResult} with the\n * material take-off `rows`, a `rowCount`, and the `generatedAt` timestamp.\n *\n * @examplePrompt Get the material take-off\n * @examplePrompt How much of each material is used?\n * @examplePrompt Read the surface area by material\n * @examplePrompt Give me the material quantities before I render them\n * @examplePrompt What's in the material take-off right now?\n *\n * # Example\n * ```ts\n * const mto = await snaptrude.program.spreadsheet.getMaterialTakeoff()\n * for (const row of mto.rows) {\n * console.log(row.objectType, row.family, row.material, row.quantity, row.unit)\n * }\n * ```\n */\n public abstract getMaterialTakeoff(): PluginApiReturn<PluginProgramSpreadsheetGetMaterialTakeoffResult>\n\n // --- Sheet construction (lower-level cell writes; render is the high-level path) ---\n\n /**\n * Create (or reuse/replace) a named sheet in the report workbook.\n *\n * The blank-canvas complement to {@linkcode PluginProgramSpreadsheetApi.render}:\n * use it when you want an empty sheet to write into with\n * {@linkcode PluginProgramSpreadsheetApi.setValues} /\n * {@linkcode PluginProgramSpreadsheetApi.setFormulas}. By default an existing\n * sheet of the same name is reused; pass `whenExists` to replace or error.\n *\n * @param name - The sheet name to create.\n * @param whenExists - Behaviour if the name exists: `\"reuse\"` (default),\n * `\"replace\"`, or `\"error\"`.\n * @returns A {@linkcode PluginProgramSpreadsheetCreateSheetResult} with the resolved\n * `sheetName`, whether it `replaced` an existing sheet, and its `sheetIndex`.\n * @throws If the sheet exists and `whenExists` is `\"error\"`.\n *\n * @examplePrompt Create a sheet called Summary\n * @examplePrompt Add a new blank sheet named Takeoff\n * @examplePrompt Make a sheet, replacing it if it already exists\n * @examplePrompt Start a fresh sheet to write into\n *\n * # Example\n * ```ts\n * await snaptrude.program.spreadsheet.createSheet(\"Summary\")\n * ```\n */\n public abstract createSheet(\n name: string,\n whenExists?: PluginSpreadsheetWhenExists,\n ): PluginApiReturn<PluginProgramSpreadsheetCreateSheetResult>\n\n /**\n * Write a block of raw values into a sheet, anchored at a cell.\n *\n * The low-level cell-write path (the high-level path is\n * {@linkcode PluginProgramSpreadsheetApi.render}, which adds a header, format,\n * and totals). Values are written row-major from `startCell` (default `\"A1\"`);\n * columns are auto-sized to fit.\n *\n * @param sheetName - Sheet to write into.\n * @param values - Row-major matrix of values to write.\n * @param startCell - Top-left anchor in A1 notation (default `\"A1\"`).\n * @returns A {@linkcode PluginProgramSpreadsheetSetValuesResult} with the written\n * `startCell`, `rowCount`, and `colCount`.\n * @throws If the sheet does not exist or `startCell` is invalid.\n *\n * @examplePrompt Write these rows to the sheet starting at A1\n * @examplePrompt Put this data into the Summary sheet\n * @examplePrompt Set the values in the takeoff sheet from B2\n * @examplePrompt Drop this matrix of numbers onto a sheet\n *\n * # Example\n * ```ts\n * await snaptrude.program.spreadsheet.setValues(\n * \"Summary\",\n * [[\"Department\", \"Area\"], [\"FOH\", 120.5]],\n * \"A1\",\n * )\n * ```\n */\n public abstract setValues(\n sheetName: string,\n values: PluginSpreadsheetCell[][],\n startCell?: string,\n ): PluginApiReturn<PluginProgramSpreadsheetSetValuesResult>\n\n /**\n * Set formulas on individual cells of a sheet.\n *\n * Each entry targets one cell (A1 notation) with a formula expression (a\n * leading `=` is optional). Pairs with\n * {@linkcode PluginProgramSpreadsheetApi.recalculate} when you need to force a\n * recompute.\n *\n * @param sheetName - Sheet to set formulas on.\n * @param formulas - The cell → formula assignments (see\n * {@linkcode PluginSpreadsheetFormula}).\n * @returns A {@linkcode PluginProgramSpreadsheetSetFormulasResult} with the `count`\n * of formulas set.\n * @throws If the sheet does not exist or a cell address is invalid.\n *\n * @examplePrompt Put a SUM formula in cell B10\n * @examplePrompt Set formulas totalling each column\n * @examplePrompt Add a formula to compute the variance column\n * @examplePrompt Write =SUM(B2:B9) into B10 on the Summary sheet\n *\n * # Example\n * ```ts\n * await snaptrude.program.spreadsheet.setFormulas(\"Summary\", [\n * { cell: \"B10\", formula: \"=SUM(B2:B9)\" },\n * ])\n * ```\n */\n public abstract setFormulas(\n sheetName: string,\n formulas: PluginSpreadsheetFormula[],\n ): PluginApiReturn<PluginProgramSpreadsheetSetFormulasResult>\n\n // --- Formatting ---\n\n /**\n * Apply cell styles to one or more ranges of a sheet.\n *\n * Sets fill/text color, font, alignment, and number format on each given range.\n * For a quick single-range emphasis,\n * {@linkcode PluginProgramSpreadsheetApi.highlightCells} is the shortcut.\n *\n * @param sheetName - Sheet containing the ranges.\n * @param ranges - Range → style assignments (each a range + a\n * {@linkcode PluginSpreadsheetCellStyle}).\n * @returns A {@linkcode PluginProgramSpreadsheetSetStylesResult} with the `count` of\n * ranges styled.\n * @throws If the sheet does not exist or a range is invalid.\n *\n * @examplePrompt Make the header row bold with a grey fill\n * @examplePrompt Right-align the numbers in column B\n * @examplePrompt Format A1:D1 with a background color and bold text\n * @examplePrompt Style the totals row\n *\n * # Example\n * ```ts\n * await snaptrude.program.spreadsheet.setStyles(\"Summary\", [\n * { range: \"A1:D1\", style: { fontWeight: \"bold\", backColor: \"#eef\" } },\n * ])\n * ```\n */\n public abstract setStyles(\n sheetName: string,\n ranges: PluginSpreadsheetRangeStyle[],\n ): PluginApiReturn<PluginProgramSpreadsheetSetStylesResult>\n\n /**\n * Merge a contiguous range of cells into a single spanned cell.\n *\n * Optionally sets the merged cell's value. (Verb ruling: `merge` is a\n * spreadsheet-domain verb here — spanning cells — not the CSG-solid `union`.)\n *\n * @param sheetName - Sheet containing the range.\n * @param range - A1 range to merge into one spanned cell.\n * @param value - Optional value to set in the merged (top-left) cell.\n * @returns A {@linkcode PluginProgramSpreadsheetMergeCellsResult} echoing the merged\n * sheet and range.\n * @throws If the sheet does not exist or the range is invalid.\n *\n * @examplePrompt Merge cells A1 to D1 for the title\n * @examplePrompt Span the header across the first four columns\n * @examplePrompt Merge A1:D1 and set it to \"Program Summary\"\n *\n * # Example\n * ```ts\n * await snaptrude.program.spreadsheet.mergeCells(\"Summary\", \"A1:D1\", \"Program Summary\")\n * ```\n */\n public abstract mergeCells(\n sheetName: string,\n range: string,\n value?: PluginSpreadsheetCell,\n ): PluginApiReturn<PluginProgramSpreadsheetMergeCellsResult>\n\n /**\n * Add a conditional-formatting rule to a range.\n *\n * Currently supports data-bar rules (a proportional in-cell bar); more rule\n * types are reserved. For static (non-conditional) emphasis use\n * {@linkcode PluginProgramSpreadsheetApi.highlightCells} or\n * {@linkcode PluginProgramSpreadsheetApi.setStyles}.\n *\n * @param sheetName - Sheet containing the range.\n * @param range - A1 range to format.\n * @param rule - The conditional-format rule (see\n * {@linkcode PluginSpreadsheetConditionalRule}).\n * @returns A {@linkcode PluginProgramSpreadsheetAddConditionalFormattingResult} with\n * whether a rule was `created`.\n * @throws If the sheet does not exist or the range is invalid.\n *\n * @examplePrompt Add data bars to the area column\n * @examplePrompt Show a proportional bar inside cells B2:B10\n * @examplePrompt Add conditional formatting data bars to the quantities\n *\n * # Example\n * ```ts\n * await snaptrude.program.spreadsheet.addConditionalFormatting(\"Summary\", \"B2:B10\", {\n * type: \"dataBar\",\n * color: \"#4f9cf9\",\n * })\n * ```\n */\n public abstract addConditionalFormatting(\n sheetName: string,\n range: string,\n rule: PluginSpreadsheetConditionalRule,\n ): PluginApiReturn<PluginProgramSpreadsheetAddConditionalFormattingResult>\n\n // --- Recalculation & atomic batching ---\n\n /**\n * Recompute the workbook's formula results.\n *\n * Recalculates every formula (or, when `sheetName` is given, focuses that sheet\n * before recalculating the workbook). (Verb ruling: `recalculate` recomputes\n * formulas — distinct from {@linkcode PluginProgramSpreadsheetBindingsApi.refresh},\n * which re-pulls a binding's source data.)\n *\n * @param options - Optional `sheetName` to focus before recalculating; omit for\n * the whole workbook.\n * @returns A {@linkcode PluginProgramSpreadsheetRecalculateResult} with the recompute\n * `scope` and the focused `sheetName` (or `null`).\n *\n * @examplePrompt Recalculate the workbook\n * @examplePrompt Recompute all the formulas\n * @examplePrompt Recalculate the Summary sheet\n *\n * # Example\n * ```ts\n * await snaptrude.program.spreadsheet.recalculate()\n * ```\n */\n public abstract recalculate(options?: {\n sheetName?: string\n }): PluginApiReturn<PluginProgramSpreadsheetRecalculateResult>\n\n /**\n * Run a sequence of sheet operations as one atomic, undoable transaction.\n *\n * Batches cell-construction operations (createSheet/setValues/setFormulas/\n * setStyles/mergeCells) so they commit together and undo as a single step, with\n * events suspended for speed. (`transaction` is the sanctioned §4 atomic-batch\n * verb.)\n *\n * @param operations - The operations to run in order. Each operation's `args`\n * stays that method's schema **object** (the wire shape), not positional.\n * @param label - Optional undo-history label for the batch.\n * @returns A {@linkcode PluginProgramSpreadsheetTransactionResult} with the\n * `operationCount` and each operation's `results`.\n * @throws If any operation names an unsupported method or fails.\n *\n * @examplePrompt Create a sheet and write to it in one atomic step\n * @examplePrompt Batch these sheet edits as a single undoable action\n * @examplePrompt Run these operations together as one transaction\n *\n * # Example\n * ```ts\n * await snaptrude.program.spreadsheet.transaction(\n * [\n * { method: \"createSheet\", args: { name: \"Summary\" } },\n * { method: \"setValues\", args: { sheetName: \"Summary\", values: [[\"A\", 1]] } },\n * ],\n * \"Build summary\",\n * )\n * ```\n */\n public abstract transaction(\n operations: PluginSpreadsheetTransactionOp[],\n label?: string,\n ): PluginApiReturn<PluginProgramSpreadsheetTransactionResult>\n\n // --- Meta: script escape hatch, audit log, liveness ---\n\n /**\n * Run an author-supplied script against a sandboxed workbook facade.\n *\n * The escape hatch for advanced automation the typed methods don't cover: the\n * `script` runs against a restricted `workbook` object with browser/network\n * globals blocked, and its return value is handed back as `result`. Prefer the\n * typed methods where they exist. (Verb ruling: `run` executes a script.)\n *\n * @param script - Script source, run against the sandboxed `workbook` facade.\n * @returns A {@linkcode PluginProgramSpreadsheetRunScriptResult} with the script's\n * `result`.\n * @throws If the script uses a blocked browser/network API or throws at runtime.\n *\n * @examplePrompt Run this SpreadJS script against the workbook\n * @examplePrompt Execute a custom script on the sheets\n * @examplePrompt Run a script to tweak the workbook\n *\n * # Example\n * ```ts\n * const { result } = await snaptrude.program.spreadsheet.runScript(\n * \"return workbook.getSheetCount();\",\n * )\n * ```\n */\n public abstract runScript(\n script: string,\n ): PluginApiReturn<PluginProgramSpreadsheetRunScriptResult>\n\n /**\n * Read the recent audit log of program-spreadsheet operations.\n *\n * Returns the most recent operations performed through this API (method, sheet,\n * status, summary, timestamp), newest first — useful for showing the user what\n * a plugin changed. (Renamed from the host's `getRecentEntries` for §4 `get`\n * compliance.)\n *\n * @param options - Optional `limit` on the entries returned (clamped 1–100,\n * default 20).\n * @returns A {@linkcode PluginProgramSpreadsheetGetAuditLogResult} with the audit\n * `entries`, newest first.\n *\n * @examplePrompt Show the recent spreadsheet actions\n * @examplePrompt What did the plugin change in the workbook?\n * @examplePrompt Get the last 10 audit log entries\n * @examplePrompt Read the program spreadsheet audit log\n *\n * # Example\n * ```ts\n * const { entries } = await snaptrude.program.spreadsheet.getAuditLog({ limit: 10 })\n * ```\n */\n public abstract getAuditLog(options?: {\n limit?: number\n }): PluginApiReturn<PluginProgramSpreadsheetGetAuditLogResult>\n\n /**\n * Ping the program-mode spreadsheet bridge to check it is reachable.\n *\n * A liveness probe: resolves with `{ pong: true }` when the program-mode\n * workbook bridge is up. (Verb ruling: `ping` is a meta liveness verb.)\n *\n * @returns A {@linkcode PluginProgramSpreadsheetPingResult} — `{ pong: true }` when\n * the bridge is reachable.\n *\n * @examplePrompt Ping the spreadsheet bridge\n * @examplePrompt Is the program spreadsheet available?\n * @examplePrompt Check the spreadsheet connection is alive\n *\n * # Example\n * ```ts\n * const { pong } = await snaptrude.program.spreadsheet.ping()\n * ```\n */\n public abstract ping(): PluginApiReturn<PluginProgramSpreadsheetPingResult>\n\n // --- Custom program sheets (grouped program views) -----------------------------\n\n /**\n * Create a **custom program sheet** — a live view of the program grouped by a\n * configurable hierarchy (the Program tab's \"Custom\" sheet with *Organize →\n * Group by*).\n *\n * Unlike {@linkcode PluginProgramSpreadsheetApi.createSheet} (a blank sheet you\n * write cells into), a custom sheet is data-bound: its rows are the project's\n * spaces, re-rendered from the model, grouped by `groupBy` in order (e.g.\n * `[\"departmentName\", \"<metadataHeaderId>\", \"label\"]` for a healthcare\n * department → sub-department → room hierarchy) and showing the `properties`\n * columns. Valid property ids come from\n * {@linkcode PluginProgramSpreadsheetApi.listCustomSheetProperties} — built-in\n * properties plus every program metadata header id. The configuration is\n * saved with the sheet.\n *\n * @param name - The sheet name to create (must not already exist).\n * @param options - Optional `groupBy` (property ids, outermost first; default\n * none), `properties` (column property ids, in order; default\n * `[\"label\", \"areas\"]`), and `groupColors` (property id → CSS hex fill for\n * that level's group headers).\n * @returns The sheet's {@linkcode PluginSpreadsheetCustomSheet} configuration.\n * @throws If a sheet named `name` already exists, or a property id is unknown.\n *\n * @examplePrompt Create a custom sheet grouped by department then sub-department\n * @examplePrompt Make a program view grouped by storey and label\n * @examplePrompt Build a grouped program sheet for the healthcare hierarchy\n * @examplePrompt Add a custom sheet with a department → room type group-by\n *\n * # Example\n * ```ts\n * const { headers } = await snaptrude.program.metadata.listHeaders()\n * const subDept = headers.find((h) => h.name === \"Sub-department\")\n * await snaptrude.program.spreadsheet.createCustomSheet(\"Clinical Program\", {\n * groupBy: [\"departmentName\", subDept.headerId, \"label\"],\n * properties: [\"label\", \"count\", \"areas\", \"netAreaAchieved\"],\n * })\n * ```\n */\n public abstract createCustomSheet(\n name: string,\n options?: {\n groupBy?: string[]\n properties?: string[]\n groupColors?: Record<string, string>\n },\n ): PluginApiReturn<PluginProgramSpreadsheetCustomSheetResult>\n\n /**\n * Update a custom program sheet's group-by hierarchy, columns, or group colors.\n *\n * A **sparse** update: omitted fields are left unchanged. The sheet is\n * re-rendered once and the configuration saved.\n *\n * @param sheetName - The custom sheet to update.\n * @param options - Any of `groupBy`, `properties`, `groupColors` (see\n * {@linkcode PluginProgramSpreadsheetApi.createCustomSheet}).\n * @returns The sheet's updated {@linkcode PluginSpreadsheetCustomSheet}.\n * @throws If `sheetName` is not a custom program sheet, or a property id is\n * unknown.\n *\n * @examplePrompt Change the custom sheet to group by storey first\n * @examplePrompt Add the Occupancy column to my grouped program sheet\n * @examplePrompt Regroup the Clinical Program sheet by department only\n *\n * # Example\n * ```ts\n * await snaptrude.program.spreadsheet.updateCustomSheet(\"Clinical Program\", {\n * groupBy: [\"storey\", \"departmentName\"],\n * })\n * ```\n */\n public abstract updateCustomSheet(\n sheetName: string,\n options: {\n groupBy?: string[]\n properties?: string[]\n groupColors?: Record<string, string>\n },\n ): PluginApiReturn<PluginProgramSpreadsheetCustomSheetResult>\n\n /**\n * Get a custom program sheet's configuration.\n *\n * @param sheetName - The sheet to read.\n * @returns Its {@linkcode PluginSpreadsheetCustomSheet}, or `null` when no\n * custom program sheet has that name (plain sheets return `null` too).\n *\n * @examplePrompt How is the Clinical Program sheet grouped?\n * @examplePrompt Get the group-by configuration of this custom sheet\n *\n * # Example\n * ```ts\n * const cfg = await snaptrude.program.spreadsheet.getCustomSheet(\"Clinical Program\")\n * if (cfg) console.log(cfg.groupBy, cfg.properties)\n * ```\n */\n public abstract getCustomSheet(\n sheetName: string,\n ): PluginApiReturn<PluginProgramSpreadsheetGetCustomSheetResult>\n\n /**\n * List the custom program sheets in the workbook with their configurations.\n *\n * @returns A {@linkcode PluginProgramSpreadsheetListCustomSheetsResult} with a\n * `sheets` array (empty when there are none).\n *\n * @examplePrompt List the custom program sheets\n * @examplePrompt Which sheets are grouped program views?\n *\n * # Example\n * ```ts\n * const { sheets } = await snaptrude.program.spreadsheet.listCustomSheets()\n * ```\n */\n public abstract listCustomSheets(): PluginApiReturn<PluginProgramSpreadsheetListCustomSheetsResult>\n\n /**\n * List the property ids a custom program sheet can show as columns or group by.\n *\n * Built-in properties (`label`, `departmentName`, `storey`, `areas`, …) plus\n * one entry per program metadata header (its `headerId`). `groupable` is\n * `false` for computed properties that can only be columns.\n *\n * @returns A {@linkcode PluginProgramSpreadsheetListCustomSheetPropertiesResult}.\n *\n * @examplePrompt What can I group a custom program sheet by?\n * @examplePrompt List the columns available for a custom sheet\n *\n * # Example\n * ```ts\n * const { properties } = await snaptrude.program.spreadsheet.listCustomSheetProperties()\n * const groupable = properties.filter((p) => p.groupable).map((p) => p.id)\n * ```\n */\n public abstract listCustomSheetProperties(): PluginApiReturn<PluginProgramSpreadsheetListCustomSheetPropertiesResult>\n}\n\n/**\n * Program-spreadsheet templates — capture a sheet's layout as a named, reusable\n * template and re-apply it to another sheet.\n *\n * Accessed via `snaptrude.program.spreadsheet.templates`.\n */\nexport abstract class PluginProgramSpreadsheetTemplatesApi {\n constructor() {}\n\n /**\n * Save a sheet's current layout as a named template.\n *\n * (Renamed from the host's `save` to `create` for §4 compliance.)\n *\n * @param name - Template name (re-saving the same name overwrites it).\n * @param sheetName - The source sheet to capture.\n * @param scope - Template scope: `\"project\"` (default) or `\"global\"`.\n * @returns A {@linkcode PluginProgramSpreadsheetTemplatesCreateResult} with the new\n * template's `id`, `name`, `scope`, and source `sheetName`.\n * @throws If the source sheet does not exist.\n *\n * @examplePrompt Save this sheet as a reusable template\n * @examplePrompt Create a template from the Summary sheet\n * @examplePrompt Save the current layout as a template called \"Cover\"\n *\n * # Example\n * ```ts\n * await snaptrude.program.spreadsheet.templates.create(\"Cover\", \"Summary\")\n * ```\n */\n public abstract create(\n name: string,\n sheetName: string,\n scope?: PluginSpreadsheetTemplateScope,\n ): PluginApiReturn<PluginProgramSpreadsheetTemplatesCreateResult>\n\n /**\n * Apply a saved template to a target sheet (create-or-replace).\n *\n * @param name - The template to apply.\n * @param targetSheetName - The sheet to create/replace from the template.\n * @returns A {@linkcode PluginProgramSpreadsheetTemplatesApplyResult} echoing the\n * applied template and target sheet.\n * @throws If the named template does not exist.\n *\n * @examplePrompt Apply the Cover template to a new sheet\n * @examplePrompt Use my saved template on the Report sheet\n * @examplePrompt Apply template \"Cover\" to sheet \"Report\"\n *\n * # Example\n * ```ts\n * await snaptrude.program.spreadsheet.templates.apply(\"Cover\", \"Report\")\n * ```\n */\n public abstract apply(\n name: string,\n targetSheetName: string,\n ): PluginApiReturn<PluginProgramSpreadsheetTemplatesApplyResult>\n}\n\n/**\n * Program-spreadsheet data bindings — bind a live program dataset to a sheet\n * range so it can be refreshed on demand.\n *\n * A binding links a dataset (e.g. the takeoff) to a target sheet range; a refresh\n * re-pulls the current data and rewrites the range. Accessed via\n * `snaptrude.program.spreadsheet.bindings`.\n */\nexport abstract class PluginProgramSpreadsheetBindingsApi {\n constructor() {}\n\n /**\n * Create a named binding from a dataset to a sheet range (and do an initial refresh).\n *\n * @param name - Binding name (re-using a name overwrites it).\n * @param source - The dataset to bind (see\n * {@linkcode PluginSpreadsheetBindingSource}).\n * @param target - The target sheet range (see\n * {@linkcode PluginSpreadsheetBindingTarget}).\n * @param mode - How a refresh writes into the target (default `\"replaceRange\"`).\n * @returns A {@linkcode PluginProgramSpreadsheetBindingsCreateResult} — the stored\n * binding plus the `refresh` result of the initial pull.\n * @throws If the target sheet does not exist.\n *\n * @examplePrompt Bind the takeoff to the Data sheet\n * @examplePrompt Create a live binding from the takeoff to A1 on Data\n * @examplePrompt Link the takeoff dataset to a sheet range\n *\n * # Example\n * ```ts\n * await snaptrude.program.spreadsheet.bindings.create(\n * \"takeoff\",\n * { dataset: \"takeoff\" },\n * { sheetName: \"Data\", startCell: \"A1\" },\n * )\n * ```\n */\n public abstract create(\n name: string,\n source: PluginSpreadsheetBindingSource,\n target: PluginSpreadsheetBindingTarget,\n mode?: PluginSpreadsheetBindingMode,\n ): PluginApiReturn<PluginProgramSpreadsheetBindingsCreateResult>\n\n /**\n * Refresh one binding (by name) or all bindings, re-pulling their source data.\n *\n * (Verb ruling: `refresh` re-pulls a binding's live source into its target\n * range — distinct from {@linkcode PluginProgramSpreadsheetApi.recalculate},\n * which recomputes formulas.)\n *\n * @param options - Optional binding `name`; omit it to refresh every binding.\n * @returns A {@linkcode PluginProgramSpreadsheetBindingsRefreshResult} with a\n * `refreshed` entry per binding that was updated.\n * @throws If a `name` is given but no binding with that name exists.\n *\n * @examplePrompt Refresh the takeoff binding\n * @examplePrompt Re-pull all my data bindings\n * @examplePrompt Refresh every binding on the sheets\n *\n * # Example\n * ```ts\n * await snaptrude.program.spreadsheet.bindings.refresh({ name: \"takeoff\" })\n * ```\n */\n public abstract refresh(options?: {\n name?: string\n }): PluginApiReturn<PluginProgramSpreadsheetBindingsRefreshResult>\n\n /**\n * List all data bindings in the workbook.\n *\n * @returns A {@linkcode PluginProgramSpreadsheetBindingsListResult} with every\n * stored {@linkcode PluginSpreadsheetBinding}.\n *\n * @examplePrompt List the data bindings\n * @examplePrompt What bindings are set up on the sheets?\n * @examplePrompt Show all the live data bindings\n *\n * # Example\n * ```ts\n * const { bindings } = await snaptrude.program.spreadsheet.bindings.list()\n * ```\n */\n public abstract list(): PluginApiReturn<PluginProgramSpreadsheetBindingsListResult>\n}\n\n/**\n * Program-spreadsheet plugin datasets — named, plugin-supplied row sets stored\n * alongside the workbook so bindings can render and re-render them.\n *\n * A dataset is uploaded with {@linkcode PluginProgramSpreadsheetDatasetsApi.set}\n * (upsert by name). A binding whose source is `{ dataset: \"plugin\", name }`\n * renders the dataset at its target anchor, and re-renders automatically every\n * time the dataset is set again — the \"refresh on rerun\" behaviour. Accessed\n * via `snaptrude.program.spreadsheet.datasets`.\n */\nexport abstract class PluginProgramSpreadsheetDatasetsApi {\n constructor() {}\n\n /**\n * Store (upsert) a named plugin dataset.\n *\n * Re-using a `name` replaces the dataset and automatically refreshes every\n * binding whose source is `{ dataset: \"plugin\", name }` — no extra plugin\n * calls needed to keep bound ranges current. Limits: at most 10,000 rows and\n * 2 MB serialized.\n *\n * @param name - Dataset name (re-using a name overwrites it).\n * @param columns - Column definitions (see {@linkcode PluginSpreadsheetColumn});\n * their order is the column order bindings render.\n * @param rows - Row objects keyed by column `key`.\n * @returns A {@linkcode PluginProgramSpreadsheetDatasetsSetResult} with the\n * stored row count.\n * @throws If the dataset exceeds 10,000 rows or 2 MB serialized.\n *\n * @examplePrompt Store my plugin's rows as a named dataset\n * @examplePrompt Upload data the sheets can bind to and refresh\n * @examplePrompt Save a dataset for a live sheet binding\n * @examplePrompt Push my computed table so a binding keeps it fresh\n *\n * # Example\n * ```ts\n * await snaptrude.program.spreadsheet.datasets.set(\n * \"costs\",\n * [\n * { key: \"item\", header: \"Item\" },\n * { key: \"cost\", header: \"Cost\", align: \"right\" },\n * ],\n * [{ item: \"Concrete\", cost: 120 }],\n * )\n * ```\n */\n public abstract set(\n name: string,\n columns: PluginSpreadsheetColumn[],\n rows: Record<string, PluginSpreadsheetCell>[],\n ): PluginApiReturn<PluginProgramSpreadsheetDatasetsSetResult>\n\n /**\n * List the stored plugin datasets.\n *\n * @returns A {@linkcode PluginProgramSpreadsheetDatasetsListResult} with each\n * dataset's `name`, `rowCount`, and `updatedAt` timestamp.\n *\n * @examplePrompt List my plugin datasets\n * @examplePrompt What datasets are stored for the sheets?\n * @examplePrompt Show the data sets bindings can use\n *\n * # Example\n * ```ts\n * const { datasets } = await snaptrude.program.spreadsheet.datasets.list()\n * ```\n */\n public abstract list(): PluginApiReturn<PluginProgramSpreadsheetDatasetsListResult>\n}\n\n/** A single spreadsheet cell value. */\nexport const PluginSpreadsheetCell = z.union([\n z.string(),\n z.number(),\n z.boolean(),\n z.null(),\n])\nexport type PluginSpreadsheetCell = z.infer<typeof PluginSpreadsheetCell>\n\n/** Horizontal alignment for a rendered column. */\nexport const PluginSpreadsheetAlign = z.enum([\"left\", \"center\", \"right\"])\nexport type PluginSpreadsheetAlign = z.infer<typeof PluginSpreadsheetAlign>\n\n/**\n * A column definition for {@linkcode PluginProgramSpreadsheetApi.render}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `key` | `string` | Key into each row object |\n * | `header` | `string` | Column header text |\n * | `width` | `number?` | Fixed column width in px; auto-fit when omitted |\n * | `format` | `string?` | Number/date format string applied to the data cells |\n * | `align` | `\"left\" \\| \"center\" \\| \"right\"?` | Cell alignment |\n */\nexport const PluginSpreadsheetColumn = z.object({\n key: z.string(),\n header: z.string(),\n width: z.number().optional(),\n format: z.string().optional(),\n align: PluginSpreadsheetAlign.optional(),\n})\nexport type PluginSpreadsheetColumn = z.infer<typeof PluginSpreadsheetColumn>\n\n/**\n * A totals-row aggregation for {@linkcode PluginProgramSpreadsheetApi.render}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `column` | `string` | The {@linkcode PluginSpreadsheetColumn} `key` to aggregate |\n * | `fn` | `\"sum\" \\| \"avg\" \\| \"count\" \\| \"min\" \\| \"max\"` | Aggregation function |\n * | `label` | `string?` | Optional label printed in the first column of the totals row |\n */\nexport const PluginSpreadsheetTotal = z.object({\n column: z.string(),\n fn: z.enum([\"sum\", \"avg\", \"count\", \"min\", \"max\"]),\n label: z.string().optional(),\n})\nexport type PluginSpreadsheetTotal = z.infer<typeof PluginSpreadsheetTotal>\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.render}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | Target sheet name |\n * | `columns` | {@linkcode PluginSpreadsheetColumn}`[]` | Column definitions (at least one) |\n * | `rows` | `Record<string, cell>[]` | Row objects keyed by column `key` |\n * | `title` | `string?` | Optional title rendered above the header |\n * | `startCell` | `string?` | Top-left anchor in A1 notation (default `\"A1\"`) |\n * | `whenExists` | `\"replace\" \\| \"error\"?` | Behaviour if the sheet exists (default `\"replace\"`) |\n * | `asTable` | `boolean?` | Wrap the block in a table (default `true`) |\n * | `freezeHeader` | `boolean?` | Freeze rows above the data (default `true`) |\n * | `totals` | {@linkcode PluginSpreadsheetTotal}`[]?` | Optional totals row |\n */\nexport const PluginProgramSpreadsheetRenderArgs = z.object({\n sheetName: z.string(),\n columns: z.array(PluginSpreadsheetColumn),\n rows: z.array(z.record(z.string(), PluginSpreadsheetCell)),\n title: z.string().optional(),\n startCell: z.string().optional(),\n whenExists: z.enum([\"replace\", \"error\"]).optional(),\n asTable: z.boolean().optional(),\n freezeHeader: z.boolean().optional(),\n totals: z.array(PluginSpreadsheetTotal).optional(),\n})\nexport type PluginProgramSpreadsheetRenderArgs = z.infer<\n typeof PluginProgramSpreadsheetRenderArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.render}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | The sheet that was written |\n * | `range` | `string` | A1 range covering the full written block (title + header + data + totals) |\n * | `headerRange` | `string` | A1 range of the header row alone |\n * | `rowCount` | `number` | Number of data rows written (excludes title/header/totals) |\n * | `colCount` | `number` | Number of columns written |\n * | `tableName` | `string \\| null` | The created table's name, or `null` when not wrapped as a table |\n * | `replaced` | `boolean` | Whether an existing sheet was replaced |\n */\nexport const PluginProgramSpreadsheetRenderResult = z.object({\n sheetName: z.string(),\n range: z.string(),\n headerRange: z.string(),\n rowCount: z.number(),\n colCount: z.number(),\n tableName: z.string().nullable(),\n replaced: z.boolean(),\n})\nexport type PluginProgramSpreadsheetRenderResult = z.infer<\n typeof PluginProgramSpreadsheetRenderResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.renderTakeoff} and\n * {@linkcode PluginProgramSpreadsheetApi.renderProgram} — the dataset render\n * methods. There is **no data argument**: the host pulls and projects the\n * takeoff/program dataset itself. All fields are optional. (Both methods share\n * this args type, as `import.image`/`import.pdf` share their args.)\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string?` | Target sheet name (a default is used when omitted) |\n * | `title` | `string?` | Optional title rendered above the table |\n * | `filter` | `string?` | Case-insensitive substring — a row is kept when any of its cell values contains it (blank/omitted keeps every row). Not a query language. |\n */\nexport const PluginProgramSpreadsheetRenderDatasetArgs = z.object({\n sheetName: z.string().optional(),\n title: z.string().optional(),\n filter: z.string().optional(),\n})\nexport type PluginProgramSpreadsheetRenderDatasetArgs = z.infer<\n typeof PluginProgramSpreadsheetRenderDatasetArgs\n>\n\n/** The workbook export file format. */\nexport const PluginSpreadsheetExportFormat = z.enum([\"xlsx\", \"pdf\", \"csv\"])\nexport type PluginSpreadsheetExportFormat = z.infer<\n typeof PluginSpreadsheetExportFormat\n>\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.export}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `format` | `\"xlsx\" \\| \"pdf\" \\| \"csv\"` | The file format to export to |\n * | `fileName` | `string?` | Desired file name (without extension); a default is used when omitted |\n * | `sheetName` | `string?` | For `csv`: the sheet to serialize (the active sheet when omitted). Ignored by `xlsx`/`pdf`. |\n */\nexport const PluginProgramSpreadsheetExportArgs = z.object({\n format: PluginSpreadsheetExportFormat,\n fileName: z.string().optional(),\n sheetName: z.string().optional(),\n})\nexport type PluginProgramSpreadsheetExportArgs = z.infer<\n typeof PluginProgramSpreadsheetExportArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.export} — the exported workbook file.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `format` | `\"xlsx\" \\| \"pdf\" \\| \"csv\"` | The format the file was exported in |\n * | `fileName` | `string` | The file name (with extension) |\n * | `mimeType` | `string` | The file's MIME type |\n * | `dataBase64` | `string` | The file's bytes, base64-encoded |\n */\nexport const PluginProgramSpreadsheetExportResult = z.object({\n format: PluginSpreadsheetExportFormat,\n fileName: z.string(),\n mimeType: z.string(),\n dataBase64: z.string(),\n})\nexport type PluginProgramSpreadsheetExportResult = z.infer<\n typeof PluginProgramSpreadsheetExportResult\n>\n\n/**\n * Metadata for one sheet in {@linkcode PluginProgramSpreadsheetListSheetsResult}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | Sheet name |\n * | `index` | `number` | Zero-based sheet index |\n * | `visible` | `boolean` | Whether the sheet is visible |\n * | `rowCount` | `number` | Total rows in the sheet |\n * | `colCount` | `number` | Total columns in the sheet |\n */\nexport const PluginSpreadsheetSheetInfo = z.object({\n name: z.string(),\n index: z.number(),\n visible: z.boolean(),\n rowCount: z.number(),\n colCount: z.number(),\n})\nexport type PluginSpreadsheetSheetInfo = z.infer<\n typeof PluginSpreadsheetSheetInfo\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.listSheets}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheets` | {@linkcode PluginSpreadsheetSheetInfo}`[]` | All sheets in the workbook |\n * | `activeSheet` | `string \\| null` | The active sheet name, or `null` |\n */\nexport const PluginProgramSpreadsheetListSheetsResult = z.object({\n sheets: z.array(PluginSpreadsheetSheetInfo),\n activeSheet: z.string().nullable(),\n})\nexport type PluginProgramSpreadsheetListSheetsResult = z.infer<\n typeof PluginProgramSpreadsheetListSheetsResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.getSheetData}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | Sheet to read |\n * | `range` | `string?` | A1 range; defaults to the sheet's used range |\n * | `header` | `boolean?` | When `true`, also return records keyed by the first (header) row |\n */\nexport const PluginProgramSpreadsheetGetSheetDataArgs = z.object({\n sheetName: z.string(),\n range: z.string().optional(),\n header: z.boolean().optional(),\n})\nexport type PluginProgramSpreadsheetGetSheetDataArgs = z.infer<\n typeof PluginProgramSpreadsheetGetSheetDataArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.getSheetData}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | The sheet that was read |\n * | `range` | `string` | A1 range that was read |\n * | `values` | `cell[][]` | Row-major matrix of cell values |\n * | `records` | `Record<string, cell>[]?` | Present only when `header` was `true`: rows keyed by header |\n */\nexport const PluginProgramSpreadsheetGetSheetDataResult = z.object({\n sheetName: z.string(),\n range: z.string(),\n values: z.array(z.array(PluginSpreadsheetCell)),\n records: z.array(z.record(z.string(), PluginSpreadsheetCell)).optional(),\n})\nexport type PluginProgramSpreadsheetGetSheetDataResult = z.infer<\n typeof PluginProgramSpreadsheetGetSheetDataResult\n>\n\n/** Chart type for {@linkcode PluginProgramSpreadsheetApi.addChart}. */\nexport const PluginSpreadsheetChartType = z.enum([\n \"column\",\n \"bar\",\n \"line\",\n \"pie\",\n \"area\",\n \"scatter\",\n])\nexport type PluginSpreadsheetChartType = z.infer<\n typeof PluginSpreadsheetChartType\n>\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.addChart}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | Sheet to add the chart to |\n * | `chartType` | `\"column\" \\| \"bar\" \\| \"line\" \\| \"pie\" \\| \"area\" \\| \"scatter\"` | Chart type |\n * | `dataRange` | `string` | A1 range driving the chart |\n * | `title` | `string?` | Optional chart title |\n * | `position` | `{ x; y }?` | Top-left position (sheet coords) |\n * | `size` | `{ width; height }?` | Chart size in px |\n */\nexport const PluginProgramSpreadsheetAddChartArgs = z.object({\n sheetName: z.string(),\n chartType: PluginSpreadsheetChartType,\n dataRange: z.string(),\n title: z.string().optional(),\n position: z.object({ x: z.number(), y: z.number() }).optional(),\n size: z.object({ width: z.number(), height: z.number() }).optional(),\n})\nexport type PluginProgramSpreadsheetAddChartArgs = z.infer<\n typeof PluginProgramSpreadsheetAddChartArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.addChart}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `chartId` | `string` | Id of the created chart |\n */\nexport const PluginProgramSpreadsheetAddChartResult = z.object({\n chartId: z.string(),\n})\nexport type PluginProgramSpreadsheetAddChartResult = z.infer<\n typeof PluginProgramSpreadsheetAddChartResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.highlightCells}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | Sheet containing the range |\n * | `range` | `string` | A1 range to highlight |\n * | `color` | `string?` | CSS hex background color (a default emphasis is used when omitted) |\n * | `bold` | `boolean?` | Also bold the text |\n */\nexport const PluginProgramSpreadsheetHighlightCellsArgs = z.object({\n sheetName: z.string(),\n range: z.string(),\n color: z.string().optional(),\n bold: z.boolean().optional(),\n})\nexport type PluginProgramSpreadsheetHighlightCellsArgs = z.infer<\n typeof PluginProgramSpreadsheetHighlightCellsArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.highlightCells}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | The sheet that was highlighted |\n * | `range` | `string` | The range that was highlighted |\n */\nexport const PluginProgramSpreadsheetHighlightCellsResult = z.object({\n sheetName: z.string(),\n range: z.string(),\n})\nexport type PluginProgramSpreadsheetHighlightCellsResult = z.infer<\n typeof PluginProgramSpreadsheetHighlightCellsResult\n>\n\n// --- getProgram / getTakeoff dataset reads ---------------------------------\n\n/**\n * One department row in the area-program dataset. Areas are in the enclosing\n * {@linkcode PluginProgramSpreadsheetGetProgramResult}'s `units`. This mirrors\n * {@linkcode PluginProgramDepartmentMetric}, plus a derived `variance`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `departmentId` | `string` | Department id |\n * | `name` | `string` | Department name |\n * | `targetArea` | `number \\| null` | Target area, or `null` if no target is set |\n * | `allocatedArea` | `number` | Area currently allocated to this department |\n * | `variance` | `number \\| null` | `allocatedArea − targetArea`, or `null` when no target |\n */\nexport const PluginSpreadsheetProgramRow = z.object({\n departmentId: z.string(),\n name: z.string(),\n targetArea: z.number().nullable(),\n allocatedArea: z.number(),\n variance: z.number().nullable(),\n})\nexport type PluginSpreadsheetProgramRow = z.infer<\n typeof PluginSpreadsheetProgramRow\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.getProgram} — the area-program\n * dataset {@linkcode PluginProgramSpreadsheetApi.renderProgram} draws. For the\n * canonical summary read, see {@linkcode PluginProgramMetricsApi.get}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `units` | `\"ft2\" \\| \"m2\"` | The unit every area in this dataset is reported in |\n * | `rows` | {@linkcode PluginSpreadsheetProgramRow}`[]` | Per-department program rows |\n * | `totalTargetArea` | `number` | Sum of all department target areas |\n * | `totalAllocatedArea` | `number` | Total allocated area |\n * | `generatedAt` | `string` | ISO timestamp of when the dataset was projected |\n */\nexport const PluginProgramSpreadsheetGetProgramResult = z.object({\n units: PluginAreaUnit,\n rows: z.array(PluginSpreadsheetProgramRow),\n totalTargetArea: z.number(),\n totalAllocatedArea: z.number(),\n generatedAt: z.string(),\n})\nexport type PluginProgramSpreadsheetGetProgramResult = z.infer<\n typeof PluginProgramSpreadsheetGetProgramResult\n>\n\n/**\n * One family-aggregated row in the quantity/material takeoff dataset. The takeoff\n * is grouped by object type + category + family (not per element), so each row is\n * a rolled-up quantity for that family.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `objectType` | `string` | Element object type (e.g. `\"Wall\"`, `\"Door\"`, `\"Window\"`) |\n * | `category` | `string` | Element category |\n * | `family` | `string` | Element family the quantity is aggregated over |\n * | `quantity` | `number` | Aggregated quantity in `unit` |\n * | `unit` | `string` | Quantity unit (e.g. `\"m\"` for length, `\"m2\"` for area) |\n */\nexport const PluginSpreadsheetTakeoffRow = z.object({\n objectType: z.string(),\n category: z.string(),\n family: z.string(),\n quantity: z.number(),\n unit: z.string(),\n})\nexport type PluginSpreadsheetTakeoffRow = z.infer<\n typeof PluginSpreadsheetTakeoffRow\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.getTakeoff} — the takeoff dataset\n * {@linkcode PluginProgramSpreadsheetApi.renderTakeoff} draws.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `rows` | {@linkcode PluginSpreadsheetTakeoffRow}`[]` | One row per aggregated object-type/category/family group |\n * | `rowCount` | `number` | Number of rows |\n * | `generatedAt` | `string` | ISO timestamp of when the dataset was read |\n */\nexport const PluginProgramSpreadsheetGetTakeoffResult = z.object({\n rows: z.array(PluginSpreadsheetTakeoffRow),\n rowCount: z.number(),\n generatedAt: z.string(),\n})\nexport type PluginProgramSpreadsheetGetTakeoffResult = z.infer<\n typeof PluginProgramSpreadsheetGetTakeoffResult\n>\n\n/**\n * One row in the material take-off dataset. The take-off is grouped by element\n * object type + family + applied material, so each row is a rolled-up surface\n * area for that material on that family.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `objectType` | `string` | Element object type the material sits on (`\"wall\"`, `\"floor\"`, `\"ceiling\"`, `\"slab\"`, `\"roof\"`) |\n * | `family` | `string` | Element family the quantity is aggregated under |\n * | `material` | `string` | Applied material name |\n * | `quantity` | `number` | Rolled-up surface area in `unit` |\n * | `unit` | `string` | Area unit (e.g. `\"sq.ft\"` or `\"sq.m\"`) |\n */\nexport const PluginSpreadsheetMaterialTakeoffRow = z.object({\n objectType: z.string(),\n family: z.string(),\n material: z.string(),\n quantity: z.number(),\n unit: z.string(),\n})\nexport type PluginSpreadsheetMaterialTakeoffRow = z.infer<\n typeof PluginSpreadsheetMaterialTakeoffRow\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.getMaterialTakeoff} — the native\n * material take-off dataset (surface area per material). Empty when no materials are\n * applied.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `rows` | {@linkcode PluginSpreadsheetMaterialTakeoffRow}`[]` | One row per object-type/family/material group |\n * | `rowCount` | `number` | Number of rows |\n * | `generatedAt` | `string` | ISO timestamp of when the dataset was read |\n */\nexport const PluginProgramSpreadsheetGetMaterialTakeoffResult = z.object({\n rows: z.array(PluginSpreadsheetMaterialTakeoffRow),\n rowCount: z.number(),\n generatedAt: z.string(),\n})\nexport type PluginProgramSpreadsheetGetMaterialTakeoffResult = z.infer<\n typeof PluginProgramSpreadsheetGetMaterialTakeoffResult\n>\n\n// --- createSheet -----------------------------------------------------------\n\n/** Behaviour when {@linkcode PluginProgramSpreadsheetApi.createSheet} targets an existing sheet name. */\nexport const PluginSpreadsheetWhenExists = z.enum([\"reuse\", \"replace\", \"error\"])\nexport type PluginSpreadsheetWhenExists = z.infer<\n typeof PluginSpreadsheetWhenExists\n>\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.createSheet}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | The sheet name to create |\n * | `whenExists` | `\"reuse\" \\| \"replace\" \\| \"error\"?` | Behaviour if the name exists (default `\"reuse\"`) |\n */\nexport const PluginProgramSpreadsheetCreateSheetArgs = z.object({\n name: z.string(),\n whenExists: PluginSpreadsheetWhenExists.optional(),\n})\nexport type PluginProgramSpreadsheetCreateSheetArgs = z.infer<\n typeof PluginProgramSpreadsheetCreateSheetArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.createSheet}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | The resolved sheet name |\n * | `replaced` | `boolean` | Whether an existing sheet was replaced/cleared |\n * | `sheetIndex` | `number \\| null` | Zero-based index of the sheet, or `null` if unknown |\n */\nexport const PluginProgramSpreadsheetCreateSheetResult = z.object({\n sheetName: z.string(),\n replaced: z.boolean(),\n sheetIndex: z.number().nullable(),\n})\nexport type PluginProgramSpreadsheetCreateSheetResult = z.infer<\n typeof PluginProgramSpreadsheetCreateSheetResult\n>\n\n// --- setValues -------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.setValues}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | Sheet to write into |\n * | `values` | `cell[][]` | Row-major matrix of values to write |\n * | `startCell` | `string?` | Top-left anchor in A1 notation (default `\"A1\"`) |\n */\nexport const PluginProgramSpreadsheetSetValuesArgs = z.object({\n sheetName: z.string(),\n values: z.array(z.array(PluginSpreadsheetCell)),\n startCell: z.string().optional(),\n})\nexport type PluginProgramSpreadsheetSetValuesArgs = z.infer<\n typeof PluginProgramSpreadsheetSetValuesArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.setValues}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | The sheet that was written |\n * | `startCell` | `string` | The anchor cell values were written from |\n * | `rowCount` | `number` | Number of rows written |\n * | `colCount` | `number` | Widest row's column count |\n */\nexport const PluginProgramSpreadsheetSetValuesResult = z.object({\n sheetName: z.string(),\n startCell: z.string(),\n rowCount: z.number(),\n colCount: z.number(),\n})\nexport type PluginProgramSpreadsheetSetValuesResult = z.infer<\n typeof PluginProgramSpreadsheetSetValuesResult\n>\n\n// --- setFormulas -----------------------------------------------------------\n\n/**\n * A single cell formula for {@linkcode PluginProgramSpreadsheetApi.setFormulas}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `cell` | `string` | Target cell in A1 notation |\n * | `formula` | `string` | Formula expression (a leading `=` is optional) |\n */\nexport const PluginSpreadsheetFormula = z.object({\n cell: z.string(),\n formula: z.string(),\n})\nexport type PluginSpreadsheetFormula = z.infer<typeof PluginSpreadsheetFormula>\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.setFormulas}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | Sheet to set formulas on |\n * | `formulas` | {@linkcode PluginSpreadsheetFormula}`[]` | The cell → formula assignments |\n */\nexport const PluginProgramSpreadsheetSetFormulasArgs = z.object({\n sheetName: z.string(),\n formulas: z.array(PluginSpreadsheetFormula),\n})\nexport type PluginProgramSpreadsheetSetFormulasArgs = z.infer<\n typeof PluginProgramSpreadsheetSetFormulasArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.setFormulas}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | The sheet that was written |\n * | `count` | `number` | Number of formulas set |\n */\nexport const PluginProgramSpreadsheetSetFormulasResult = z.object({\n sheetName: z.string(),\n count: z.number(),\n})\nexport type PluginProgramSpreadsheetSetFormulasResult = z.infer<\n typeof PluginProgramSpreadsheetSetFormulasResult\n>\n\n// --- setStyles -------------------------------------------------------------\n\n/** Font weight for a {@linkcode PluginSpreadsheetCellStyle}. */\nexport const PluginSpreadsheetFontWeight = z.enum([\"normal\", \"bold\"])\nexport type PluginSpreadsheetFontWeight = z.infer<\n typeof PluginSpreadsheetFontWeight\n>\n\n/** Font style for a {@linkcode PluginSpreadsheetCellStyle}. */\nexport const PluginSpreadsheetFontStyle = z.enum([\"normal\", \"italic\"])\nexport type PluginSpreadsheetFontStyle = z.infer<\n typeof PluginSpreadsheetFontStyle\n>\n\n/** Horizontal cell alignment for a {@linkcode PluginSpreadsheetCellStyle}. */\nexport const PluginSpreadsheetHorizontalAlign = z.enum([\n \"left\",\n \"center\",\n \"right\",\n \"general\",\n])\nexport type PluginSpreadsheetHorizontalAlign = z.infer<\n typeof PluginSpreadsheetHorizontalAlign\n>\n\n/** Vertical cell alignment for a {@linkcode PluginSpreadsheetCellStyle}. */\nexport const PluginSpreadsheetVerticalAlign = z.enum([\n \"top\",\n \"center\",\n \"bottom\",\n])\nexport type PluginSpreadsheetVerticalAlign = z.infer<\n typeof PluginSpreadsheetVerticalAlign\n>\n\n/**\n * A cell style for {@linkcode PluginProgramSpreadsheetApi.setStyles}. All fields\n * are optional; only the ones provided are applied.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `backColor` | `string?` | Background (fill) color, CSS hex |\n * | `foreColor` | `string?` | Text color, CSS hex |\n * | `font` | `string?` | Full CSS font shorthand (overrides the parts below) |\n * | `fontFamily` | `string?` | Font family |\n * | `fontSize` | `number?` | Font size in px |\n * | `fontWeight` | `\"normal\" \\| \"bold\"?` | Font weight |\n * | `fontStyle` | `\"normal\" \\| \"italic\"?` | Font style |\n * | `formatter` | `string?` | Number/date format string |\n * | `horizontalAlign` | `\"left\" \\| \"center\" \\| \"right\" \\| \"general\"?` | Horizontal alignment |\n * | `verticalAlign` | `\"top\" \\| \"center\" \\| \"bottom\"?` | Vertical alignment |\n */\nexport const PluginSpreadsheetCellStyle = z.object({\n backColor: z.string().optional(),\n foreColor: z.string().optional(),\n font: z.string().optional(),\n fontFamily: z.string().optional(),\n fontSize: z.number().optional(),\n fontWeight: PluginSpreadsheetFontWeight.optional(),\n fontStyle: PluginSpreadsheetFontStyle.optional(),\n formatter: z.string().optional(),\n horizontalAlign: PluginSpreadsheetHorizontalAlign.optional(),\n verticalAlign: PluginSpreadsheetVerticalAlign.optional(),\n})\nexport type PluginSpreadsheetCellStyle = z.infer<\n typeof PluginSpreadsheetCellStyle\n>\n\n/**\n * A range + the style to apply to it, for {@linkcode PluginProgramSpreadsheetApi.setStyles}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `range` | `string` | A1 range to style |\n * | `style` | {@linkcode PluginSpreadsheetCellStyle} | The style to apply |\n */\nexport const PluginSpreadsheetRangeStyle = z.object({\n range: z.string(),\n style: PluginSpreadsheetCellStyle,\n})\nexport type PluginSpreadsheetRangeStyle = z.infer<\n typeof PluginSpreadsheetRangeStyle\n>\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.setStyles}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | Sheet containing the ranges |\n * | `ranges` | {@linkcode PluginSpreadsheetRangeStyle}`[]` | Range → style assignments |\n */\nexport const PluginProgramSpreadsheetSetStylesArgs = z.object({\n sheetName: z.string(),\n ranges: z.array(PluginSpreadsheetRangeStyle),\n})\nexport type PluginProgramSpreadsheetSetStylesArgs = z.infer<\n typeof PluginProgramSpreadsheetSetStylesArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.setStyles}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | The sheet that was styled |\n * | `count` | `number` | Number of ranges styled |\n */\nexport const PluginProgramSpreadsheetSetStylesResult = z.object({\n sheetName: z.string(),\n count: z.number(),\n})\nexport type PluginProgramSpreadsheetSetStylesResult = z.infer<\n typeof PluginProgramSpreadsheetSetStylesResult\n>\n\n// --- mergeCells ------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.mergeCells}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | Sheet containing the range |\n * | `range` | `string` | A1 range to merge into one spanned cell |\n * | `value` | `cell?` | Optional value to set in the merged (top-left) cell |\n */\nexport const PluginProgramSpreadsheetMergeCellsArgs = z.object({\n sheetName: z.string(),\n range: z.string(),\n value: PluginSpreadsheetCell.optional(),\n})\nexport type PluginProgramSpreadsheetMergeCellsArgs = z.infer<\n typeof PluginProgramSpreadsheetMergeCellsArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.mergeCells}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | The sheet that was merged |\n * | `range` | `string` | The range that was merged |\n */\nexport const PluginProgramSpreadsheetMergeCellsResult = z.object({\n sheetName: z.string(),\n range: z.string(),\n})\nexport type PluginProgramSpreadsheetMergeCellsResult = z.infer<\n typeof PluginProgramSpreadsheetMergeCellsResult\n>\n\n// --- addConditionalFormatting ----------------------------------------------\n\n/** Conditional-formatting rule type. Currently only `dataBar`; more are reserved. */\nexport const PluginSpreadsheetConditionalRuleType = z.enum([\"dataBar\"])\nexport type PluginSpreadsheetConditionalRuleType = z.infer<\n typeof PluginSpreadsheetConditionalRuleType\n>\n\n/**\n * A conditional-formatting rule for {@linkcode PluginProgramSpreadsheetApi.addConditionalFormatting}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `type` | `\"dataBar\"` | The rule kind (a proportional in-cell bar) |\n * | `color` | `string?` | Bar color, CSS hex (a default is used when omitted) |\n */\nexport const PluginSpreadsheetConditionalRule = z.object({\n type: PluginSpreadsheetConditionalRuleType,\n color: z.string().optional(),\n})\nexport type PluginSpreadsheetConditionalRule = z.infer<\n typeof PluginSpreadsheetConditionalRule\n>\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.addConditionalFormatting}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | Sheet containing the range |\n * | `range` | `string` | A1 range to format |\n * | `rule` | {@linkcode PluginSpreadsheetConditionalRule} | The conditional-format rule |\n */\nexport const PluginProgramSpreadsheetAddConditionalFormattingArgs = z.object({\n sheetName: z.string(),\n range: z.string(),\n rule: PluginSpreadsheetConditionalRule,\n})\nexport type PluginProgramSpreadsheetAddConditionalFormattingArgs = z.infer<\n typeof PluginProgramSpreadsheetAddConditionalFormattingArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.addConditionalFormatting}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | The sheet that was formatted |\n * | `range` | `string` | The range the rule was applied to |\n * | `created` | `boolean` | Whether a data-bar rule was created (vs a fallback fill) |\n */\nexport const PluginProgramSpreadsheetAddConditionalFormattingResult = z.object({\n sheetName: z.string(),\n range: z.string(),\n created: z.boolean(),\n})\nexport type PluginProgramSpreadsheetAddConditionalFormattingResult = z.infer<\n typeof PluginProgramSpreadsheetAddConditionalFormattingResult\n>\n\n// --- recalculate -----------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.recalculate}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string?` | A sheet to focus before recalculating; omit for the whole workbook |\n */\nexport const PluginProgramSpreadsheetRecalculateArgs = z.object({\n sheetName: z.string().optional(),\n})\nexport type PluginProgramSpreadsheetRecalculateArgs = z.infer<\n typeof PluginProgramSpreadsheetRecalculateArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.recalculate}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `scope` | `\"sheet\" \\| \"workbook\"` | Whether a single sheet was focused or the whole workbook |\n * | `sheetName` | `string \\| null` | The focused sheet, or `null` for a workbook-wide recompute |\n */\nexport const PluginProgramSpreadsheetRecalculateResult = z.object({\n scope: z.enum([\"sheet\", \"workbook\"]),\n sheetName: z.string().nullable(),\n})\nexport type PluginProgramSpreadsheetRecalculateResult = z.infer<\n typeof PluginProgramSpreadsheetRecalculateResult\n>\n\n// --- transaction -----------------------------------------------------------\n\n/** A batchable operation name for {@linkcode PluginProgramSpreadsheetApi.transaction}. */\nexport const PluginSpreadsheetTransactionMethod = z.enum([\n \"createSheet\",\n \"setValues\",\n \"setFormulas\",\n \"setStyles\",\n \"mergeCells\",\n])\nexport type PluginSpreadsheetTransactionMethod = z.infer<\n typeof PluginSpreadsheetTransactionMethod\n>\n\n/**\n * One operation within a {@linkcode PluginProgramSpreadsheetApi.transaction}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `method` | {@linkcode PluginSpreadsheetTransactionMethod} | The batchable method to run |\n * | `args` | `unknown` | The `method`'s own arguments object |\n */\nexport const PluginSpreadsheetTransactionOp = z.object({\n method: PluginSpreadsheetTransactionMethod,\n args: z.unknown(),\n})\nexport type PluginSpreadsheetTransactionOp = z.infer<\n typeof PluginSpreadsheetTransactionOp\n>\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.transaction}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `operations` | {@linkcode PluginSpreadsheetTransactionOp}`[]` | The operations to run in order |\n * | `label` | `string?` | Undo-history label for the batch |\n */\nexport const PluginProgramSpreadsheetTransactionArgs = z.object({\n operations: z.array(PluginSpreadsheetTransactionOp),\n label: z.string().optional(),\n})\nexport type PluginProgramSpreadsheetTransactionArgs = z.infer<\n typeof PluginProgramSpreadsheetTransactionArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.transaction}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `label` | `string` | The undo-history label used |\n * | `operationCount` | `number` | Number of operations run |\n * | `results` | `unknown[]` | Each operation's result, in order |\n */\nexport const PluginProgramSpreadsheetTransactionResult = z.object({\n label: z.string(),\n operationCount: z.number(),\n results: z.array(z.unknown()),\n})\nexport type PluginProgramSpreadsheetTransactionResult = z.infer<\n typeof PluginProgramSpreadsheetTransactionResult\n>\n\n// --- runScript -------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.runScript}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `script` | `string` | Script source, run against a sandboxed `workbook` facade |\n */\nexport const PluginProgramSpreadsheetRunScriptArgs = z.object({\n script: z.string(),\n})\nexport type PluginProgramSpreadsheetRunScriptArgs = z.infer<\n typeof PluginProgramSpreadsheetRunScriptArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.runScript}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `result` | `unknown` | The script's return value (`null` when it returns nothing) |\n */\nexport const PluginProgramSpreadsheetRunScriptResult = z.object({\n result: z.unknown(),\n})\nexport type PluginProgramSpreadsheetRunScriptResult = z.infer<\n typeof PluginProgramSpreadsheetRunScriptResult\n>\n\n// --- getAuditLog -----------------------------------------------------------\n\n/** Status of an audit-log entry. */\nexport const PluginSpreadsheetAuditStatus = z.enum([\"success\", \"error\"])\nexport type PluginSpreadsheetAuditStatus = z.infer<\n typeof PluginSpreadsheetAuditStatus\n>\n\n/**\n * One entry in the program-spreadsheet audit log.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Entry id |\n * | `pluginId` | `string` | Id of the plugin that performed the operation |\n * | `method` | `string` | The API method invoked |\n * | `label` | `string?` | Optional operation label |\n * | `sheetName` | `string?` | The sheet the operation targeted, if any |\n * | `timestamp` | `string` | ISO timestamp |\n * | `status` | `\"success\" \\| \"error\"` | Whether the operation succeeded |\n * | `summary` | `string?` | Short human-readable summary |\n */\nexport const PluginSpreadsheetAuditEntry = z.object({\n id: z.string(),\n pluginId: z.string(),\n method: z.string(),\n label: z.string().optional(),\n sheetName: z.string().optional(),\n timestamp: z.string(),\n status: PluginSpreadsheetAuditStatus,\n summary: z.string().optional(),\n})\nexport type PluginSpreadsheetAuditEntry = z.infer<\n typeof PluginSpreadsheetAuditEntry\n>\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.getAuditLog}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `limit` | `number?` | Max entries to return (clamped 1–100, default 20) |\n */\nexport const PluginProgramSpreadsheetGetAuditLogArgs = z.object({\n limit: z.number().optional(),\n})\nexport type PluginProgramSpreadsheetGetAuditLogArgs = z.infer<\n typeof PluginProgramSpreadsheetGetAuditLogArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.getAuditLog}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `entries` | {@linkcode PluginSpreadsheetAuditEntry}`[]` | Recent audit entries, newest first |\n */\nexport const PluginProgramSpreadsheetGetAuditLogResult = z.object({\n entries: z.array(PluginSpreadsheetAuditEntry),\n})\nexport type PluginProgramSpreadsheetGetAuditLogResult = z.infer<\n typeof PluginProgramSpreadsheetGetAuditLogResult\n>\n\n// --- ping ------------------------------------------------------------------\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.ping}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `pong` | `boolean` | `true` when the program-mode spreadsheet bridge is reachable |\n */\nexport const PluginProgramSpreadsheetPingResult = z.object({\n pong: z.boolean(),\n})\nexport type PluginProgramSpreadsheetPingResult = z.infer<\n typeof PluginProgramSpreadsheetPingResult\n>\n\n// --- templates.* -----------------------------------------------------------\n\n/** The visibility scope of a saved template. */\nexport const PluginSpreadsheetTemplateScope = z.enum([\"project\", \"global\"])\nexport type PluginSpreadsheetTemplateScope = z.infer<\n typeof PluginSpreadsheetTemplateScope\n>\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetTemplatesApi.create}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | Template name (re-saving the same name overwrites it) |\n * | `sheetName` | `string` | The source sheet to capture |\n * | `scope` | `\"project\" \\| \"global\"?` | Template scope (default `\"project\"`) |\n */\nexport const PluginProgramSpreadsheetTemplatesCreateArgs = z.object({\n name: z.string(),\n sheetName: z.string(),\n scope: PluginSpreadsheetTemplateScope.optional(),\n})\nexport type PluginProgramSpreadsheetTemplatesCreateArgs = z.infer<\n typeof PluginProgramSpreadsheetTemplatesCreateArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetTemplatesApi.create}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | The new template's id |\n * | `name` | `string` | The template name |\n * | `scope` | `\"project\" \\| \"global\"` | The template scope |\n * | `sheetName` | `string` | The source sheet captured |\n */\nexport const PluginProgramSpreadsheetTemplatesCreateResult = z.object({\n id: z.string(),\n name: z.string(),\n scope: PluginSpreadsheetTemplateScope,\n sheetName: z.string(),\n})\nexport type PluginProgramSpreadsheetTemplatesCreateResult = z.infer<\n typeof PluginProgramSpreadsheetTemplatesCreateResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetTemplatesApi.apply}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | The template to apply |\n * | `targetSheetName` | `string` | The sheet to create/replace from the template |\n */\nexport const PluginProgramSpreadsheetTemplatesApplyArgs = z.object({\n name: z.string(),\n targetSheetName: z.string(),\n})\nexport type PluginProgramSpreadsheetTemplatesApplyArgs = z.infer<\n typeof PluginProgramSpreadsheetTemplatesApplyArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetTemplatesApi.apply}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | The template that was applied |\n * | `targetSheetName` | `string` | The sheet it was applied to |\n */\nexport const PluginProgramSpreadsheetTemplatesApplyResult = z.object({\n name: z.string(),\n targetSheetName: z.string(),\n})\nexport type PluginProgramSpreadsheetTemplatesApplyResult = z.infer<\n typeof PluginProgramSpreadsheetTemplatesApplyResult\n>\n\n// --- bindings.* ------------------------------------------------------------\n\n/**\n * A binding's data source — the built-in takeoff dataset, or a named plugin\n * dataset stored via {@linkcode PluginProgramSpreadsheetDatasetsApi.set}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `dataset` | `\"takeoff\" \\| \"plugin\"` | The dataset kind the binding pulls from |\n * | `name` | `string` | (`\"plugin\"` only) The stored dataset's name |\n */\nexport const PluginSpreadsheetBindingSource = z.discriminatedUnion(\"dataset\", [\n z.object({ dataset: z.literal(\"takeoff\") }),\n z.object({ dataset: z.literal(\"plugin\"), name: z.string() }),\n])\nexport type PluginSpreadsheetBindingSource = z.infer<\n typeof PluginSpreadsheetBindingSource\n>\n\n/** How a binding refresh writes into its target range. Currently only `replaceRange`. */\nexport const PluginSpreadsheetBindingMode = z.enum([\"replaceRange\"])\nexport type PluginSpreadsheetBindingMode = z.infer<\n typeof PluginSpreadsheetBindingMode\n>\n\n/**\n * A binding's target range, for {@linkcode PluginProgramSpreadsheetBindingsApi.create}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | The sheet to write into |\n * | `startCell` | `string?` | Top-left anchor in A1 notation (default `\"A1\"`) |\n */\nexport const PluginSpreadsheetBindingTarget = z.object({\n sheetName: z.string(),\n startCell: z.string().optional(),\n})\nexport type PluginSpreadsheetBindingTarget = z.infer<\n typeof PluginSpreadsheetBindingTarget\n>\n\n/**\n * A stored data binding.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Binding id |\n * | `name` | `string` | Binding name (unique per workbook) |\n * | `pluginId` | `string` | Id of the plugin that owns the binding |\n * | `source` | {@linkcode PluginSpreadsheetBindingSource} | The bound dataset |\n * | `target` | `{ sheetName: string; startCell: string }` | The resolved target range anchor |\n * | `mode` | {@linkcode PluginSpreadsheetBindingMode} | How a refresh writes into the target |\n * | `createdAt` | `string` | ISO creation timestamp |\n * | `updatedAt` | `string` | ISO last-refresh timestamp |\n */\nexport const PluginSpreadsheetBinding = z.object({\n id: z.string(),\n name: z.string(),\n pluginId: z.string(),\n source: PluginSpreadsheetBindingSource,\n target: z.object({\n sheetName: z.string(),\n startCell: z.string(),\n }),\n mode: PluginSpreadsheetBindingMode,\n createdAt: z.string(),\n updatedAt: z.string(),\n})\nexport type PluginSpreadsheetBinding = z.infer<typeof PluginSpreadsheetBinding>\n\n/**\n * The outcome of refreshing a single binding.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | The refreshed binding's id |\n * | `name` | `string` | The refreshed binding's name |\n * | `sheetName` | `string` | The sheet that was rewritten |\n * | `rowCount` | `number` | Number of rows written |\n */\nexport const PluginSpreadsheetBindingRefresh = z.object({\n id: z.string(),\n name: z.string(),\n sheetName: z.string(),\n rowCount: z.number(),\n})\nexport type PluginSpreadsheetBindingRefresh = z.infer<\n typeof PluginSpreadsheetBindingRefresh\n>\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetBindingsApi.create}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | Binding name (re-using a name overwrites it) |\n * | `source` | {@linkcode PluginSpreadsheetBindingSource} | The dataset to bind |\n * | `target` | {@linkcode PluginSpreadsheetBindingTarget} | The target sheet range |\n * | `mode` | {@linkcode PluginSpreadsheetBindingMode}`?` | How a refresh writes (default `\"replaceRange\"`) |\n */\nexport const PluginProgramSpreadsheetBindingsCreateArgs = z.object({\n name: z.string(),\n source: PluginSpreadsheetBindingSource,\n target: PluginSpreadsheetBindingTarget,\n mode: PluginSpreadsheetBindingMode.optional(),\n})\nexport type PluginProgramSpreadsheetBindingsCreateArgs = z.infer<\n typeof PluginProgramSpreadsheetBindingsCreateArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetBindingsApi.create} — the stored\n * {@linkcode PluginSpreadsheetBinding} plus the initial `refresh` outcome.\n */\nexport const PluginProgramSpreadsheetBindingsCreateResult =\n PluginSpreadsheetBinding.extend({\n refresh: PluginSpreadsheetBindingRefresh,\n })\nexport type PluginProgramSpreadsheetBindingsCreateResult = z.infer<\n typeof PluginProgramSpreadsheetBindingsCreateResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetBindingsApi.refresh}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string?` | The binding to refresh; omit to refresh every binding |\n */\nexport const PluginProgramSpreadsheetBindingsRefreshArgs = z.object({\n name: z.string().optional(),\n})\nexport type PluginProgramSpreadsheetBindingsRefreshArgs = z.infer<\n typeof PluginProgramSpreadsheetBindingsRefreshArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetBindingsApi.refresh}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `refreshed` | {@linkcode PluginSpreadsheetBindingRefresh}`[]` | One entry per binding refreshed |\n */\nexport const PluginProgramSpreadsheetBindingsRefreshResult = z.object({\n refreshed: z.array(PluginSpreadsheetBindingRefresh),\n})\nexport type PluginProgramSpreadsheetBindingsRefreshResult = z.infer<\n typeof PluginProgramSpreadsheetBindingsRefreshResult\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetBindingsApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `bindings` | {@linkcode PluginSpreadsheetBinding}`[]` | Every stored binding |\n */\nexport const PluginProgramSpreadsheetBindingsListResult = z.object({\n bindings: z.array(PluginSpreadsheetBinding),\n})\nexport type PluginProgramSpreadsheetBindingsListResult = z.infer<\n typeof PluginProgramSpreadsheetBindingsListResult\n>\n\n// --- datasets.* ------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetDatasetsApi.set}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | Dataset name (re-using a name overwrites it) |\n * | `columns` | {@linkcode PluginSpreadsheetColumn}`[]` | Column definitions (at least one) |\n * | `rows` | `Record<string, cell>[]` | Row objects keyed by column `key` (max 10,000) |\n */\nexport const PluginProgramSpreadsheetDatasetsSetArgs = z.object({\n name: z.string(),\n columns: z.array(PluginSpreadsheetColumn).min(1),\n rows: z.array(z.record(z.string(), PluginSpreadsheetCell)).max(10_000),\n})\nexport type PluginProgramSpreadsheetDatasetsSetArgs = z.infer<\n typeof PluginProgramSpreadsheetDatasetsSetArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetDatasetsApi.set}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | The stored dataset's name |\n * | `rowCount` | `number` | Number of rows stored |\n */\nexport const PluginProgramSpreadsheetDatasetsSetResult = z.object({\n name: z.string(),\n rowCount: z.number(),\n})\nexport type PluginProgramSpreadsheetDatasetsSetResult = z.infer<\n typeof PluginProgramSpreadsheetDatasetsSetResult\n>\n\n/**\n * A stored plugin dataset's summary, for\n * {@linkcode PluginProgramSpreadsheetDatasetsApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | Dataset name |\n * | `rowCount` | `number` | Number of rows stored |\n * | `updatedAt` | `string` | ISO timestamp of the last `set` |\n */\nexport const PluginSpreadsheetDataset = z.object({\n name: z.string(),\n rowCount: z.number(),\n updatedAt: z.string(),\n})\nexport type PluginSpreadsheetDataset = z.infer<typeof PluginSpreadsheetDataset>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetDatasetsApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `datasets` | {@linkcode PluginSpreadsheetDataset}`[]` | Every stored dataset |\n */\nexport const PluginProgramSpreadsheetDatasetsListResult = z.object({\n datasets: z.array(PluginSpreadsheetDataset),\n})\nexport type PluginProgramSpreadsheetDatasetsListResult = z.infer<\n typeof PluginProgramSpreadsheetDatasetsListResult\n>\n\n// --- addImage --------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.addImage}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | Sheet to place the image on |\n * | `image` | `{ dataUri: string }` | Base64 `data:image/png\\|jpeg\\|svg+xml` URI (max 2 MB; URLs rejected) |\n * | `anchorCell` | `string?` | Anchor cell in A1 notation (default `\"A1\"`) |\n * | `size` | `{ width; height }?` | Size in px; intrinsic size when omitted |\n * | `name` | `string?` | Stable picture id — same name replaces on rerun; generated when omitted |\n */\nexport const PluginProgramSpreadsheetAddImageArgs = z.object({\n sheetName: z.string(),\n image: z.object({\n dataUri: z\n .string()\n .max(2_097_152, \"image.dataUri exceeds the 2 MB limit\")\n .regex(\n /^data:image\\/(png|jpeg|svg\\+xml);base64,/,\n \"image.dataUri must be a base64 data:image/png, jpeg, or svg+xml URI\",\n ),\n }),\n anchorCell: z.string().optional(),\n size: z.object({ width: z.number(), height: z.number() }).optional(),\n name: z.string().optional(),\n})\nexport type PluginProgramSpreadsheetAddImageArgs = z.infer<\n typeof PluginProgramSpreadsheetAddImageArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.addImage}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | The sheet the image was placed on |\n * | `name` | `string` | The picture's (stable or generated) name |\n */\nexport const PluginProgramSpreadsheetAddImageResult = z.object({\n sheetName: z.string(),\n name: z.string(),\n})\nexport type PluginProgramSpreadsheetAddImageResult = z.infer<\n typeof PluginProgramSpreadsheetAddImageResult\n>\n\n/**\n * A custom program sheet's configuration — the grouped, data-bound program view\n * created by {@linkcode PluginProgramSpreadsheetApi.createCustomSheet}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | The sheet's name |\n * | `groupBy` | `string[]` | Group-by hierarchy, outermost first (property ids); `[]` when ungrouped |\n * | `properties` | `string[]` | Column property ids in column order (`\"\"` marks an unassigned column) |\n * | `groupColors` | `Record<string, string>` | Property id → CSS hex fill of that level's group headers |\n * | `proposalId` | `string \\| null` | The proposal the sheet belongs to, or `null` when the project has none |\n */\nexport const PluginSpreadsheetCustomSheet = z.object({\n sheetName: z.string(),\n groupBy: z.array(z.string()),\n properties: z.array(z.string()),\n groupColors: z.record(z.string(), z.string()),\n proposalId: z.string().nullable(),\n})\nexport type PluginSpreadsheetCustomSheet = z.infer<\n typeof PluginSpreadsheetCustomSheet\n>\n\n/**\n * The configurable part of a custom program sheet, for\n * {@linkcode PluginProgramSpreadsheetApi.createCustomSheet} /\n * {@linkcode PluginProgramSpreadsheetApi.updateCustomSheet}. All fields optional.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `groupBy` | `string[]?` | Group-by hierarchy, outermost first (property ids) |\n * | `properties` | `string[]?` | Column property ids, in order |\n * | `groupColors` | `Record<string, string>?` | Property id → CSS hex fill for that level's group headers |\n */\nexport const PluginSpreadsheetCustomSheetConfig = z.object({\n groupBy: z.array(z.string().trim().min(1)).max(10).optional(),\n properties: z.array(z.string()).max(50).optional(),\n groupColors: z.record(z.string(), z.string()).optional(),\n})\nexport type PluginSpreadsheetCustomSheetConfig = z.infer<\n typeof PluginSpreadsheetCustomSheetConfig\n>\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.createCustomSheet}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | The sheet name to create |\n * | `groupBy` / `properties` / `groupColors` | — | See {@linkcode PluginSpreadsheetCustomSheetConfig} |\n */\nexport const PluginProgramSpreadsheetCreateCustomSheetArgs =\n PluginSpreadsheetCustomSheetConfig.extend({\n name: z.string().trim().min(1),\n })\nexport type PluginProgramSpreadsheetCreateCustomSheetArgs = z.infer<\n typeof PluginProgramSpreadsheetCreateCustomSheetArgs\n>\n\n/**\n * Arguments for {@linkcode PluginProgramSpreadsheetApi.updateCustomSheet}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetName` | `string` | The custom sheet to update |\n * | `groupBy` / `properties` / `groupColors` | — | Sparse; see {@linkcode PluginSpreadsheetCustomSheetConfig} |\n */\nexport const PluginProgramSpreadsheetUpdateCustomSheetArgs =\n PluginSpreadsheetCustomSheetConfig.extend({\n sheetName: z.string().trim().min(1),\n })\nexport type PluginProgramSpreadsheetUpdateCustomSheetArgs = z.infer<\n typeof PluginProgramSpreadsheetUpdateCustomSheetArgs\n>\n\n/** Result of `createCustomSheet` / `updateCustomSheet` — the sheet's configuration. */\nexport const PluginProgramSpreadsheetCustomSheetResult = PluginSpreadsheetCustomSheet\nexport type PluginProgramSpreadsheetCustomSheetResult = z.infer<\n typeof PluginProgramSpreadsheetCustomSheetResult\n>\n\n/** Arguments for {@linkcode PluginProgramSpreadsheetApi.getCustomSheet}. */\nexport const PluginProgramSpreadsheetGetCustomSheetArgs = z.object({\n sheetName: z.string().trim().min(1),\n})\nexport type PluginProgramSpreadsheetGetCustomSheetArgs = z.infer<\n typeof PluginProgramSpreadsheetGetCustomSheetArgs\n>\n\n/** Result of {@linkcode PluginProgramSpreadsheetApi.getCustomSheet} — the configuration, or `null`. */\nexport const PluginProgramSpreadsheetGetCustomSheetResult =\n PluginSpreadsheetCustomSheet.nullable()\nexport type PluginProgramSpreadsheetGetCustomSheetResult = z.infer<\n typeof PluginProgramSpreadsheetGetCustomSheetResult\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.listCustomSheets}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheets` | {@linkcode PluginSpreadsheetCustomSheet}`[]` | Every custom program sheet |\n */\nexport const PluginProgramSpreadsheetListCustomSheetsResult = z.object({\n sheets: z.array(PluginSpreadsheetCustomSheet),\n})\nexport type PluginProgramSpreadsheetListCustomSheetsResult = z.infer<\n typeof PluginProgramSpreadsheetListCustomSheetsResult\n>\n\n/**\n * A property a custom program sheet can show or group by.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Property id (built-in key, or a metadata `headerId`) |\n * | `name` | `string` | Display name |\n * | `groupable` | `boolean` | Whether the sheet can group by it |\n * | `custom` | `boolean` | `true` for program metadata headers, `false` for built-ins |\n */\nexport const PluginSpreadsheetCustomSheetProperty = z.object({\n id: z.string(),\n name: z.string(),\n groupable: z.boolean(),\n custom: z.boolean(),\n})\nexport type PluginSpreadsheetCustomSheetProperty = z.infer<\n typeof PluginSpreadsheetCustomSheetProperty\n>\n\n/**\n * Result of {@linkcode PluginProgramSpreadsheetApi.listCustomSheetProperties}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `properties` | {@linkcode PluginSpreadsheetCustomSheetProperty}`[]` | Built-ins first, then metadata headers |\n */\nexport const PluginProgramSpreadsheetListCustomSheetPropertiesResult = z.object({\n properties: z.array(PluginSpreadsheetCustomSheetProperty),\n})\nexport type PluginProgramSpreadsheetListCustomSheetPropertiesResult = z.infer<\n typeof PluginProgramSpreadsheetListCustomSheetPropertiesResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport { PluginAreaUnit } from \"./metrics\"\n\n/**\n * Program site — read the planning context of the project's site/plot.\n *\n * The **site** is the set of plot/parcel footprints the program is planned\n * against: their total area, per-parcel footprints (world XZ plan coordinates\n * in Snaptrude units — the same plan space as a space's `planPoints`), and —\n * when the project is geo-located on terrain — their geographic\n * (latitude/longitude) rings. This is the program-planning view of the site;\n * zoning numbers (FAR/FSI, height limits) live in the Site Analysis sheet and\n * are available through `getSiteAnalysis`, not the site-context reads. One\n * context exception is `listEdges`: it reads each parcel's per-edge\n * front/side/rear classification and effective base setbacks, the same\n * resolution the buildable-envelope setback pills show on canvas.\n *\n * Site context methods are total reads: they return plain records and never\n * throw — `get` returns an empty snapshot (zero totals) when there is no site,\n * and the `list` methods return `[]`. Site Analysis is the exception:\n * `getSiteAnalysis` reads the active proposal's persisted analysis sheet and\n * `updateSiteAnalysis` performs a safe merge/append write to that sheet.\n *\n * Accessed via `snaptrude.program.site`.\n */\nexport abstract class PluginProgramSiteApi {\n constructor() {}\n\n /**\n * Get a snapshot of the project site.\n *\n * @returns A {@linkcode PluginProgramSiteSnapshot} with the total site area,\n * parcel count, and each parcel's footprint. Empty (zero totals, no\n * parcels) when the project has no site.\n *\n * @examplePrompt What is my total site area?\n * @examplePrompt Give me a summary of the site\n * @examplePrompt How many site parcels are on the canvas?\n * @examplePrompt Show the site footprint and its area\n * @examplePrompt How big is the plot I'm building on?\n *\n * # Example\n * ```ts\n * const site = await snaptrude.program.site.get()\n * console.log(`${site.totalArea} ${site.units} across ${site.polygonCount} parcels`)\n * ```\n */\n public abstract get(): PluginApiReturn<PluginProgramSiteSnapshot>\n\n /**\n * Get just the total site area.\n *\n * A fast single-metric read — the same number as\n * {@linkcode PluginProgramSiteSnapshot.totalArea}, without computing the\n * per-parcel footprints.\n *\n * @returns A {@linkcode PluginProgramSiteAreaResult} with the total `area` and\n * its `units` (`0` when there is no site).\n *\n * @examplePrompt What's the total site area?\n * @examplePrompt How big is my plot in square metres?\n * @examplePrompt Total parcel area on the site\n *\n * # Example\n * ```ts\n * const { area, units } = await snaptrude.program.site.getArea()\n * ```\n */\n public abstract getArea(): PluginApiReturn<PluginProgramSiteAreaResult>\n\n /**\n * List the site parcels with their footprints (world XZ plan coordinates,\n * Snaptrude units).\n *\n * @returns A {@linkcode PluginProgramSiteListPolygonsResult} with each parcel's\n * id, area, label, and 2D footprint (XZ ground-plane points). Empty when the\n * project has no site.\n *\n * @examplePrompt List every site parcel\n * @examplePrompt Show me each plot's footprint\n * @examplePrompt Which site parcels are largest?\n * @examplePrompt Get the boundary points of the site polygons\n *\n * # Example\n * ```ts\n * const { polygons, units } = await snaptrude.program.site.listPolygons()\n * for (const p of polygons) console.log(p.label, p.area, units, p.footprint.length)\n * ```\n */\n public abstract listPolygons(): PluginApiReturn<PluginProgramSiteListPolygonsResult>\n\n /**\n * List each site parcel's boundary edges with their front / side / rear\n * classification — the same per-edge roles the buildable-envelope setback\n * pills show on canvas, so a plugin can pick up the user's edge tagging\n * without asking them to set it up again.\n *\n * Role resolution mirrors the canvas exactly: the user's per-edge overrides\n * (set via the setback pills) win over the tangent heuristic\n * (`roleSource: \"override\"` vs `\"heuristic\"`). `\"street\"` never appears — it\n * resolves to `front`. When a parcel has a linked zoning buildable envelope,\n * `setbackMeters` is the effective base setback of the edge (a per-edge\n * exception when one exists, else the role's value); parcels without a\n * zoning envelope classify by heuristic alone and report\n * `setbackMeters: null`.\n *\n * Coordinates are **world meters** (`lengthUnit: \"m\"`) — unlike\n * {@linkcode PluginProgramSiteApi.listPolygons} footprints, which are\n * Snaptrude units. `edgeIndex` is keyed to the canonicalized (sanitized,\n * CCW) boundary, the same index space the envelope's `edgeOverrides` use.\n * `staleOverridesDropped: true` means the site was edited after the\n * envelope was configured and the persisted overrides no longer align with\n * the live boundary — the heuristic classification is reported instead.\n *\n * @returns A {@linkcode PluginProgramSiteListEdgesResult} with one entry per\n * parcel. Empty when the project has no site.\n *\n * @examplePrompt Which edge of my site is the front?\n * @examplePrompt Read the front / side / rear edges of the plot\n * @examplePrompt What setback applies to each site edge?\n * @examplePrompt Get the site edges the way the setback pills show them\n *\n * # Example\n * ```ts\n * const { sites } = await snaptrude.program.site.listEdges()\n * for (const site of sites)\n * for (const e of site.edges)\n * console.log(e.edgeIndex, e.role, e.roleSource, e.setbackMeters)\n * ```\n */\n public abstract listEdges(): PluginApiReturn<PluginProgramSiteListEdgesResult>\n\n /**\n * List the site parcels as geographic latitude/longitude rings.\n *\n * Only available when the project is geo-located on terrain; returns `[]` (no\n * rings) otherwise. Use {@linkcode PluginProgramSiteApi.listPolygons} for the\n * world-coordinate footprints that are always available.\n *\n * @returns A {@linkcode PluginProgramSiteListGeoPolygonsResult} with one ring\n * of `{ lat, lng, alt }` points per parcel.\n *\n * @examplePrompt Give me the site polygons in latitude and longitude\n * @examplePrompt Export the plot boundary as geo-coordinates\n * @examplePrompt Where is the site located geographically?\n * @examplePrompt Get the site parcels as lat/long rings\n *\n * # Example\n * ```ts\n * const { polygons } = await snaptrude.program.site.listGeoPolygons()\n * const firstRing = polygons[0] // [{ lat, lng, alt }, ...]\n * ```\n */\n public abstract listGeoPolygons(): PluginApiReturn<PluginProgramSiteListGeoPolygonsResult>\n\n /**\n * Get the geo-referenced terrain context of the site.\n *\n * The map center and bounds, the terrain size in metres, the effective zoom,\n * and each parcel's geographic footprint and area — the context used for site\n * analysis. Returns `null` when the project is not geo-located on terrain.\n *\n * @returns A {@linkcode PluginProgramSiteContext}, or `null`.\n *\n * @examplePrompt What's the site's map center and bounds?\n * @examplePrompt How wide is the terrain in metres?\n * @examplePrompt Give me the parcel geometry and areas\n * @examplePrompt Where is the site located on the map?\n *\n * # Example\n * ```ts\n * const ctx = await snaptrude.program.site.getContext()\n * if (ctx) console.log(ctx.center, ctx.widthInM)\n * ```\n */\n public abstract getContext(): PluginApiReturn<PluginProgramSiteContextResult>\n\n /**\n * Get the project's geographic location.\n *\n * The latitude/longitude the site is geo-located at — the location the\n * `analysis.*` sun and daylight studies compute against. Returns `null`\n * when the project is not geo-located on terrain (there is no fallback\n * location).\n *\n * Note the spelling: this result uses the full `{ latitude, longitude }`\n * keys, while the older site reads\n * ({@linkcode PluginProgramSiteApi.getContext},\n * {@linkcode PluginProgramSiteApi.listGeoPolygons}) use the abbreviated\n * `{ lat, lng }`.\n *\n * @returns A {@linkcode PluginProgramSiteLocationResult} —\n * `{ latitude, longitude }`, or `null` when the project has no\n * geo-located site.\n *\n * @examplePrompt What's the project's latitude and longitude?\n * @examplePrompt Where is this site located?\n * @examplePrompt Get the geographic coordinates of the project\n *\n * # Example\n * ```ts\n * const location = await snaptrude.program.site.getLocation()\n * if (location) console.log(location.latitude, location.longitude)\n * ```\n */\n public abstract getLocation(): PluginApiReturn<PluginProgramSiteLocationResult>\n\n /**\n * Get the site's true-north angle.\n *\n * The angle in degrees the model's north (the scene's +Z axis) is rotated\n * from true north, derived from the terrain's rotation. `0` means the\n * model is aligned to true north. Reported in `[0, 360)`,\n * **clockwise-positive** viewed from above (Babylon's +Y rotation sign) —\n * note this is the opposite sign convention to `design.transform.rotate`,\n * where positive = CCW. Returns `null` when the project is not\n * geo-located on terrain.\n *\n * @returns A {@linkcode PluginProgramSiteNorthAngleResult} — the angle in\n * degrees (`[0, 360)`, clockwise-positive), or `null` when the project\n * has no geo-located site.\n *\n * @examplePrompt What's the north angle of the site?\n * @examplePrompt How far is the model rotated from true north?\n * @examplePrompt Get the project's true north direction\n *\n * # Example\n * ```ts\n * const northAngle = await snaptrude.program.site.getNorthAngle()\n * if (northAngle !== null) console.log(`${northAngle}° from true north`)\n * ```\n */\n public abstract getNorthAngle(): PluginApiReturn<PluginProgramSiteNorthAngleResult>\n\n /**\n * Get the IANA timezone of the project's geographic location.\n *\n * The timezone identifier (e.g. `\"America/New_York\"`, `\"Asia/Kolkata\"`)\n * looked up from the same geo-location\n * {@linkcode PluginProgramSiteApi.getLocation} returns. Returns `null` when\n * the project is not geo-located on terrain.\n *\n * @returns A {@linkcode PluginProgramSiteTimezoneResult} — the IANA timezone\n * id string, or `null` when the project has no geo-located site.\n *\n * @examplePrompt What timezone is this project in?\n * @examplePrompt Get the site's IANA timezone\n * @examplePrompt What's the local time zone at the project location?\n * @examplePrompt Which timezone should I use for the sun study schedule?\n *\n * # Example\n * ```ts\n * const timezone = await snaptrude.program.site.getTimezone()\n * if (timezone) console.log(`Project timezone: ${timezone}`)\n * ```\n */\n public abstract getTimezone(): PluginApiReturn<PluginProgramSiteTimezoneResult>\n\n /**\n * Get the weather file resolved for the project site.\n *\n * A lightweight read of the EPW weather file the `analysis.*` sun and\n * daylight studies compute against — its catalog id, station name, content\n * checksum, and how it was selected. This is the same file (same\n * `weatherFileId`) that {@linkcode PluginAnalysisWeatherApi.getSeries} reads\n * rows from and that a daylight run echoes in its provenance. Returns `null`\n * when the project has no geo-located site (no weather file to resolve).\n *\n * @returns A {@linkcode PluginProgramSiteWeatherResult} —\n * `{ weatherFileId, station, checksum, selectionMethod }`, or `null` when\n * the project has no geo-located site.\n *\n * @examplePrompt Which weather file is my project using?\n * @examplePrompt What EPW station was resolved for this site?\n * @examplePrompt Get the weather file id for the daylight analysis\n *\n * # Example\n * ```ts\n * const weather = await snaptrude.program.site.getWeather()\n * if (weather) console.log(weather.station, weather.selectionMethod)\n * ```\n */\n public abstract getWeather(): PluginApiReturn<PluginProgramSiteWeatherResult>\n\n /**\n * Read the persisted Site Analysis sheet for the active proposal.\n *\n * The read works without Program mode open. When a Program tab is open, the\n * host first makes a best-effort attempt to flush its pending edits. The\n * active proposal's sheet is preferred, with the base `\"Site Analysis\"`\n * sheet as fallback. Returns `null` when no parseable sheet is persisted.\n *\n * @returns The persisted site location, polygon, and constraint rows, or\n * `null` when no Site Analysis data exists. This read never throws.\n *\n * @examplePrompt Read the current Site Analysis sheet\n * @examplePrompt What zoning constraints are recorded for this proposal?\n *\n * # Example\n * ```ts\n * const analysis = await snaptrude.program.site.getSiteAnalysis()\n * if (analysis) console.log(analysis.sheetName, analysis.rows)\n * ```\n */\n public abstract getSiteAnalysis(): PluginApiReturn<PluginProgramSiteAnalysisResult>\n\n /**\n * Merge sourced site/zoning constraints into the active proposal's persisted\n * Site Analysis sheet.\n *\n * Rows match by trimmed, case-insensitive Category + Description. Matched\n * rows update only Quantity/Unit; unmatched rows append. Existing labels,\n * formatting, and unrelated rows remain intact. There is deliberately no\n * replace/reset mode — an appended duplicate row can NEVER be removed, so\n * there is no \"write now, fix later\". Send only the rows that were actually\n * sourced, ALWAYS call `getSiteAnalysis` first and reuse the sheet's exact\n * labels, and never use `program.spreadsheet.setValues` for Site Analysis\n * data — this method is the only correct writer. Check the result:\n * `updatedRows` should account for every row that had a template match; an\n * unexpected `appendedRows` usually means a label did not match the read's\n * vocabulary.\n *\n * When Program mode is open, the host writes through the live sheet. Without\n * a live tab it safely merges the persisted document. The call rejects rather\n * than risk overwriting unflushed live edits or an unparseable existing sheet.\n *\n * @param rows - One or more sourced constraint rows to merge or append.\n * @param options - Optional site location and JSON-encoded site polygon.\n * @returns The target sheet name and merge counts.\n * @throws PRECONDITION_FAILED when proposals exist but none is active, or\n * when live Program edits cannot be flushed safely.\n *\n * @examplePrompt Save these zoning constraints to Site Analysis\n * @examplePrompt Update the FAR and setbacks in the Site Analysis sheet\n *\n * # Example\n * ```ts\n * const result = await snaptrude.program.site.updateSiteAnalysis([\n * { category: \"FAR\", description: \"Floor Area Ratio\", quantity: 3.5, unit: \"ratio\" },\n * { category: \"Set backs\", description: \"Front setbacks\", quantity: 6, unit: \"m\" },\n * ])\n * console.log(result.sheetName, result.updatedRows, result.appendedRows)\n * ```\n */\n public abstract updateSiteAnalysis(\n rows: PluginSiteAnalysisRow[],\n options?: PluginSiteAnalysisUpdateOptions,\n ): PluginApiReturn<PluginProgramSiteAnalysisUpdateResult>\n}\n\n/**\n * A constraint row stored in the Site Analysis sheet. A `quantity` of `\"-\"`\n * means the value is not recorded yet — never echo `\"-\"` back as a sourced\n * value.\n */\nexport const PluginSiteAnalysisRow = z.object({\n category: z.string().min(1),\n description: z.string(),\n quantity: z.union([z.string(), z.number()]),\n unit: z.string(),\n})\nexport type PluginSiteAnalysisRow = z.infer<typeof PluginSiteAnalysisRow>\n\n/**\n * Optional site context persisted alongside Site Analysis constraint rows.\n * `null` and `undefined` are both accepted as \"no options\".\n */\nexport const PluginSiteAnalysisUpdateOptions = z\n .object({\n siteLocation: z.object({ lat: z.number(), lng: z.number() }).optional(),\n /** JSON-encoded polygon string written to the Site Polygon row. */\n sitePolygon: z.string().optional(),\n })\n .nullish()\nexport type PluginSiteAnalysisUpdateOptions = z.infer<\n typeof PluginSiteAnalysisUpdateOptions\n>\n\n/** Persisted Site Analysis data for the active proposal. */\nexport const PluginProgramSiteAnalysisResult = z\n .object({\n sheetName: z.string(),\n siteLocation: z.object({ lat: z.number(), lng: z.number() }).nullable(),\n sitePolygon: z.string().nullable(),\n rows: z.array(PluginSiteAnalysisRow),\n })\n .nullable()\nexport type PluginProgramSiteAnalysisResult = z.infer<\n typeof PluginProgramSiteAnalysisResult\n>\n\n/** Result of a merge/append Site Analysis write. */\nexport const PluginProgramSiteAnalysisUpdateResult = z.object({\n sheetName: z.string(),\n /** Constraint rows on the sheet after the write. */\n rowCount: z.number(),\n updatedRows: z.number(),\n appendedRows: z.number(),\n})\nexport type PluginProgramSiteAnalysisUpdateResult = z.infer<\n typeof PluginProgramSiteAnalysisUpdateResult\n>\n\n/**\n * A 2D ground-plane point of a site parcel footprint, in world XZ plan\n * coordinates (Snaptrude units) — the same plan space as a space's\n * `planPoints`, so site parcels overlay space footprints directly.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `x` | `number` | X coordinate (world, Snaptrude units) |\n * | `z` | `number` | Z coordinate (world, Snaptrude units) |\n */\nexport const PluginSiteFootprintPoint = z.object({\n x: z.number(),\n z: z.number(),\n})\nexport type PluginSiteFootprintPoint = z.infer<typeof PluginSiteFootprintPoint>\n\n/**\n * A geographic point of a site parcel ring (WGS84).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `lat` | `number` | Latitude (degrees) |\n * | `lng` | `number` | Longitude (degrees) |\n * | `alt` | `number` | Altitude (metres) |\n */\nexport const PluginSiteGeoPoint = z.object({\n lat: z.number(),\n lng: z.number(),\n alt: z.number(),\n})\nexport type PluginSiteGeoPoint = z.infer<typeof PluginSiteGeoPoint>\n\n/**\n * A single site parcel/polygon. Its `area` is in the enclosing result's `units`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Unique parcel/component id |\n * | `area` | `number` | Footprint area, in the result's `units` |\n * | `label` | `string` | Parcel label / room type (e.g. `\"site\"`) |\n * | `footprint` | {@linkcode PluginSiteFootprintPoint}`[]` | 2D ground-plane boundary points |\n */\nexport const PluginSitePolygon = z.object({\n id: z.string(),\n area: z.number(),\n label: z.string(),\n footprint: z.array(PluginSiteFootprintPoint),\n})\nexport type PluginSitePolygon = z.infer<typeof PluginSitePolygon>\n\n/**\n * Result of {@linkcode PluginProgramSiteApi.get}. Per the area-units convention,\n * `units` sits on this snapshot and every `area` below is reported in it.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `units` | `\"ft2\" \\| \"m2\"` | The unit all areas in this snapshot are reported in |\n * | `totalArea` | `number` | Total area of all site parcels |\n * | `polygonCount` | `number` | Number of site parcels |\n * | `polygons` | {@linkcode PluginSitePolygon}`[]` | Each site parcel with its footprint |\n */\nexport const PluginProgramSiteSnapshot = z.object({\n units: PluginAreaUnit,\n totalArea: z.number(),\n polygonCount: z.number(),\n polygons: z.array(PluginSitePolygon),\n})\nexport type PluginProgramSiteSnapshot = z.infer<\n typeof PluginProgramSiteSnapshot\n>\n\n/**\n * Result of {@linkcode PluginProgramSiteApi.getArea}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `units` | `\"ft2\" \\| \"m2\"` | The unit `area` is reported in |\n * | `area` | `number` | Total site area |\n */\nexport const PluginProgramSiteAreaResult = z.object({\n units: PluginAreaUnit,\n area: z.number(),\n})\nexport type PluginProgramSiteAreaResult = z.infer<\n typeof PluginProgramSiteAreaResult\n>\n\n/**\n * Result of {@linkcode PluginProgramSiteApi.listPolygons}. `units` sits on the\n * response; each parcel's `area` is in it.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `units` | `\"ft2\" \\| \"m2\"` | The unit every parcel `area` is reported in |\n * | `polygons` | {@linkcode PluginSitePolygon}`[]` | All site parcels |\n */\nexport const PluginProgramSiteListPolygonsResult = z.object({\n units: PluginAreaUnit,\n polygons: z.array(PluginSitePolygon),\n})\nexport type PluginProgramSiteListPolygonsResult = z.infer<\n typeof PluginProgramSiteListPolygonsResult\n>\n\n/**\n * Result of {@linkcode PluginProgramSiteApi.listGeoPolygons} — one ring of\n * geographic points per parcel (`[]` when the project is not geo-located).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `polygons` | {@linkcode PluginSiteGeoPoint}`[][]` | One ring per parcel; each ring is a list of geographic points |\n */\nexport const PluginProgramSiteListGeoPolygonsResult = z.object({\n polygons: z.array(z.array(PluginSiteGeoPoint)),\n})\nexport type PluginProgramSiteListGeoPolygonsResult = z.infer<\n typeof PluginProgramSiteListGeoPolygonsResult\n>\n\n/**\n * A geographic latitude/longitude point (no altitude).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `lat` | `number` | Latitude (degrees) |\n * | `lng` | `number` | Longitude (degrees) |\n */\nexport const PluginSiteLatLng = z.object({\n lat: z.number(),\n lng: z.number(),\n})\nexport type PluginSiteLatLng = z.infer<typeof PluginSiteLatLng>\n\n/**\n * Geographic bounds of the site terrain.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `north` | `number` | North latitude |\n * | `south` | `number` | South latitude |\n * | `east` | `number` | East longitude |\n * | `west` | `number` | West longitude |\n */\nexport const PluginSiteBounds = z.object({\n north: z.number(),\n south: z.number(),\n east: z.number(),\n west: z.number(),\n})\nexport type PluginSiteBounds = z.infer<typeof PluginSiteBounds>\n\n/**\n * A site parcel in the terrain context — geographic footprint and area.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `footprint` | {@linkcode PluginSiteLatLng}`[]` | Geographic boundary points |\n * | `area` | `number` | Parcel area (project area units) |\n */\nexport const PluginSiteContextParcel = z.object({\n footprint: z.array(PluginSiteLatLng),\n area: z.number(),\n})\nexport type PluginSiteContextParcel = z.infer<typeof PluginSiteContextParcel>\n\n/**\n * The geo-referenced terrain context of the site.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `center` | {@linkcode PluginSiteLatLng} | Map center |\n * | `bounds` | {@linkcode PluginSiteBounds} | Terrain bounds |\n * | `widthInM` | `number` | Terrain width in metres |\n * | `heightInM` | `number` | Terrain height in metres |\n * | `zoom` | `number` | Effective map zoom |\n * | `parcels` | {@linkcode PluginSiteContextParcel}`[]` | Site parcels with geographic footprints |\n */\nexport const PluginProgramSiteContext = z.object({\n center: PluginSiteLatLng,\n bounds: PluginSiteBounds,\n widthInM: z.number(),\n heightInM: z.number(),\n zoom: z.number(),\n parcels: z.array(PluginSiteContextParcel),\n})\nexport type PluginProgramSiteContext = z.infer<typeof PluginProgramSiteContext>\n\n/**\n * Result of {@linkcode PluginProgramSiteApi.getContext} — the terrain context, or\n * `null` when the project is not geo-located.\n */\nexport const PluginProgramSiteContextResult =\n PluginProgramSiteContext.nullable()\nexport type PluginProgramSiteContextResult = z.infer<\n typeof PluginProgramSiteContextResult\n>\n\n/**\n * The project's geographic location (WGS84).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `latitude` | `number` | Latitude (degrees) |\n * | `longitude` | `number` | Longitude (degrees) |\n */\nexport const PluginProgramSiteLocation = z.object({\n latitude: z.number(),\n longitude: z.number(),\n})\nexport type PluginProgramSiteLocation = z.infer<\n typeof PluginProgramSiteLocation\n>\n\n/**\n * Result of {@linkcode PluginProgramSiteApi.getLocation} — the location, or\n * `null` when the project is not geo-located.\n */\nexport const PluginProgramSiteLocationResult =\n PluginProgramSiteLocation.nullable()\nexport type PluginProgramSiteLocationResult = z.infer<\n typeof PluginProgramSiteLocationResult\n>\n\n/**\n * Result of {@linkcode PluginProgramSiteApi.getNorthAngle} — the true-north\n * angle in degrees (`[0, 360)`, clockwise-positive), or `null` when the\n * project is not geo-located.\n */\nexport const PluginProgramSiteNorthAngleResult = z.number().nullable()\nexport type PluginProgramSiteNorthAngleResult = z.infer<\n typeof PluginProgramSiteNorthAngleResult\n>\n\n/**\n * Result of {@linkcode PluginProgramSiteApi.getTimezone} — the IANA timezone\n * id of the project's geo-location (e.g. `\"Europe/London\"`), or `null` when\n * the project is not geo-located.\n */\nexport const PluginProgramSiteTimezoneResult = z.string().nullable()\nexport type PluginProgramSiteTimezoneResult = z.infer<\n typeof PluginProgramSiteTimezoneResult\n>\n\n/**\n * A summary of the weather file resolved for the project site. Matches the\n * relevant fields of `analysis.weather`'s `PluginWeatherSourceMeta`;\n * `selectionMethod` is one of `\"nearest-station\"`, `\"user-upload\"`, or\n * `\"project-override\"`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `weatherFileId` | `string` | Catalog id (same key as `core.io.import.epw`) |\n * | `station` | `string` | Station/file name |\n * | `checksum` | `string` | sha256 of the EPW file |\n * | `selectionMethod` | `\"nearest-station\" \\| \"user-upload\" \\| \"project-override\"` | How the file was matched |\n */\nexport const PluginProgramSiteWeather = z.object({\n weatherFileId: z.string(),\n station: z.string(),\n checksum: z.string(),\n selectionMethod: z.enum([\n \"nearest-station\",\n \"user-upload\",\n \"project-override\",\n ]),\n})\nexport type PluginProgramSiteWeather = z.infer<typeof PluginProgramSiteWeather>\n\n/**\n * Result of {@linkcode PluginProgramSiteApi.getWeather} — the resolved weather\n * file summary, or `null` when the project has no geo-located site.\n */\nexport const PluginProgramSiteWeatherResult =\n PluginProgramSiteWeather.nullable()\nexport type PluginProgramSiteWeatherResult = z.infer<\n typeof PluginProgramSiteWeatherResult\n>\n\n/**\n * Front / side / rear classification of one site-boundary edge. `\"street\"`\n * never appears — the backend synonym resolves to `front`.\n */\nexport const PluginProgramSiteEdgeRole = z.enum([\"front\", \"side\", \"rear\"])\nexport type PluginProgramSiteEdgeRole = z.infer<\n typeof PluginProgramSiteEdgeRole\n>\n\n/**\n * One boundary edge of a site parcel, as {@linkcode PluginProgramSiteApi.listEdges}\n * returns it. Coordinates and lengths are world meters.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `edgeIndex` | `number` | Index in the canonicalized (sanitized, CCW) boundary — the same index space the envelope's `edgeOverrides` use |\n * | `start` / `end` | `{ x, z }` | Edge endpoints, world XZ meters |\n * | `lengthMeters` | `number` | Edge length in meters |\n * | `role` | `\"front\" \\| \"side\" \\| \"rear\"` | Resolved classification (user override wins over the heuristic) |\n * | `roleSource` | `\"override\" \\| \"heuristic\"` | Whether the user reassigned this edge's role or the tangent heuristic classified it |\n * | `setbackMeters` | `number \\| null` | Effective base setback (per-edge exception ?? role value); `null` when the parcel has no zoning envelope |\n */\nexport const PluginProgramSiteEdge = z.object({\n edgeIndex: z.number().int().nonnegative(),\n start: z.object({ x: z.number(), z: z.number() }),\n end: z.object({ x: z.number(), z: z.number() }),\n lengthMeters: z.number(),\n role: PluginProgramSiteEdgeRole,\n roleSource: z.enum([\"override\", \"heuristic\"]),\n setbackMeters: z.number().nullable(),\n})\nexport type PluginProgramSiteEdge = z.infer<typeof PluginProgramSiteEdge>\n\n/**\n * Per-parcel entry of {@linkcode PluginProgramSiteApi.listEdges}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `siteId` | `string` | The parcel's component id (same id space as `listPolygons`) |\n * | `buildableEnvelopeId` | `string \\| null` | Linked zoning envelope, when one exists |\n * | `usingLiveSiteBoundary` | `boolean` | `true` when edges come from the live site boundary; `false` when only the envelope's persisted source polygon was resolvable |\n * | `staleOverridesDropped` | `boolean` | `true` when persisted edge overrides no longer index-align with the live boundary and were ignored |\n * | `edges` | `PluginProgramSiteEdge[]` | The classified boundary edges |\n */\nexport const PluginProgramSiteEdgesEntry = z.object({\n siteId: z.string(),\n buildableEnvelopeId: z.string().nullable(),\n usingLiveSiteBoundary: z.boolean(),\n staleOverridesDropped: z.boolean(),\n edges: z.array(PluginProgramSiteEdge),\n})\nexport type PluginProgramSiteEdgesEntry = z.infer<\n typeof PluginProgramSiteEdgesEntry\n>\n\n/**\n * Result of {@linkcode PluginProgramSiteApi.listEdges} — one entry per site\n * parcel; empty when the project has no site. `lengthUnit` is always `\"m\"`.\n */\nexport const PluginProgramSiteListEdgesResult = z.object({\n lengthUnit: z.literal(\"m\"),\n sites: z.array(PluginProgramSiteEdgesEntry),\n})\nexport type PluginProgramSiteListEdgesResult = z.infer<\n typeof PluginProgramSiteListEdgesResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * Program cores — the building's vertical-circulation cores (lift / stair / shaft\n * shells).\n *\n * Read the cores in the scene and test whether a space is a core. Both are reads;\n * there is no core-generation trigger on this surface (core geometry is owned by\n * the geometry layer, not here).\n *\n * Accessed via `snaptrude.program.cores`.\n */\nexport abstract class PluginProgramCoresApi {\n constructor() {}\n\n /**\n * List the cores currently in the building.\n *\n * @returns A {@linkcode PluginProgramCoresListResult} with each core's id,\n * core group, and storey (empty when there are none).\n *\n * @examplePrompt List the cores in the building\n * @examplePrompt How many cores are placed?\n * @examplePrompt Show me the lift and stair cores\n * @examplePrompt Which storeys have cores?\n *\n * # Example\n * ```ts\n * const { cores } = await snaptrude.program.cores.list()\n * ```\n */\n public abstract list(): PluginApiReturn<PluginProgramCoresListResult>\n\n /**\n * Test whether a space is a circulation core element.\n *\n * @param id - The id of the space to test.\n * @returns `true` if the space is a core element.\n *\n * @examplePrompt Is this space a core?\n * @examplePrompt Does this mass belong to the core?\n * @examplePrompt Check if space sp_1 is a circulation core\n *\n * # Example\n * ```ts\n * const isCore = await snaptrude.program.cores.isCore(\"sp_1\")\n * ```\n */\n public abstract isCore(id: string): PluginApiReturn<boolean>\n}\n\n/**\n * A circulation core element in the building.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Component id of the core element |\n * | `groupId` | `string` | Id of the core group this element belongs to |\n * | `storey` | `number` | Storey (floor number) the element sits on |\n */\nexport const PluginProgramCore = z.object({\n id: z.string(),\n groupId: z.string(),\n storey: z.number(),\n})\nexport type PluginProgramCore = z.infer<typeof PluginProgramCore>\n\n/** Result of {@linkcode PluginProgramCoresApi.list}. */\nexport const PluginProgramCoresListResult = z.object({\n cores: z.array(PluginProgramCore),\n})\nexport type PluginProgramCoresListResult = z.infer<\n typeof PluginProgramCoresListResult\n>\n\n/** Arguments for {@linkcode PluginProgramCoresApi.isCore}. */\nexport const PluginProgramCoresIsCoreArgs = z.object({\n id: z.string(),\n})\nexport type PluginProgramCoresIsCoreArgs = z.infer<\n typeof PluginProgramCoresIsCoreArgs\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * Program classification — the catalog of space-classification options, and a\n * space's display tags.\n *\n * Only the genuinely program-specific reads live here: the selectable\n * space-type / area-class / mass-type options, and a space's tags for display.\n * Reading or changing a *single space's* classification (its type, label, area\n * class, or department) is done through the space reads/writes, not duplicated\n * here. Tag definitions are managed elsewhere — these methods are display-only.\n *\n * Both methods are reads.\n *\n * Accessed via `snaptrude.program.classification`.\n */\nexport abstract class PluginProgramClassificationApi {\n constructor() {}\n\n /**\n * List the selectable classification options for spaces.\n *\n * The static catalog (project-independent) of space types, area classes, and\n * mass types a space can be classified as.\n *\n * @returns A {@linkcode PluginProgramClassificationTypesResult} with the\n * `spaceTypes`, `areaClasses`, and `massTypes` option lists.\n *\n * @examplePrompt What space types can I assign?\n * @examplePrompt List the available area classifications\n * @examplePrompt What classification options exist?\n * @examplePrompt Show me the valid space-type values\n *\n * # Example\n * ```ts\n * const { spaceTypes, areaClasses } = await snaptrude.program.classification.listTypes()\n * ```\n */\n public abstract listTypes(): PluginApiReturn<PluginProgramClassificationTypesResult>\n\n /**\n * List a space's tags for display, grouped by category.\n *\n * @param id - The id of the space whose tags to read.\n * @returns A {@linkcode PluginProgramClassificationTagsResult} with the space's\n * `tagIds` and `tagsByCategory`.\n *\n * @examplePrompt What tags are on this space?\n * @examplePrompt Show the tags grouped by category for sp_4\n * @examplePrompt List this room's display tags\n * @examplePrompt Which tags does this space have?\n *\n * # Example\n * ```ts\n * const { tagsByCategory } = await snaptrude.program.classification.listTags(\"sp_4\")\n * ```\n */\n public abstract listTags(\n id: string,\n ): PluginApiReturn<PluginProgramClassificationTagsResult>\n}\n\n/**\n * A selectable classification option.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `value` | `string` | Stored value |\n * | `label` | `string` | Display label |\n */\nexport const PluginClassificationOption = z.object({\n value: z.string(),\n label: z.string(),\n})\nexport type PluginClassificationOption = z.infer<\n typeof PluginClassificationOption\n>\n\n/**\n * Result of {@linkcode PluginProgramClassificationApi.listTypes}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaceTypes` | {@linkcode PluginClassificationOption}`[]` | Selectable space types |\n * | `areaClasses` | {@linkcode PluginClassificationOption}`[]` | Selectable area classes (NET/GROSS/EXCLUDED) |\n * | `massTypes` | {@linkcode PluginClassificationOption}`[]` | Selectable mass/object types |\n */\nexport const PluginProgramClassificationTypesResult = z.object({\n spaceTypes: z.array(PluginClassificationOption),\n areaClasses: z.array(PluginClassificationOption),\n massTypes: z.array(PluginClassificationOption),\n})\nexport type PluginProgramClassificationTypesResult = z.infer<\n typeof PluginProgramClassificationTypesResult\n>\n\n/** Arguments for {@linkcode PluginProgramClassificationApi.listTags}. */\nexport const PluginProgramClassificationListTagsArgs = z.object({\n id: z.string(),\n})\nexport type PluginProgramClassificationListTagsArgs = z.infer<\n typeof PluginProgramClassificationListTagsArgs\n>\n\n/**\n * A display tag on a space.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `tagId` | `string` | Tag id |\n * | `tagName` | `string` | Tag display name |\n * | `color` | `string` | CSS hex color string |\n * | `categoryId` | `string` | Id of the tag's category |\n */\nexport const PluginSpaceTag = z.object({\n tagId: z.string(),\n tagName: z.string(),\n color: z.string(),\n categoryId: z.string(),\n})\nexport type PluginSpaceTag = z.infer<typeof PluginSpaceTag>\n\n/**\n * Result of {@linkcode PluginProgramClassificationApi.listTags}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `tagIds` | `string[]` | All tag ids on the space |\n * | `tagsByCategory` | `Record<string, `{@linkcode PluginSpaceTag}`>` | Tags keyed by category name |\n */\nexport const PluginProgramClassificationTagsResult = z.object({\n tagIds: z.array(z.string()),\n tagsByCategory: z.record(z.string(), PluginSpaceTag),\n})\nexport type PluginProgramClassificationTagsResult = z.infer<\n typeof PluginProgramClassificationTagsResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport { PluginAreaUnit } from \"./metrics\"\nimport { PluginAreaClass } from \"../entity/space\"\nimport { ComponentHandle } from \"../../handles\"\n\n/**\n * Program areas — the FAR / built-up-area rollup for the active program.\n *\n * This is the zoning-compliance lens on the model: carpet / built-up / excluded\n * area totals, the site area, and the achieved-vs-target **FAR** (Floor Area\n * Ratio). It complements {@linkcode PluginProgramMetricsApi}, which is the\n * department-allocation lens (targets vs allocated area per department); this\n * namespace is the whole-program area/FAR summary and its groupings.\n *\n * **Net vs gross:** the `carpet` / `builtUp` / `excluded` figures are the three\n * area *classes* — every space's whole footprint lands in exactly one, chosen\n * by its `areaClass` (see {@linkcode PluginProgramAreaMember.areaClass}). In UI\n * terms `carpet` is the **Net Internal Area** (Σ of `NET`-classed spaces), and\n * `builtUp` is the **Gross External Area** (Σ of `GROSS`-classed spaces) — so\n * \"net\" and \"gross\" are the plain-English synonyms for `carpet` and `builtUp`.\n * These are not wall-thickness carpet-vs-super-built-up; there is no common-area\n * math. Note the summary also folds in non-mass elements (floors count as NET,\n * slabs as GROSS), so **summing `listMembers` spaces does not reconcile to the\n * summary totals**.\n *\n * `achievedFar` is **derived and read-only** — computed from built-up area and\n * site area. `targetFar` is the goal you set (via {@linkcode\n * PluginProgramAreasApi.update}); the two are the same ratio, one measured and\n * one intended. The surface speaks **FAR only** — there is no FSI alias.\n *\n * Reads return plain records and never throw. Because the engine only recomputes\n * the breakdown live while the Area sidebar is open, a headless plugin should call\n * {@linkcode PluginProgramAreasApi.refresh} first to guarantee the reads are fresh.\n * The only write — {@linkcode PluginProgramAreasApi.update} — is a non-undoable\n * settings write for the FAR inputs. With an active design option (proposal),\n * those inputs are per-option: `update` writes and the reads report the active\n * option's own target FAR / site area / deviation, matching the product's Area\n * settings dialog.\n *\n * Accessed via `snaptrude.program.areas`.\n */\nexport abstract class PluginProgramAreasApi {\n constructor() {}\n\n /**\n * Get the program area summary.\n *\n * Returns the whole-program totals — carpet / built-up / excluded area, site\n * area, and achieved vs target FAR — with the area unit they are reported in.\n *\n * @returns A {@linkcode PluginProgramAreaSummary}. `achievedFar` is `null` when\n * the site area or target FAR is not set.\n *\n * @examplePrompt What's my FAR right now?\n * @examplePrompt Give me the program area summary\n * @examplePrompt What is my net internal area vs gross external area?\n * @examplePrompt How much net and gross area do I have?\n * @examplePrompt How much built-up area have I used against the site area?\n * @examplePrompt Am I within my FAR target?\n *\n * # Example\n * ```ts\n * const s = await snaptrude.program.areas.get()\n * console.log(s.builtUp, s.siteArea, s.achievedFar, s.targetFar, s.areaUnit)\n * ```\n */\n public abstract get(): PluginApiReturn<PluginProgramAreasGetResult>\n\n /**\n * List the program area rollup grouped by a facet.\n *\n * Breaks the program's area totals into buckets — by label, storey, building,\n * department, or tag category.\n *\n * @param groupBy - The facet to group by (a {@linkcode PluginProgramAreaGroupBy}).\n * @param categoryId - The tag category to group by (required when `groupBy` is\n * `\"tagCategories\"`).\n * @returns A {@linkcode PluginProgramAreasListResult} with a `groups` array\n * (empty when the program has no spaces).\n *\n * @examplePrompt Break down the area by storey\n * @examplePrompt Break down net vs gross area by storey\n * @examplePrompt Show the area rollup per department\n * @examplePrompt Group the built-up area by building\n *\n * # Example\n * ```ts\n * const { groups } = await snaptrude.program.areas.list(\"storeys\")\n * for (const g of groups) console.log(g.name, g.builtUp, g.count)\n * ```\n */\n public abstract list(\n groupBy: PluginProgramAreaGroupBy,\n categoryId?: string,\n ): PluginApiReturn<PluginProgramAreasListResult>\n\n /**\n * List the spaces that make up one area group.\n *\n * Drills into a single bucket returned by {@linkcode\n * PluginProgramAreasApi.list} and returns its member spaces with their per-space\n * area figures.\n *\n * @param groupBy - The same facet used to produce the group (a {@linkcode\n * PluginProgramAreaGroupBy}).\n * @param groupId - The id of the group to expand.\n * @param categoryId - The tag category context (required when `groupBy` is\n * `\"tagCategories\"`).\n * @returns A {@linkcode PluginProgramAreasListMembersResult} with a `members`\n * array (empty when the group is missing or empty).\n *\n * @examplePrompt List the spaces on this storey with their areas\n * @examplePrompt Which spaces are net and which are gross on this storey?\n * @examplePrompt What rooms are in the Bedrooms department bucket?\n * @examplePrompt Break the storey area down into individual spaces\n *\n * # Example\n * ```ts\n * const { members } = await snaptrude.program.areas.listMembers(\"storeys\", \"st_2\")\n * for (const m of members) console.log(m.label, m.carpet)\n * ```\n */\n public abstract listMembers(\n groupBy: PluginProgramAreaGroupBy,\n groupId: string,\n categoryId?: string,\n ): PluginApiReturn<PluginProgramAreasListMembersResult>\n\n /**\n * Update the program's FAR settings.\n *\n * A **sparse** update of the program-level FAR inputs — target FAR, the site\n * area, and the allowed deviation. This is a **non-undoable settings write**\n * (persisted with the project). `achievedFar` recomputes from these.\n *\n * **Design options:** when a design option (proposal) is active, the FAR\n * inputs are **scoped to that design option** — exactly like the product's\n * Area settings dialog. `update` writes the active option's settings (each\n * option keeps its own target FAR / site area / deviation) and the reads\n * ({@linkcode PluginProgramAreasApi.get} etc.) report that same scope.\n * Without design options there is a single project-wide set of FAR inputs.\n *\n * @param options - Any of `targetFar`, `siteArea`, `deviation`; omitted fields\n * are left unchanged.\n * @returns The recomputed {@linkcode PluginProgramAreaSummary}.\n * @throws If a value is out of range.\n *\n * @examplePrompt Set the target FAR to 2.5\n * @examplePrompt Override the site area to 5000\n * @examplePrompt Allow a 10 percent FAR deviation\n *\n * # Example\n * ```ts\n * const s = await snaptrude.program.areas.update({ targetFar: 2.5 })\n * console.log(s.achievedFar, s.targetFar)\n * ```\n */\n public abstract update(options?: {\n targetFar?: number\n siteArea?: number\n deviation?: number\n }): PluginApiReturn<PluginProgramAreasUpdateResult>\n\n /**\n * Force the area breakdown to recompute, then return the fresh summary.\n *\n * The engine recomputes the carpet / built-up / FAR totals live **only while the\n * Area sidebar is open**. A plugin running with the sidebar closed can therefore\n * read a stale — or never-computed — snapshot from {@linkcode\n * PluginProgramAreasApi.get} / {@linkcode PluginProgramAreasApi.list}. Call\n * `refresh` first to guarantee those reads reflect the current model. It is a\n * read (no write gate) — it recomputes derived totals, it does not change the model.\n *\n * **Cost:** a full pass over every space, floor and mass in the active structure.\n * Not free on large models — call it once before a batch of reads, not per read.\n *\n * @returns The freshly-recomputed {@linkcode PluginProgramAreaSummary}.\n *\n * @examplePrompt Recompute the area totals before I read them\n * @examplePrompt Refresh the FAR numbers\n * @examplePrompt Make sure the area breakdown is up to date\n *\n * # Example\n * ```ts\n * const s = await snaptrude.program.areas.refresh()\n * const { groups } = await snaptrude.program.areas.list(\"storeys\") // now fresh\n * console.log(s.builtUp, groups.length)\n * ```\n */\n public abstract refresh(): PluginApiReturn<PluginProgramAreasRefreshResult>\n}\n\n/**\n * The whole-program area / FAR summary. All area figures are in `areaUnit`;\n * ratios (`achievedFar`, `targetFar`, `deviation`) are unitless.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `count` | `number` | Number of spaces in the program (a count, not an area) |\n * | `hiddenCount` | `number` | Number of those spaces that are currently hidden |\n * | `carpet` | `number` | Total **Net Internal Area** (\"net\") — Σ `NET`-classed spaces + floors — in `areaUnit` |\n * | `builtUp` | `number` | Total **Gross External Area** (\"gross\") — Σ `GROSS`-classed spaces + slabs — in `areaUnit` |\n * | `excluded` | `number` | Total **Excluded Area** — Σ `EXCLUDED`-classed spaces — in `areaUnit` |\n * | `farBuiltUp` | `number` | Built-up area that counts toward FAR, in `areaUnit` |\n * | `siteArea` | `number` | Site/plot area in `areaUnit` |\n * | `achievedFar` | `number \\| null` | Derived FAR (`farBuiltUp / siteArea`); `null` when site area or target FAR is unset |\n * | `targetFar` | `number` | The target FAR goal (set via `update`) |\n * | `deviation` | `number` | Allowed FAR deviation, as a percentage |\n * | `areaUnit` | `\"ft2\" \\| \"m2\"` | The unit every area figure above is reported in |\n */\nexport const PluginProgramAreaSummary = z.object({\n count: z.number().int(),\n hiddenCount: z.number().int(),\n carpet: z.number(),\n builtUp: z.number(),\n excluded: z.number(),\n farBuiltUp: z.number(),\n siteArea: z.number(),\n achievedFar: z.number().nullable(),\n targetFar: z.number(),\n deviation: z.number(),\n areaUnit: PluginAreaUnit,\n})\nexport type PluginProgramAreaSummary = z.infer<typeof PluginProgramAreaSummary>\n\n/**\n * One bucket of the area rollup. Carpet / built-up / excluded figures are in the\n * program's area unit (see {@linkcode PluginProgramAreaSummary.areaUnit});\n * `targetArea` is in `targetUnit`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `groupId` | `string` | Stable id of the group (e.g. storey id, department id) |\n * | `name` | `string` | Display name of the group |\n * | `color` | `string` | CSS hex color string for the group |\n * | `carpet` | `number` | Net Internal Area (\"net\") for this group |\n * | `builtUp` | `number` | Gross External Area (\"gross\") for this group |\n * | `excluded` | `number` | Excluded area for this group |\n * | `count` | `number` | Number of spaces in this group |\n * | `targetArea` | `number` | Target area for this group, in `targetUnit` |\n * | `targetUnit` | `\"ft2\" \\| \"m2\"` | The unit `targetArea` is reported in |\n */\nexport const PluginProgramAreaGroup = z.object({\n groupId: z.string(),\n name: z.string(),\n color: z.string(),\n carpet: z.number(),\n builtUp: z.number(),\n excluded: z.number(),\n count: z.number().int(),\n targetArea: z.number(),\n targetUnit: PluginAreaUnit,\n})\nexport type PluginProgramAreaGroup = z.infer<typeof PluginProgramAreaGroup>\n\n/**\n * A single space within an area group. Area figures are in the program's area\n * unit (see {@linkcode PluginProgramAreaSummary.areaUnit}).\n *\n * `areaClass` is the space's effective classification — it decides which of the\n * three buckets the space's whole footprint lands in: `NET` → `carpet` (\"net\"),\n * `GROSS` → `builtUp` (\"gross\"), `EXCLUDED` → `excluded`. So for any one member\n * exactly one of `carpet` / `builtUp` / `excluded` is non-zero.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `ComponentHandle` | `Component.id` of the space (never a mesh handle) |\n * | `label` | `string` | Display label of the space |\n * | `areaClass` | {@linkcode PluginAreaClass} | Effective area class — `\"NET\"` (→ `carpet`), `\"GROSS\"` (→ `builtUp`), or `\"EXCLUDED\"` |\n * | `carpet` | `number` | Net area of the space (non-zero when `areaClass` is `\"NET\"`) |\n * | `builtUp` | `number` | Gross area of the space (non-zero when `areaClass` is `\"GROSS\"`) |\n * | `excluded` | `number` | Excluded area of the space (non-zero when `areaClass` is `\"EXCLUDED\"`) |\n * | `isVisible` | `boolean` | Whether the space is currently visible |\n */\nexport const PluginProgramAreaMember = z.object({\n id: ComponentHandle,\n label: z.string(),\n areaClass: PluginAreaClass,\n carpet: z.number(),\n builtUp: z.number(),\n excluded: z.number(),\n isVisible: z.boolean(),\n})\nexport type PluginProgramAreaMember = z.infer<typeof PluginProgramAreaMember>\n\n/** The facet to group the area rollup by. */\nexport const PluginProgramAreaGroupBy = z.enum([\n \"labels\",\n \"storeys\",\n \"buildings\",\n \"departments\",\n \"tagCategories\",\n])\nexport type PluginProgramAreaGroupBy = z.infer<typeof PluginProgramAreaGroupBy>\n\n/** Result of {@linkcode PluginProgramAreasApi.get} — the program area summary. */\nexport const PluginProgramAreasGetResult = PluginProgramAreaSummary\nexport type PluginProgramAreasGetResult = z.infer<\n typeof PluginProgramAreasGetResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramAreasApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `groupBy` | {@linkcode PluginProgramAreaGroupBy} | The facet to group by |\n * | `categoryId` | `string \\| undefined` | Tag category to group by; required when `groupBy` is `\"tagCategories\"` |\n */\nexport const PluginProgramAreasListArgs = z\n .object({\n groupBy: PluginProgramAreaGroupBy,\n categoryId: z.string().optional(),\n })\n .refine((v) => v.groupBy !== \"tagCategories\" || v.categoryId != null, {\n message: 'categoryId is required when groupBy is \"tagCategories\"',\n })\nexport type PluginProgramAreasListArgs = z.infer<\n typeof PluginProgramAreasListArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramAreasApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `groups` | {@linkcode PluginProgramAreaGroup}`[]` | The area buckets (empty when the program has no spaces) |\n */\nexport const PluginProgramAreasListResult = z.object({\n groups: z.array(PluginProgramAreaGroup),\n})\nexport type PluginProgramAreasListResult = z.infer<\n typeof PluginProgramAreasListResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramAreasApi.listMembers}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `groupBy` | {@linkcode PluginProgramAreaGroupBy} | The facet the group came from |\n * | `groupId` | `string` | The id of the group to expand |\n * | `categoryId` | `string \\| undefined` | Tag category context; required when `groupBy` is `\"tagCategories\"` |\n */\nexport const PluginProgramAreasListMembersArgs = z\n .object({\n groupBy: PluginProgramAreaGroupBy,\n groupId: z.string(),\n categoryId: z.string().optional(),\n })\n .refine((v) => v.groupBy !== \"tagCategories\" || v.categoryId != null, {\n message: 'categoryId is required when groupBy is \"tagCategories\"',\n })\nexport type PluginProgramAreasListMembersArgs = z.infer<\n typeof PluginProgramAreasListMembersArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramAreasApi.listMembers}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `members` | {@linkcode PluginProgramAreaMember}`[]` | The group's member spaces (empty when missing or empty) |\n */\nexport const PluginProgramAreasListMembersResult = z.object({\n members: z.array(PluginProgramAreaMember),\n})\nexport type PluginProgramAreasListMembersResult = z.infer<\n typeof PluginProgramAreasListMembersResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramAreasApi.update}. Sparse — only the\n * provided fields are changed.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `targetFar` | `number \\| undefined` | New target FAR (≥ 0) |\n * | `siteArea` | `number \\| undefined` | Site-area override (> 0), in the program's area unit |\n * | `deviation` | `number \\| undefined` | Allowed FAR deviation percentage (0–100) |\n */\nexport const PluginProgramAreasUpdateArgs = z.object({\n targetFar: z.number().min(0).optional(),\n siteArea: z.number().positive().optional(),\n deviation: z.number().min(0).max(100).optional(),\n})\nexport type PluginProgramAreasUpdateArgs = z.infer<\n typeof PluginProgramAreasUpdateArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramAreasApi.update} — the recomputed program\n * area summary.\n */\nexport const PluginProgramAreasUpdateResult = PluginProgramAreaSummary\nexport type PluginProgramAreasUpdateResult = z.infer<\n typeof PluginProgramAreasUpdateResult\n>\n\n/**\n * Result of {@linkcode PluginProgramAreasApi.refresh} — the freshly-recomputed\n * program area summary.\n */\nexport const PluginProgramAreasRefreshResult = PluginProgramAreaSummary\nexport type PluginProgramAreasRefreshResult = z.infer<\n typeof PluginProgramAreasRefreshResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport { ComponentHandle } from \"../../handles\"\nimport { PluginSpreadsheetCell } from \"./spreadsheet\"\n\n/**\n * Program metadata — the user-defined **custom columns** of the Program Data\n * sheet and their per-space values.\n *\n * In program mode a **metadata header** is a custom column (e.g. \"Department\n * Code\", \"Occupancy\", \"Finish Level\") added to the Program Data sheet; every\n * space can then carry one **value** per header. This is the same store the\n * Program tab reads and writes — values set here appear in the default Program\n * Data sheet and in every custom sheet that shows the column, and values typed\n * into the sheet are read back here. The store lives in the editor, so **no\n * method in this namespace needs the Program tab open**.\n *\n * Headers are identified by a stable `headerId`. Values are plain cells\n * (`string | number | boolean | null`); `null` clears a value. Departments and\n * labels also carry metadata in the engine, but this namespace covers **spaces**\n * (Mass/Floor components) — the per-space case plugins asked for.\n *\n * Reads never throw for a miss (`get` returns `null`, `list*` return `[]`).\n * Writes are undoable and autosaved.\n *\n * Accessed via `snaptrude.program.metadata`.\n */\nexport abstract class PluginProgramMetadataApi {\n constructor() {}\n\n /**\n * List the metadata headers (custom columns) of the Program Data sheet.\n *\n * @returns A {@linkcode PluginProgramMetadataListHeadersResult} with a\n * `headers` array (empty when the project has no custom columns).\n *\n * @examplePrompt List the custom metadata columns in program mode\n * @examplePrompt What metadata headers does the program sheet have?\n * @examplePrompt Show every custom column with its id\n *\n * # Example\n * ```ts\n * const { headers } = await snaptrude.program.metadata.listHeaders()\n * for (const h of headers) console.log(h.headerId, h.name)\n * ```\n */\n public abstract listHeaders(): PluginApiReturn<PluginProgramMetadataListHeadersResult>\n\n /**\n * Create a metadata header (a custom column on the Program Data sheet).\n *\n * Header names are unique per sheet: when a header with the same name already\n * exists it is **reused** and returned (no duplicate column is created), so\n * re-running a plugin is safe. The new column is appended after the sheet's\n * existing custom columns.\n *\n * @param name - Column header text (non-empty).\n * @param options - Optional `description` shown in the column's description\n * row.\n * @returns The created (or reused) {@linkcode PluginProgramMetadataHeader}.\n * @throws If `name` is blank.\n *\n * @examplePrompt Add a custom column called Occupancy to the program sheet\n * @examplePrompt Create a metadata header named Department Code\n * @examplePrompt Make a new program-mode column for Finish Level\n *\n * # Example\n * ```ts\n * const header = await snaptrude.program.metadata.createHeader(\"Occupancy\", {\n * description: \"People per room\",\n * })\n * ```\n */\n public abstract createHeader(\n name: string,\n options?: { description?: string },\n ): PluginApiReturn<PluginProgramMetadataCreateHeaderResult>\n\n /**\n * Get one space's metadata values, keyed by header id.\n *\n * @param spaceId - The space (component id) to read.\n * @returns A {@linkcode PluginProgramMetadataRecord} — `values` holds one entry\n * per header the space has a value for — or `null` when no space has that id.\n *\n * @examplePrompt Get the metadata on this space\n * @examplePrompt Read the custom column values for space sp_12\n * @examplePrompt What is this room's Occupancy metadata?\n *\n * # Example\n * ```ts\n * const record = await snaptrude.program.metadata.get(\"sp_12\")\n * if (record) console.log(record.values)\n * ```\n */\n public abstract get(\n spaceId: ComponentHandle,\n ): PluginApiReturn<PluginProgramMetadataGetResult>\n\n /**\n * List metadata values for many spaces at once.\n *\n * Pass `spaceIds` to read specific spaces (unknown ids are skipped), or omit\n * it to read every space in the project. One host round-trip regardless of\n * the number of spaces — prefer this over calling\n * {@linkcode PluginProgramMetadataApi.get} in a loop.\n *\n * @param spaceIds - Optional space ids to read; omit for all spaces.\n * @returns A {@linkcode PluginProgramMetadataListResult} with one\n * {@linkcode PluginProgramMetadataRecord} per space.\n *\n * @performance One round-trip for any number of spaces — use this instead of a `get` loop.\n *\n * @examplePrompt List the metadata of every space\n * @examplePrompt Read the custom column values for all rooms\n * @examplePrompt Get the metadata for these three spaces in one call\n *\n * # Example\n * ```ts\n * const { records } = await snaptrude.program.metadata.list()\n * const byId = Object.fromEntries(records.map((r) => [r.spaceId, r.values]))\n * ```\n */\n public abstract list(\n spaceIds?: ComponentHandle[],\n ): PluginApiReturn<PluginProgramMetadataListResult>\n\n /**\n * Update a space's metadata values (sparse).\n *\n * Only the headers present in `values` change; `null` clears a value. Every\n * key must be an existing `headerId` (create columns first with\n * {@linkcode PluginProgramMetadataApi.createHeader}). Undoable as one step.\n *\n * @param spaceId - The space (component id) to write.\n * @param values - Header id → new cell value (`null` clears).\n * @returns The space's full {@linkcode PluginProgramMetadataRecord} after the\n * write.\n * @throws If the space or any header id does not exist.\n *\n * @examplePrompt Set the Occupancy metadata of this space to 4\n * @examplePrompt Store a department code on the selected rooms\n * @examplePrompt Write custom column values onto space sp_12\n * @examplePrompt Clear the Finish Level metadata on this space\n *\n * # Example\n * ```ts\n * const header = await snaptrude.program.metadata.createHeader(\"Occupancy\")\n * await snaptrude.program.metadata.update(\"sp_12\", { [header.headerId]: 4 })\n * ```\n */\n public abstract update(\n spaceId: ComponentHandle,\n values: Record<string, PluginSpreadsheetCell>,\n ): PluginApiReturn<PluginProgramMetadataUpdateResult>\n\n /**\n * Update metadata values on many spaces in one undoable step.\n *\n * The plural of {@linkcode PluginProgramMetadataApi.update}: each item names a\n * space and its sparse `values`. Results are in input order. Capped at 1000\n * items per call.\n *\n * @param items - The spaces and the values to write on each.\n * @returns A {@linkcode PluginProgramMetadataUpdateManyResult} with one record\n * per item, in input order.\n * @throws If any space or header id does not exist (nothing is written).\n *\n * @performance One round-trip and one undo step for up to 1000 spaces — use this instead of an `update` loop.\n *\n * @examplePrompt Fill the Occupancy column for all bedrooms\n * @examplePrompt Write metadata onto every selected space at once\n * @examplePrompt Bulk-update custom column values\n *\n * # Example\n * ```ts\n * await snaptrude.program.metadata.updateMany([\n * { spaceId: \"sp_12\", values: { [headerId]: 4 } },\n * { spaceId: \"sp_13\", values: { [headerId]: 2 } },\n * ])\n * ```\n */\n public abstract updateMany(\n items: PluginProgramMetadataUpdateItem[],\n ): PluginApiReturn<PluginProgramMetadataUpdateManyResult>\n}\n\n/**\n * A metadata header — one custom column of the Program Data sheet.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `headerId` | `string` | Stable header id (the key used in metadata `values`) |\n * | `name` | `string` | Column header text |\n * | `description` | `string` | Column description (`\"\"` when none) |\n * | `columnIndex` | `number` | Zero-based column index on the Program Data sheet |\n */\nexport const PluginProgramMetadataHeader = z.object({\n headerId: z.string(),\n name: z.string(),\n description: z.string(),\n columnIndex: z.number(),\n})\nexport type PluginProgramMetadataHeader = z.infer<\n typeof PluginProgramMetadataHeader\n>\n\n/**\n * Result of {@linkcode PluginProgramMetadataApi.listHeaders}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `headers` | {@linkcode PluginProgramMetadataHeader}`[]` | Every custom column, in sheet column order |\n */\nexport const PluginProgramMetadataListHeadersResult = z.object({\n headers: z.array(PluginProgramMetadataHeader),\n})\nexport type PluginProgramMetadataListHeadersResult = z.infer<\n typeof PluginProgramMetadataListHeadersResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramMetadataApi.createHeader}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | Column header text (non-empty) |\n * | `description` | `string?` | Column description |\n */\nexport const PluginProgramMetadataCreateHeaderArgs = z.object({\n name: z.string().trim().min(1),\n description: z.string().optional(),\n})\nexport type PluginProgramMetadataCreateHeaderArgs = z.infer<\n typeof PluginProgramMetadataCreateHeaderArgs\n>\n\n/** Result of {@linkcode PluginProgramMetadataApi.createHeader} — the created or reused header. */\nexport const PluginProgramMetadataCreateHeaderResult = PluginProgramMetadataHeader\nexport type PluginProgramMetadataCreateHeaderResult = z.infer<\n typeof PluginProgramMetadataCreateHeaderResult\n>\n\n/**\n * One space's metadata values.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaceId` | `ComponentHandle` | The space's component id |\n * | `values` | `Record<string, cell>` | Header id → value, one entry per header the space has a value for |\n */\nexport const PluginProgramMetadataRecord = z.object({\n spaceId: ComponentHandle,\n values: z.record(z.string(), PluginSpreadsheetCell),\n})\nexport type PluginProgramMetadataRecord = z.infer<\n typeof PluginProgramMetadataRecord\n>\n\n/** Arguments for {@linkcode PluginProgramMetadataApi.get}. */\nexport const PluginProgramMetadataGetArgs = z.object({\n spaceId: ComponentHandle,\n})\nexport type PluginProgramMetadataGetArgs = z.infer<\n typeof PluginProgramMetadataGetArgs\n>\n\n/** Result of {@linkcode PluginProgramMetadataApi.get} — the record, or `null` when the space does not exist. */\nexport const PluginProgramMetadataGetResult = PluginProgramMetadataRecord.nullable()\nexport type PluginProgramMetadataGetResult = z.infer<\n typeof PluginProgramMetadataGetResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramMetadataApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaceIds` | `ComponentHandle[]?` | Spaces to read; omit for every space |\n */\nexport const PluginProgramMetadataListArgs = z.object({\n spaceIds: z.array(ComponentHandle).optional(),\n})\nexport type PluginProgramMetadataListArgs = z.infer<\n typeof PluginProgramMetadataListArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramMetadataApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `records` | {@linkcode PluginProgramMetadataRecord}`[]` | One record per space (unknown ids skipped) |\n */\nexport const PluginProgramMetadataListResult = z.object({\n records: z.array(PluginProgramMetadataRecord),\n})\nexport type PluginProgramMetadataListResult = z.infer<\n typeof PluginProgramMetadataListResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramMetadataApi.update}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spaceId` | `ComponentHandle` | The space to write |\n * | `values` | `Record<string, cell>` | Header id → new value (`null` clears); at least one entry |\n */\nexport const PluginProgramMetadataUpdateArgs = z.object({\n spaceId: ComponentHandle,\n values: z\n .record(z.string(), PluginSpreadsheetCell)\n .refine((v) => Object.keys(v).length > 0, \"values must not be empty\"),\n})\nexport type PluginProgramMetadataUpdateArgs = z.infer<\n typeof PluginProgramMetadataUpdateArgs\n>\n\n/** Result of {@linkcode PluginProgramMetadataApi.update} — the space's record after the write. */\nexport const PluginProgramMetadataUpdateResult = PluginProgramMetadataRecord\nexport type PluginProgramMetadataUpdateResult = z.infer<\n typeof PluginProgramMetadataUpdateResult\n>\n\n/** One item of {@linkcode PluginProgramMetadataApi.updateMany} — mirrors the `update` args. */\nexport const PluginProgramMetadataUpdateItem = PluginProgramMetadataUpdateArgs\nexport type PluginProgramMetadataUpdateItem = z.infer<\n typeof PluginProgramMetadataUpdateItem\n>\n\n/** Maximum items per {@linkcode PluginProgramMetadataApi.updateMany} call. */\nexport const PLUGIN_PROGRAM_METADATA_BATCH_LIMIT = 1000\n\n/**\n * Arguments for {@linkcode PluginProgramMetadataApi.updateMany}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `items` | {@linkcode PluginProgramMetadataUpdateItem}`[]` | 1–1000 space updates |\n */\nexport const PluginProgramMetadataUpdateManyArgs = z.object({\n items: z\n .array(PluginProgramMetadataUpdateItem)\n .min(1)\n .max(PLUGIN_PROGRAM_METADATA_BATCH_LIMIT),\n})\nexport type PluginProgramMetadataUpdateManyArgs = z.infer<\n typeof PluginProgramMetadataUpdateManyArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramMetadataApi.updateMany}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `records` | {@linkcode PluginProgramMetadataRecord}`[]` | One record per item, in input order |\n */\nexport const PluginProgramMetadataUpdateManyResult = z.object({\n records: z.array(PluginProgramMetadataRecord),\n})\nexport type PluginProgramMetadataUpdateManyResult = z.infer<\n typeof PluginProgramMetadataUpdateManyResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport { PluginAreaUnit } from \"./metrics\"\n\n/**\n * Program labels — the **program rows** of the active program: each label is a\n * named space type (e.g. \"Exam Room\", \"Nurse Station\") inside a department,\n * carrying an area **target** and a unit **count** target.\n *\n * Labels are the non-department planning entities of program mode. A\n * department groups labels; a label groups the spaces (masses) that realize it.\n * Target areas live on **departments** ({@linkcode PluginProgramDepartmentsApi.setTargetArea})\n * and on **labels** (this namespace) — an individual space carries no target of\n * its own; it inherits its label's `targetArea / targetCount`. Program Blocks\n * (spaces whose space type is \"Program Block\") are likewise targeted through\n * their label.\n *\n * `targetArea` is the label's **total** target (all units), in the response's\n * `units`; the per-unit target is `targetArea / targetCount`.\n *\n * Reads never throw for a miss (`get` returns `null`, `list` returns `[]`).\n * Writes are undoable and autosaved; when the project has proposals they write\n * the **active proposal's** targets.\n *\n * Accessed via `snaptrude.program.labels`.\n */\nexport abstract class PluginProgramLabelsApi {\n constructor() {}\n\n /**\n * List the labels (program rows) of the active program.\n *\n * @returns A {@linkcode PluginProgramLabelsListResult} with `units` and a\n * `labels` array (empty when the project has none).\n *\n * @examplePrompt List all the program rows with their target areas\n * @examplePrompt What space labels are in the program and how many of each?\n * @examplePrompt Show every label with its department and target\n *\n * # Example\n * ```ts\n * const { units, labels } = await snaptrude.program.labels.list()\n * for (const l of labels) console.log(l.name, l.targetArea, units, l.targetCount)\n * ```\n */\n public abstract list(): PluginApiReturn<PluginProgramLabelsListResult>\n\n /**\n * Get a single label by id.\n *\n * @param labelId - The id of the label to read.\n * @returns The matching {@linkcode PluginProgramLabelRecord} (with `units`), or\n * `null` if no label has that id.\n *\n * @examplePrompt Get the label with id lbl_12\n * @examplePrompt What is the target area of this program row?\n *\n * # Example\n * ```ts\n * const label = await snaptrude.program.labels.get(\"lbl_12\")\n * if (label) console.log(label.name, label.targetArea, label.units)\n * ```\n */\n public abstract get(\n labelId: string,\n ): PluginApiReturn<PluginProgramLabelsGetResult>\n\n /**\n * Create labels (program rows), optionally creating their spaces too.\n *\n * Each item names a label and its targets. A label is unique per\n * (`name`, department); an item matching an existing label **updates** that\n * label's targets instead of duplicating it. Unknown departments are created\n * by name when `departmentName` is given; omit both department fields for the\n * Default department.\n *\n * By default only the program rows are created (targets to plan against).\n * Pass `createSpaces: true` to also instantiate `targetCount` massing spaces\n * per label on the canvas, laid out automatically — the same path Program\n * Mode's CSV/Interpret import uses.\n *\n * @param items - The labels to create (1–500).\n * @param options - Optional `units` for every `targetArea` (defaults to the\n * project's area units) and `createSpaces` (default `false`).\n * @returns A {@linkcode PluginProgramLabelsCreateResult} with the created or\n * updated label ids, in input order.\n * @throws If an item's `departmentId` does not exist.\n *\n * @examplePrompt Add program rows for 4 exam rooms of 120 sqft each\n * @examplePrompt Create the labels from this interpreted program\n * @examplePrompt Add an Office label with a 2000 sqft target to the Admin department\n * @examplePrompt Create program rows and their spaces on the canvas\n *\n * # Example\n * ```ts\n * const { labelIds } = await snaptrude.program.labels.create(\n * [{ name: \"Exam Room\", targetArea: 480, targetCount: 4, departmentName: \"Clinic\" }],\n * { units: \"ft2\" },\n * )\n * ```\n */\n public abstract create(\n items: PluginProgramLabelCreateItem[],\n options?: { units?: PluginAreaUnit; createSpaces?: boolean },\n ): PluginApiReturn<PluginProgramLabelsCreateResult>\n\n /**\n * Set a label's total target area.\n *\n * The goal the program metrics compare the label's allocated area against.\n * Pass `units` to say what unit `targetArea` is in; it defaults to the\n * project's area units. Read it back with {@linkcode PluginProgramLabelsApi.get}.\n *\n * @param labelId - The id of the label whose target to set.\n * @param targetArea - The total target area (all units), in `units`.\n * @param units - Unit of `targetArea` (defaults to the project's area units).\n * @returns The updated {@linkcode PluginProgramLabelRecord} with its `units`.\n * @throws If no label has the given id.\n *\n * @examplePrompt Set the Exam Room target area to 480 sqft\n * @examplePrompt Give the Corridor program row a 150 sqm target\n * @examplePrompt Update the area target for label lbl_12\n *\n * # Example\n * ```ts\n * await snaptrude.program.labels.setTargetArea(\"lbl_12\", 480, \"ft2\")\n * ```\n */\n public abstract setTargetArea(\n labelId: string,\n targetArea: number,\n units?: PluginAreaUnit,\n ): PluginApiReturn<PluginProgramLabelsSetTargetAreaResult>\n\n /**\n * Set a label's target unit count (how many of this space the program calls for).\n *\n * @param labelId - The id of the label whose count to set.\n * @param targetCount - The target number of units (a non-negative integer).\n * @returns The updated {@linkcode PluginProgramLabelRecord} with its `units`.\n * @throws If no label has the given id.\n *\n * @examplePrompt Set the Exam Room count to 6\n * @examplePrompt The program needs 12 patient rooms — update the count\n *\n * # Example\n * ```ts\n * await snaptrude.program.labels.setTargetCount(\"lbl_12\", 6)\n * ```\n */\n public abstract setTargetCount(\n labelId: string,\n targetCount: number,\n ): PluginApiReturn<PluginProgramLabelsSetTargetCountResult>\n}\n\n/**\n * A label (program row) in the active program. `targetArea` is the label's\n * **total** target in the enclosing response's `units`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Stable label id |\n * | `name` | `string` | Label (space type) name |\n * | `color` | `string` | CSS hex color string |\n * | `departmentId` | `string` | Id of the department the label belongs to |\n * | `targetArea` | `number \\| null` | Total target area (all units), or `null` when no target is set |\n * | `targetCount` | `number` | Target number of units (`0` when unset) |\n * | `storey` | `number \\| null` | Default storey new spaces of this label are placed on |\n */\nexport const PluginProgramLabel = z.object({\n id: z.string(),\n name: z.string(),\n color: z.string(),\n departmentId: z.string(),\n targetArea: z.number().nullable(),\n targetCount: z.number(),\n storey: z.number().nullable(),\n})\nexport type PluginProgramLabel = z.infer<typeof PluginProgramLabel>\n\n/** A bare label record carrying its area `units` (per the area-units convention). */\nexport const PluginProgramLabelRecord = PluginProgramLabel.extend({\n units: PluginAreaUnit,\n})\nexport type PluginProgramLabelRecord = z.infer<typeof PluginProgramLabelRecord>\n\n/**\n * Result of {@linkcode PluginProgramLabelsApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `units` | `\"ft2\" \\| \"m2\"` | The unit every `targetArea` below is reported in |\n * | `labels` | {@linkcode PluginProgramLabel}`[]` | All labels in the active program |\n */\nexport const PluginProgramLabelsListResult = z.object({\n units: PluginAreaUnit,\n labels: z.array(PluginProgramLabel),\n})\nexport type PluginProgramLabelsListResult = z.infer<\n typeof PluginProgramLabelsListResult\n>\n\n/** Arguments for {@linkcode PluginProgramLabelsApi.get}. */\nexport const PluginProgramLabelsGetArgs = z.object({\n labelId: z.string(),\n})\nexport type PluginProgramLabelsGetArgs = z.infer<\n typeof PluginProgramLabelsGetArgs\n>\n\n/** Result of {@linkcode PluginProgramLabelsApi.get} — the record with `units`, or `null`. */\nexport const PluginProgramLabelsGetResult = PluginProgramLabelRecord.nullable()\nexport type PluginProgramLabelsGetResult = z.infer<\n typeof PluginProgramLabelsGetResult\n>\n\n/**\n * One label to create via {@linkcode PluginProgramLabelsApi.create}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | Label (space type) name |\n * | `targetArea` | `number?` | Total target area (all units), in the call's `units` |\n * | `targetCount` | `number?` | Target number of units (default `1`) |\n * | `departmentId` | `string?` | Existing department to file the label under |\n * | `departmentName` | `string?` | Department by name — created when it does not exist (ignored when `departmentId` is given) |\n * | `color` | `string?` | CSS hex color (a light color is generated when omitted) |\n * | `storey` | `number?` | Storey for spaces of this label (default `1`) |\n * | `height` | `number?` | Space height in project length units (for `createSpaces`) |\n * | `width` | `number?` | Space width in project length units (for `createSpaces`) |\n */\nexport const PluginProgramLabelCreateItem = z.object({\n name: z.string().trim().min(1),\n targetArea: z.number().finite().nonnegative().optional(),\n targetCount: z.number().int().nonnegative().optional(),\n departmentId: z.string().optional(),\n departmentName: z.string().optional(),\n color: z.string().optional(),\n storey: z.number().int().optional(),\n height: z.number().finite().positive().optional(),\n width: z.number().finite().positive().optional(),\n})\nexport type PluginProgramLabelCreateItem = z.infer<\n typeof PluginProgramLabelCreateItem\n>\n\n/** Maximum items per {@linkcode PluginProgramLabelsApi.create} call. */\nexport const PLUGIN_PROGRAM_LABELS_BATCH_LIMIT = 500\n\n/**\n * Arguments for {@linkcode PluginProgramLabelsApi.create}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `items` | {@linkcode PluginProgramLabelCreateItem}`[]` | 1–500 labels |\n * | `units` | `\"ft2\" \\| \"m2\"?` | Unit of every `targetArea` (defaults to the project's area units) |\n * | `createSpaces` | `boolean?` | Also create `targetCount` spaces per label on the canvas (default `false`) |\n */\nexport const PluginProgramLabelsCreateArgs = z.object({\n items: z\n .array(PluginProgramLabelCreateItem)\n .min(1)\n .max(PLUGIN_PROGRAM_LABELS_BATCH_LIMIT),\n units: PluginAreaUnit.optional(),\n createSpaces: z.boolean().optional(),\n})\nexport type PluginProgramLabelsCreateArgs = z.infer<\n typeof PluginProgramLabelsCreateArgs\n>\n\n/**\n * Result of {@linkcode PluginProgramLabelsApi.create}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `labelIds` | `string[]` | The created (or updated) label ids, in input order |\n * | `createdSpaces` | `boolean` | Whether spaces were also created on the canvas |\n */\nexport const PluginProgramLabelsCreateResult = z.object({\n labelIds: z.array(z.string()),\n createdSpaces: z.boolean(),\n})\nexport type PluginProgramLabelsCreateResult = z.infer<\n typeof PluginProgramLabelsCreateResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramLabelsApi.setTargetArea}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `labelId` | `string` | The label whose target to set |\n * | `targetArea` | `number` | Total target area, in `units` |\n * | `units` | `\"ft2\" \\| \"m2\"?` | Unit of `targetArea` (defaults to the project's area units) |\n */\nexport const PluginProgramLabelsSetTargetAreaArgs = z.object({\n labelId: z.string(),\n targetArea: z.number().finite().nonnegative(),\n units: PluginAreaUnit.optional(),\n})\nexport type PluginProgramLabelsSetTargetAreaArgs = z.infer<\n typeof PluginProgramLabelsSetTargetAreaArgs\n>\n\n/** Result of {@linkcode PluginProgramLabelsApi.setTargetArea} — the updated record with `units`. */\nexport const PluginProgramLabelsSetTargetAreaResult = PluginProgramLabelRecord\nexport type PluginProgramLabelsSetTargetAreaResult = z.infer<\n typeof PluginProgramLabelsSetTargetAreaResult\n>\n\n/**\n * Arguments for {@linkcode PluginProgramLabelsApi.setTargetCount}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `labelId` | `string` | The label whose count to set |\n * | `targetCount` | `number` | Target number of units (non-negative integer) |\n */\nexport const PluginProgramLabelsSetTargetCountArgs = z.object({\n labelId: z.string(),\n targetCount: z.number().int().nonnegative(),\n})\nexport type PluginProgramLabelsSetTargetCountArgs = z.infer<\n typeof PluginProgramLabelsSetTargetCountArgs\n>\n\n/** Result of {@linkcode PluginProgramLabelsApi.setTargetCount} — the updated record with `units`. */\nexport const PluginProgramLabelsSetTargetCountResult = PluginProgramLabelRecord\nexport type PluginProgramLabelsSetTargetCountResult = z.infer<\n typeof PluginProgramLabelsSetTargetCountResult\n>\n","import { PluginProgramDepartmentsApi } from \"./departments\"\nimport { PluginProgramAdjacencyApi } from \"./adjacency\"\nimport { PluginProgramLayoutApi } from \"./layout\"\nimport { PluginProgramMetricsApi } from \"./metrics\"\nimport { PluginProgramSpreadsheetApi } from \"./spreadsheet\"\nimport { PluginProgramSiteApi } from \"./site\"\nimport { PluginProgramCoresApi } from \"./cores\"\nimport { PluginProgramClassificationApi } from \"./classification\"\nimport { PluginProgramAreasApi } from \"./areas\"\nimport { PluginProgramMetadataApi } from \"./metadata\"\nimport { PluginProgramLabelsApi } from \"./labels\"\n\n/**\n * Program-mode planning APIs.\n *\n * The program is the space-planning layer: department groupings with area targets,\n * the labels (program rows) inside them, the metrics that track allocated area\n * against those targets, and the reports that render program/takeoff data to\n * spreadsheet sheets. Reads describe the *plan* (intent + targets); the geometry\n * that realizes the plan is authored through the geometry namespaces.\n *\n * - {@linkcode PluginProgramApi.departments} — Read & edit program departments (groupings + targets)\n * - {@linkcode PluginProgramApi.labels} — Read & edit program labels (program rows: per-type area/count targets)\n * - {@linkcode PluginProgramApi.metadata} — Read & edit program metadata (custom columns) on spaces\n * - {@linkcode PluginProgramApi.adjacency} — Read & compute the department/space adjacency matrix\n * - {@linkcode PluginProgramApi.layout} — Arrange/pack spaces in the envelope\n * - {@linkcode PluginProgramApi.metrics} — Read the area-program summary\n * - {@linkcode PluginProgramApi.areas} — FAR / built-up-area rollup and groupings\n * - {@linkcode PluginProgramApi.spreadsheet} — Render program data to sheets, custom grouped sheets, read back\n * - {@linkcode PluginProgramApi.site} — Read site/plot context and read/update Site Analysis\n * - {@linkcode PluginProgramApi.cores} — Read & predicate vertical-circulation cores (reads only)\n * - {@linkcode PluginProgramApi.classification} — Classification catalog + display tags\n *\n * Accessed via `snaptrude.program`.\n */\nexport abstract class PluginProgramApi {\n /** Program departments — groupings, area targets, and space assignment. See {@linkcode PluginProgramDepartmentsApi}. */\n public abstract departments: PluginProgramDepartmentsApi\n /** Program labels — the program rows with per-type area/count targets. See {@linkcode PluginProgramLabelsApi}. */\n public abstract labels: PluginProgramLabelsApi\n /** Program metadata — custom columns and their per-space values. See {@linkcode PluginProgramMetadataApi}. */\n public abstract metadata: PluginProgramMetadataApi\n /** Adjacency matrix — read & compute proximity relationships. See {@linkcode PluginProgramAdjacencyApi}. */\n public abstract adjacency: PluginProgramAdjacencyApi\n /** Arrange/pack spaces in the envelope (async job family). See {@linkcode PluginProgramLayoutApi}. */\n public abstract layout: PluginProgramLayoutApi\n /** Area-program summary — targets vs allocated area. See {@linkcode PluginProgramMetricsApi}. */\n public abstract metrics: PluginProgramMetricsApi\n /** FAR / built-up-area rollup and groupings. See {@linkcode PluginProgramAreasApi}. */\n public abstract areas: PluginProgramAreasApi\n /** Spreadsheet reports — render tables to sheets, custom grouped sheets, export, read back. See {@linkcode PluginProgramSpreadsheetApi}. */\n public abstract spreadsheet: PluginProgramSpreadsheetApi\n /** Site/plot context and persisted Site Analysis. See {@linkcode PluginProgramSiteApi}. */\n public abstract site: PluginProgramSiteApi\n /** Vertical-circulation cores — read & predicate (reads only). See {@linkcode PluginProgramCoresApi}. */\n public abstract cores: PluginProgramCoresApi\n /** Classification catalog (space/area/mass types) + display tags. See {@linkcode PluginProgramClassificationApi}. */\n public abstract classification: PluginProgramClassificationApi\n\n constructor() {}\n}\n\nexport * from \"./departments\"\nexport * from \"./adjacency\"\nexport * from \"./layout\"\nexport * from \"./metrics\"\nexport * from \"./spreadsheet\"\nexport * from \"./site\"\nexport * from \"./cores\"\nexport * from \"./classification\"\nexport * from \"./areas\"\nexport * from \"./metadata\"\nexport * from \"./labels\"\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * Presentation views — read the saved plan, 3D, and elevation views used to prepare presentations.\n *\n * A **view** is a saved camera/plan: a sheet-ready 2D plan or a saved 3D view.\n * A plugin can list and read views, capture them to images, activate one (move\n * the camera to it), save the current camera as a new view, rename or delete a\n * saved view, and read/update a view's display settings (background, color\n * mode, view mode, axis, edges, labels). Each view carries the `storey` it was\n * saved on.\n *\n * `list`/`get`/`getActive`/`capture`/`getSettings` are reads;\n * `setActive`/`create`/`rename`/`delete`/`updateSettings` are writes (a view is\n * a camera bookmark — not geometry).\n *\n * Accessed via `snaptrude.presentation.views`.\n */\nexport abstract class PluginPresentationViewsApi {\n constructor() {}\n\n /**\n * List the saved views in the active project.\n *\n * @returns A {@linkcode PluginPresentationViewsListResult} with a `views` array\n * of plan, 3D, and elevation views (empty when the project has none).\n *\n * @examplePrompt List all the saved views\n * @examplePrompt What plan, 3D, and elevation views are in this project?\n * @examplePrompt Which view is currently active?\n * @examplePrompt Get the names of every presentation view\n * @examplePrompt Show me the 3D views I can put on a sheet\n *\n * # Example\n * ```ts\n * const { views } = await snaptrude.presentation.views.list()\n * const active = views.find((v) => v.isActive)\n * const plans = views.filter((v) => v.type === \"2d\")\n * ```\n */\n public abstract list(): PluginApiReturn<PluginPresentationViewsListResult>\n\n /**\n * Get a single saved view by id.\n *\n * @param viewId - The id of the view to read.\n * @returns The matching {@linkcode PluginPresentationView}, or `null` if no view\n * has that id.\n *\n * @examplePrompt Get the view with id view_12345\n * @examplePrompt Find the saved view called Ground Floor Plan\n * @examplePrompt Look up a presentation view by its id\n *\n * # Example\n * ```ts\n * const view = await snaptrude.presentation.views.get(\"view_123\")\n * ```\n */\n public abstract get(\n viewId: string,\n ): PluginApiReturn<PluginPresentationViewsGetResult>\n\n /**\n * Get the currently active view.\n *\n * @returns The active {@linkcode PluginPresentationView}, or `null` if none is\n * active.\n *\n * @examplePrompt Which view is active right now?\n * @examplePrompt Get the current view\n * @examplePrompt What view am I looking at?\n *\n * # Example\n * ```ts\n * const active = await snaptrude.presentation.views.getActive()\n * ```\n */\n public abstract getActive(): PluginApiReturn<PluginPresentationViewsGetResult>\n\n /**\n * Capture a screenshot of a view as a base64 image.\n *\n * Captures the **current** viewport by default; pass `viewId` to capture a\n * specific saved view (which briefly applies that view to the scene). Use\n * `format`/`resolution` to control the output.\n *\n * @param options - All optional: `viewId` (capture this saved view; omit for the\n * current viewport), `format` (default `\"png\"`), `resolution` (e.g. `\"1280x720\"`).\n * @returns A {@linkcode PluginPresentationViewsCaptureResult} with the base64\n * `image` data URL.\n * @throws If a given `viewId` does not exist.\n *\n * @examplePrompt Take a screenshot of the current view\n * @examplePrompt Capture this view as a PNG\n * @examplePrompt Render the Site Plan view to an image\n * @examplePrompt Export a high-res image of what I'm looking at\n *\n * # Example\n * ```ts\n * const { image } = await snaptrude.presentation.views.capture({ format: \"png\" })\n * ```\n */\n public abstract capture(options?: {\n viewId?: string\n format?: \"png\" | \"jpeg\"\n resolution?: string\n }): PluginApiReturn<PluginPresentationViewsCaptureResult>\n\n /**\n * Activate a saved view — move the camera to it and restore its scene state.\n *\n * Applies the view's camera, visibility, and storey state to the live scene.\n * Paired with {@linkcode PluginPresentationViewsApi.getActive}.\n *\n * @param viewId - The id of the view to activate.\n * @returns The id of the now-active view.\n * @throws If no view has the given id.\n *\n * @examplePrompt Switch to the Ground Floor plan view\n * @examplePrompt Make the 3D view active\n * @examplePrompt Go to the view named Entrance\n * @examplePrompt Load the site plan view\n *\n * # Example\n * ```ts\n * await snaptrude.presentation.views.setActive(\"view_123\")\n * ```\n */\n public abstract setActive(\n viewId: string,\n ): PluginApiReturn<PluginPresentationViewsSetActiveResult>\n\n /**\n * Save the current camera/scene state as a new named view.\n *\n * Creates a camera bookmark (not geometry) from where the camera is now.\n *\n * @param name - Name for the new view (a default is used when omitted).\n * @param options - Optional `kind` (what kind of view to save, default\n * `\"view\"`) and `storey` (save the view on a specific storey — the storey is\n * activated first, matching the storey the view is captured on; when omitted\n * the current active storey is used).\n * @returns The newly created {@linkcode PluginPresentationView}.\n * @throws If `storey` is given but no such storey exists, or the view could\n * not be saved.\n *\n * @examplePrompt Save the current view as Lobby Perspective\n * @examplePrompt Create a new saved view from where I'm looking\n * @examplePrompt Bookmark this camera angle\n * @examplePrompt Save the current 2D plan as a view\n * @examplePrompt Save a view on the second storey\n *\n * # Example\n * ```ts\n * const view = await snaptrude.presentation.views.create(\"Lobby\")\n * // …or capture the view on a specific storey\n * const level2 = await snaptrude.presentation.views.create(\"Level 2\", { storey: 2 })\n * ```\n */\n public abstract create(\n name?: string,\n options?: { kind?: \"view\" | \"sitePlan\"; storey?: number },\n ): PluginApiReturn<PluginPresentationViewsCreateResult>\n\n /**\n * Read a saved view's display settings (background, color mode, view mode,\n * axis, edges, labels).\n *\n * @param viewId - The id of the view to read settings for.\n * @returns The view's {@linkcode PluginPresentationViewSettings}, or `null` if\n * no view has that id.\n *\n * @examplePrompt What color mode is the Ground Floor view using?\n * @examplePrompt Get the display settings for this view\n * @examplePrompt Does the site plan view show axes and labels?\n * @examplePrompt Read the background color of the active view\n *\n * # Example\n * ```ts\n * const settings = await snaptrude.presentation.views.getSettings(\"view_123\")\n * if (settings) console.log(settings.colorMode, settings.backgroundColor)\n * ```\n */\n public abstract getSettings(\n viewId: string,\n ): PluginApiReturn<PluginPresentationViewsGetSettingsResult>\n\n /**\n * Update a saved view's display settings (partial patch — only the fields you\n * pass change).\n *\n * Persists and broadcasts the change; if the view is currently active, the\n * scene updates live. **Not Ctrl+Z-undoable** — view-metadata edits publish\n * directly and do not land on the local undo stack; this matches how the\n * product's own view-settings UI behaves.\n *\n * Label patch semantics (mirroring the product, which stores no label\n * selection while labels are off):\n *\n * - `{ showLabels: true }` alone turns labels on; if the view has no stored\n * selection, the product defaults (`objectLabels`, `areas`) are applied —\n * labels are never \"on\" with an empty selection (the product has no such\n * state; labels render only while the selection is non-empty).\n * - `{ labels: [...] }` alone sets the selection **and turns labels on**.\n * - `{ showLabels: false }` turns labels off and **clears the stored\n * selection** — a later read returns `labels: []`.\n * - `{ showLabels: false, labels: [...] }` with a non-empty list is\n * contradictory and **rejected** (nothing is silently discarded).\n * - `{ showLabels: true, labels: [] }` is likewise contradictory and\n * **rejected** — pass a non-empty list, or omit `labels` to get the\n * defaults.\n *\n * `colorMode` accepts the `tag:<categoryId>` scheme strings returned by\n * {@linkcode PluginPresentationViewsApi.getSettings}, so read-then-write\n * round-trips a tag-based color scheme unchanged.\n *\n * `viewMode` (`\"perspective\"` | `\"isometric\"`) applies to **3D views only** —\n * a 2D plan is always orthographic, so a `viewMode: \"perspective\"` patch on a\n * 2D view is rejected. `\"isometric\"` on a 2D plan is accepted as a no-op (the\n * plan is already orthographic), so writing back exactly what\n * {@linkcode PluginPresentationViewsApi.getSettings} returned is always a\n * no-op. On the active view the camera switches projection live (the same\n * toggle the view-settings modal runs); on other views the mode is applied\n * the next time the view is activated.\n *\n * @param viewId - The id of the view to update.\n * @param settings - A partial {@linkcode PluginPresentationViewSettings}; any\n * omitted field is left unchanged.\n * @returns The updated view's `id`.\n * @throws If writes are disabled, no view has the given id, the patch is\n * contradictory (`showLabels: false` with a non-empty `labels`, or\n * `showLabels: true` with an explicitly empty `labels` list), or\n * `viewMode: \"perspective\"` is patched on a 2D plan view.\n *\n * @examplePrompt Switch this view to monochrome color mode\n * @examplePrompt Turn off the axis in the Ground Floor view\n * @examplePrompt Set the background of the active view to white\n * @examplePrompt Show area labels on this view\n * @examplePrompt Enable hidden-line mode for the section view\n * @examplePrompt Make the 3D view orthographic instead of perspective\n *\n * # Example\n * ```ts\n * await snaptrude.presentation.views.updateSettings(\"view_123\", {\n * colorMode: \"monochrome\",\n * showAxis: false,\n * })\n * // switch a 3D view to an isometric (orthographic) projection\n * await snaptrude.presentation.views.updateSettings(\"view_123\", {\n * viewMode: \"isometric\",\n * })\n * ```\n */\n public abstract updateSettings(\n viewId: string,\n settings: Partial<PluginPresentationViewSettings>,\n ): PluginApiReturn<PluginPresentationViewsUpdateSettingsResult>\n\n /**\n * Rename a saved view.\n *\n * Sets the view's display name — the same label the views panel shows and\n * that `list`/`get` read back. Names must be unique within the proposal and\n * cannot contain the characters `\\ : { } [ ] | ; < > ? ~`.\n *\n * @param viewId - The id of the view to rename.\n * @param name - The new display name (non-empty after trimming).\n * @returns The updated {@linkcode PluginPresentationView}.\n * @throws If writes are disabled, no view has the given id, the name is\n * empty/invalid, or another view in the proposal already has that name.\n *\n * @examplePrompt Rename the view Lobby to Entrance Lobby\n * @examplePrompt Call this view Ground Floor Plan\n * @examplePrompt Change the active view's name to Final\n *\n * # Example\n * ```ts\n * const view = await snaptrude.presentation.views.rename(\"view_123\", \"Entrance\")\n * ```\n */\n public abstract rename(\n viewId: string,\n name: string,\n ): PluginApiReturn<PluginPresentationViewsRenameResult>\n\n /**\n * Delete a saved view.\n *\n * Removes the view from the project and the views panel (the panel's own\n * delete action). If the deleted view was active, the product falls back to\n * the default plan/3D view. Default views (the built-in plan/3D/site-plan\n * entries) cannot be deleted.\n *\n * @param viewId - The id of the view to delete.\n * @returns The id of the deleted view\n * (a {@linkcode PluginPresentationViewsDeleteResult}).\n * @throws If writes are disabled, no view has the given id, or the view is a\n * default view.\n *\n * @examplePrompt Delete the view named Old Draft\n * @examplePrompt Remove this saved view\n * @examplePrompt Get rid of the Lobby camera bookmark\n *\n * # Example\n * ```ts\n * const { id } = await snaptrude.presentation.views.delete(\"view_123\")\n * ```\n */\n public abstract delete(\n viewId: string,\n ): PluginApiReturn<PluginPresentationViewsDeleteResult>\n}\n\n/**\n * The rendering category of a saved view. Existing plan/3D values are preserved;\n * elevations are identified independently of their orthographic 3D camera.\n */\nexport const PluginPresentationViewType = z.enum([\"2d\", \"3d\", \"elevation\"])\nexport type PluginPresentationViewType = z.infer<\n typeof PluginPresentationViewType\n>\n\n/** The native semantic kind persisted for a saved view. */\nexport const PluginPresentationViewKind = z.enum([\n \"plan\",\n \"sitePlan\",\n \"threeD\",\n \"elevation\",\n])\nexport type PluginPresentationViewKind = z.infer<\n typeof PluginPresentationViewKind\n>\n\n/** Cardinal direction for an elevation; non-elevation views return `null`. */\nexport const PluginPresentationElevationDirection = z.enum([\n \"north\",\n \"east\",\n \"south\",\n \"west\",\n])\nexport type PluginPresentationElevationDirection = z.infer<\n typeof PluginPresentationElevationDirection\n>\n\n/**\n * A saved presentation view.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Unique view id |\n * | `name` | `string` | Display name |\n * | `type` | `\"2d\" \\| \"3d\" \\| \"elevation\"` | Rendering category; elevations are distinct from ordinary 3D views |\n * | `kind` | {@linkcode PluginPresentationViewKind} | Native semantic view kind |\n * | `direction` | {@linkcode PluginPresentationElevationDirection}` \\| null` | Cardinal direction for elevations; otherwise `null` |\n * | `isActive` | `boolean` | Whether this view is the currently active view |\n * | `storey` | `number \\| null` | The storey this view was saved on (`null` on older views saved without one) |\n */\nexport const PluginPresentationView = z.object({\n id: z.string(),\n name: z.string(),\n type: PluginPresentationViewType,\n kind: PluginPresentationViewKind,\n direction: PluginPresentationElevationDirection.nullable(),\n isActive: z.boolean(),\n storey: z.number().nullable(),\n})\nexport type PluginPresentationView = z.infer<typeof PluginPresentationView>\n\n/**\n * Result of {@linkcode PluginPresentationViewsApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `views` | {@linkcode PluginPresentationView}`[]` | All saved views in the project |\n */\nexport const PluginPresentationViewsListResult = z.object({\n views: z.array(PluginPresentationView),\n})\nexport type PluginPresentationViewsListResult = z.infer<\n typeof PluginPresentationViewsListResult\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationViewsApi.get}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | The id of the view to read |\n */\nexport const PluginPresentationViewsGetArgs = z.object({\n id: z.string(),\n})\nexport type PluginPresentationViewsGetArgs = z.infer<\n typeof PluginPresentationViewsGetArgs\n>\n\n/**\n * Result of {@linkcode PluginPresentationViewsApi.get} and\n * {@linkcode PluginPresentationViewsApi.getActive} — the view, or `null` when\n * there is no match / no active view.\n */\nexport const PluginPresentationViewsGetResult =\n PluginPresentationView.nullable()\nexport type PluginPresentationViewsGetResult = z.infer<\n typeof PluginPresentationViewsGetResult\n>\n\n/** Image format for {@linkcode PluginPresentationViewsApi.capture}. */\nexport const PluginPresentationImageFormat = z.enum([\"png\", \"jpeg\"])\nexport type PluginPresentationImageFormat = z.infer<\n typeof PluginPresentationImageFormat\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationViewsApi.capture}. All optional.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `viewId` | `string \\| undefined` | Capture this saved view; omit to capture the current viewport |\n * | `format` | `\"png\" \\| \"jpeg\" \\| undefined` | Output image format (default `\"png\"`) |\n * | `resolution` | `string \\| undefined` | Optional resolution hint (e.g. `\"1280x720\"`) |\n */\nexport const PluginPresentationViewsCaptureArgs = z.object({\n viewId: z.string().optional(),\n format: PluginPresentationImageFormat.optional(),\n resolution: z.string().optional(),\n})\nexport type PluginPresentationViewsCaptureArgs = z.infer<\n typeof PluginPresentationViewsCaptureArgs\n>\n\n/**\n * Result of {@linkcode PluginPresentationViewsApi.capture}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `image` | `string` | The captured image as a base64 data URL |\n */\nexport const PluginPresentationViewsCaptureResult = z.object({\n image: z.string(),\n})\nexport type PluginPresentationViewsCaptureResult = z.infer<\n typeof PluginPresentationViewsCaptureResult\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationViewsApi.setActive}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | The id of the view to activate |\n */\nexport const PluginPresentationViewsSetActiveArgs = z.object({\n id: z.string(),\n})\nexport type PluginPresentationViewsSetActiveArgs = z.infer<\n typeof PluginPresentationViewsSetActiveArgs\n>\n\n/** Result of {@linkcode PluginPresentationViewsApi.setActive} — the active view's id. */\nexport const PluginPresentationViewsSetActiveResult = z.object({\n id: z.string(),\n})\nexport type PluginPresentationViewsSetActiveResult = z.infer<\n typeof PluginPresentationViewsSetActiveResult\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationViewsApi.create}. All optional.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string \\| undefined` | Name for the new view (a default is used when omitted) |\n * | `kind` | `\"view\" \\| \"sitePlan\" \\| undefined` | What kind of view to save (default `\"view\"`). `\"plan\"` is not offered — the host cannot create a plan view without a `planId` this API does not carry. |\n * | `storey` | `number \\| undefined` | Save the view on this storey (activated before capture); defaults to the current active storey |\n */\nexport const PluginPresentationViewsCreateArgs = z.object({\n name: z.string().optional(),\n kind: z.enum([\"view\", \"sitePlan\"]).optional(),\n storey: z.number().optional(),\n})\nexport type PluginPresentationViewsCreateArgs = z.infer<\n typeof PluginPresentationViewsCreateArgs\n>\n\n/** Result of {@linkcode PluginPresentationViewsApi.create} — the saved view. */\nexport const PluginPresentationViewsCreateResult = PluginPresentationView\nexport type PluginPresentationViewsCreateResult = z.infer<\n typeof PluginPresentationViewsCreateResult\n>\n\n/**\n * How the scene is colored in a view.\n *\n * | Value | Meaning |\n * |---|---|\n * | `monochrome` | Flat single-tone shading |\n * | `texture` | Material textures/colors |\n * | `department` | Colored by department |\n * | `tag:<categoryId>` | Colored by a project tag category (e.g. `tag:ZONE`) |\n *\n * The `tag:<categoryId>` form carries the product's tag-based color schemes\n * losslessly — reading a view that uses one returns the `tag:` string as-is,\n * and writing it back preserves the scheme (read-then-write is safe).\n */\nexport const PluginPresentationViewColorMode = z.union([\n z.enum([\"monochrome\", \"texture\", \"department\"]),\n z.templateLiteral([\"tag:\", z.string()]),\n])\nexport type PluginPresentationViewColorMode = z.infer<\n typeof PluginPresentationViewColorMode\n>\n\n/**\n * A category of on-canvas labels a view can show.\n *\n * | Value | Meaning |\n * |---|---|\n * | `objectLabels` | Per-object name labels |\n * | `areas` | Area/room labels |\n * | `department` | Department labels |\n */\nexport const PluginPresentationViewLabel = z.enum([\n \"objectLabels\",\n \"areas\",\n \"department\",\n])\nexport type PluginPresentationViewLabel = z.infer<\n typeof PluginPresentationViewLabel\n>\n\n/**\n * A view's camera projection: perspective, or isometric (orthographic).\n *\n * | Value | Meaning |\n * |---|---|\n * | `perspective` | Perspective projection (vanishing points) |\n * | `isometric` | Orthographic projection (no perspective distortion) |\n *\n * Applies to 3D views; a 2D plan view is always orthographic and reads\n * `\"isometric\"`.\n */\nexport const PluginPresentationViewMode = z.enum([\"perspective\", \"isometric\"])\nexport type PluginPresentationViewMode = z.infer<\n typeof PluginPresentationViewMode\n>\n\n/**\n * A view's display settings.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `backgroundColor` | `string` | Canvas background as a hex color (e.g. `#ffffff`) |\n * | `colorMode` | {@linkcode PluginPresentationViewColorMode} | How the scene is colored (including `tag:<categoryId>` schemes) |\n * | `viewMode` | {@linkcode PluginPresentationViewMode} | Camera projection — `\"perspective\"` or `\"isometric\"` (orthographic). Writable on 3D views only; a 2D plan always reads `\"isometric\"` and accepts only `\"isometric\"` (a no-op) |\n * | `showAxis` | `boolean` | Whether the X/Y/Z axes are shown |\n * | `showEdges` | `boolean` | Whether edges are shown; `false` = hidden-line rendering |\n * | `showLabels` | `boolean` | Master label switch — whether on-canvas labels are shown at all. `true` always pairs with a non-empty `labels` selection (labels render only while the selection is non-empty; there is no labels-on-with-empty-selection state) |\n * | `labels` | {@linkcode PluginPresentationViewLabel}`[]` | Which label categories are selected. When `showLabels` is `false` the view stores no selection and this reads `[]` |\n */\nexport const PluginPresentationViewSettings = z.object({\n backgroundColor: z.string(),\n colorMode: PluginPresentationViewColorMode,\n viewMode: PluginPresentationViewMode,\n showAxis: z.boolean(),\n showEdges: z.boolean(),\n showLabels: z.boolean(),\n labels: z.array(PluginPresentationViewLabel),\n})\nexport type PluginPresentationViewSettings = z.infer<\n typeof PluginPresentationViewSettings\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationViewsApi.getSettings}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | The id of the view to read settings for |\n */\nexport const PluginPresentationViewsGetSettingsArgs = z.object({\n id: z.string(),\n})\nexport type PluginPresentationViewsGetSettingsArgs = z.infer<\n typeof PluginPresentationViewsGetSettingsArgs\n>\n\n/**\n * Result of {@linkcode PluginPresentationViewsApi.getSettings} — the settings,\n * or `null` when no view has that id.\n */\nexport const PluginPresentationViewsGetSettingsResult =\n PluginPresentationViewSettings.nullable()\nexport type PluginPresentationViewsGetSettingsResult = z.infer<\n typeof PluginPresentationViewsGetSettingsResult\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationViewsApi.updateSettings}. The\n * `settings` patch is partial — any omitted field is left unchanged. A patch\n * pairing `showLabels: false` with a non-empty `labels` list, or\n * `showLabels: true` with an explicitly empty `labels` list, is rejected as\n * contradictory (see the method JSDoc for the full label patch semantics).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | The id of the view to update |\n * | `settings` | `Partial<`{@linkcode PluginPresentationViewSettings}`>` | The fields to change |\n */\nexport const PluginPresentationViewsUpdateSettingsArgs = z\n .object({\n id: z.string(),\n settings: PluginPresentationViewSettings.partial(),\n })\n .refine(\n (v) =>\n !(\n v.settings.showLabels === false && (v.settings.labels?.length ?? 0) > 0\n ),\n {\n message:\n \"Contradictory patch: labels cannot be set while showLabels is false\",\n },\n )\n .refine(\n (v) =>\n !(\n v.settings.showLabels === true &&\n v.settings.labels !== undefined &&\n v.settings.labels.length === 0\n ),\n {\n message:\n \"Contradictory patch: showLabels cannot be true with an explicitly empty labels list — pass a non-empty list, or omit labels to apply the defaults\",\n },\n )\nexport type PluginPresentationViewsUpdateSettingsArgs = z.infer<\n typeof PluginPresentationViewsUpdateSettingsArgs\n>\n\n/** Result of {@linkcode PluginPresentationViewsApi.updateSettings} — the updated view's id. */\nexport const PluginPresentationViewsUpdateSettingsResult = z.object({\n id: z.string(),\n})\nexport type PluginPresentationViewsUpdateSettingsResult = z.infer<\n typeof PluginPresentationViewsUpdateSettingsResult\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationViewsApi.rename}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | The id of the view to rename |\n * | `name` | `string` | New display name (non-empty after trimming) |\n */\nexport const PluginPresentationViewsRenameArgs = z.object({\n id: z.string(),\n name: z.string().trim().min(1),\n})\nexport type PluginPresentationViewsRenameArgs = z.infer<\n typeof PluginPresentationViewsRenameArgs\n>\n\n/** Result of {@linkcode PluginPresentationViewsApi.rename} — the renamed view. */\nexport const PluginPresentationViewsRenameResult = PluginPresentationView\nexport type PluginPresentationViewsRenameResult = z.infer<\n typeof PluginPresentationViewsRenameResult\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationViewsApi.delete}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | The id of the view to delete |\n */\nexport const PluginPresentationViewsDeleteArgs = z.object({\n id: z.string(),\n})\nexport type PluginPresentationViewsDeleteArgs = z.infer<\n typeof PluginPresentationViewsDeleteArgs\n>\n\n/**\n * Result of {@linkcode PluginPresentationViewsApi.delete}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Id of the deleted view |\n */\nexport const PluginPresentationViewsDeleteResult = z.object({\n id: z.string(),\n})\nexport type PluginPresentationViewsDeleteResult = z.infer<\n typeof PluginPresentationViewsDeleteResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * Presentation sheets — the layout sheets a presentation is assembled on.\n *\n * A **sheet** is a page in Present mode that views and diagrams are placed onto.\n * These methods require **Present mode to be open** (the documentation editor) —\n * reads return `[]`/`null` and writes throw when it is not.\n *\n * `list`/`get` are reads; the rest mutate and require Present mode to be open:\n * `create` adds a sheet, `place` drops a saved view onto a sheet as a live\n * linked view shape, `rename` relabels a sheet, `delete` removes one, `reorder`\n * moves one in the sheet order, `setActive` navigates to one, and\n * `updatePlacedView` re-renders placed views to the current model.\n *\n * Accessed via `snaptrude.presentation.sheets`.\n */\nexport abstract class PluginPresentationSheetsApi {\n constructor() {}\n\n /**\n * List the layout sheets in the presentation.\n *\n * @returns A {@linkcode PluginPresentationSheetsListResult} with a `sheets`\n * array (empty when Present mode is closed or there are no sheets).\n *\n * @examplePrompt List all the sheets\n * @examplePrompt How many layout sheets are there?\n * @examplePrompt Show me the presentation sheets\n * @examplePrompt What sheets can I place views on?\n *\n * # Example\n * ```ts\n * const { sheets } = await snaptrude.presentation.sheets.list()\n * ```\n */\n public abstract list(): PluginApiReturn<PluginPresentationSheetsListResult>\n\n /**\n * Get a single sheet by id.\n *\n * @param sheetId - The id of the sheet to read.\n * @returns The matching {@linkcode PluginPresentationSheet}, or `null`.\n *\n * @examplePrompt Get the sheet with id sheet_1\n * @examplePrompt Find the sheet called Cover\n * @examplePrompt Look up a layout sheet by id\n *\n * # Example\n * ```ts\n * const sheet = await snaptrude.presentation.sheets.get(\"sheet_1\")\n * ```\n */\n public abstract get(\n sheetId: string,\n ): PluginApiReturn<PluginPresentationSheetsGetResult>\n\n /**\n * Create a new layout sheet.\n *\n * Optionally sets the paper size and orientation — the same presets as the\n * sheet header's size dropdown (ANSI A–E, Arch A–E1, ISO A4–A0). When `size`\n * is omitted the sheet inherits the editor session's last-used size\n * (initially ANSI B) in landscape, so pass `size` explicitly whenever the\n * output format matters.\n *\n * Requires Present mode to be open.\n *\n * @param name - Display name for the new sheet (optional).\n * @param options - Optional `size` ({@linkcode PluginSheetSize} paper preset)\n * and `orientation` ({@linkcode PluginSheetOrientation}; `\"landscape\"` when\n * omitted).\n * @returns The newly created {@linkcode PluginPresentationSheet}.\n * @throws If Present mode is not open.\n *\n * @examplePrompt Create a new sheet called Cover\n * @examplePrompt Add an A3 landscape layout sheet\n * @examplePrompt Make a new ANSI D presentation sheet named Floor Plans\n * @examplePrompt Create a portrait A4 sheet\n *\n * # Example\n * ```ts\n * const sheet = await snaptrude.presentation.sheets.create(\"Cover\", {\n * size: \"ISO_A3\",\n * orientation: \"landscape\",\n * })\n * ```\n */\n public abstract create(\n name?: string,\n options?: { size?: PluginSheetSize; orientation?: PluginSheetOrientation },\n ): PluginApiReturn<PluginPresentationSheet>\n\n /**\n * Set a sheet's paper size and/or orientation.\n *\n * Applies one of the standard paper presets — exactly what the sheet header's\n * size dropdown and orientation toggle do. Content that no longer fits inside\n * the resized sheet is moved out onto the canvas (matching the native\n * behavior). Undoable. Does not change the user's remembered default size for\n * new sheets.\n *\n * Requires Present mode to be open.\n *\n * @param sheetId - The id of the sheet to resize.\n * @param size - The paper preset ({@linkcode PluginSheetSize}).\n * @param orientation - Optional {@linkcode PluginSheetOrientation}; keeps the\n * sheet's current orientation when omitted.\n * @returns The updated {@linkcode PluginPresentationSheet}.\n * @throws If Present mode is not open, or the sheet id is invalid.\n *\n * @examplePrompt Resize sheet_1 to A1\n * @examplePrompt Make the cover sheet ANSI B portrait\n * @examplePrompt Change this sheet to Arch D landscape\n * @examplePrompt Set the sheet size to A3\n *\n * # Example\n * ```ts\n * const sheet = await snaptrude.presentation.sheets.setSize(\"sheet_1\", \"ISO_A1\", \"portrait\")\n * ```\n */\n public abstract setSize(\n sheetId: string,\n size: PluginSheetSize,\n orientation?: PluginSheetOrientation,\n ): PluginApiReturn<PluginPresentationSheet>\n\n /**\n * Set a sheet's print margin.\n *\n * Applies one of the canonical margin presets — exactly what the sheet\n * header's margin dropdown does. `margin` is unit-agnostic: `0 | 0.25 |\n * 0.5 | 0.75 | 1`, displayed as None / 1/4″ / 1/2″ / 3/4″ / 1″ on imperial\n * projects and None / 5mm / 10mm / 15mm / 20mm on metric ones. One undo\n * step. Requires Present mode to be open.\n *\n * @param sheetId - The id of the sheet.\n * @param margin - The margin preset ({@linkcode PluginSheetMargin}).\n * @returns The updated {@linkcode PluginPresentationSheet}.\n * @throws If Present mode is not open, or the sheet id is invalid.\n *\n * @examplePrompt Set the sheet margin to half an inch\n * @examplePrompt Remove the margins on sheet_1\n * @examplePrompt Give the cover sheet a 10mm margin\n *\n * # Example\n * ```ts\n * const sheet = await snaptrude.presentation.sheets.setMargin(\"sheet_1\", 0.5)\n * ```\n */\n public abstract setMargin(\n sheetId: string,\n margin: PluginSheetMargin,\n ): PluginApiReturn<PluginPresentationSheet>\n\n /**\n * Place a saved view onto a sheet.\n *\n * Drops the given view onto the sheet through the **native view drop** — the\n * placed shape is a live linked view (the same shape the Present views panel's\n * drag-drop creates), re-rendered from the current model, so\n * {@linkcode PluginPresentationSheetsApi.updatePlacedView} refreshes it later.\n * 2D and site-plan views are placed at an architectural scale: the closest\n * standard scale by default (the native auto-fit), or the explicit `scale`\n * when given — one of the standard values the placed view's Scale dropdown\n * offers for the project's unit system (metric `10, 20, 50, 100, 150, 200,\n * 250, 500, 1000`; imperial e.g. `48` for 1/4″ = 1′, `96` for 1/8″ = 1′,\n * `120` for 1″ = 10′). 3D views have no scale — passing `scale` for one\n * throws. Requires Present mode to be open.\n *\n * @param sheetId - The id of the sheet to place onto.\n * @param viewId - The id of the saved view to place.\n * @param options - Optional `position` ({@linkcode PluginSheetPosition}) —\n * where to place it (sheet centre when omitted) — and `scale` — a standard\n * scale value for the project's unit system (2D/site-plan views only;\n * auto-fit when omitted).\n * @returns A {@linkcode PluginPresentationSheetsPlaceResult} with the created\n * `shapeId`.\n * @throws If Present mode is not open, the sheet/view id is invalid, `scale`\n * is not a standard value for the project's unit system, or `scale` is\n * passed for a 3D view.\n *\n * @examplePrompt Place the floor plan on Sheet 1\n * @examplePrompt Add this 3D view to the cover sheet\n * @examplePrompt Drop the site plan onto the layout sheet at 100, 200\n * @examplePrompt Put the Ground Floor view on sheet_1 at 1:100 scale\n * @examplePrompt Place the plan on the cover sheet at quarter-inch scale\n *\n * # Example\n * ```ts\n * const { shapeId } = await snaptrude.presentation.sheets.place(\"sheet_1\", \"view_123\", {\n * scale: 100, // 1:100 (metric project)\n * })\n * ```\n */\n public abstract place(\n sheetId: string,\n viewId: string,\n options?: { position?: PluginSheetPosition; scale?: number },\n ): PluginApiReturn<PluginPresentationSheetsPlaceResult>\n\n /**\n * Move an existing drawing onto a sheet without copying or regenerating it.\n *\n * The position is sheet-local and defaults to the sheet centre. The drawing's\n * stable id remains unchanged; `shapeId` therefore identifies the moved drawing.\n *\n * @param sheetId - The id of the destination sheet.\n * @param drawingId - The id of an existing drawing deliverable.\n * @param options - Optional sheet-local `position` (sheet centre when omitted).\n * @returns A {@linkcode PluginPresentationSheetsPlaceResult} containing the drawing shape id.\n * @throws If Present mode is closed, either id is invalid, or the shape is not a drawing.\n *\n * @examplePrompt Move this drawing onto the cover sheet\n * @examplePrompt Place drawing_1 at the center of sheet_2\n */\n public abstract placeDrawing(\n sheetId: string,\n drawingId: string,\n options?: { position?: PluginSheetPosition },\n ): PluginApiReturn<PluginPresentationSheetsPlaceResult>\n\n /**\n * Rename a layout sheet.\n *\n * Sets the sheet's display name — the same label the sheets panel shows and\n * that `list`/`get` read back. Requires Present mode to be open.\n *\n * @param sheetId - The id of the sheet to rename.\n * @param name - The new display name (non-empty after trimming).\n * @returns The updated {@linkcode PluginPresentationSheet}.\n * @throws If Present mode is not open, or the sheet id is invalid.\n *\n * @examplePrompt Rename sheet_1 to Cover\n * @examplePrompt Call the first sheet Ground Floor\n * @examplePrompt Change this sheet's name to Site Plan\n *\n * # Example\n * ```ts\n * const sheet = await snaptrude.presentation.sheets.rename(\"sheet_1\", \"Cover\")\n * ```\n */\n public abstract rename(\n sheetId: string,\n name: string,\n ): PluginApiReturn<PluginPresentationSheet>\n\n /**\n * Delete a layout sheet.\n *\n * Removes the sheet (and its placed content) from the presentation and\n * renumbers the remaining sheets. Requires Present mode to be open. Auto-diagram\n * sheets cannot be deleted through the API — remove those from the Present UI.\n *\n * @param sheetId - The id of the sheet to delete.\n * @returns The id of the deleted sheet\n * (a {@linkcode PluginPresentationSheetsDeleteResult}).\n * @throws If Present mode is not open, the sheet id is invalid, or the sheet is\n * an auto-diagram sheet.\n *\n * @examplePrompt Delete sheet_2\n * @examplePrompt Remove the Cover sheet\n * @examplePrompt Get rid of the last layout sheet\n *\n * # Example\n * ```ts\n * const { id } = await snaptrude.presentation.sheets.delete(\"sheet_2\")\n * ```\n */\n public abstract delete(\n sheetId: string,\n ): PluginApiReturn<PluginPresentationSheetsDeleteResult>\n\n /**\n * Move a sheet to a new position in the sheet order.\n *\n * Reorders the sheet to the given zero-based `index` (as the sheets panel does\n * when a sheet is dragged) and renumbers the rest. `index` is clamped to the\n * valid range. Requires Present mode to be open.\n *\n * @param sheetId - The id of the sheet to move.\n * @param index - Zero-based target position in the sheet order.\n * @returns The sheets in their new order\n * (a {@linkcode PluginPresentationSheetsListResult}).\n * @throws If Present mode is not open, or the sheet id is invalid.\n *\n * @examplePrompt Move sheet_3 to the front\n * @examplePrompt Make the Cover sheet the first sheet\n * @examplePrompt Reorder this sheet to position 2\n *\n * # Example\n * ```ts\n * const { sheets } = await snaptrude.presentation.sheets.reorder(\"sheet_3\", 0)\n * ```\n */\n public abstract reorder(\n sheetId: string,\n index: number,\n ): PluginApiReturn<PluginPresentationSheetsListResult>\n\n /**\n * Make a sheet the active sheet.\n *\n * Selects the sheet in the sheets panel and navigates the Present canvas to it\n * (the same as clicking it in the panel). Requires Present mode to be open.\n *\n * @param sheetId - The id of the sheet to activate.\n * @returns The now-active {@linkcode PluginPresentationSheet}.\n * @throws If Present mode is not open, or the sheet id is invalid.\n *\n * @examplePrompt Go to sheet_1\n * @examplePrompt Open the Cover sheet\n * @examplePrompt Switch to the Site Plan sheet\n *\n * # Example\n * ```ts\n * await snaptrude.presentation.sheets.setActive(\"sheet_1\")\n * ```\n */\n public abstract setActive(\n sheetId: string,\n ): PluginApiReturn<PluginPresentationSheet>\n\n /**\n * Refresh placed views to the current model state.\n *\n * Re-renders the presentation's placed **views** — the live linked view shapes —\n * from the current model (the \"Update views\" action). Views placed with\n * {@linkcode PluginPresentationSheetsApi.place} are live view shapes too, so\n * they refresh along with panel-dropped ones. Pass `sheetId` to refresh only\n * the views on that sheet, or omit it to refresh every placed view. Requires\n * Present mode to be open.\n *\n * @param options - Optional `sheetId` — restrict the refresh to one sheet's\n * placed views (all placed views when omitted).\n * @returns The ids of the placed view shapes submitted for refresh\n * (a {@linkcode PluginPresentationSheetsUpdatePlacedViewResult}).\n * @throws If Present mode is not open, or `sheetId` is given but invalid.\n *\n * @examplePrompt Update all the placed views to the latest model\n * @examplePrompt Refresh the views on sheet_1\n * @examplePrompt Re-render the placed views after my edits\n *\n * # Example\n * ```ts\n * const { shapeIds } = await snaptrude.presentation.sheets.updatePlacedView()\n * ```\n */\n public abstract updatePlacedView(options?: {\n sheetId?: string\n }): PluginApiReturn<PluginPresentationSheetsUpdatePlacedViewResult>\n}\n\n/**\n * Standard paper-size presets for layout sheets — identical to the sheet\n * header's size dropdown in Present mode.\n */\nexport const PluginSheetSize = z.enum([\n \"ANSI_A\",\n \"ANSI_B\",\n \"ANSI_C\",\n \"ANSI_D\",\n \"ANSI_E\",\n \"ARCH_A\",\n \"ARCH_B\",\n \"ARCH_C\",\n \"ARCH_D\",\n \"ARCH_E\",\n \"ARCH_E1\",\n \"ISO_A4\",\n \"ISO_A3\",\n \"ISO_A2\",\n \"ISO_A1\",\n \"ISO_A0\",\n])\nexport type PluginSheetSize = z.infer<typeof PluginSheetSize>\n\n/** Sheet orientation. */\nexport const PluginSheetOrientation = z.enum([\"landscape\", \"portrait\"])\nexport type PluginSheetOrientation = z.infer<typeof PluginSheetOrientation>\n\n/**\n * Canonical sheet margin presets — unit-agnostic values matching the sheet\n * header's margin dropdown: `0` (None), `0.25`, `0.5`, `0.75`, `1`.\n * Displayed as None / 1/4″ / 1/2″ / 3/4″ / 1″ on imperial projects and\n * None / 5mm / 10mm / 15mm / 20mm on metric ones.\n */\nexport const PluginSheetMargin = z.union([\n z.literal(0),\n z.literal(0.25),\n z.literal(0.5),\n z.literal(0.75),\n z.literal(1),\n])\nexport type PluginSheetMargin = z.infer<typeof PluginSheetMargin>\n\n/**\n * A layout sheet in the presentation.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Unique sheet id |\n * | `name` | `string` | Display name |\n * | `size` | {@linkcode PluginSheetSize}` \\| null` | Paper preset (`null` for a legacy sheet without one) |\n * | `orientation` | {@linkcode PluginSheetOrientation}` \\| null` | Orientation (`null` for a legacy sheet) |\n * | `margin` | {@linkcode PluginSheetMargin}` \\| null` | Margin preset (`null` when absent/unrecognized) |\n */\nexport const PluginPresentationSheet = z.object({\n id: z.string(),\n name: z.string(),\n size: PluginSheetSize.nullable(),\n orientation: PluginSheetOrientation.nullable(),\n margin: PluginSheetMargin.nullable(),\n})\nexport type PluginPresentationSheet = z.infer<typeof PluginPresentationSheet>\n\n/**\n * A position on a sheet/canvas (sheet coordinates).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `x` | `number` | X coordinate |\n * | `y` | `number` | Y coordinate |\n */\nexport const PluginSheetPosition = z.object({\n x: z.number(),\n y: z.number(),\n})\nexport type PluginSheetPosition = z.infer<typeof PluginSheetPosition>\n\n/** Result of {@linkcode PluginPresentationSheetsApi.list}. */\nexport const PluginPresentationSheetsListResult = z.object({\n sheets: z.array(PluginPresentationSheet),\n})\nexport type PluginPresentationSheetsListResult = z.infer<\n typeof PluginPresentationSheetsListResult\n>\n\n/** Arguments for {@linkcode PluginPresentationSheetsApi.get}. */\nexport const PluginPresentationSheetsGetArgs = z.object({\n id: z.string(),\n})\nexport type PluginPresentationSheetsGetArgs = z.infer<\n typeof PluginPresentationSheetsGetArgs\n>\n\n/** Result of {@linkcode PluginPresentationSheetsApi.get} — the sheet, or `null`. */\nexport const PluginPresentationSheetsGetResult =\n PluginPresentationSheet.nullable()\nexport type PluginPresentationSheetsGetResult = z.infer<\n typeof PluginPresentationSheetsGetResult\n>\n\n/** Arguments for {@linkcode PluginPresentationSheetsApi.create}. */\nexport const PluginPresentationSheetsCreateArgs = z.object({\n name: z.string().optional(),\n size: PluginSheetSize.optional(),\n orientation: PluginSheetOrientation.optional(),\n})\nexport type PluginPresentationSheetsCreateArgs = z.infer<\n typeof PluginPresentationSheetsCreateArgs\n>\n\n/** Arguments for {@linkcode PluginPresentationSheetsApi.setSize}. */\nexport const PluginPresentationSheetsSetSizeArgs = z.object({\n sheetId: z.string(),\n size: PluginSheetSize,\n orientation: PluginSheetOrientation.optional(),\n})\nexport type PluginPresentationSheetsSetSizeArgs = z.infer<\n typeof PluginPresentationSheetsSetSizeArgs\n>\n\n/** Arguments for {@linkcode PluginPresentationSheetsApi.setMargin}. */\nexport const PluginPresentationSheetsSetMarginArgs = z.object({\n sheetId: z.string(),\n margin: PluginSheetMargin,\n})\nexport type PluginPresentationSheetsSetMarginArgs = z.infer<\n typeof PluginPresentationSheetsSetMarginArgs\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationSheetsApi.place}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetId` | `string` | The sheet to place onto |\n * | `viewId` | `string` | The saved view to place |\n * | `position` | {@linkcode PluginSheetPosition}` \\| undefined` | Where to place it (sheet centre when omitted) |\n * | `scale` | `number \\| undefined` | Standard scale value for the project's unit system (2D/site-plan views only; auto-fit to the closest standard scale when omitted) |\n */\nexport const PluginPresentationSheetsPlaceArgs = z.object({\n sheetId: z.string(),\n viewId: z.string(),\n position: PluginSheetPosition.optional(),\n scale: z.number().positive().optional(),\n})\nexport type PluginPresentationSheetsPlaceArgs = z.infer<\n typeof PluginPresentationSheetsPlaceArgs\n>\n\n/** Arguments for moving an existing drawing onto a sheet. */\nexport const PluginPresentationSheetsPlaceDrawingArgs = z.object({\n sheetId: z.string().min(1),\n drawingId: z.string().min(1),\n position: PluginSheetPosition.optional(),\n})\nexport type PluginPresentationSheetsPlaceDrawingArgs = z.infer<\n typeof PluginPresentationSheetsPlaceDrawingArgs\n>\n\n/**\n * Result of {@linkcode PluginPresentationSheetsApi.place}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `shapeId` | `string` | Id of the created canvas shape |\n */\nexport const PluginPresentationSheetsPlaceResult = z.object({\n shapeId: z.string(),\n})\nexport type PluginPresentationSheetsPlaceResult = z.infer<\n typeof PluginPresentationSheetsPlaceResult\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationSheetsApi.rename}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetId` | `string` | The sheet to rename |\n * | `name` | `string` | New display name (non-empty after trimming) |\n */\nexport const PluginPresentationSheetsRenameArgs = z.object({\n sheetId: z.string(),\n name: z.string().trim().min(1),\n})\nexport type PluginPresentationSheetsRenameArgs = z.infer<\n typeof PluginPresentationSheetsRenameArgs\n>\n\n/** Arguments for {@linkcode PluginPresentationSheetsApi.delete}. */\nexport const PluginPresentationSheetsDeleteArgs = z.object({\n sheetId: z.string(),\n})\nexport type PluginPresentationSheetsDeleteArgs = z.infer<\n typeof PluginPresentationSheetsDeleteArgs\n>\n\n/**\n * Result of {@linkcode PluginPresentationSheetsApi.delete}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Id of the deleted sheet |\n */\nexport const PluginPresentationSheetsDeleteResult = z.object({\n id: z.string(),\n})\nexport type PluginPresentationSheetsDeleteResult = z.infer<\n typeof PluginPresentationSheetsDeleteResult\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationSheetsApi.reorder}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetId` | `string` | The sheet to move |\n * | `index` | `number` | Zero-based target position (clamped to range) |\n */\nexport const PluginPresentationSheetsReorderArgs = z.object({\n sheetId: z.string(),\n index: z.number().int().min(0),\n})\nexport type PluginPresentationSheetsReorderArgs = z.infer<\n typeof PluginPresentationSheetsReorderArgs\n>\n\n/** Arguments for {@linkcode PluginPresentationSheetsApi.setActive}. */\nexport const PluginPresentationSheetsSetActiveArgs = z.object({\n sheetId: z.string(),\n})\nexport type PluginPresentationSheetsSetActiveArgs = z.infer<\n typeof PluginPresentationSheetsSetActiveArgs\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationSheetsApi.updatePlacedView}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetId` | `string`? | Restrict the refresh to one sheet's placed views |\n */\nexport const PluginPresentationSheetsUpdatePlacedViewArgs = z.object({\n sheetId: z.string().optional(),\n})\nexport type PluginPresentationSheetsUpdatePlacedViewArgs = z.infer<\n typeof PluginPresentationSheetsUpdatePlacedViewArgs\n>\n\n/**\n * Result of {@linkcode PluginPresentationSheetsApi.updatePlacedView}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `shapeIds` | `string[]` | Ids of the placed view shapes submitted for refresh |\n */\nexport const PluginPresentationSheetsUpdatePlacedViewResult = z.object({\n shapeIds: z.array(z.string()),\n})\nexport type PluginPresentationSheetsUpdatePlacedViewResult = z.infer<\n typeof PluginPresentationSheetsUpdatePlacedViewResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport { PluginSheetPosition } from \"./sheets\"\n\n/**\n * Placed views — the view shapes already laid out on the Present canvas.\n *\n * A **placed view** is a saved view that has been dropped onto a layout sheet\n * (or the open canvas) — the live linked view shape\n * {@linkcode PluginPresentationSheetsApi.place} and the Present views panel's\n * drag-drop create. These methods read and edit the **layout** of those\n * shapes: where they sit, how big they are, their rotation, their\n * architectural scale, and their crop. To refresh a placed view's **content** from the current model,\n * use `sheets.updatePlacedView` — content refresh lives on `sheets`,\n * layout lives here.\n *\n * Reads (`list`) return `[]` when Present mode is closed; `get` and every\n * write require Present mode to be open.\n *\n * Accessed via `snaptrude.presentation.placedViews`.\n */\nexport abstract class PluginPresentationPlacedViewsApi {\n constructor() {}\n\n /**\n * List the placed views in the presentation.\n *\n * Returns every placed view shape on the Present canvas — those nested on a\n * layout sheet (with that sheet's id) and those sitting loose on the canvas\n * (`sheetId: null`). Pass `sheetId` to list only the views placed on that\n * sheet.\n *\n * @param sheetId - Optional sheet id to filter by.\n * @returns A {@linkcode PluginPresentationPlacedViewsListResult} with a\n * `placedViews` array (empty when Present mode is closed or nothing is\n * placed).\n * @throws If `sheetId` is given but is not a sheet (with Present mode open).\n *\n * @examplePrompt Which views are placed on this sheet?\n * @examplePrompt List all the placed views in the presentation\n * @examplePrompt What's laid out on sheet 2?\n * @examplePrompt Show me every view on the Present canvas\n *\n * # Example\n * ```ts\n * const { placedViews } = await snaptrude.presentation.placedViews.list(\"sheet_1\")\n * ```\n */\n public abstract list(\n sheetId?: string,\n ): PluginApiReturn<PluginPresentationPlacedViewsListResult>\n\n /**\n * Get a single placed view by its shape id.\n *\n * `shapeId` is the id `sheets.place` returned (also reported by `list`).\n *\n * @param shapeId - The id of the placed view shape to read.\n * @returns The matching {@linkcode PluginPlacedView}.\n * @throws If Present mode is not open, or `shapeId` is unknown or is not a\n * placed view.\n *\n * @examplePrompt Get the placed view I just dropped on the sheet\n * @examplePrompt Read the position and scale of this placed view\n * @examplePrompt What's the crop on shape_123?\n *\n * # Example\n * ```ts\n * const placed = await snaptrude.presentation.placedViews.get(shapeId)\n * console.log(placed.position, placed.scale)\n * ```\n */\n public abstract get(shapeId: string): PluginApiReturn<PluginPlacedView>\n\n /**\n * Move a placed view.\n *\n * Repositions the placed view on its sheet — `position` is the new top-left\n * in sheet coordinates (page coordinates when the view is not on a sheet).\n * Pass `options.sheetId` to move it onto another sheet: the view is\n * reparented to that sheet and then positioned at `position` in the new\n * sheet's coordinates. A grouped placed view (e.g. a sustainability 3D view\n * with its legend) moves as one unit. Requires Present mode to be open.\n *\n * @param shapeId - The placed view shape to move.\n * @param position - New top-left position (sheet-local; page coordinates\n * when off-sheet).\n * @param options - Optional `sheetId` — move the view onto that sheet.\n * @returns The updated {@linkcode PluginPlacedView}.\n * @throws If Present mode is not open, `shapeId` is unknown or is not a\n * placed view, or `options.sheetId` is given but is not a sheet.\n *\n * @examplePrompt Move the site plan view to the top-left of sheet 2\n * @examplePrompt Reposition this placed view to 100, 200\n * @examplePrompt Move the hero perspective onto the cover sheet\n *\n * # Example\n * ```ts\n * await snaptrude.presentation.placedViews.move(shapeId, { x: 50, y: 50 }, {\n * sheetId: \"sheet_2\",\n * })\n * ```\n */\n public abstract move(\n shapeId: string,\n position: PluginSheetPosition,\n options?: { sheetId?: string },\n ): PluginApiReturn<PluginPlacedView>\n\n /**\n * Resize a placed view by a uniform factor.\n *\n * Scales the placed view about its top-left corner — the same resize as\n * dragging a corner handle, aspect ratio held. Works for 2D and 3D views;\n * labels and grouped legends resize with it. For a 2D view the **printed\n * architectural scale changes proportionally** (doubling the size of a\n * 1:100 plan makes it a 1:50 plan) — use\n * {@linkcode PluginPresentationPlacedViewsApi.setScale} to land on an exact\n * standard scale instead. Requires Present mode to be open.\n *\n * @param shapeId - The placed view shape to resize.\n * @param factor - Uniform scale factor, between `0.01` and `100`\n * (`2` doubles the size, `0.5` halves it).\n * @returns The updated {@linkcode PluginPlacedView}.\n * @throws If Present mode is not open, `shapeId` is unknown or is not a\n * placed view, or `factor` is not a finite number between 0.01 and 100.\n *\n * @examplePrompt Make this placed view twice as big\n * @examplePrompt Shrink the placed 3D view to half its size\n * @examplePrompt Scale the placed plan up by 50%\n *\n * # Example\n * ```ts\n * await snaptrude.presentation.placedViews.scale(shapeId, 2)\n * ```\n */\n public abstract scale(\n shapeId: string,\n factor: number,\n ): PluginApiReturn<PluginPlacedView>\n\n /**\n * Set a placed view's architectural scale.\n *\n * Applies a standard architectural scale — the same write the placed view's\n * Scale dropdown performs. The view (and its labels) is resized about its\n * top-left corner so it prints at the given scale. `scale` must be one of\n * the standard values for the project's unit system (metric `10, 20, 50,\n * 100, 150, 200, 250, 500, 1000`; imperial e.g. `48` for 1/4″ = 1′, `96`\n * for 1/8″ = 1′, `120` for 1″ = 10′) — the same validation as\n * `sheets.place`. 3D views have no architectural scale — calling this on\n * one throws. Requires Present mode to be open.\n *\n * @param shapeId - The placed view shape to set the scale of.\n * @param scale - A standard scale value for the project's unit system.\n * @returns The updated {@linkcode PluginPlacedView}.\n * @throws If Present mode is not open, `shapeId` is unknown or is not a\n * placed view, `scale` is not a standard value for the project's unit\n * system, or the placed view is a 3D view.\n *\n * @examplePrompt Set the plan to 1:100\n * @examplePrompt Change the placed floor plan to 1:50 scale\n * @examplePrompt Make this view print at quarter-inch scale\n *\n * # Example\n * ```ts\n * await snaptrude.presentation.placedViews.setScale(shapeId, 100) // 1:100\n * ```\n */\n public abstract setScale(\n shapeId: string,\n scale: number,\n ): PluginApiReturn<PluginPlacedView>\n\n /**\n * Crop a placed view, or clear its crop.\n *\n * `crop` selects the visible window as **fractions (0–1) of the uncropped\n * view** — `topLeft` must be strictly less than `bottomRight` on both axes.\n * The visible region stays anchored on the page (the same behavior as the\n * interactive crop), labels that fall outside the crop are hidden, and the\n * view's architectural scale is untouched. Pass `null` to clear the crop\n * and restore the full view in its uncropped footprint (the interactive\n * double-click-edge reset). The crop **survives a content refresh**\n * (`sheets.updatePlacedView` recomputes it against the refreshed content\n * and only clears it when the refreshed view no longer overlaps the cropped\n * region). Requires Present mode to be open.\n *\n * @param shapeId - The placed view shape to crop.\n * @param crop - The crop window ({@linkcode PluginPlacedViewCrop},\n * fractions of the uncropped view), or `null` to clear.\n * @returns The updated {@linkcode PluginPlacedView}.\n * @throws If Present mode is not open, `shapeId` is unknown or is not a\n * placed view, or `crop` is not a valid 0–1 rectangle with\n * `topLeft < bottomRight` on both axes.\n *\n * @examplePrompt Crop the placed view to its left half\n * @examplePrompt Crop this view to the top-right quarter\n * @examplePrompt Remove the crop from the placed plan\n *\n * # Example\n * ```ts\n * // keep the left half of the view\n * await snaptrude.presentation.placedViews.setCrop(shapeId, {\n * topLeft: { x: 0, y: 0 },\n * bottomRight: { x: 0.5, y: 1 },\n * })\n * await snaptrude.presentation.placedViews.setCrop(shapeId, null) // clear\n * ```\n */\n public abstract setCrop(\n shapeId: string,\n crop: PluginPlacedViewCrop | null,\n ): PluginApiReturn<PluginPlacedView>\n\n /**\n * Set a placed view's rotation.\n *\n * `rotation` is the **absolute** angle in radians (tldraw convention) — the\n * value the record's `rotation` field reads back. Values are canonicalized\n * into `[0, 2π)`, so pass values in that range for an exact round-trip\n * (`setRotation(-0.1)` reads back as `2π − 0.1`). The view rotates about\n * its bounds center (the same pivot as the UI rotation handle). One undo\n * step. Joins the absolute-setter family\n * `setScale`/`setCrop`/`setOpacity`/`setMask`.\n *\n * **3D placed views only.** 2D and site-plan views cannot rotate — the\n * canvas hides their rotate handle and reverts any rotation applied to\n * them — so calling this on one is refused with a typed\n * `PRECONDITION_FAILED` instead of silently no-opping. Requires Present\n * mode to be open.\n *\n * @param shapeId - The placed view shape to rotate.\n * @param rotation - Absolute rotation in radians.\n * @returns The updated {@linkcode PluginPlacedView}.\n * @throws If Present mode is not open, `shapeId` is unknown or is not a\n * placed view, `rotation` is not a finite number, or the placed view is\n * a 2D/site-plan view (`PRECONDITION_FAILED` — only 3D views rotate).\n *\n * @examplePrompt Rotate the placed plan 90 degrees\n * @examplePrompt Set this view's rotation to 45 degrees\n * @examplePrompt Straighten the placed view back to zero rotation\n *\n * # Example\n * ```ts\n * await snaptrude.presentation.placedViews.setRotation(shapeId, Math.PI / 2)\n * ```\n */\n public abstract setRotation(\n shapeId: string,\n rotation: number,\n ): PluginApiReturn<PluginPlacedView>\n\n // ── Present-mode styling ──────────────────────────────────────────────────\n\n /**\n * Read a placed view's (or sheet's) per-category presentation styles.\n *\n * Returns one entry per style category (Site, Topography, Space, Department,\n * Envelope, Facade/Mass, Dimension, Reference Line, Adjacency Line,\n * Landscape elements — sheets/auto-diagrams use their 3-category subset)\n * with the **fully-resolved** setting values (defaults merged with this\n * shape's overrides), which setting ids the category supports, whether the\n * category is currently disabled (not visible in the view), and — where the\n * category has a Text tab — its label font styles. Styles are stored **per\n * placed shape instance**: the same saved view placed twice can read\n * differently. Requires Present mode to be open.\n *\n * @param shapeId - The placed view (or sheet) shape to read.\n * @throws If Present mode is not open or `shapeId` is not a styleable shape.\n *\n * @examplePrompt What line weights is this placed plan using?\n * @examplePrompt Read the styling of the placed view\n * @examplePrompt Which categories can I restyle on this sheet?\n *\n * # Example\n * ```ts\n * const styles = await snaptrude.presentation.placedViews.getStyles(shapeId)\n * const space = styles.categories.find((c) => c.category === \"Space\")\n * console.log(space?.settings.cutWidth, space?.settings.lineStyle)\n * ```\n */\n public abstract getStyles(\n shapeId: string,\n ): PluginApiReturn<PluginPlacedViewStylesResult>\n\n /**\n * Update one style category on a placed view (or sheet) — the same writes\n * the Present sidebar's per-category configure panel performs.\n *\n * The patch may set any subset of the category's settings: line style (7\n * dash patterns), line color, dash-length multiplier, line weights in mm\n * (`cutWidth` vs `projectionWidth` — Revit-style cut/projection distinction;\n * `lineWidth` where the category has a single weight; `treeLineWidth` /\n * `parkingLineWidth` for Landscape), fill `colorMode` (`department`,\n * `texture`, `monochrome`, or `tag:<tagCategoryId>`), per-category fill\n * `opacity` (0–100), category `hide` (cascades to that category's labels),\n * the shape restyle `viewStyle` (`NONE` block / `FILLET` rounded with\n * `radius` / `BUBBLE`) with `radius` and `inset`, and Topography's\n * map/building sub-controls. Settings a category does not support are\n * rejected. Writes are validated, undoable (one history stop per call), and\n * re-render immediately; on an auto-diagram sheet the write fans out to\n * every sheet in its diagram set. Requires Present mode to be open.\n *\n * @param shapeId - The placed view (or sheet) shape to restyle.\n * @param category - The style category to update.\n * @param patch - The settings to change (at least one).\n * @returns The category's resolved styles after the update.\n * @throws If Present mode is not open, `shapeId` is not a styleable shape,\n * the category does not apply to this shape, or a patched setting is not\n * supported by the category or fails validation.\n *\n * @examplePrompt Set the Space cut line weight to 0.5 mm\n * @examplePrompt Make the Department lines dashed\n * @examplePrompt Color the plan by department\n * @examplePrompt Hide the dimension lines on this view\n * @examplePrompt Round the space corners with a 2 ft radius\n *\n * # Example\n * ```ts\n * await snaptrude.presentation.placedViews.updateStyles(shapeId, \"Space\", {\n * cutWidth: 0.5,\n * projectionWidth: 0.18,\n * lineStyle: \"dashed\",\n * colorMode: \"department\",\n * })\n * ```\n */\n public abstract updateStyles(\n shapeId: string,\n category: PluginViewSettingCategory,\n patch: PluginPlacedViewStylePatch,\n ): PluginApiReturn<PluginPlacedViewCategoryStyles>\n\n /**\n * Reset a placed view's styling to defaults — the sidebar's Reset button:\n * clears every per-category override and view-style restyle, resets font\n * styles, and un-hides labels hidden by styling. Undoable. Requires Present\n * mode to be open.\n *\n * @param shapeId - The placed view (or sheet) shape to reset.\n * @throws If Present mode is not open or `shapeId` is not a styleable shape.\n *\n * @examplePrompt Reset this view's styling\n * @examplePrompt Clear all the style overrides on the placed plan\n * @examplePrompt Put the sheet styles back to default\n */\n public abstract resetStyles(shapeId: string): PluginApiReturn<void>\n\n /**\n * Update a category's label typography on a placed view (or sheet) — the\n * configure panel's Text tab.\n *\n * Each category carries font styles per label type — `objectLabels` (the\n * name), `department`, and `areas` — with `fontFamily` (system families or\n * any Google Font; the font is loaded automatically and the weight is\n * coerced to an available variant), `fontWeight` (numeric string, e.g.\n * `\"700\"`), `fontStyle` (`normal`/`italic`), and `fontSize` in px.\n * `visibleTypes` controls which label types render (an empty array hides\n * all). Not every category has a Text tab — Topography, Facade/Mass,\n * Reference Line and Adjacency Line reject font writes. Undoable. Requires\n * Present mode to be open.\n *\n * @param shapeId - The placed view (or sheet) shape.\n * @param category - The style category whose labels to restyle.\n * @param patch - The font styles to change (at least one field).\n * @throws If Present mode is not open, `shapeId` is not a styleable shape,\n * or the category has no Text tab.\n *\n * @examplePrompt Set the space labels to Roboto 24px bold\n * @examplePrompt Hide the area values on this plan\n * @examplePrompt Make the department labels italic\n *\n * # Example\n * ```ts\n * await snaptrude.presentation.placedViews.updateFontStyles(shapeId, \"Space\", {\n * objectLabels: { fontFamily: \"Roboto\", fontWeight: \"700\", fontSize: 24 },\n * visibleTypes: [\"objectLabels\", \"areas\"],\n * })\n * ```\n */\n public abstract updateFontStyles(\n shapeId: string,\n category: PluginViewSettingCategory,\n patch: PluginPlacedViewFontStylesPatch,\n ): PluginApiReturn<void>\n\n // ── Labels ────────────────────────────────────────────────────────────────\n\n /**\n * List a placed view's labels — the text children generated from the view's\n * content (space names, departments, areas, dimension texts). Each label is\n * individually addressable: it can be moved and hidden. Returns `[]` when\n * the view has no labels. Requires Present mode to be open.\n *\n * @param shapeId - The placed view shape whose labels to list.\n * @throws If Present mode is not open or `shapeId` is not a placed view.\n *\n * @examplePrompt List the labels on this placed plan\n * @examplePrompt Where are the space labels on this view?\n * @examplePrompt Show every label with its position\n *\n * # Example\n * ```ts\n * const { labels } = await snaptrude.presentation.placedViews.listLabels(shapeId)\n * for (const l of labels) console.log(l.text, l.position, l.hidden)\n * ```\n */\n public abstract listLabels(\n shapeId: string,\n ): PluginApiReturn<PluginPlacedViewLabelsResult>\n\n /**\n * Move a label to a new position (coordinates relative to its placed view,\n * the same space `listLabels` reports). The manual position survives style\n * changes. Undoable. Requires Present mode to be open.\n *\n * @param labelId - The label to move (from `listLabels`).\n * @param position - New position, parent-view-relative.\n * @throws If Present mode is not open or `labelId` is not a view label.\n *\n * @examplePrompt Move the kitchen label out of the hatched area\n * @examplePrompt Nudge this label 20 units right\n * @examplePrompt Reposition the area text\n */\n public abstract moveLabel(\n labelId: string,\n position: PluginSheetPosition,\n ): PluginApiReturn<PluginPlacedViewLabel>\n\n /**\n * Hide or show a single label (independent of the category-level label\n * visibility). Undoable. Requires Present mode to be open.\n *\n * @param labelId - The label to hide/show (from `listLabels`).\n * @param hidden - `true` to hide, `false` to show.\n * @throws If Present mode is not open or `labelId` is not a view label.\n *\n * @examplePrompt Hide the corridor label\n * @examplePrompt Show that label again\n */\n public abstract setLabelHidden(\n labelId: string,\n hidden: boolean,\n ): PluginApiReturn<PluginPlacedViewLabel>\n\n // ── Canvas shapes (generated outputs, images, annotations) ────────────────\n\n /**\n * List the top-level shapes on the Present canvas — not just placed views:\n * AI-generated outputs, placed diagram images, plugin-created shapes, and\n * annotations, each with an `origin` classification. Sheets (frames) are\n * not listed — use `sheets.list`. Pass `sheetId` to list only shapes on\n * that sheet. Returns `[]` when Present mode is closed.\n *\n * @param sheetId - Optional sheet id to filter by.\n *\n * @examplePrompt List everything on the Present canvas\n * @examplePrompt What AI outputs are on this sheet?\n * @examplePrompt Show all the shapes with their positions\n *\n * # Example\n * ```ts\n * const { shapes } = await snaptrude.presentation.placedViews.listShapes()\n * const aiOutputs = shapes.filter((s) => s.origin === \"aiOutput\")\n * ```\n */\n public abstract listShapes(\n sheetId?: string,\n ): PluginApiReturn<PluginCanvasShapesListResult>\n\n /**\n * Get one Present-canvas shape by id — works for any top-level shape\n * (placed view, AI output, diagram image, plugin shape, annotation), unlike\n * `get` which requires a placed view. Requires Present mode to be open.\n *\n * @param shapeId - The shape to read.\n * @throws If Present mode is not open or `shapeId` is unknown or a sheet.\n *\n * @examplePrompt Inspect the AI render I just generated\n * @examplePrompt Get the bounds of that diagram image\n */\n public abstract getShape(shapeId: string): PluginApiReturn<PluginCanvasShape>\n\n /**\n * Move any Present-canvas shape (AI outputs, diagram images, plugin shapes,\n * annotations — placed views too). Same coordinate semantics as `move`:\n * sheet-local when on a sheet, page coordinates otherwise; pass\n * `options.sheetId` to reparent onto a sheet. Undoable. Requires Present\n * mode to be open.\n *\n * @param shapeId - The shape to move.\n * @param position - New top-left position.\n * @param options - Optional `sheetId` to move the shape onto that sheet.\n * @throws If Present mode is not open, `shapeId` is unknown or a sheet, or\n * `options.sheetId` is not a sheet.\n *\n * @examplePrompt Move the AI render to the top of the sheet\n * @examplePrompt Put the diagram image at 100, 200\n */\n public abstract moveShape(\n shapeId: string,\n position: PluginSheetPosition,\n options?: { sheetId?: string },\n ): PluginApiReturn<PluginCanvasShape>\n\n /**\n * Resize any Present-canvas shape by a uniform factor about its top-left\n * corner (aspect ratio held) — the generic counterpart of `scale`, which\n * requires a placed view. Undoable. Requires Present mode to be open.\n *\n * @param shapeId - The shape to resize.\n * @param factor - Uniform scale factor, between `0.01` and `100`.\n * @throws If Present mode is not open, `shapeId` is unknown or a sheet, or\n * `factor` is out of range.\n *\n * @examplePrompt Make the AI output half its size\n * @examplePrompt Double the size of that image\n */\n public abstract resizeShape(\n shapeId: string,\n factor: number,\n ): PluginApiReturn<PluginCanvasShape>\n\n /**\n * Rotate any Present-canvas shape (AI outputs, diagram images, plugin\n * shapes, annotations — placed views too) — the generic counterpart of\n * `setRotation`, same target rules as `moveShape`. Same semantics:\n * **absolute** angle in radians (tldraw convention) about the shape's\n * bounds center, canonicalized into `[0, 2π)` (pass values in that range\n * for an exact round-trip via `getShape`), one undo step. A placed view\n * passed here falls under the same 3D-only rule as `setRotation`: 2D and\n * site-plan views cannot rotate and are refused with a typed\n * `PRECONDITION_FAILED`. Requires Present mode to be open.\n *\n * @param shapeId - The shape to rotate.\n * @param rotation - Absolute rotation in radians.\n * @throws If Present mode is not open, `shapeId` is unknown or a sheet,\n * `rotation` is not a finite number, or the target is a 2D/site-plan\n * placed view (`PRECONDITION_FAILED` — only 3D views rotate).\n *\n * @examplePrompt Rotate the AI render 15 degrees\n * @examplePrompt Turn that image sideways\n * @examplePrompt Reset the diagram image's rotation\n */\n public abstract rotateShape(\n shapeId: string,\n rotation: number,\n ): PluginApiReturn<PluginCanvasShape>\n\n /**\n * Delete Present-canvas shapes. One undoable step for the whole batch, and\n * the batch is atomic — if any id is rejected, nothing is deleted.\n *\n * Deletable: placed views, AI-generated outputs, placed diagram images, and\n * shapes created by plugins. **Not deletable here:** sheets (use\n * `sheets.delete`) and user-drawn annotations (draw, geo, arrow, note,\n * text, highlight, line shapes without a plugin ownership stamp) — those\n * reject with a validation error. Requires Present mode to be open.\n *\n * @param shapeIds - The shapes to delete (at least one).\n * @returns The deleted shape ids.\n * @throws If Present mode is not open, or any id is unknown, a sheet, or a\n * protected user annotation.\n *\n * @examplePrompt Delete the old AI renders from the sheet\n * @examplePrompt Remove that placed view\n * @examplePrompt Clear the diagram images I placed\n *\n * # Example\n * ```ts\n * const { deleted } = await snaptrude.presentation.placedViews.deleteShapes([\n * out1.shapeId,\n * out2.shapeId,\n * ])\n * ```\n */\n public abstract deleteShapes(\n shapeIds: string[],\n ): PluginApiReturn<PluginCanvasShapesDeleteResult>\n\n /**\n * Set a Present-canvas shape's opacity (`0` transparent .. `1` opaque) —\n * the sidebar's whole-shape opacity slider (distinct from the per-category\n * fill opacity in `updateStyles`). Works for any top-level shape. Undoable.\n * Requires Present mode to be open.\n *\n * @param shapeId - The shape to change.\n * @param opacity - Target opacity, `0`..`1`.\n * @throws If Present mode is not open or `shapeId` is unknown or a sheet.\n *\n * @examplePrompt Fade the placed view to 50%\n * @examplePrompt Make the AI render semi-transparent\n */\n public abstract setOpacity(\n shapeId: string,\n opacity: number,\n ): PluginApiReturn<void>\n\n /**\n * Toggle \"mask context buildings\" on a placed view — mutes the inactive\n * proposal buildings in the view (the sidebar checkbox). Views only.\n * Undoable. Requires Present mode to be open.\n *\n * @param shapeId - The placed view shape.\n * @param enabled - `true` to mask context buildings.\n * @throws If Present mode is not open or `shapeId` is not a placed view.\n *\n * @examplePrompt Mask the context buildings in this view\n * @examplePrompt Show the neighboring proposals again in the placed view\n */\n public abstract setMask(\n shapeId: string,\n enabled: boolean,\n ): PluginApiReturn<void>\n}\n\n/** A point in crop space — fractions (0–1) of the uncropped view. */\nconst PluginPlacedViewCropPoint = z.object({\n x: z.number().min(0).max(1),\n y: z.number().min(0).max(1),\n})\n\n/**\n * A crop window on a placed view, as fractions (0–1) of the **uncropped**\n * view. `topLeft` must be strictly less than `bottomRight` on both axes.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `topLeft` | `{ x: number, y: number }` | Top-left of the visible window (fractions 0–1) |\n * | `bottomRight` | `{ x: number, y: number }` | Bottom-right of the visible window (fractions 0–1) |\n */\nexport const PluginPlacedViewCrop = z\n .object({\n topLeft: PluginPlacedViewCropPoint,\n bottomRight: PluginPlacedViewCropPoint,\n })\n .refine(\n (c) => c.topLeft.x < c.bottomRight.x && c.topLeft.y < c.bottomRight.y,\n { message: \"topLeft must be strictly less than bottomRight on both axes\" },\n )\nexport type PluginPlacedViewCrop = z.infer<typeof PluginPlacedViewCrop>\n\n/**\n * A view placed on the Present canvas.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `shapeId` | `string` | Id of the placed view shape (the id `sheets.place` returns) |\n * | `sheetId` | `string \\| null` | The sheet it sits on (`null` when placed loose on the canvas) |\n * | `title` | `string` | The saved view's title |\n * | `viewId` | `string` | Id of the saved view it was placed from |\n * | `position` | {@linkcode PluginSheetPosition} | Top-left position — sheet-local when on a sheet, page coordinates otherwise |\n * | `size` | `{ width: number, height: number }` | Displayed size on the canvas |\n * | `scale` | `number \\| null` | Architectural scale (e.g. `100` = 1:100); `null` for 3D views |\n * | `is3d` | `boolean` | Whether it is a 3D view (no architectural scale) |\n * | `rotation` | `number` | Rotation in radians, `[0, 2π)` (nonzero only for 3D views — 2D views cannot rotate; set with `setRotation`) |\n * | `crop` | {@linkcode PluginPlacedViewCrop}` \\| null` | Crop window (`null` when uncropped) |\n * | `isUnlinked` | `boolean` | Whether the source proposal was removed (an unlinked view no longer refreshes) |\n */\nexport const PluginPlacedView = z.object({\n shapeId: z.string(),\n sheetId: z.string().nullable(),\n title: z.string(),\n viewId: z.string(),\n position: PluginSheetPosition,\n size: z.object({ width: z.number(), height: z.number() }),\n scale: z.number().nullable(),\n is3d: z.boolean(),\n rotation: z.number(),\n crop: PluginPlacedViewCrop.nullable(),\n isUnlinked: z.boolean(),\n})\nexport type PluginPlacedView = z.infer<typeof PluginPlacedView>\n\n/** Arguments for {@linkcode PluginPresentationPlacedViewsApi.list}. */\nexport const PluginPresentationPlacedViewsListArgs = z.object({\n sheetId: z.string().optional(),\n})\nexport type PluginPresentationPlacedViewsListArgs = z.infer<\n typeof PluginPresentationPlacedViewsListArgs\n>\n\n/** Result of {@linkcode PluginPresentationPlacedViewsApi.list}. */\nexport const PluginPresentationPlacedViewsListResult = z.object({\n placedViews: z.array(PluginPlacedView),\n})\nexport type PluginPresentationPlacedViewsListResult = z.infer<\n typeof PluginPresentationPlacedViewsListResult\n>\n\n/** Arguments for {@linkcode PluginPresentationPlacedViewsApi.get}. */\nexport const PluginPresentationPlacedViewsGetArgs = z.object({\n shapeId: z.string(),\n})\nexport type PluginPresentationPlacedViewsGetArgs = z.infer<\n typeof PluginPresentationPlacedViewsGetArgs\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationPlacedViewsApi.move}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `shapeId` | `string` | The placed view shape to move |\n * | `position` | {@linkcode PluginSheetPosition} | New top-left position (sheet-local; page coordinates when off-sheet) |\n * | `sheetId` | `string`? | Move the view onto this sheet |\n */\nexport const PluginPresentationPlacedViewsMoveArgs = z.object({\n shapeId: z.string(),\n position: PluginSheetPosition,\n sheetId: z.string().optional(),\n})\nexport type PluginPresentationPlacedViewsMoveArgs = z.infer<\n typeof PluginPresentationPlacedViewsMoveArgs\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationPlacedViewsApi.scale}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `shapeId` | `string` | The placed view shape to resize |\n * | `factor` | `number` | Uniform scale factor (0.01–100) |\n */\nexport const PluginPresentationPlacedViewsScaleArgs = z.object({\n shapeId: z.string(),\n factor: z.number().finite().min(0.01).max(100),\n})\nexport type PluginPresentationPlacedViewsScaleArgs = z.infer<\n typeof PluginPresentationPlacedViewsScaleArgs\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationPlacedViewsApi.setScale}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `shapeId` | `string` | The placed view shape to set the scale of |\n * | `scale` | `number` | A standard scale value for the project's unit system |\n */\nexport const PluginPresentationPlacedViewsSetScaleArgs = z.object({\n shapeId: z.string(),\n scale: z.number().positive(),\n})\nexport type PluginPresentationPlacedViewsSetScaleArgs = z.infer<\n typeof PluginPresentationPlacedViewsSetScaleArgs\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationPlacedViewsApi.setCrop}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `shapeId` | `string` | The placed view shape to crop |\n * | `crop` | {@linkcode PluginPlacedViewCrop}` \\| null` | The crop window, or `null` to clear |\n */\nexport const PluginPresentationPlacedViewsSetCropArgs = z.object({\n shapeId: z.string(),\n crop: PluginPlacedViewCrop.nullable(),\n})\nexport type PluginPresentationPlacedViewsSetCropArgs = z.infer<\n typeof PluginPresentationPlacedViewsSetCropArgs\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationPlacedViewsApi.setRotation}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `shapeId` | `string` | The placed view shape to rotate |\n * | `rotation` | `number` | Absolute rotation in radians (tldraw convention) |\n */\nexport const PluginPresentationPlacedViewsSetRotationArgs = z.object({\n shapeId: z.string().min(1),\n rotation: z.number().finite(),\n})\nexport type PluginPresentationPlacedViewsSetRotationArgs = z.infer<\n typeof PluginPresentationPlacedViewsSetRotationArgs\n>\n\n// ── Styling ─────────────────────────────────────────────────────────────────\n\n/**\n * A Present-mode style category — mirrors the host's view-settings CATEGORIES\n * exactly. Sheets/auto-diagrams use the subset Space / Department /\n * Adjacency Line.\n */\nexport const PluginViewSettingCategory = z.enum([\n \"Site\",\n \"Topography\",\n \"Space\",\n \"Department\",\n \"Envelope\",\n \"Facade/Mass\",\n \"Dimension\",\n \"Grid\",\n \"Walls\",\n \"Columns\",\n \"Beams\",\n \"Doors\",\n \"Windows\",\n \"Floors\",\n \"Slabs\",\n \"Roofs\",\n \"Ceilings\",\n \"Staircase\",\n \"Furniture\",\n \"Families\",\n \"Underlay\",\n \"Overlay\",\n \"Reference Line\",\n \"Adjacency Line\",\n \"Landscape elements\",\n])\nexport type PluginViewSettingCategory = z.infer<\n typeof PluginViewSettingCategory\n>\n\n/** The 7 line dash styles (mirrors the host's LINE_STYLES). */\nexport const PluginViewLineStyle = z.enum([\n \"solid\",\n \"dashed\",\n \"dashdot\",\n \"hidden\",\n \"center\",\n \"phantom\",\n \"propertyLine\",\n])\nexport type PluginViewLineStyle = z.infer<typeof PluginViewLineStyle>\n\n/**\n * A style patch for one category — any subset of the category's settings.\n * Line weights are in **mm** (the printed weight); `opacity` values are 0–100;\n * `colorMode` is `department` / `texture` / `monochrome` or `tag:<tagCategoryId>`\n * (color by a project tag category).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `hide` | `boolean`? | Hide the category (cascades to its labels) |\n * | `lineStyle` | {@linkcode PluginViewLineStyle}? | Dash pattern |\n * | `lineColor` | `string`? | Line color, 6-digit hex (`#` optional) |\n * | `dashLength` | `number`? | Dash-length multiplier (> 0; `1` = default) |\n * | `lineWidth` | `number`? | Single line weight in mm (Dimension: 0.05–0.7; otherwise non-negative) |\n * | `cutWidth` | `number`? | Cut-line weight in mm |\n * | `projectionWidth` | `number`? | Projection-line weight in mm |\n * | `treeLineWidth` | `number`? | Tree line weight in mm (Landscape) |\n * | `parkingLineWidth` | `number`? | Parking line weight in mm (Landscape) |\n * | `colorMode` | `string`? | `department` \\| `texture` \\| `monochrome` \\| `tag:<id>` |\n * | `opacity` | `number`? | Category fill opacity, 0–100 |\n * | `mapOpacity` | `number`? | Topography: map image opacity, 0–100 |\n * | `buildingOpacity` | `number`? | Topography: context-building opacity, 0–100 |\n * | `mapHide` | `boolean`? | Topography: hide the map image |\n * | `buildingHide` | `boolean`? | Topography: hide context buildings |\n * | `noBuilding` | `boolean`? | Topography: suppress buildings entirely |\n * | `viewStyle` | `\"NONE\" \\| \"FILLET\" \\| \"BUBBLE\"`? | Shape restyle: block / rounded corner / bubble |\n * | `radius` | `number`? | Corner radius for `FILLET` (feet) |\n * | `inset` | `number`? | Shape inset/offset |\n */\nexport const PluginPlacedViewStylePatch = z\n .object({\n hide: z.boolean().optional(),\n lineStyle: PluginViewLineStyle.optional(),\n lineColor: z\n .string()\n .regex(/^#?[0-9a-fA-F]{6}$/, \"lineColor must be a 6-digit hex color\")\n .optional(),\n dashLength: z.number().finite().positive().optional(),\n lineWidth: z.number().finite().min(0).optional(),\n cutWidth: z.number().finite().min(0).optional(),\n projectionWidth: z.number().finite().min(0).optional(),\n treeLineWidth: z.number().finite().min(0).optional(),\n parkingLineWidth: z.number().finite().min(0).optional(),\n colorMode: z\n .string()\n .refine(\n (v) =>\n [\"department\", \"texture\", \"monochrome\"].includes(v) ||\n v.startsWith(\"tag:\"),\n {\n message:\n \"colorMode must be department, texture, monochrome, or tag:<tagCategoryId>\",\n },\n )\n .optional(),\n opacity: z.number().finite().min(0).max(100).optional(),\n mapOpacity: z.number().finite().min(0).max(100).optional(),\n buildingOpacity: z.number().finite().min(0).max(100).optional(),\n mapHide: z.boolean().optional(),\n buildingHide: z.boolean().optional(),\n noBuilding: z.boolean().optional(),\n viewStyle: z.enum([\"NONE\", \"FILLET\", \"BUBBLE\"]).optional(),\n radius: z.number().finite().min(0).optional(),\n inset: z.number().finite().min(0).optional(),\n // Dimension lengths are plotted paper millimetres, independent of drawing scale.\n markerKind: z\n .enum([\"architecturalTick\", \"openArrow\", \"closedArrow\"])\n .optional(),\n markerSize: z.number().finite().min(0.5).max(6).optional(),\n extensionLineVisible: z.boolean().optional(),\n extensionLineStyle: PluginViewLineStyle.optional(),\n extensionGap: z.number().finite().min(0).max(10).optional(),\n extensionOvershoot: z.number().finite().min(0).max(10).optional(),\n textColor: z\n .string()\n .regex(/^(inherit|#[0-9a-fA-F]{6})$/)\n .optional(),\n textGap: z.number().finite().min(0).max(10).optional(),\n textPlacement: z.literal(\"above\").optional(),\n textMask: z.boolean().optional(),\n textMaskColor: z\n .string()\n .regex(/^#[0-9a-fA-F]{6}$/)\n .optional(),\n textMaskPadding: z.number().finite().min(0).max(5).optional(),\n })\n .strict()\n .refine((p) => Object.keys(p).length > 0, {\n message: \"patch must set at least one setting\",\n })\nexport type PluginPlacedViewStylePatch = z.infer<\n typeof PluginPlacedViewStylePatch\n>\n\n/** Label types that carry typography in a category's Text tab. */\nexport const PluginViewLabelType = z.enum([\n \"objectLabels\",\n \"department\",\n \"areas\",\n])\nexport type PluginViewLabelType = z.infer<typeof PluginViewLabelType>\n\n/** Typography patch for one label type. `fontSize` is in px. */\nconst PluginViewFontVariantPatch = z\n .object({\n fontFamily: z.string().min(1).optional(),\n fontWeight: z\n .string()\n .regex(\n /^\\d{3}$/,\n 'fontWeight must be a numeric string like \"400\" or \"700\"',\n )\n .optional(),\n fontStyle: z.enum([\"normal\", \"italic\"]).optional(),\n fontSize: z.number().finite().positive().max(200).optional(),\n })\n .strict()\n\n/**\n * Font-styles patch for one category's Text tab.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `visibleTypes` | {@linkcode PluginViewLabelType}`[]`? | Which label types render (`[]` hides all) |\n * | `objectLabels` | variant? | Typography for the name label |\n * | `department` | variant? | Typography for the department label |\n * | `areas` | variant? | Typography for the area/tag values |\n */\nexport const PluginPlacedViewFontStylesPatch = z\n .object({\n visibleTypes: z.array(PluginViewLabelType).optional(),\n objectLabels: PluginViewFontVariantPatch.optional(),\n department: PluginViewFontVariantPatch.optional(),\n areas: PluginViewFontVariantPatch.optional(),\n })\n .strict()\n .refine((p) => Object.keys(p).length > 0, {\n message: \"patch must set at least one field\",\n })\nexport type PluginPlacedViewFontStylesPatch = z.infer<\n typeof PluginPlacedViewFontStylesPatch\n>\n\n/** Resolved font styles for one label type (fontSize in px). */\nexport type PluginViewFontVariant = {\n fontFamily?: string\n fontWeight?: string\n fontStyle?: \"normal\" | \"italic\"\n fontSize?: number\n}\n\n/**\n * One category's resolved styles on a placed view or sheet.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `category` | {@linkcode PluginViewSettingCategory} | The category |\n * | `disabled` | `boolean` | Not visible in this view — settings are inert |\n * | `supported` | `string[]` | Setting ids this category accepts in `updateStyles` |\n * | `settings` | {@linkcode PluginPlacedViewStylePatch} | Fully-resolved values (defaults + overrides) |\n * | `fontStyles` | object? | Resolved Text-tab typography (absent when the category has no Text tab) |\n */\nexport type PluginPlacedViewCategoryStyles = {\n category: PluginViewSettingCategory\n disabled: boolean\n supported: string[]\n settings: PluginPlacedViewStylePatch\n fontStyles?: {\n visibleTypes: PluginViewLabelType[]\n objectLabels?: PluginViewFontVariant\n department?: PluginViewFontVariant\n areas?: PluginViewFontVariant\n }\n}\n\n/**\n * Result of {@link PluginPresentationPlacedViewsApi.getStyles}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `shapeId` | `string` | The styleable shape read |\n * | `kind` | `\"view\" \\| \"sheet\"` | Placed view or auto-diagram sheet |\n * | `categories` | {@linkcode PluginPlacedViewCategoryStyles}`[]` | One entry per applicable category |\n */\nexport type PluginPlacedViewStylesResult = {\n shapeId: string\n kind: \"view\" | \"sheet\"\n categories: PluginPlacedViewCategoryStyles[]\n}\n\n/** Arguments for {@link PluginPresentationPlacedViewsApi.updateStyles}. */\nexport const PluginPlacedViewsUpdateStylesArgs = z\n .object({\n shapeId: z.string().min(1),\n category: PluginViewSettingCategory,\n patch: PluginPlacedViewStylePatch,\n })\n .refine(\n ({ category, patch }) =>\n category !== \"Dimension\" ||\n patch.lineWidth === undefined ||\n (patch.lineWidth >= 0.05 && patch.lineWidth <= 0.7),\n {\n message: \"Dimension lineWidth must be between 0.05 and 0.7 paper mm\",\n path: [\"patch\", \"lineWidth\"],\n },\n )\nexport type PluginPlacedViewsUpdateStylesArgs = z.infer<\n typeof PluginPlacedViewsUpdateStylesArgs\n>\n\n/** Arguments for {@link PluginPresentationPlacedViewsApi.updateFontStyles}. */\nexport const PluginPlacedViewsUpdateFontStylesArgs = z.object({\n shapeId: z.string().min(1),\n category: PluginViewSettingCategory,\n patch: PluginPlacedViewFontStylesPatch,\n})\nexport type PluginPlacedViewsUpdateFontStylesArgs = z.infer<\n typeof PluginPlacedViewsUpdateFontStylesArgs\n>\n\n// ── Labels ──────────────────────────────────────────────────────────────────\n\n/**\n * A label on a placed view — a text child generated from the view content.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `labelId` | `string` | The label's shape id (addressable in `moveLabel`/`setLabelHidden`) |\n * | `category` | `string \\| null` | The style category it belongs to (`null` when untagged) |\n * | `text` | `string` | The rendered text |\n * | `position` | {@linkcode PluginSheetPosition} | Position relative to the placed view |\n * | `scale` | `number` | The label's scale factor |\n * | `hidden` | `boolean` | Whether it is currently hidden |\n */\nexport type PluginPlacedViewLabel = {\n labelId: string\n category: string | null\n text: string\n position: PluginSheetPosition\n scale: number\n hidden: boolean\n}\n\n/** Result of {@link PluginPresentationPlacedViewsApi.listLabels}. */\nexport type PluginPlacedViewLabelsResult = {\n labels: PluginPlacedViewLabel[]\n}\n\n/** Arguments for {@link PluginPresentationPlacedViewsApi.moveLabel}. */\nexport const PluginPlacedViewsMoveLabelArgs = z.object({\n labelId: z.string().min(1),\n position: PluginSheetPosition,\n})\nexport type PluginPlacedViewsMoveLabelArgs = z.infer<\n typeof PluginPlacedViewsMoveLabelArgs\n>\n\n/** Arguments for {@link PluginPresentationPlacedViewsApi.setLabelHidden}. */\nexport const PluginPlacedViewsSetLabelHiddenArgs = z.object({\n labelId: z.string().min(1),\n hidden: z.boolean(),\n})\nexport type PluginPlacedViewsSetLabelHiddenArgs = z.infer<\n typeof PluginPlacedViewsSetLabelHiddenArgs\n>\n\n// ── Canvas shapes ───────────────────────────────────────────────────────────\n\n/**\n * Origin classification of a Present-canvas shape: a placed view, an\n * AI-generated output, a placed diagram image, a plugin-created shape, a\n * user annotation, a plain image, or other.\n */\nexport const PluginCanvasShapeOrigin = z.enum([\n \"view\",\n \"aiOutput\",\n \"diagram\",\n \"pluginShape\",\n \"annotation\",\n \"image\",\n \"other\",\n])\nexport type PluginCanvasShapeOrigin = z.infer<typeof PluginCanvasShapeOrigin>\n\n/**\n * A top-level shape on the Present canvas.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `shapeId` | `string` | The shape id |\n * | `type` | `string` | The canvas shape type (`view`, `image`, `geo`, `text`, …) |\n * | `origin` | {@linkcode PluginCanvasShapeOrigin} | What created it |\n * | `sheetId` | `string \\| null` | The sheet it sits on (`null` when loose on the canvas) |\n * | `position` | {@linkcode PluginSheetPosition} | Top-left position (sheet-local; page coordinates off-sheet) |\n * | `size` | `{ width: number, height: number }` | Displayed size |\n * | `rotation` | `number` | Rotation in radians |\n * | `opacity` | `number` | Whole-shape opacity, 0–1 |\n */\nexport type PluginCanvasShape = {\n shapeId: string\n type: string\n origin: PluginCanvasShapeOrigin\n sheetId: string | null\n position: PluginSheetPosition\n size: { width: number; height: number }\n rotation: number\n opacity: number\n}\n\n/** Result of {@link PluginPresentationPlacedViewsApi.listShapes}. */\nexport type PluginCanvasShapesListResult = {\n shapes: PluginCanvasShape[]\n}\n\n/** Result of {@link PluginPresentationPlacedViewsApi.deleteShapes}. */\nexport type PluginCanvasShapesDeleteResult = {\n deleted: string[]\n}\n\n/** Arguments for {@link PluginPresentationPlacedViewsApi.moveShape}. */\nexport const PluginPlacedViewsMoveShapeArgs = z.object({\n shapeId: z.string().min(1),\n position: PluginSheetPosition,\n sheetId: z.string().optional(),\n})\nexport type PluginPlacedViewsMoveShapeArgs = z.infer<\n typeof PluginPlacedViewsMoveShapeArgs\n>\n\n/** Arguments for {@link PluginPresentationPlacedViewsApi.resizeShape}. */\nexport const PluginPlacedViewsResizeShapeArgs = z.object({\n shapeId: z.string().min(1),\n factor: z.number().finite().min(0.01).max(100),\n})\nexport type PluginPlacedViewsResizeShapeArgs = z.infer<\n typeof PluginPlacedViewsResizeShapeArgs\n>\n\n/** Arguments for {@link PluginPresentationPlacedViewsApi.rotateShape}. */\nexport const PluginPresentationPlacedViewsRotateShapeArgs = z.object({\n shapeId: z.string().min(1),\n rotation: z.number().finite(),\n})\nexport type PluginPresentationPlacedViewsRotateShapeArgs = z.infer<\n typeof PluginPresentationPlacedViewsRotateShapeArgs\n>\n\n/** Arguments for {@link PluginPresentationPlacedViewsApi.deleteShapes}. */\nexport const PluginPlacedViewsDeleteShapesArgs = z.object({\n shapeIds: z.array(z.string().min(1)).min(1).max(500),\n})\nexport type PluginPlacedViewsDeleteShapesArgs = z.infer<\n typeof PluginPlacedViewsDeleteShapesArgs\n>\n\n/** Arguments for {@link PluginPresentationPlacedViewsApi.setOpacity}. */\nexport const PluginPlacedViewsSetOpacityArgs = z.object({\n shapeId: z.string().min(1),\n opacity: z.number().finite().min(0).max(1),\n})\nexport type PluginPlacedViewsSetOpacityArgs = z.infer<\n typeof PluginPlacedViewsSetOpacityArgs\n>\n\n/** Arguments for {@link PluginPresentationPlacedViewsApi.setMask}. */\nexport const PluginPlacedViewsSetMaskArgs = z.object({\n shapeId: z.string().min(1),\n enabled: z.boolean(),\n})\nexport type PluginPlacedViewsSetMaskArgs = z.infer<\n typeof PluginPlacedViewsSetMaskArgs\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * Presentation diagrams — generate program diagrams from the model, and place\n * diagram images onto sheets.\n *\n * {@linkcode PluginPresentationDiagramsApi.generateProgram} runs the Present-mode\n * **Program** action: it reads the project's spaces and departments and creates\n * new layout sheets holding the generated diagram graphics.\n * {@linkcode PluginPresentationDiagramsApi.generateSite} runs the site-diagram\n * flow (location / streetview / climate sheets from the map terrain).\n * {@linkcode PluginPresentationDiagramsApi.place} drops ready diagram images (by\n * URL) onto an existing sheet and returns the created canvas shape ids. All\n * require Present mode to be open. Adjacency data itself is read and computed via\n * `program.adjacency` (see {@linkcode PluginProgramAdjacencyApi}).\n *\n * There is intentionally no `generateAdjacency`: the host's adjacency generator\n * runs its sheet-creation task fire-and-forget internally, so a single call\n * cannot return the sheets it creates. Use the Present-mode Adjacency menu for\n * those, then `place` a pre-rendered adjacency image if you need it via the API.\n *\n * Accessed via `snaptrude.presentation.diagrams`.\n */\nexport abstract class PluginPresentationDiagramsApi {\n constructor() {}\n\n /**\n * Generate the program (space + department) diagrams for the current model,\n * creating new layout sheets.\n *\n * Runs the same auto-generation as the Present-mode **Program** action: reads\n * the project's spaces and departments, lays them out to scale, and creates one\n * or more new sheets holding the generated diagram graphics. Unlike\n * {@linkcode PluginPresentationDiagramsApi.place} (which pastes ready image URLs\n * onto an existing sheet), this generates the graphics from the model and\n * creates its own sheets. Requires Present mode to be open.\n *\n * @returns A {@linkcode PluginPresentationDiagramsGenerateResult} with the\n * `sheetIds` of the sheets created.\n * @throws If Present mode is not open, or the model has no spaces/departments to\n * generate from.\n *\n * @examplePrompt Generate the program diagrams for this project\n * @examplePrompt Auto-generate the space and department diagram sheets\n * @examplePrompt Create the program layout sheets from the model\n *\n * # Example\n * ```ts\n * const { sheetIds } = await snaptrude.presentation.diagrams.generateProgram()\n * ```\n */\n public abstract generateProgram(): PluginApiReturn<PluginPresentationDiagramsGenerateResult>\n\n /**\n * Generate the site diagrams (location, streetview, climate) for the\n * current model, creating new layout sheets.\n *\n * Runs the same auto-generation as the Present-mode site-diagram flow:\n * each requested type renders its diagram from the project's map terrain\n * and creates a new sheet holding it. `options.types` selects which\n * diagrams to generate (default: all three). Generation is tracked\n * per-proposal — a type whose diagram already exists for the active\n * proposal is **skipped, not duplicated** (the same semantics as the\n * mount-time flow; existing site-diagram sheets are never replaced), so\n * `sheetIds` contains only the sheets created by this call and can be\n * empty when everything already exists. Requires Present mode to be open\n * and the project to have a map terrain to generate from.\n *\n * @param options - Optional `types` — which site diagrams to generate\n * (default: `[\"location\", \"streetview\", \"climate\"]`).\n * @returns A {@linkcode PluginPresentationDiagramsGenerateResult} with the\n * `sheetIds` of the sheets created by this call (empty when all requested\n * types were already generated).\n * @throws If Present mode is not open or the project has no map terrain to\n * generate from.\n *\n * @examplePrompt Generate the site diagrams for this project\n * @examplePrompt Create the location, streetview and climate sheets\n * @examplePrompt Add a climate diagram sheet to the presentation\n *\n * # Example\n * ```ts\n * const { sheetIds } = await snaptrude.presentation.diagrams.generateSite({\n * types: [\"location\", \"climate\"],\n * })\n * ```\n */\n public abstract generateSite(options?: {\n types?: PluginPresentationSiteDiagramType[]\n }): PluginApiReturn<PluginPresentationDiagramsGenerateResult>\n\n /**\n * Place diagram images onto a sheet.\n *\n * Drops the given diagram image URLs onto a sheet and returns the ids of the\n * created canvas shapes. Requires Present mode to be open.\n *\n * @param sheetId - The sheet to place onto.\n * @param urls - Diagram image URLs to place.\n * @param options - Optional `position` (`{ x, y }`, relative to the sheet)\n * for the placed images.\n * @returns A {@linkcode PluginPresentationDiagramsPlaceResult} with the created\n * `shapeIds`.\n * @throws If Present mode is not open or the sheet id is invalid.\n *\n * @examplePrompt Place the adjacency diagram on Sheet 1\n * @examplePrompt Add the program diagram to the cover sheet\n * @examplePrompt Drop these diagram images onto the layout\n *\n * # Example\n * ```ts\n * await snaptrude.presentation.diagrams.place(\"sheet_1\", [\n * \"https://example.com/program-diagram.png\",\n * ])\n * ```\n */\n public abstract place(\n sheetId: string,\n urls: string[],\n options?: { position?: { x: number; y: number } },\n ): PluginApiReturn<PluginPresentationDiagramsPlaceResult>\n}\n\n/**\n * Arguments for {@linkcode PluginPresentationDiagramsApi.place}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetId` | `string` | The sheet to place onto |\n * | `urls` | `string[]` | Diagram image URLs |\n * | `position` | `{ x: number; y: number } \\| undefined` | Where to place them |\n */\nexport const PluginPresentationDiagramsPlaceArgs = z.object({\n sheetId: z.string(),\n urls: z.array(z.string()),\n position: z.object({ x: z.number(), y: z.number() }).optional(),\n})\nexport type PluginPresentationDiagramsPlaceArgs = z.infer<\n typeof PluginPresentationDiagramsPlaceArgs\n>\n\n/**\n * Result of {@linkcode PluginPresentationDiagramsApi.place}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `shapeIds` | `string[]` | Ids of the created canvas shapes |\n */\nexport const PluginPresentationDiagramsPlaceResult = z.object({\n shapeIds: z.array(z.string()),\n})\nexport type PluginPresentationDiagramsPlaceResult = z.infer<\n typeof PluginPresentationDiagramsPlaceResult\n>\n\n/** A site diagram type — the three diagrams of the Present-mode site flow. */\nexport const PluginPresentationSiteDiagramType = z.enum([\n \"location\",\n \"streetview\",\n \"climate\",\n])\nexport type PluginPresentationSiteDiagramType = z.infer<\n typeof PluginPresentationSiteDiagramType\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationDiagramsApi.generateSite}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `types` | {@linkcode PluginPresentationSiteDiagramType}`[] \\| undefined` | Which site diagrams to generate (default: all three) |\n */\nexport const PluginPresentationDiagramsGenerateSiteArgs = z.object({\n types: z.array(PluginPresentationSiteDiagramType).min(1).optional(),\n})\nexport type PluginPresentationDiagramsGenerateSiteArgs = z.infer<\n typeof PluginPresentationDiagramsGenerateSiteArgs\n>\n\n/**\n * Result of {@linkcode PluginPresentationDiagramsApi.generateProgram} and\n * {@linkcode PluginPresentationDiagramsApi.generateSite}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetIds` | `string[]` | Ids of the sheets created by the generator |\n */\nexport const PluginPresentationDiagramsGenerateResult = z.object({\n sheetIds: z.array(z.string()),\n})\nexport type PluginPresentationDiagramsGenerateResult = z.infer<\n typeof PluginPresentationDiagramsGenerateResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * Presentation annotate — add annotations to Present-mode sheets.\n *\n * The scriptable counterparts to the Present toolbar's annotation tools (all\n * require Present mode to be open; each returns the created canvas shape id):\n *\n * - {@linkcode PluginPresentationAnnotateApi.text} — text labels (titles, captions)\n * - {@linkcode PluginPresentationAnnotateApi.arrow} — arrows pointing between two points\n * - {@linkcode PluginPresentationAnnotateApi.note} — sticky notes\n * - {@linkcode PluginPresentationAnnotateApi.shape} — geo shapes (rectangle, ellipse, cloud, …)\n *\n * The freehand Draw tool is not scriptable (a stroke is an interactive gesture,\n * not a single call) and is intentionally not exposed here.\n *\n * Accessed via `snaptrude.presentation.annotate`.\n */\nexport abstract class PluginPresentationAnnotateApi {\n constructor() {}\n\n /**\n * Add a text label to a sheet.\n *\n * Creates a text shape on the given sheet and returns the id of the created\n * canvas shape. Requires Present mode to be open.\n *\n * Typography is optional: `fontFamily` takes a system family or any Google\n * Font (loaded automatically, the same loader as the Present text panel);\n * `fontWeight` is a numeric weight string like `\"400\"` or `\"700\"`,\n * optionally with the `-italic` suffix (e.g. `\"700-italic\"`) — the Present\n * text panel's value domain; a weight the chosen font does not provide\n * falls back to regular. `fontSizePx` sets an exact pixel size, overriding\n * the `size` preset when both are given (`size` alone keeps working).\n *\n * @param sheetId - The sheet to annotate, or `undefined` when `options.drawingId` is used.\n * @param text - The label text.\n * @param options - Optional `position` (`{ x, y }`, relative to the sheet's\n * top-left, in canvas units — defaults to the sheet's top-left corner),\n * `size` (`\"s\" | \"m\" | \"l\" | \"xl\"`, default `\"m\"`), `color` (a named\n * palette color, default `\"black\"`), and typography: `fontFamily`,\n * `fontWeight` (numeric string, `-italic` suffix allowed), `fontSizePx`\n * (positive px size overriding the `size` preset), and `drawingId` to parent\n * the annotation to a drawing. Pass exactly one parent: `sheetId` or `drawingId`.\n * @returns A {@linkcode PluginPresentationAnnotateTextResult} with the created\n * `shapeId`.\n * @throws If Present mode is not open or the sheet id is invalid.\n *\n * @examplePrompt Add a title \"Ground Floor\" to Sheet 1\n * @examplePrompt Label the cover sheet with the project name\n * @examplePrompt Caption this view with \"Proposed layout\"\n * @examplePrompt Put a red note on the sheet\n *\n * # Example\n * ```ts\n * const { shapeId } = await snaptrude.presentation.annotate.text(\n * \"sheet_1\",\n * \"Ground Floor\",\n * { position: { x: 40, y: 40 }, size: \"xl\", color: \"blue\" },\n * )\n * ```\n */\n public abstract text(\n sheetId: string | undefined,\n text: string,\n options?: {\n position?: { x: number; y: number }\n size?: PluginAnnotateTextSize\n color?: PluginAnnotateTextColor\n fontFamily?: string\n fontWeight?: string\n fontSizePx?: number\n drawingId?: string\n },\n ): PluginApiReturn<PluginPresentationAnnotateTextResult>\n\n /**\n * Draw an arrow on a sheet.\n *\n * Creates an arrow shape from `start` to `end` (both relative to the sheet's\n * top-left, in canvas units — the same Arrow tool as the Present toolbar) and\n * returns the id of the created canvas shape. Requires Present mode to be open.\n *\n * @param sheetId - The sheet to annotate, or `undefined` when `options.drawingId` is used.\n * @param start - Arrow tail position (`{ x, y }`, relative to the sheet's top-left).\n * @param end - Arrow head position (`{ x, y }`, relative to the sheet's top-left).\n * @param options - Optional `color` (a named palette color, default `\"black\"`)\n * `size` (stroke weight preset, `\"s\" | \"m\" | \"l\" | \"xl\"`, default `\"m\"`),\n * and `drawingId` to parent the arrow to a drawing. Pass exactly one parent.\n * @returns A {@linkcode PluginPresentationAnnotateResult} with the created\n * `shapeId`.\n * @throws If Present mode is not open or the sheet id is invalid.\n *\n * @examplePrompt Draw an arrow pointing at the entrance on Sheet 1\n * @examplePrompt Add a red arrow from the title to the plan view\n * @examplePrompt Point an arrow at the top-left view on the cover sheet\n *\n * # Example\n * ```ts\n * const { shapeId } = await snaptrude.presentation.annotate.arrow(\n * \"sheet_1\",\n * { x: 100, y: 200 },\n * { x: 300, y: 250 },\n * { color: \"red\" },\n * )\n * ```\n */\n public abstract arrow(\n sheetId: string | undefined,\n start: { x: number; y: number },\n end: { x: number; y: number },\n options?: {\n color?: PluginAnnotateTextColor\n size?: PluginAnnotateTextSize\n drawingId?: string\n },\n ): PluginApiReturn<PluginPresentationAnnotateResult>\n\n /**\n * Add a sticky note to a sheet.\n *\n * Creates a note shape (the same Note tool as the Present toolbar — a\n * fixed-size sticky square that grows with its text) and returns the id of\n * the created canvas shape. Requires Present mode to be open.\n *\n * @param sheetId - The sheet to annotate, or `undefined` when `options.drawingId` is used.\n * @param text - The note text.\n * @param options - Optional `position` (`{ x, y }`, relative to the sheet's\n * top-left, in canvas units — defaults to the sheet's top-left corner),\n * `color` (a named palette color, default `\"black\"` — rendered as the\n * note's sticky fill), and `size` (text size preset,\n * `\"s\" | \"m\" | \"l\" | \"xl\"`, default `\"m\"`), and `drawingId` to parent\n * the note to a drawing. Pass exactly one parent.\n * @returns A {@linkcode PluginPresentationAnnotateResult} with the created\n * `shapeId`.\n * @throws If Present mode is not open or the sheet id is invalid.\n *\n * @examplePrompt Add a sticky note \"Review this wall\" to Sheet 1\n * @examplePrompt Put a yellow note on the cover sheet\n * @examplePrompt Leave a note next to the ground floor plan\n *\n * # Example\n * ```ts\n * const { shapeId } = await snaptrude.presentation.annotate.note(\n * \"sheet_1\",\n * \"Review this wall\",\n * { position: { x: 60, y: 120 }, color: \"yellow\" },\n * )\n * ```\n */\n public abstract note(\n sheetId: string | undefined,\n text: string,\n options?: {\n position?: { x: number; y: number }\n color?: PluginAnnotateTextColor\n size?: PluginAnnotateTextSize\n drawingId?: string\n },\n ): PluginApiReturn<PluginPresentationAnnotateResult>\n\n /**\n * Draw a geo shape (rectangle, ellipse, cloud, …) on a sheet.\n *\n * Creates a geo shape of the given `kind` — the same shapes as the Present\n * toolbar's shape flyout — sized to `bounds` (relative to the sheet's\n * top-left, in canvas units) and returns the id of the created canvas shape.\n * Requires Present mode to be open.\n *\n * @param sheetId - The sheet to annotate, or `undefined` when `options.drawingId` is used.\n * @param kind - The shape kind ({@linkcode PluginAnnotateGeoKind}), e.g.\n * `\"rectangle\"`, `\"ellipse\"`, `\"cloud\"`.\n * @param bounds - Placement box `{ x, y, w, h }` — `x`/`y` are the shape's\n * top-left relative to the sheet's top-left; `w`/`h` must be positive.\n * @param options - Optional `color` (a named palette color, default\n * `\"black\"`) and `fill` ({@linkcode PluginAnnotateFill}, default `\"none\"` —\n * outline only), and `drawingId` to parent the shape to a drawing. Pass\n * exactly one parent: `sheetId` or `drawingId`.\n * @returns A {@linkcode PluginPresentationAnnotateResult} with the created\n * `shapeId`.\n * @throws If Present mode is not open or the sheet id is invalid.\n *\n * @examplePrompt Draw a revision cloud around the kitchen on Sheet 2\n * @examplePrompt Add a red rectangle highlight to the sheet\n * @examplePrompt Draw an ellipse around the entrance on the cover sheet\n *\n * # Example\n * ```ts\n * const { shapeId } = await snaptrude.presentation.annotate.shape(\n * \"sheet_1\",\n * \"cloud\",\n * { x: 80, y: 80, w: 240, h: 160 },\n * { color: \"red\" },\n * )\n * ```\n */\n public abstract shape(\n sheetId: string | undefined,\n kind: PluginAnnotateGeoKind,\n bounds: { x: number; y: number; w: number; h: number },\n options?: {\n color?: PluginAnnotateTextColor\n fill?: PluginAnnotateFill\n drawingId?: string\n },\n ): PluginApiReturn<PluginPresentationAnnotateResult>\n}\n\n/** Text size preset (the same s/m/l/xl scale as the Present-mode text tool). */\nexport const PluginAnnotateTextSize = z.enum([\"s\", \"m\", \"l\", \"xl\"])\nexport type PluginAnnotateTextSize = z.infer<typeof PluginAnnotateTextSize>\n\n/** Named palette color (the same swatches as the Present-mode style panel). */\nexport const PluginAnnotateTextColor = z.enum([\n \"black\",\n \"grey\",\n \"light-violet\",\n \"violet\",\n \"blue\",\n \"light-blue\",\n \"yellow\",\n \"orange\",\n \"green\",\n \"light-green\",\n \"light-red\",\n \"red\",\n \"white\",\n])\nexport type PluginAnnotateTextColor = z.infer<typeof PluginAnnotateTextColor>\n\n/**\n * Arguments for {@linkcode PluginPresentationAnnotateApi.text}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetId` | `string \\| undefined` | The sheet parent; omit when `drawingId` is set |\n * | `drawingId` | `string \\| undefined` | The drawing parent; mutually exclusive with `sheetId` |\n * | `text` | `string` | The label text |\n * | `position` | `{ x: number; y: number } \\| undefined` | Where to place it (relative to the sheet) |\n * | `size` | {@linkcode PluginAnnotateTextSize}` \\| undefined` | Text size preset |\n * | `color` | {@linkcode PluginAnnotateTextColor}` \\| undefined` | Text color |\n * | `fontFamily` | `string \\| undefined` | System family or any Google Font (loaded automatically) |\n * | `fontWeight` | `string \\| undefined` | Numeric weight like `\"400\"`/`\"700\"`, `-italic` suffix allowed |\n * | `fontSizePx` | `number \\| undefined` | Exact px size (overrides the `size` preset) |\n */\nexport const PluginPresentationAnnotateTextArgs = z\n .object({\n sheetId: z.string().min(1).optional(),\n drawingId: z.string().min(1).optional(),\n text: z.string(),\n position: z.object({ x: z.number(), y: z.number() }).optional(),\n size: PluginAnnotateTextSize.optional(),\n color: PluginAnnotateTextColor.optional(),\n fontFamily: z.string().min(1).optional(),\n fontWeight: z\n .string()\n .regex(\n /^\\d{3}(-italic)?$/,\n 'fontWeight must be a numeric string like \"400\" or \"700\", optionally with the \"-italic\" suffix',\n )\n .optional(),\n fontSizePx: z.number().finite().positive().optional(),\n })\n .refine(({ sheetId, drawingId }) => Boolean(sheetId) !== Boolean(drawingId), {\n message: \"exactly one of sheetId or drawingId is required\",\n })\nexport type PluginPresentationAnnotateTextArgs = z.infer<\n typeof PluginPresentationAnnotateTextArgs\n>\n\n/**\n * Result of {@linkcode PluginPresentationAnnotateApi.text}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `shapeId` | `string` | Id of the created text shape |\n */\nexport const PluginPresentationAnnotateTextResult = z.object({\n shapeId: z.string(),\n})\nexport type PluginPresentationAnnotateTextResult = z.infer<\n typeof PluginPresentationAnnotateTextResult\n>\n\n/**\n * Geo shape kind — the shapes offered by the Present-mode toolbar's shape\n * flyout.\n */\nexport const PluginAnnotateGeoKind = z.enum([\n \"rectangle\",\n \"ellipse\",\n \"triangle\",\n \"diamond\",\n \"hexagon\",\n \"oval\",\n \"rhombus\",\n \"star\",\n \"cloud\",\n \"heart\",\n \"x-box\",\n \"check-box\",\n \"arrow-left\",\n \"arrow-up\",\n \"arrow-right\",\n \"arrow-down\",\n])\nexport type PluginAnnotateGeoKind = z.infer<typeof PluginAnnotateGeoKind>\n\n/** Fill style for geo shapes (the same fills as the Present-mode style panel). */\nexport const PluginAnnotateFill = z.enum([\"none\", \"semi\", \"solid\", \"pattern\"])\nexport type PluginAnnotateFill = z.infer<typeof PluginAnnotateFill>\n\n/**\n * Arguments for {@linkcode PluginPresentationAnnotateApi.arrow}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetId` | `string \\| undefined` | The sheet parent; omit when `drawingId` is set |\n * | `drawingId` | `string \\| undefined` | The drawing parent; mutually exclusive with `sheetId` |\n * | `start` | `{ x: number; y: number }` | Arrow tail (relative to the sheet) |\n * | `end` | `{ x: number; y: number }` | Arrow head (relative to the sheet) |\n * | `color` | {@linkcode PluginAnnotateTextColor}` \\| undefined` | Arrow color |\n * | `size` | {@linkcode PluginAnnotateTextSize}` \\| undefined` | Stroke weight preset |\n */\nexport const PluginPresentationAnnotateArrowArgs = z\n .object({\n sheetId: z.string().min(1).optional(),\n drawingId: z.string().min(1).optional(),\n start: z.object({ x: z.number(), y: z.number() }),\n end: z.object({ x: z.number(), y: z.number() }),\n color: PluginAnnotateTextColor.optional(),\n size: PluginAnnotateTextSize.optional(),\n })\n .refine(({ sheetId, drawingId }) => Boolean(sheetId) !== Boolean(drawingId), {\n message: \"exactly one of sheetId or drawingId is required\",\n })\nexport type PluginPresentationAnnotateArrowArgs = z.infer<\n typeof PluginPresentationAnnotateArrowArgs\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationAnnotateApi.note}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetId` | `string \\| undefined` | The sheet parent; omit when `drawingId` is set |\n * | `drawingId` | `string \\| undefined` | The drawing parent; mutually exclusive with `sheetId` |\n * | `text` | `string` | The note text |\n * | `position` | `{ x: number; y: number } \\| undefined` | Where to place it (relative to the sheet) |\n * | `color` | {@linkcode PluginAnnotateTextColor}` \\| undefined` | Sticky fill color |\n * | `size` | {@linkcode PluginAnnotateTextSize}` \\| undefined` | Text size preset |\n */\nexport const PluginPresentationAnnotateNoteArgs = z\n .object({\n sheetId: z.string().min(1).optional(),\n drawingId: z.string().min(1).optional(),\n text: z.string(),\n position: z.object({ x: z.number(), y: z.number() }).optional(),\n color: PluginAnnotateTextColor.optional(),\n size: PluginAnnotateTextSize.optional(),\n })\n .refine(({ sheetId, drawingId }) => Boolean(sheetId) !== Boolean(drawingId), {\n message: \"exactly one of sheetId or drawingId is required\",\n })\nexport type PluginPresentationAnnotateNoteArgs = z.infer<\n typeof PluginPresentationAnnotateNoteArgs\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationAnnotateApi.shape}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetId` | `string \\| undefined` | The sheet parent; omit when `drawingId` is set |\n * | `drawingId` | `string \\| undefined` | The drawing parent; mutually exclusive with `sheetId` |\n * | `kind` | {@linkcode PluginAnnotateGeoKind} | The shape kind |\n * | `bounds` | `{ x: number; y: number; w: number; h: number }` | Placement box (relative to the sheet; `w`/`h` positive) |\n * | `color` | {@linkcode PluginAnnotateTextColor}` \\| undefined` | Outline color |\n * | `fill` | {@linkcode PluginAnnotateFill}` \\| undefined` | Fill style |\n */\nexport const PluginPresentationAnnotateShapeArgs = z\n .object({\n sheetId: z.string().min(1).optional(),\n drawingId: z.string().min(1).optional(),\n kind: PluginAnnotateGeoKind,\n bounds: z.object({\n x: z.number(),\n y: z.number(),\n w: z.number().positive(),\n h: z.number().positive(),\n }),\n color: PluginAnnotateTextColor.optional(),\n fill: PluginAnnotateFill.optional(),\n })\n .refine(({ sheetId, drawingId }) => Boolean(sheetId) !== Boolean(drawingId), {\n message: \"exactly one of sheetId or drawingId is required\",\n })\nexport type PluginPresentationAnnotateShapeArgs = z.infer<\n typeof PluginPresentationAnnotateShapeArgs\n>\n\n/**\n * Result of {@linkcode PluginPresentationAnnotateApi.arrow},\n * {@linkcode PluginPresentationAnnotateApi.note}, and\n * {@linkcode PluginPresentationAnnotateApi.shape}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `shapeId` | `string` | Id of the created canvas shape |\n */\nexport const PluginPresentationAnnotateResult = z.object({\n shapeId: z.string(),\n})\nexport type PluginPresentationAnnotateResult = z.infer<\n typeof PluginPresentationAnnotateResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport {\n PluginAnnotateFill,\n PluginAnnotateGeoKind,\n PluginAnnotateTextColor,\n PluginAnnotateTextSize,\n} from \"./annotate\"\n\n/**\n * Presentation shapes — a stable, plugin-owned shape lifecycle on Present-mode\n * sheets.\n *\n * Where {@linkcode PluginPresentationAnnotateApi} is fire-and-forget (every call\n * creates a new shape), this namespace keys each shape by a caller-chosen\n * string, so rerunning an analysis **updates** its sheet output in place instead\n * of duplicating it:\n *\n * - {@linkcode PluginPresentationShapesApi.upsert} — create or update the shape for a key\n * - {@linkcode PluginPresentationShapesApi.remove} — delete the shape for a key\n * - {@linkcode PluginPresentationShapesApi.removeAll} — delete every shape owned by the calling plugin\n * - {@linkcode PluginPresentationShapesApi.list} — the calling plugin's shapes (key, shapeId, type)\n *\n * Shape ids are derived deterministically from the plugin id + key, so upserts\n * converge across collaborators. Mutations require **Present mode to be open**\n * (`list` is a never-throw read); each plugin sees and mutates only its own\n * shapes.\n *\n * Accessed via `snaptrude.presentation.shapes`.\n */\nexport abstract class PluginPresentationShapesApi {\n constructor() {}\n\n /**\n * Create or update the shape for a key.\n *\n * The `shape` spec is a discriminated union over the same four annotation\n * kinds as `presentation.annotate` (`text`, `note`, `arrow`, `geo`), with the\n * same option shapes. On first call for a key the shape is created on the\n * target sheet (`options.sheetId`, default: the first sheet) and nested under\n * the sheet frame; on later calls the existing shape is updated in place —\n * same shape id, same sheet, one undo step. Positions/bounds are relative to\n * the sheet's top-left, in canvas units. Requires Present mode to be open.\n *\n * @param key - Caller-chosen stable identifier for the shape (per plugin).\n * @param shape - The desired shape ({@linkcode PluginPresentationShapeSpec}):\n * `{ type: \"text\" | \"note\" | \"arrow\" | \"geo\", ... }` with the matching\n * annotate options. Changing `type` for an existing key replaces the shape\n * (same shape id).\n * @param options - Optional `sheetId` — the sheet to create the shape on\n * (defaults to the first sheet; ignored on update, the shape stays on its\n * sheet).\n * @returns A {@linkcode PluginPresentationShapesUpsertResult} — the stable\n * `shapeId` and `created` (`true` on first upsert, `false` on update).\n * @throws If Present mode is not open, the args are invalid, or the sheet id\n * is unknown.\n *\n * @examplePrompt Update the analysis label on the sheet instead of adding another one\n * @examplePrompt Rerun the daylight report and refresh its annotations\n * @examplePrompt Keep one \"FAR: 2.4\" text on Sheet 1 that updates on each run\n * @examplePrompt Draw a highlight rectangle the plugin can move on the next run\n *\n * # Example\n * ```ts\n * const { shapeId, created } = await snaptrude.presentation.shapes.upsert(\"far-label\", {\n * type: \"text\",\n * text: \"FAR: 2.4\",\n * position: { x: 40, y: 40 },\n * color: \"blue\",\n * })\n * ```\n */\n public abstract upsert(\n key: string,\n shape: PluginPresentationShapeSpec,\n options?: { sheetId?: string },\n ): PluginApiReturn<PluginPresentationShapesUpsertResult>\n\n /**\n * Delete the shape for a key.\n *\n * Removes the calling plugin's shape registered under `key`, if it exists.\n * Requires Present mode to be open.\n *\n * @param key - The key passed to {@linkcode PluginPresentationShapesApi.upsert}.\n * @returns `true` if a shape was deleted, `false` if no shape exists for the\n * key.\n * @throws If Present mode is not open or the key is invalid.\n *\n * @examplePrompt Remove the FAR label from the sheet\n * @examplePrompt Delete the highlight the plugin added earlier\n * @examplePrompt Clear the \"far-label\" annotation\n *\n * # Example\n * ```ts\n * const removed = await snaptrude.presentation.shapes.remove(\"far-label\")\n * ```\n */\n public abstract remove(key: string): PluginApiReturn<boolean>\n\n /**\n * Delete every shape owned by the calling plugin.\n *\n * Removes all shapes the calling plugin has upserted (across all sheets) in\n * one undo step. Requires Present mode to be open.\n *\n * @returns The number of shapes deleted.\n * @throws If Present mode is not open.\n *\n * @examplePrompt Clear all annotations my plugin added\n * @examplePrompt Reset the sheet output before rerunning the analysis\n * @examplePrompt Remove every plugin-drawn shape\n *\n * # Example\n * ```ts\n * const deleted = await snaptrude.presentation.shapes.removeAll()\n * ```\n */\n public abstract removeAll(): PluginApiReturn<number>\n\n /**\n * List the calling plugin's shapes.\n *\n * Returns one entry per live shape the calling plugin has upserted — its\n * `key`, the underlying canvas `shapeId`, and the shape `type`. A never-throw\n * read: returns an empty list when Present mode is closed.\n *\n * @returns A {@linkcode PluginPresentationShapesListResult} with the `shapes`\n * array (empty when Present mode is closed).\n *\n * @examplePrompt Which annotations has my plugin placed on the sheets?\n * @examplePrompt List the plugin's shapes before refreshing them\n * @examplePrompt Check whether the FAR label already exists\n *\n * # Example\n * ```ts\n * const { shapes } = await snaptrude.presentation.shapes.list()\n * const hasLabel = shapes.some((s) => s.key === \"far-label\")\n * ```\n */\n public abstract list(): PluginApiReturn<PluginPresentationShapesListResult>\n}\n\n/**\n * Desired shape for {@linkcode PluginPresentationShapesApi.upsert} — a\n * discriminated union over the four annotate kinds, with the same option shapes\n * as the matching `presentation.annotate` call:\n *\n * | `type` | Payload | Mirrors |\n * |---|---|---|\n * | `\"text\"` | `text`, `position?`, `size?`, `color?` | `annotate.text` |\n * | `\"note\"` | `text`, `position?`, `color?`, `size?` | `annotate.note` |\n * | `\"arrow\"` | `start`, `end`, `color?`, `size?` | `annotate.arrow` |\n * | `\"geo\"` | `kind`, `bounds`, `color?`, `fill?` | `annotate.shape` |\n *\n * Positions/bounds are relative to the sheet's top-left, in canvas units.\n */\nexport const PluginPresentationShapeSpec = z.discriminatedUnion(\"type\", [\n z.object({\n type: z.literal(\"text\"),\n text: z.string(),\n position: z.object({ x: z.number(), y: z.number() }).optional(),\n size: PluginAnnotateTextSize.optional(),\n color: PluginAnnotateTextColor.optional(),\n }),\n z.object({\n type: z.literal(\"note\"),\n text: z.string(),\n position: z.object({ x: z.number(), y: z.number() }).optional(),\n color: PluginAnnotateTextColor.optional(),\n size: PluginAnnotateTextSize.optional(),\n }),\n z.object({\n type: z.literal(\"arrow\"),\n start: z.object({ x: z.number(), y: z.number() }),\n end: z.object({ x: z.number(), y: z.number() }),\n color: PluginAnnotateTextColor.optional(),\n size: PluginAnnotateTextSize.optional(),\n }),\n z.object({\n type: z.literal(\"geo\"),\n kind: PluginAnnotateGeoKind,\n bounds: z.object({\n x: z.number(),\n y: z.number(),\n w: z.number().positive(),\n h: z.number().positive(),\n }),\n color: PluginAnnotateTextColor.optional(),\n fill: PluginAnnotateFill.optional(),\n }),\n])\nexport type PluginPresentationShapeSpec = z.infer<\n typeof PluginPresentationShapeSpec\n>\n\n/** The shape kind of a plugin-owned shape (the `type` discriminant of {@linkcode PluginPresentationShapeSpec}). */\nexport const PluginPresentationShapeType = z.enum([\n \"text\",\n \"note\",\n \"arrow\",\n \"geo\",\n])\nexport type PluginPresentationShapeType = z.infer<\n typeof PluginPresentationShapeType\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationShapesApi.upsert}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `key` | `string` | Caller-chosen stable identifier (non-empty) |\n * | `shape` | {@linkcode PluginPresentationShapeSpec} | The desired shape |\n * | `sheetId` | `string \\| undefined` | Sheet to create on (default: first sheet; ignored on update) |\n */\nexport const PluginPresentationShapesUpsertArgs = z.object({\n key: z.string().min(1),\n shape: PluginPresentationShapeSpec,\n sheetId: z.string().optional(),\n})\nexport type PluginPresentationShapesUpsertArgs = z.infer<\n typeof PluginPresentationShapesUpsertArgs\n>\n\n/**\n * Result of {@linkcode PluginPresentationShapesApi.upsert}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `shapeId` | `string` | Stable id of the canvas shape for the key |\n * | `created` | `boolean` | `true` if the shape was created, `false` if updated |\n */\nexport const PluginPresentationShapesUpsertResult = z.object({\n shapeId: z.string(),\n created: z.boolean(),\n})\nexport type PluginPresentationShapesUpsertResult = z.infer<\n typeof PluginPresentationShapesUpsertResult\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationShapesApi.remove}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `key` | `string` | The key the shape was upserted under (non-empty) |\n */\nexport const PluginPresentationShapesRemoveArgs = z.object({\n key: z.string().min(1),\n})\nexport type PluginPresentationShapesRemoveArgs = z.infer<\n typeof PluginPresentationShapesRemoveArgs\n>\n\n/**\n * Result of {@linkcode PluginPresentationShapesApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `shapes` | `{ key: string; shapeId: string; type: `{@linkcode PluginPresentationShapeType}` }[]` | One entry per live plugin-owned shape |\n */\nexport const PluginPresentationShapesListResult = z.object({\n shapes: z.array(\n z.object({\n key: z.string(),\n shapeId: z.string(),\n type: PluginPresentationShapeType,\n }),\n ),\n})\nexport type PluginPresentationShapesListResult = z.infer<\n typeof PluginPresentationShapesListResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport type { PluginCanvasShape } from \"./placedViews\"\n\n/**\n * Presentation tables — place data tables on Present-mode sheets.\n *\n * The scriptable counterpart of the Present canvas's table paste (the flow\n * that turns a Program-mode / Google Sheets / Excel paste into a table\n * shape): {@linkcode PluginPresentationTablesApi.place} takes plain string\n * rows and creates one table shape on a sheet. Requires Present mode to be\n * open.\n *\n * Accessed via `snaptrude.presentation.tables`.\n */\nexport abstract class PluginPresentationTablesApi {\n constructor() {}\n\n /**\n * Place a table on a sheet.\n *\n * Creates one table shape from `table.rows` — the same shape the Present\n * canvas creates when a spreadsheet table is pasted — and returns the\n * created shape as a canvas-shape record. Cells are plain strings; rows may\n * be ragged (each row needs at least one cell), capped at 10000 cells\n * total. `options.position` is sheet-local (relative to the sheet's\n * top-left, in canvas units — the same convention as `annotate.*`); when\n * omitted the table lands where the paste flow would auto-place it (the\n * center of the current viewport). One undo step. Requires Present mode to\n * be open.\n *\n * @param sheetId - The sheet to place the table on.\n * @param table - The table content: `rows`, an array of string-cell rows.\n * @param options - Optional `position` (`{ x, y }`, relative to the sheet's\n * top-left — defaults to the paste flow's auto-placement).\n * @returns The created table shape as a {@linkcode PluginCanvasShape}.\n * @throws If Present mode is not open, the sheet id is invalid, or the\n * table is empty / exceeds the 10000-cell cap.\n *\n * @examplePrompt Put the area schedule as a table on Sheet 1\n * @examplePrompt Add a table of the room programs to the cover sheet\n * @examplePrompt Paste this data as a table on the sheet\n * @examplePrompt Place a two-column table of space names and areas\n *\n * # Example\n * ```ts\n * const shape = await snaptrude.presentation.tables.place(\"sheet_1\", {\n * rows: [\n * [\"Room\", \"Area\"],\n * [\"Kitchen\", \"12.4 m²\"],\n * [\"Living\", \"28.0 m²\"],\n * ],\n * }, { position: { x: 40, y: 40 } })\n * ```\n */\n public abstract place(\n sheetId: string,\n table: { rows: string[][] },\n options?: { position?: { x: number; y: number } },\n ): PluginApiReturn<PluginCanvasShape>\n}\n\n/**\n * Arguments for {@linkcode PluginPresentationTablesApi.place}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetId` | `string` | The sheet to place the table on |\n * | `rows` | `string[][]` | Table cells as string rows (non-empty; ≤ 10000 cells total) |\n * | `position` | `{ x: number; y: number } \\| undefined` | Sheet-local position (default: paste auto-placement) |\n */\nexport const PluginPresentationTablesPlaceArgs = z.object({\n sheetId: z.string().min(1),\n rows: z\n .array(z.array(z.string()).min(1))\n .min(1)\n .refine((rows) => rows.reduce((n, r) => n + r.length, 0) <= 10000, {\n message: \"table exceeds the 10000-cell cap\",\n }),\n position: z.object({ x: z.number(), y: z.number() }).optional(),\n})\nexport type PluginPresentationTablesPlaceArgs = z.infer<\n typeof PluginPresentationTablesPlaceArgs\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * Presentation slideshow — run the Present-mode slideshow programmatically.\n *\n * The scriptable counterpart of the Present toolbar's slideshow: the\n * slideshow plays **all** layout sheets in sheet order (hidden sheets are\n * excluded) — `start` options only pick the slide it opens on. Slide\n * navigation stays with the user (arrow keys / on-screen controls); there is\n * intentionally no `next`/`prev`/`goTo`.\n *\n * **Fullscreen caveat:** browsers only grant fullscreen on a user gesture. A\n * plugin-triggered `start` usually fails that check, in which case the\n * slideshow catches the rejection and runs non-fullscreen (same slides, same\n * controls, windowed).\n *\n * `start`/`stop` are write-gated; `getState` is a never-throw read. Requires\n * Present mode to be open (except `getState`, which reports not-running).\n *\n * Accessed via `snaptrude.presentation.slideshow`.\n */\nexport abstract class PluginPresentationSlideshowApi {\n constructor() {}\n\n /**\n * Start the slideshow.\n *\n * Launches the Present-mode slideshow over all layout sheets in sheet\n * order. `options.startIndex` opens on that slide (clamped into range);\n * otherwise `options.sheetIds` naming exactly one sheet opens on that\n * sheet; otherwise it opens on the first slide. May run non-fullscreen —\n * see the fullscreen caveat on\n * {@linkcode PluginPresentationSlideshowApi}. Requires Present mode to be\n * open with at least one sheet.\n *\n * @param options - Optional `sheetIds` (open on this sheet when exactly one\n * id is given) and `startIndex` (0-based slide to open on; wins over\n * `sheetIds`; clamped into range).\n * @returns The {@linkcode PluginPresentationSlideshowState} after the\n * launch (`running: true` and the opening `index`).\n * @throws If Present mode is not open or there are no sheets to present.\n *\n * @examplePrompt Start the slideshow\n * @examplePrompt Present the sheets from the beginning\n * @examplePrompt Start the slideshow on sheet 3\n * @examplePrompt Play the presentation starting at the cover sheet\n *\n * # Example\n * ```ts\n * const state = await snaptrude.presentation.slideshow.start({ startIndex: 2 })\n * console.log(state.running, state.index) // true, 2\n * ```\n */\n public abstract start(options?: {\n sheetIds?: string[]\n startIndex?: number\n }): PluginApiReturn<PluginPresentationSlideshowState>\n\n /**\n * Stop the slideshow.\n *\n * Closes the running slideshow (exiting fullscreen if it entered it) and\n * returns to the Present canvas. A no-op when no slideshow is running.\n *\n * @returns The {@linkcode PluginPresentationSlideshowState} after the stop\n * (`running: false`).\n * @throws If Present mode is not open.\n *\n * @examplePrompt Stop the slideshow\n * @examplePrompt Exit the presentation\n * @examplePrompt Close the slideshow and go back to the sheets\n *\n * # Example\n * ```ts\n * await snaptrude.presentation.slideshow.stop()\n * ```\n */\n public abstract stop(): PluginApiReturn<PluginPresentationSlideshowState>\n\n /**\n * Read the slideshow state.\n *\n * A never-throw read: reports whether a slideshow is running and, when it\n * is, the current 0-based slide index (`index` is `null` when not\n * running — including when Present mode is closed).\n *\n * @returns The current {@linkcode PluginPresentationSlideshowState}.\n *\n * @examplePrompt Is the slideshow running?\n * @examplePrompt Which slide is the presentation on?\n * @examplePrompt Check the slideshow state\n *\n * # Example\n * ```ts\n * const { running, index } = await snaptrude.presentation.slideshow.getState()\n * ```\n */\n public abstract getState(): PluginApiReturn<PluginPresentationSlideshowState>\n}\n\n/**\n * Arguments for {@linkcode PluginPresentationSlideshowApi.start}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sheetIds` | `string[] \\| undefined` | Open on this sheet when exactly one id is given |\n * | `startIndex` | `number \\| undefined` | 0-based slide to open on (wins over `sheetIds`; clamped) |\n */\nexport const PluginPresentationSlideshowStartArgs = z.object({\n sheetIds: z.array(z.string().min(1)).optional(),\n startIndex: z.number().int().min(0).optional(),\n})\nexport type PluginPresentationSlideshowStartArgs = z.infer<\n typeof PluginPresentationSlideshowStartArgs\n>\n\n/**\n * The slideshow state — result of {@linkcode PluginPresentationSlideshowApi.start},\n * {@linkcode PluginPresentationSlideshowApi.stop}, and\n * {@linkcode PluginPresentationSlideshowApi.getState}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `running` | `boolean` | Whether a slideshow is currently running |\n * | `index` | `number \\| null` | Current 0-based slide index (`null` when not running) |\n */\nexport const PluginPresentationSlideshowState = z.object({\n running: z.boolean(),\n index: z.number().int().nullable(),\n})\nexport type PluginPresentationSlideshowState = z.infer<\n typeof PluginPresentationSlideshowState\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * Presentation import — bring external reference assets onto the Present canvas.\n *\n * `import.image` places a reference image, `import.pdf` places a PDF (each\n * page vectorized to SVG), and `import.svg` places a validated SVG onto\n * the current Present sheet, returning the created canvas shape ids. Requires\n * Present mode to be open.\n *\n * The host engine takes a browser `File`, which a plugin worker cannot supply,\n * so these accept a `url`/`dataUrl` that the host adapts into a `File`.\n *\n * Accessed via `snaptrude.presentation.import`.\n */\nexport abstract class PluginPresentationImportApi {\n constructor() {}\n\n /**\n * Import a reference image onto the current Present sheet.\n *\n * To place the image as a **scene** underlay to trace over — on a storey,\n * not the Present canvas — use `core.io.import.image` instead.\n *\n * @param source - The asset to import: exactly one of `url` or `dataUrl`.\n * @param options - Optional `position` — where to place the image on the\n * sheet.\n * @returns A {@linkcode PluginPresentationImportResult} with the created\n * `shapeIds`.\n * @throws If Present mode is not open or the source cannot be loaded.\n *\n * @examplePrompt Import this reference image onto the present canvas\n * @examplePrompt Add a JPG to my presentation board\n * @examplePrompt Drop this site photo onto the current sheet\n * @examplePrompt Bring an inspiration image into Present mode\n *\n * # Example\n * ```ts\n * const { shapeIds } = await snaptrude.presentation.import.image(\n * { url: \"https://example.com/ref.png\" },\n * { position: { x: 200, y: 150 } },\n * )\n * ```\n */\n public abstract image(\n source: { url?: string; dataUrl?: string },\n options?: { position?: { x: number; y: number } },\n ): PluginApiReturn<PluginPresentationImportResult>\n\n /**\n * Import a PDF onto the current Present sheet (one shape per page).\n *\n * Each page is converted to vector SVG and placed as a canvas shape.\n *\n * To bring the PDF into the **scene** as a traceable underlay — on a storey,\n * not the Present canvas — use `core.io.import.pdf` instead.\n *\n * @param source - The asset to import: exactly one of `url` or `dataUrl`.\n * @param options - Optional `position` — where to place the pages on the\n * sheet.\n * @returns A {@linkcode PluginPresentationImportResult} with one `shapeId` per\n * page.\n * @throws If Present mode is not open or the source cannot be loaded.\n *\n * @examplePrompt Import this PDF onto the present canvas\n * @examplePrompt Add a multi-page PDF reference to my board\n * @examplePrompt Bring a PDF floor plan into Present mode as vectors\n * @examplePrompt Place each page of this PDF on the sheet\n *\n * # Example\n * ```ts\n * const { shapeIds } = await snaptrude.presentation.import.pdf({\n * url: \"https://example.com/plans.pdf\",\n * })\n * ```\n */\n public abstract pdf(\n source: { url?: string; dataUrl?: string },\n options?: { position?: { x: number; y: number } },\n ): PluginApiReturn<PluginPresentationImportResult>\n\n /**\n * Import an SVG onto the current Present sheet as a vector asset.\n *\n * An SVG-validating variant of {@linkcode PluginPresentationImportApi.image}:\n * `import.image` already passes SVG sources through, but this method first\n * validates that the source really is an SVG and forces the vector mime\n * type. Throws if the source is not an SVG.\n *\n * **Rendering is vector up to a complexity cap.** Simple SVGs (up to ~1500\n * SVG element nodes and ~2 MB) render true-vector and stay crisp at every\n * zoom. Beyond that cap the shared canvas renderer **intentionally falls\n * back to a 1920px raster** to protect canvas performance, so very complex\n * SVGs can look rasterized when zoomed in. The stored asset is the original\n * SVG either way; only the on-canvas rendering falls back.\n *\n * @param source - The SVG to import: exactly one of `url` or `dataUrl`.\n * @param options - Optional `position` — where to place the SVG on the sheet.\n * @returns A {@linkcode PluginPresentationImportResult} with the created\n * `shapeIds`.\n * @throws If Present mode is not open, the source is not an SVG, or it cannot\n * be loaded.\n *\n * @examplePrompt Import this SVG diagram onto the present canvas\n * @examplePrompt Add a vector logo to my presentation board\n * @examplePrompt Place this SVG floor plan on the current sheet as vectors\n * @examplePrompt Bring a crisp vector graphic into Present mode\n *\n * # Example\n * ```ts\n * const { shapeIds } = await snaptrude.presentation.import.svg({\n * url: \"https://example.com/diagram.svg\",\n * })\n * ```\n */\n public abstract svg(\n source: { url?: string; dataUrl?: string },\n options?: { position?: { x: number; y: number } },\n ): PluginApiReturn<PluginPresentationImportResult>\n}\n\n/**\n * Arguments for {@linkcode PluginPresentationImportApi.image} /\n * {@linkcode PluginPresentationImportApi.pdf} /\n * {@linkcode PluginPresentationImportApi.svg}. Provide exactly one source.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `url` | `string \\| undefined` | URL of the asset to import |\n * | `dataUrl` | `string \\| undefined` | Base64 data URL of the asset |\n * | `position` | `{ x: number; y: number } \\| undefined` | Where to place it on the sheet |\n */\nexport const PluginPresentationImportArgs = z\n .object({\n url: z.string().optional(),\n dataUrl: z.string().optional(),\n position: z.object({ x: z.number(), y: z.number() }).optional(),\n })\n .refine((a) => Boolean(a.url) || Boolean(a.dataUrl), {\n message: \"Provide a url or a dataUrl\",\n })\nexport type PluginPresentationImportArgs = z.infer<\n typeof PluginPresentationImportArgs\n>\n\n/**\n * Result of an import.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `shapeIds` | `string[]` | Ids of the created canvas shapes (one per page for PDFs) |\n */\nexport const PluginPresentationImportResult = z.object({\n shapeIds: z.array(z.string()),\n})\nexport type PluginPresentationImportResult = z.infer<\n typeof PluginPresentationImportResult\n>\n","import * as z from \"zod\"\nimport type { PluginApiReturn } from \"../../types\"\n\nconst JsonPrimitive = z.union([z.string(), z.number(), z.boolean(), z.null()])\ntype JsonValue =\n | z.infer<typeof JsonPrimitive>\n | JsonValue[]\n | { [key: string]: JsonValue }\nconst JsonValue: z.ZodType<JsonValue> = z.lazy(() =>\n z.union([JsonPrimitive, z.array(JsonValue), z.record(z.string(), JsonValue)]),\n)\n\nexport const PluginAIInspirationRunMode = z.enum([\"blocking\", \"job\"])\nexport type PluginAIInspirationRunMode = z.infer<\n typeof PluginAIInspirationRunMode\n>\n\nexport const PluginAIInspirationOutputKind = z.enum([\n \"source\",\n \"image\",\n \"video\",\n])\nexport type PluginAIInspirationOutputKind = z.infer<\n typeof PluginAIInspirationOutputKind\n>\n\nexport const PluginAIInspirationModelOutput = z.enum([\"image\", \"video\"])\nexport type PluginAIInspirationModelOutput = z.infer<\n typeof PluginAIInspirationModelOutput\n>\n\nexport const PluginAIInspirationRuntimeProvider = z.enum([\"gpt\", \"fal\"])\nexport type PluginAIInspirationRuntimeProvider = z.infer<\n typeof PluginAIInspirationRuntimeProvider\n>\n\nexport const PluginAIInspirationPromptStrategy = z.enum([\n \"gpt-image\",\n \"fal-image\",\n \"fal-video\",\n])\nexport type PluginAIInspirationPromptStrategy = z.infer<\n typeof PluginAIInspirationPromptStrategy\n>\n\nexport const PluginAIInspirationShapeKind = z.enum([\"image\", \"view\", \"video\"])\nexport type PluginAIInspirationShapeKind = z.infer<\n typeof PluginAIInspirationShapeKind\n>\n\nexport const PluginAIInspirationVideoMode = z.enum([\n \"animate\",\n \"references\",\n \"keyframes\",\n])\nexport type PluginAIInspirationVideoMode = z.infer<\n typeof PluginAIInspirationVideoMode\n>\n\nexport const PluginAIInspirationVideoMotion = z.enum([\n \"auto\",\n \"slow-dolly\",\n \"subtle-orbit\",\n \"reveal\",\n])\nexport type PluginAIInspirationVideoMotion = z.infer<\n typeof PluginAIInspirationVideoMotion\n>\n\nexport const PluginAIInspirationVideoDuration = z.enum([\"4s\", \"6s\", \"8s\"])\nexport type PluginAIInspirationVideoDuration = z.infer<\n typeof PluginAIInspirationVideoDuration\n>\n\nexport const PluginAIInspirationJobStatus = z.enum([\n \"queued\",\n \"running\",\n \"completed\",\n \"failed\",\n \"cancelled\",\n])\nexport type PluginAIInspirationJobStatus = z.infer<\n typeof PluginAIInspirationJobStatus\n>\n\nexport const PluginAIInspirationWorkflowRunState = z.enum([\n \"current\",\n \"stale\",\n \"running\",\n \"failed\",\n \"superseded\",\n])\nexport type PluginAIInspirationWorkflowRunState = z.infer<\n typeof PluginAIInspirationWorkflowRunState\n>\n\nexport const PluginAIInspirationRegionHint = z.object({\n x: z.number().finite(),\n y: z.number().finite(),\n w: z.number().finite().positive(),\n h: z.number().finite().positive(),\n})\nexport type PluginAIInspirationRegionHint = z.infer<\n typeof PluginAIInspirationRegionHint\n>\n\nexport const PluginAIInspirationPoint = z.object({\n x: z.number().finite(),\n y: z.number().finite(),\n})\nexport type PluginAIInspirationPoint = z.infer<typeof PluginAIInspirationPoint>\n\nexport const PluginAIInspirationTextAnnotation = z.object({\n text: z.string().min(1),\n x: z.number().finite().optional(),\n y: z.number().finite().optional(),\n})\nexport type PluginAIInspirationTextAnnotation = z.infer<\n typeof PluginAIInspirationTextAnnotation\n>\n\nexport const PluginAIInspirationSketchGuidance = z.object({\n visualShapeIds: z.array(z.string()).optional(),\n textAnnotations: z.array(PluginAIInspirationTextAnnotation).optional(),\n})\nexport type PluginAIInspirationSketchGuidance = z.infer<\n typeof PluginAIInspirationSketchGuidance\n>\n\nexport const PluginAIInspirationSiteOptions = z.object({\n includeSatellite: z.boolean().optional(),\n includeStreetView: z.boolean().optional(),\n includeAerial: z.boolean().optional(),\n aerialBearing: z.number().finite().optional(),\n aerialPitch: z.number().finite().optional(),\n selectedDiagramIds: z.array(z.string()).optional(),\n location: z\n .object({\n lat: z.number().finite(),\n lng: z.number().finite(),\n })\n .optional(),\n})\nexport type PluginAIInspirationSiteOptions = z.infer<\n typeof PluginAIInspirationSiteOptions\n>\n\nexport const PluginAIInspirationAppliedPreset = z.object({\n source: z.enum([\"curated\", \"user\"]),\n presetId: z.string().min(1),\n name: z.string().min(1),\n savedPresetKind: z.enum([\"prompt\", \"style\"]).optional(),\n stylePrompt: z.string().optional(),\n modelId: z.string().optional(),\n referenceImages: z\n .array(\n z.object({\n url: z.string().min(1),\n label: z.string().optional(),\n weight: z.number().finite().optional(),\n }),\n )\n .optional(),\n siteDefaults: z\n .object({\n includeSatellite: z.boolean().optional(),\n includeStreetView: z.boolean().optional(),\n includeAerial: z.boolean().optional(),\n })\n .optional(),\n})\nexport type PluginAIInspirationAppliedPreset = z.infer<\n typeof PluginAIInspirationAppliedPreset\n>\n\nexport const PluginAIInspirationVideoOptions = z.object({\n mode: PluginAIInspirationVideoMode.optional(),\n endFrameShapeId: z.string().optional(),\n motion: PluginAIInspirationVideoMotion.optional(),\n duration: PluginAIInspirationVideoDuration.optional(),\n})\nexport type PluginAIInspirationVideoOptions = z.infer<\n typeof PluginAIInspirationVideoOptions\n>\n\nexport const PluginAIInspirationModel = z.object({\n id: z.string(),\n label: z.string(),\n supportsMultiImage: z.boolean(),\n output: PluginAIInspirationModelOutput,\n isDefault: z.boolean().optional(),\n})\nexport type PluginAIInspirationModel = z.infer<typeof PluginAIInspirationModel>\n\nexport const PluginAIInspirationModelCapability = z.object({\n id: z.string(),\n output: PluginAIInspirationModelOutput,\n provider: PluginAIInspirationRuntimeProvider,\n supportsRegionEdit: z.boolean(),\n supportsReferenceImages: z.boolean(),\n supportsStylePresets: z.boolean(),\n supportsSiteContext: z.boolean(),\n supportsSketchGuidance: z.boolean(),\n supportsKeyframes: z.boolean(),\n maxExtraImages: z.number().int().nonnegative(),\n promptStrategy: PluginAIInspirationPromptStrategy,\n})\nexport type PluginAIInspirationModelCapability = z.infer<\n typeof PluginAIInspirationModelCapability\n>\n\nexport const PluginAIInspirationListModelsResult = z.object({\n defaultModelId: z.string(),\n models: z.array(PluginAIInspirationModel),\n})\nexport type PluginAIInspirationListModelsResult = z.infer<\n typeof PluginAIInspirationListModelsResult\n>\n\nexport const PluginAIInspirationGetModelCapabilitiesArgs = z.object({\n modelId: z.string().min(1),\n})\nexport type PluginAIInspirationGetModelCapabilitiesArgs = z.infer<\n typeof PluginAIInspirationGetModelCapabilitiesArgs\n>\n\nexport const PluginAIInspirationGetModelCapabilitiesResult = z.object({\n capability: PluginAIInspirationModelCapability,\n})\nexport type PluginAIInspirationGetModelCapabilitiesResult = z.infer<\n typeof PluginAIInspirationGetModelCapabilitiesResult\n>\n\nexport const PluginAIInspirationShapeRef = z.object({\n shapeId: z.string(),\n assetId: z.string().optional(),\n kind: PluginAIInspirationShapeKind,\n name: z.string().optional(),\n width: z.number().finite().optional(),\n height: z.number().finite().optional(),\n outputType: PluginAIInspirationOutputKind.optional(),\n sourceUrl: z.string().optional(),\n})\nexport type PluginAIInspirationShapeRef = z.infer<\n typeof PluginAIInspirationShapeRef\n>\n\nexport const PluginAIInspirationListSourcesArgs = z.object({\n includeGenerated: z.boolean().optional(),\n includeSourceUrl: z.boolean().optional(),\n})\nexport type PluginAIInspirationListSourcesArgs = z.infer<\n typeof PluginAIInspirationListSourcesArgs\n>\n\nexport const PluginAIInspirationListSourcesResult = z.object({\n sources: z.array(PluginAIInspirationShapeRef),\n})\nexport type PluginAIInspirationListSourcesResult = z.infer<\n typeof PluginAIInspirationListSourcesResult\n>\n\nexport const PluginAIInspirationGetSelectionResult = z.object({\n selectedShapeIds: z.array(z.string()),\n sources: z.array(PluginAIInspirationShapeRef),\n})\nexport type PluginAIInspirationGetSelectionResult = z.infer<\n typeof PluginAIInspirationGetSelectionResult\n>\n\nexport const PluginAIInspirationPresetReferenceImage = z.object({\n url: z.string(),\n label: z.string().optional(),\n weight: z.number().finite().optional(),\n})\nexport type PluginAIInspirationPresetReferenceImage = z.infer<\n typeof PluginAIInspirationPresetReferenceImage\n>\n\nexport const PluginAIInspirationPreset = z.object({\n id: z.string(),\n name: z.string(),\n source: z.enum([\"curated\", \"user\"]),\n modelId: z.string(),\n categoryId: z.string().optional(),\n categoryLabel: z.string().optional(),\n description: z.string().optional(),\n previewImageUrl: z.string().optional(),\n prompt: z.string().optional(),\n stylePrompt: z.string().optional(),\n tags: z.array(z.string()).optional(),\n referenceImages: z.array(PluginAIInspirationPresetReferenceImage).optional(),\n siteDefaults: PluginAIInspirationAppliedPreset.shape.siteDefaults.optional(),\n})\nexport type PluginAIInspirationPreset = z.infer<\n typeof PluginAIInspirationPreset\n>\n\nexport const PluginAIInspirationPresetCategory = z.object({\n id: z.string(),\n label: z.string(),\n description: z.string().optional(),\n})\nexport type PluginAIInspirationPresetCategory = z.infer<\n typeof PluginAIInspirationPresetCategory\n>\n\nexport const PluginAIInspirationGetPresetCatalogResult = z.object({\n categories: z.array(PluginAIInspirationPresetCategory),\n presets: z.array(PluginAIInspirationPreset),\n})\nexport type PluginAIInspirationGetPresetCatalogResult = z.infer<\n typeof PluginAIInspirationGetPresetCatalogResult\n>\n\nexport const PluginAIInspirationOutput = z.object({\n outputType: z.enum([\"image\", \"video\"]),\n shapeId: z.string(),\n assetId: z.string(),\n outputUrl: z.string().optional(),\n position: PluginAIInspirationPoint.optional(),\n})\nexport type PluginAIInspirationOutput = z.infer<\n typeof PluginAIInspirationOutput\n>\n\nexport const PluginAIInspirationDebugSummary = z.record(z.string(), JsonValue)\nexport type PluginAIInspirationDebugSummary = z.infer<\n typeof PluginAIInspirationDebugSummary\n>\n\nexport const PluginAIInspirationRunMetadata = z.object({\n geometryFidelity: z.string().optional(),\n debugSummary: PluginAIInspirationDebugSummary.optional(),\n streetViewComposite: z\n .object({\n used: z.boolean(),\n renderMode: z.enum([\"prompt_refs_default\", \"masked_composite_pilot\"]),\n backgroundLabel: z.string().optional(),\n })\n .optional(),\n})\nexport type PluginAIInspirationRunMetadata = z.infer<\n typeof PluginAIInspirationRunMetadata\n>\n\nexport const PluginAIInspirationGenerateArgs = z.object({\n sourceShapeId: z.string().min(1),\n prompt: z.string().min(1).max(8000),\n modelId: z.string().optional(),\n referenceShapeIds: z.array(z.string()).max(12).optional(),\n referenceStrength: z.enum([\"low\", \"medium\", \"high\"]).optional(),\n appliedPreset: PluginAIInspirationAppliedPreset.optional(),\n site: PluginAIInspirationSiteOptions.optional(),\n regionHint: PluginAIInspirationRegionHint.optional(),\n sketchGuidance: PluginAIInspirationSketchGuidance.optional(),\n video: PluginAIInspirationVideoOptions.optional(),\n runMode: PluginAIInspirationRunMode.optional(),\n})\nexport type PluginAIInspirationGenerateArgs = z.infer<\n typeof PluginAIInspirationGenerateArgs\n>\n\nexport const PluginAIInspirationRunResult = z.object({\n jobId: z.string().optional(),\n output: PluginAIInspirationOutput.optional(),\n outputs: z.array(PluginAIInspirationOutput).optional(),\n metadata: PluginAIInspirationRunMetadata.optional(),\n})\nexport type PluginAIInspirationRunResult = z.infer<\n typeof PluginAIInspirationRunResult\n>\n\nexport const PluginAIInspirationRefineArgs = z.object({\n sourceShapeId: z.string().min(1),\n upscaleFactor: z\n .union([z.literal(1.5), z.literal(2), z.literal(4)])\n .optional(),\n runMode: PluginAIInspirationRunMode.optional(),\n})\nexport type PluginAIInspirationRefineArgs = z.infer<\n typeof PluginAIInspirationRefineArgs\n>\n\nexport const PluginAIInspirationJobProgress = z.object({\n currentStep: z.number().int().nonnegative().optional(),\n totalSteps: z.number().int().nonnegative().optional(),\n message: z.string().optional(),\n})\nexport type PluginAIInspirationJobProgress = z.infer<\n typeof PluginAIInspirationJobProgress\n>\n\nexport const PluginAIInspirationJob = z.object({\n jobId: z.string(),\n status: PluginAIInspirationJobStatus,\n createdAt: z.number(),\n updatedAt: z.number(),\n completedAt: z.number().optional(),\n method: z.string(),\n progress: PluginAIInspirationJobProgress.optional(),\n result: PluginAIInspirationRunResult.optional(),\n error: z.string().optional(),\n})\nexport type PluginAIInspirationJob = z.infer<typeof PluginAIInspirationJob>\n\nexport const PluginAIInspirationJobArgs = z.object({\n jobId: z.string().min(1),\n})\nexport type PluginAIInspirationJobArgs = z.infer<\n typeof PluginAIInspirationJobArgs\n>\n\nexport const PluginAIInspirationGetJobResult = z.object({\n job: PluginAIInspirationJob,\n})\nexport type PluginAIInspirationGetJobResult = z.infer<\n typeof PluginAIInspirationGetJobResult\n>\n\nexport const PluginAIInspirationCancelJobResult = z.object({\n job: PluginAIInspirationJob,\n})\nexport type PluginAIInspirationCancelJobResult = z.infer<\n typeof PluginAIInspirationCancelJobResult\n>\n\nexport const PluginAIInspirationListJobsResult = z.object({\n jobs: z.array(PluginAIInspirationJob),\n})\nexport type PluginAIInspirationListJobsResult = z.infer<\n typeof PluginAIInspirationListJobsResult\n>\n\nexport const PluginAIInspirationWorkflowVideoConfig = z.object({\n mode: z.string().optional(),\n motion: z.string().optional(),\n duration: z.string().optional(),\n endFrameShapeId: z.string().optional(),\n})\nexport type PluginAIInspirationWorkflowVideoConfig = z.infer<\n typeof PluginAIInspirationWorkflowVideoConfig\n>\n\nexport const PluginAIInspirationGenerationInputSnapshot = z.object({\n version: z.literal(1),\n meta: z.record(z.string(), JsonValue),\n regionHint: PluginAIInspirationRegionHint.optional(),\n sketchGuidance: z.record(z.string(), JsonValue).optional(),\n sourceCameraHeading: z.number().finite().optional(),\n})\nexport type PluginAIInspirationGenerationInputSnapshot = z.infer<\n typeof PluginAIInspirationGenerationInputSnapshot\n>\n\nexport const PluginAIInspirationWorkflowOperation = z.object({\n type: z.enum([\"generate-image\", \"refine\", \"generate-video\"]),\n label: z.string(),\n prompt: z.string().optional(),\n modelId: z.string().optional(),\n presetId: z.string().optional(),\n presetName: z.string().optional(),\n refineFactor: z.number().finite().optional(),\n sourceRole: z.enum([\"primary\", \"end-frame\"]).optional(),\n video: PluginAIInspirationWorkflowVideoConfig.optional(),\n generationInput: PluginAIInspirationGenerationInputSnapshot.optional(),\n})\nexport type PluginAIInspirationWorkflowOperation = z.infer<\n typeof PluginAIInspirationWorkflowOperation\n>\n\nexport const PluginAIInspirationWorkflowNode = z.object({\n id: z.string(),\n shapeId: z.string(),\n assetId: z.string().optional(),\n kind: PluginAIInspirationOutputKind,\n state: z.string(),\n runState: PluginAIInspirationWorkflowRunState.optional(),\n staleReason: z.string().optional(),\n incomingEdgeIds: z.array(z.string()),\n outgoingEdgeIds: z.array(z.string()),\n createdAt: z.number(),\n updatedAt: z.number(),\n})\nexport type PluginAIInspirationWorkflowNode = z.infer<\n typeof PluginAIInspirationWorkflowNode\n>\n\nexport const PluginAIInspirationWorkflowEdge = z.object({\n id: z.string(),\n sourceNodeId: z.string(),\n targetNodeId: z.string(),\n arrowShapeId: z.string().optional(),\n operation: PluginAIInspirationWorkflowOperation,\n state: z.string(),\n runState: PluginAIInspirationWorkflowRunState.optional(),\n staleReason: z.string().optional(),\n supersededByEdgeId: z.string().optional(),\n createdAt: z.number(),\n updatedAt: z.number(),\n})\nexport type PluginAIInspirationWorkflowEdge = z.infer<\n typeof PluginAIInspirationWorkflowEdge\n>\n\nexport const PluginAIInspirationWorkflow = z.object({\n id: z.string(),\n rootNodeIds: z.array(z.string()),\n nodeIds: z.array(z.string()),\n edgeIds: z.array(z.string()),\n nodes: z.record(z.string(), PluginAIInspirationWorkflowNode),\n edges: z.record(z.string(), PluginAIInspirationWorkflowEdge),\n createdAt: z.number(),\n updatedAt: z.number(),\n})\nexport type PluginAIInspirationWorkflow = z.infer<\n typeof PluginAIInspirationWorkflow\n>\n\nexport const PluginAIInspirationGetWorkflowForShapeArgs = z.object({\n shapeId: z.string().min(1),\n})\nexport type PluginAIInspirationGetWorkflowForShapeArgs = z.infer<\n typeof PluginAIInspirationGetWorkflowForShapeArgs\n>\n\nexport const PluginAIInspirationGetWorkflowForShapeResult = z.object({\n workflow: PluginAIInspirationWorkflow.nullable(),\n node: PluginAIInspirationWorkflowNode.nullable(),\n})\nexport type PluginAIInspirationGetWorkflowForShapeResult = z.infer<\n typeof PluginAIInspirationGetWorkflowForShapeResult\n>\n\nexport const PluginAIInspirationRerunPlanResult = z.discriminatedUnion(\n \"status\",\n [\n z.object({\n status: z.literal(\"ready\"),\n workflowId: z.string(),\n sourceNode: PluginAIInspirationWorkflowNode,\n edgeIds: z.array(z.string()),\n edges: z.array(PluginAIInspirationWorkflowEdge),\n recipe: z.lazy(() => PluginAIInspirationRecipe),\n needsEndFrame: z.boolean(),\n defaultEndFrameShapeId: z.string().nullable(),\n }),\n z.object({\n status: z.literal(\"unavailable\"),\n reason: z.enum([\n \"not-in-workflow\",\n \"source-only\",\n \"running\",\n \"missing-input\",\n ]),\n }),\n ],\n)\nexport type PluginAIInspirationRerunPlanResult = z.infer<\n typeof PluginAIInspirationRerunPlanResult\n>\n\nexport const PluginAIInspirationRerunSelectedBranchArgs = z.object({\n shapeId: z.string().min(1),\n endFrameShapeId: z.string().optional(),\n runMode: PluginAIInspirationRunMode.optional(),\n})\nexport type PluginAIInspirationRerunSelectedBranchArgs = z.infer<\n typeof PluginAIInspirationRerunSelectedBranchArgs\n>\n\nexport const PluginAIInspirationRecipeSlot = z.object({\n id: z.string(),\n type: z.enum([\"source-image\", \"end-frame\"]),\n label: z.string(),\n required: z.boolean(),\n})\nexport type PluginAIInspirationRecipeSlot = z.infer<\n typeof PluginAIInspirationRecipeSlot\n>\n\nexport const PluginAIInspirationRecipeStep = z.object({\n id: z.string(),\n operation: PluginAIInspirationWorkflowOperation,\n outputKind: z.enum([\"image\", \"video\"]),\n})\nexport type PluginAIInspirationRecipeStep = z.infer<\n typeof PluginAIInspirationRecipeStep\n>\n\nexport const PluginAIInspirationRecipeDraft = z.object({\n name: z.string(),\n summary: z.string(),\n slots: z.array(PluginAIInspirationRecipeSlot),\n steps: z.array(PluginAIInspirationRecipeStep).min(1).max(20),\n outputKind: z.enum([\"image\", \"video\"]),\n thumbnailUrl: z.string().optional(),\n})\nexport type PluginAIInspirationRecipeDraft = z.infer<\n typeof PluginAIInspirationRecipeDraft\n>\n\nexport const PluginAIInspirationRecipe: z.ZodType<\n PluginAIInspirationRecipeDraft & {\n version: 1\n id: string\n fingerprint?: string\n createdAt: number\n updatedAt: number\n }\n> = PluginAIInspirationRecipeDraft.extend({\n version: z.literal(1),\n id: z.string(),\n fingerprint: z.string().optional(),\n createdAt: z.number(),\n updatedAt: z.number(),\n})\nexport type PluginAIInspirationRecipe = z.infer<\n typeof PluginAIInspirationRecipe\n>\n\nexport const PluginAIInspirationExtractionResult = z.discriminatedUnion(\n \"status\",\n [\n z.object({\n status: z.literal(\"ready\"),\n draft: PluginAIInspirationRecipeDraft,\n }),\n z.object({\n status: z.literal(\"unavailable\"),\n reason: z.enum([\n \"not-in-workflow\",\n \"source-only\",\n \"missing-workflow\",\n \"missing-node\",\n \"missing-input\",\n \"invalid-workflow\",\n ]),\n }),\n ],\n)\nexport type PluginAIInspirationExtractionResult = z.infer<\n typeof PluginAIInspirationExtractionResult\n>\n\nexport const PluginAIInspirationListRecipesResult = z.object({\n recipes: z.array(PluginAIInspirationRecipe),\n})\nexport type PluginAIInspirationListRecipesResult = z.infer<\n typeof PluginAIInspirationListRecipesResult\n>\n\nexport const PluginAIInspirationSaveRecipeArgs = z.object({\n draft: PluginAIInspirationRecipeDraft,\n name: z.string().min(1),\n})\nexport type PluginAIInspirationSaveRecipeArgs = z.infer<\n typeof PluginAIInspirationSaveRecipeArgs\n>\n\nexport const PluginAIInspirationSaveRecipeResult = z.discriminatedUnion(\n \"status\",\n [\n z.object({ status: z.literal(\"saved\"), recipe: PluginAIInspirationRecipe }),\n z.object({\n status: z.literal(\"duplicate\"),\n recipe: PluginAIInspirationRecipe,\n }),\n z.object({ status: z.literal(\"invalid-name\"), recipe: z.null() }),\n z.object({ status: z.literal(\"failed\"), recipe: z.null() }),\n ],\n)\nexport type PluginAIInspirationSaveRecipeResult = z.infer<\n typeof PluginAIInspirationSaveRecipeResult\n>\n\nexport const PluginAIInspirationDeleteRecipeArgs = z.object({\n recipeId: z.string().min(1),\n})\nexport type PluginAIInspirationDeleteRecipeArgs = z.infer<\n typeof PluginAIInspirationDeleteRecipeArgs\n>\n\nexport const PluginAIInspirationDeleteRecipeResult = z.object({\n deleted: z.boolean(),\n})\nexport type PluginAIInspirationDeleteRecipeResult = z.infer<\n typeof PluginAIInspirationDeleteRecipeResult\n>\n\nexport const PluginAIInspirationExtractRecipeFromShapeArgs = z.object({\n shapeId: z.string().min(1),\n})\nexport type PluginAIInspirationExtractRecipeFromShapeArgs = z.infer<\n typeof PluginAIInspirationExtractRecipeFromShapeArgs\n>\n\nexport const PluginAIInspirationRunRecipeArgs = z.object({\n recipeId: z.string().optional(),\n recipe: PluginAIInspirationRecipe.optional(),\n slotShapeIds: z.record(z.string(), z.string()),\n runMode: PluginAIInspirationRunMode.optional(),\n})\nexport type PluginAIInspirationRunRecipeArgs = z.infer<\n typeof PluginAIInspirationRunRecipeArgs\n>\n\n/**\n * Present-mode AI Inspiration APIs.\n *\n * These APIs operate on Present canvas shape IDs and inherit the current user's\n * AI quota. They create outputs on the Present canvas; pure backend generation\n * without canvas output is intentionally out of scope.\n *\n * Long-running generations can be started with `runMode: \"job\"` and tracked via\n * {@linkcode PluginPresentationAIInspirationApi.getJob} /\n * {@linkcode PluginPresentationAIInspirationApi.listJobs}; the default\n * `\"blocking\"` mode resolves with the canvas outputs directly.\n *\n * Accessed via `snaptrude.presentation.aiInspiration`.\n */\nexport abstract class PluginPresentationAIInspirationApi {\n constructor() {}\n\n /**\n * List AI Inspiration models that can be used for generation.\n *\n * @returns The default model ID and available model descriptors.\n *\n * @examplePrompt What AI inspiration models can I use?\n * @examplePrompt List the available image and video models\n * @examplePrompt Which generation model is the default?\n *\n * # Example\n * ```ts\n * const { defaultModelId, models } = await snaptrude.presentation.aiInspiration.listModels()\n * const videoModels = models.filter((m) => m.output === \"video\")\n * console.log(defaultModelId, videoModels.map((m) => m.id))\n * ```\n */\n public abstract listModels(): PluginApiReturn<PluginAIInspirationListModelsResult>\n\n /**\n * Get capability flags for a model.\n *\n * @param args - Object containing `modelId`.\n * @returns Capability flags such as reference, site-context, region, and keyframe support.\n *\n * @examplePrompt What can this model do?\n * @examplePrompt Does the default model support reference images?\n * @examplePrompt Check if the video model supports keyframes\n *\n * # Example\n * ```ts\n * const { defaultModelId } = await snaptrude.presentation.aiInspiration.listModels()\n * const { capability } = await snaptrude.presentation.aiInspiration.getModelCapabilities({\n * modelId: defaultModelId,\n * })\n * console.log(capability.supportsReferenceImages, capability.maxExtraImages)\n * ```\n */\n public abstract getModelCapabilities(\n args: PluginAIInspirationGetModelCapabilitiesArgs,\n ): PluginApiReturn<PluginAIInspirationGetModelCapabilitiesResult>\n\n /**\n * List image/view/video sources on the current Present page.\n *\n * @param args - Optional filters. Set `includeGenerated` to include AI outputs.\n * @returns Serializable shape references using Present canvas shape IDs.\n *\n * @examplePrompt List the images I can generate from on this page\n * @examplePrompt What source shapes are on the present canvas?\n * @examplePrompt Show me the reference images available for AI generation\n *\n * # Example\n * ```ts\n * const { sources } = await snaptrude.presentation.aiInspiration.listSources({ includeGenerated: true })\n * for (const s of sources) console.log(s.kind, s.shapeId, s.name)\n * ```\n */\n public abstract listSources(\n args: PluginAIInspirationListSourcesArgs,\n ): PluginApiReturn<PluginAIInspirationListSourcesResult>\n\n /**\n * Get selected Present canvas shapes that AI Inspiration can use.\n *\n * @returns Selected shape IDs and resolved source references.\n *\n * @examplePrompt Generate from what I have selected\n * @examplePrompt Which of my selected shapes can be used as AI sources?\n *\n * # Example\n * ```ts\n * const { selectedShapeIds, sources } = await snaptrude.presentation.aiInspiration.getSelection()\n * console.log(selectedShapeIds.length, \"selected,\", sources.length, \"usable\")\n * ```\n */\n public abstract getSelection(): PluginApiReturn<PluginAIInspirationGetSelectionResult>\n\n /**\n * Get the curated AI Inspiration preset catalog.\n *\n * @returns Preset categories and presets.\n *\n * @examplePrompt What style presets are available?\n * @examplePrompt List the curated AI generation presets\n * @examplePrompt Show me the exterior render styles\n *\n * # Example\n * ```ts\n * const { categories, presets } = await snaptrude.presentation.aiInspiration.getPresetCatalog()\n * const exterior = presets.filter((p) => p.categoryLabel === \"Exterior\")\n * console.log(categories.map((c) => c.label), exterior.map((p) => p.name))\n * ```\n */\n public abstract getPresetCatalog(): PluginApiReturn<PluginAIInspirationGetPresetCatalogResult>\n\n /**\n * Generate an image or video from a Present canvas source.\n *\n * @param args - Generation options including source, prompt, model, references, site context,\n * region guidance, sketch guidance, video options, and run mode.\n * @returns The created canvas output(s) in blocking mode, or a `jobId` when\n * called with `runMode: \"job\"`.\n *\n * @examplePrompt Generate an image from this sketch\n * @examplePrompt Render the selected view as a photorealistic image\n * @examplePrompt Make an AI image of a modern facade from this source\n * @examplePrompt Generate a video from this image\n * @examplePrompt Turn this massing into a rendered street view\n *\n * # Example\n * ```ts\n * const { sources } = await snaptrude.presentation.aiInspiration.listSources({})\n * const [source, ...refs] = sources\n * const { outputs } = await snaptrude.presentation.aiInspiration.generate({\n * sourceShapeId: source.shapeId,\n * prompt: \"Photorealistic render of a modern timber facade at dusk\",\n * referenceShapeIds: refs.slice(0, 2).map((r) => r.shapeId),\n * referenceStrength: \"medium\",\n * })\n * for (const out of outputs ?? []) console.log(out.outputType, out.shapeId, out.outputUrl)\n * ```\n */\n public abstract generate(\n args: PluginAIInspirationGenerateArgs,\n ): PluginApiReturn<PluginAIInspirationRunResult>\n\n /**\n * Refine/upscale an AI Inspiration source image.\n *\n * @param args - Source shape, optional upscale factor, and run mode.\n * @returns The refined canvas output(s) in blocking mode, or a `jobId` when\n * called with `runMode: \"job\"`.\n *\n * @examplePrompt Upscale this AI image\n * @examplePrompt Refine and sharpen the selected render\n * @examplePrompt Make this generated image higher resolution\n *\n * # Example\n * ```ts\n * const { sources } = await snaptrude.presentation.aiInspiration.listSources({ includeGenerated: true })\n * const image = sources.find((s) => s.kind === \"image\")\n * if (image) {\n * const { outputs } = await snaptrude.presentation.aiInspiration.refine({\n * sourceShapeId: image.shapeId,\n * upscaleFactor: 2,\n * })\n * console.log(outputs?.[0]?.shapeId)\n * }\n * ```\n */\n public abstract refine(\n args: PluginAIInspirationRefineArgs,\n ): PluginApiReturn<PluginAIInspirationRunResult>\n\n /**\n * Get a transient AI Inspiration job by ID.\n *\n * @param args - Object containing `jobId`.\n * @returns The job with status, progress, and result when completed.\n *\n * @examplePrompt Is my render finished?\n * @examplePrompt Check on the generation job\n *\n * # Example\n * ```ts\n * const { jobId } = await snaptrude.presentation.aiInspiration.generate({\n * sourceShapeId: source.shapeId,\n * prompt: \"Aerial dusk render\",\n * runMode: \"job\",\n * })\n * const { job } = await snaptrude.presentation.aiInspiration.getJob({ jobId })\n * console.log(job.status, job.progress?.message)\n * ```\n */\n public abstract getJob(\n args: PluginAIInspirationJobArgs,\n ): PluginApiReturn<PluginAIInspirationGetJobResult>\n\n /**\n * Cancel a transient AI Inspiration job.\n *\n * @param args - Object containing `jobId`.\n * @returns The job after the cancel request.\n *\n * @examplePrompt Cancel the running generation\n * @examplePrompt Stop that AI job\n *\n * # Example\n * ```ts\n * const { job } = await snaptrude.presentation.aiInspiration.cancelJob({ jobId })\n * console.log(job.status) // \"cancelled\" (or a terminal state it already reached)\n * ```\n */\n public abstract cancelJob(\n args: PluginAIInspirationJobArgs,\n ): PluginApiReturn<PluginAIInspirationCancelJobResult>\n\n /**\n * List transient AI Inspiration jobs created in the current host session.\n *\n * @returns Jobs with their statuses, newest first.\n *\n * @examplePrompt What AI jobs are running?\n * @examplePrompt Show my generation queue\n *\n * # Example\n * ```ts\n * const { jobs } = await snaptrude.presentation.aiInspiration.listJobs()\n * const running = jobs.filter((j) => j.status === \"running\" || j.status === \"queued\")\n * console.log(running.map((j) => `${j.jobId}: ${j.method}`))\n * ```\n */\n public abstract listJobs(): PluginApiReturn<PluginAIInspirationListJobsResult>\n\n /**\n * Get the sanitized workflow graph containing a Present shape.\n *\n * @param args - Object containing `shapeId`.\n * @returns The workflow and matching node, or `null` values when the shape is not in a workflow.\n *\n * @examplePrompt Show the generation history of this image\n * @examplePrompt What workflow produced this shape?\n *\n * # Example\n * ```ts\n * const { workflow, node } = await snaptrude.presentation.aiInspiration.getWorkflowForShape({\n * shapeId: selected.shapeId,\n * })\n * if (workflow && node) console.log(node.id, workflow.edgeIds.length, \"edges\")\n * ```\n */\n public abstract getWorkflowForShape(\n args: PluginAIInspirationGetWorkflowForShapeArgs,\n ): PluginApiReturn<PluginAIInspirationGetWorkflowForShapeResult>\n\n /**\n * Build a rerun plan for the selected workflow branch ending at a shape.\n *\n * @param args - Object containing `shapeId`.\n * @returns Ready plan data or an unavailable reason.\n *\n * @examplePrompt Can I rerun the steps that produced this output?\n * @examplePrompt Preview the rerun for this generated image\n *\n * # Example\n * ```ts\n * const plan = await snaptrude.presentation.aiInspiration.getSelectedBranchRerunPlan({\n * shapeId: selected.shapeId,\n * })\n * if (plan.status === \"ready\") console.log(plan.edges.map((e) => e.operation.label))\n * else console.log(\"Cannot rerun:\", plan.reason)\n * ```\n */\n public abstract getSelectedBranchRerunPlan(\n args: PluginAIInspirationGetWorkflowForShapeArgs,\n ): PluginApiReturn<PluginAIInspirationRerunPlanResult>\n\n /**\n * Rerun the selected workflow branch and supersede the previous branch.\n *\n * @param args - Selected shape, optional end frame shape, and run mode.\n * @returns New canvas outputs in blocking mode, or a `jobId` when called with\n * `runMode: \"job\"`.\n *\n * @examplePrompt Rerun this generation branch\n * @examplePrompt Regenerate this output with the same steps\n *\n * # Example\n * ```ts\n * const { outputs } = await snaptrude.presentation.aiInspiration.rerunSelectedBranch({\n * shapeId: selected.shapeId,\n * })\n * console.log(outputs?.map((o) => o.shapeId))\n * ```\n */\n public abstract rerunSelectedBranch(\n args: PluginAIInspirationRerunSelectedBranchArgs,\n ): PluginApiReturn<PluginAIInspirationRunResult>\n\n /**\n * Extract a reusable recipe draft from the workflow path ending at a shape.\n *\n * @param args - Object containing `shapeId`.\n * @returns A recipe draft or an unavailable reason.\n *\n * @examplePrompt Turn this workflow into a reusable recipe\n * @examplePrompt Save the generation steps behind this image\n *\n * # Example\n * ```ts\n * const extraction = await snaptrude.presentation.aiInspiration.extractRecipeFromShape({\n * shapeId: selected.shapeId,\n * })\n * if (extraction.status === \"ready\") console.log(extraction.draft.summary)\n * ```\n */\n public abstract extractRecipeFromShape(\n args: PluginAIInspirationExtractRecipeFromShapeArgs,\n ): PluginApiReturn<PluginAIInspirationExtractionResult>\n\n /**\n * List project-level AI Inspiration recipes stored in Present document metadata.\n *\n * @returns Saved project recipes.\n *\n * @examplePrompt What AI recipes are saved in this project?\n * @examplePrompt List my generation recipes\n *\n * # Example\n * ```ts\n * const { recipes } = await snaptrude.presentation.aiInspiration.listRecipes()\n * console.log(recipes.map((r) => `${r.name} → ${r.outputKind}`))\n * ```\n */\n public abstract listRecipes(): PluginApiReturn<PluginAIInspirationListRecipesResult>\n\n /**\n * Save a project-level AI Inspiration recipe.\n *\n * @param args - Recipe draft and desired name.\n * @returns Saved, duplicate, invalid-name, or failed status.\n *\n * @examplePrompt Save this recipe as \"Dusk facade render\"\n * @examplePrompt Store these generation steps for reuse\n *\n * # Example\n * ```ts\n * const extraction = await snaptrude.presentation.aiInspiration.extractRecipeFromShape({\n * shapeId: selected.shapeId,\n * })\n * if (extraction.status === \"ready\") {\n * const saved = await snaptrude.presentation.aiInspiration.saveRecipe({\n * draft: extraction.draft,\n * name: \"Dusk facade render\",\n * })\n * console.log(saved.status, saved.recipe?.id)\n * }\n * ```\n */\n public abstract saveRecipe(\n args: PluginAIInspirationSaveRecipeArgs,\n ): PluginApiReturn<PluginAIInspirationSaveRecipeResult>\n\n /**\n * Delete a project-level AI Inspiration recipe.\n *\n * @param args - Object containing `recipeId`.\n * @returns Whether a recipe was deleted.\n *\n * @examplePrompt Delete the facade render recipe\n * @examplePrompt Remove that saved AI recipe\n *\n * # Example\n * ```ts\n * const { recipes } = await snaptrude.presentation.aiInspiration.listRecipes()\n * const stale = recipes.find((r) => r.name === \"Old style test\")\n * if (stale) {\n * const { deleted } = await snaptrude.presentation.aiInspiration.deleteRecipe({ recipeId: stale.id })\n * console.log(deleted)\n * }\n * ```\n */\n public abstract deleteRecipe(\n args: PluginAIInspirationDeleteRecipeArgs,\n ): PluginApiReturn<PluginAIInspirationDeleteRecipeResult>\n\n /**\n * Run a saved or inline recipe against Present canvas slot shapes.\n *\n * @param args - Saved `recipeId` or inline `recipe`, slot shape IDs, and run mode.\n * @returns Canvas outputs in blocking mode, or a `jobId` when called with\n * `runMode: \"job\"`.\n *\n * @examplePrompt Apply my saved recipe to this image\n * @examplePrompt Run the dusk-render recipe on the selected view\n *\n * # Example\n * ```ts\n * const { recipes } = await snaptrude.presentation.aiInspiration.listRecipes()\n * const recipe = recipes[0]\n * const { sources } = await snaptrude.presentation.aiInspiration.listSources({})\n * const { outputs } = await snaptrude.presentation.aiInspiration.runRecipe({\n * recipeId: recipe.id,\n * slotShapeIds: { [recipe.slots[0].id]: sources[0].shapeId },\n * })\n * console.log(outputs?.map((o) => o.outputUrl))\n * ```\n */\n public abstract runRecipe(\n args: PluginAIInspirationRunRecipeArgs,\n ): PluginApiReturn<PluginAIInspirationRunResult>\n}\n","import * as z from \"zod\"\n\n/**\n * Presentation export — render the Present-mode layout sheets to a downloadable\n * file (PDF / PNG / JPG), returning base64 by default or starting a browser download.\n *\n * This wraps the same pipeline the Present-mode **Export** dialog drives:\n * each sheet is serialized to an SVG, then either composed into a PDF\n * (svg2pdf/jspdf) or rasterized to a PNG/JPG. By default the plugin receives the\n * encoded bytes to save, upload or preview. With `download: true`, the host\n * initiates browser downloads and returns metadata only, keeping agent results small.\n *\n * Requires **Present mode to be open** (the documentation editor); the call\n * throws `PRECONDITION_FAILED` when it is not.\n *\n * The result is always a uniform `{ files }` array (see\n * {@linkcode PluginPresentationExportResult}):\n * - `pdf` with `combine` (the default) → a single multi-page file.\n * - `pdf` with `combine: false`, or any `png`/`jpg` → one file per sheet.\n *\n * Accessed via `snaptrude.presentation.export`.\n *\n * For a different export intent, see `core.io.export.model` (the 3D model as a\n * GLB/OBJ/FBX/3DS file) and `program.spreadsheet.export` (the area-program /\n * takeoff workbook as XLSX/PDF/CSV).\n *\n * The schemas and result types live here; the callable method is declared on\n * {@linkcode PluginPresentationApi.export}.\n */\n\n/** Output file format: a vector `pdf`, or a rasterized `png` / `jpg`. */\nexport const PluginPresentationExportFormat = z.enum([\"pdf\", \"png\", \"jpg\"])\nexport type PluginPresentationExportFormat = z.infer<\n typeof PluginPresentationExportFormat\n>\n\n/**\n * Target pixel dimensions for a rasterized (`png`/`jpg`) export. Ignored for\n * `pdf`, which stays vector and takes its size from the sheet.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `width` | `number` | Output width in pixels |\n * | `height` | `number` | Output height in pixels |\n */\nexport const PluginPresentationExportResolution = z.object({\n width: z.number().positive(),\n height: z.number().positive(),\n})\nexport type PluginPresentationExportResolution = z.infer<\n typeof PluginPresentationExportResolution\n>\n\n/**\n * Arguments for {@linkcode PluginPresentationApi.export}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `format` | `\"pdf\" \\| \"png\" \\| \"jpg\"` | Output format |\n * | `sheetIds` | `string[] \\| undefined` | Sheets to export (all sheets when omitted) |\n * | `combine` | `boolean \\| undefined` | PDF only: one multi-page file (default `true`) vs one file per sheet |\n * | `resolution` | {@linkcode PluginPresentationExportResolution}` \\| undefined` | Raster target size (`png`/`jpg` only) |\n * | `fileName` | `string \\| undefined` | Base file name (without extension) |\n * | `download` | `boolean \\| undefined` | Start browser downloads and return metadata without base64 (default `false`) |\n */\nexport const PluginPresentationExportArgs = z.object({\n format: PluginPresentationExportFormat,\n sheetIds: z.array(z.string()).optional(),\n combine: z.boolean().optional(),\n resolution: PluginPresentationExportResolution.optional(),\n fileName: z.string().optional(),\n download: z.boolean().optional(),\n})\nexport type PluginPresentationExportArgs = z.infer<\n typeof PluginPresentationExportArgs\n>\n\n/** Options after the positional export format. Omitted `download` preserves base64 results. */\nexport type PluginPresentationExportOptions = Omit<\n PluginPresentationExportArgs,\n \"format\"\n>\n\n/**\n * A single exported file.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `fileName` | `string` | File name including extension |\n * | `mimeType` | `string` | MIME type (`application/pdf`, `image/png`, `image/jpeg`) |\n * | `dataBase64` | `string` | The file bytes, base64-encoded (no data-URL prefix) |\n */\nexport const PluginPresentationExportFile = z.object({\n fileName: z.string(),\n mimeType: z.string(),\n dataBase64: z.string(),\n})\nexport type PluginPresentationExportFile = z.infer<\n typeof PluginPresentationExportFile\n>\n\n/**\n * Result of {@linkcode PluginPresentationApi.export} — always a `files` array.\n * A combined PDF yields one entry; per-sheet exports yield one entry each, in\n * sheet order.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `files` | {@linkcode PluginPresentationExportFile}`[]` | The exported files |\n */\nexport const PluginPresentationExportResult = z.object({\n files: z.array(PluginPresentationExportFile),\n})\nexport type PluginPresentationExportResult = z.infer<\n typeof PluginPresentationExportResult\n>\n\n/**\n * Metadata-only result for `download: true`. `downloadStarted` confirms initiation,\n * not an OS-saved file: browsers may require multiple-download permission. No base64\n * is returned. Default exports retain their existing result and TypeScript contract.\n */\nexport const PluginPresentationExportDownloadResult = z.object({\n files: z.array(\n PluginPresentationExportFile.omit({ dataBase64: true }).extend({\n byteLength: z.number().int().nonnegative(),\n }),\n ),\n downloadStarted: z.boolean(),\n})\nexport type PluginPresentationExportDownloadResult = z.infer<\n typeof PluginPresentationExportDownloadResult\n>\n","import * as z from \"zod\"\nimport type { PluginApiReturn } from \"../../types\"\nimport {\n PluginPlacedView,\n PluginPlacedViewCrop,\n PluginPlacedViewStylePatch,\n PluginViewLineStyle,\n PluginPlacedViewLabel,\n} from \"./placedViews\"\nimport { PluginSheetPosition } from \"./sheets\"\nimport { PluginPresentationExportResolution } from \"./export\"\n\n/** Drawing ids are existing Present view-shape ids, stable across reload and undo. */\nexport const PluginDrawingIdArgs = z\n .object({ drawingId: z.string().min(1) })\n .strict()\nexport const PluginDrawingNameArgs = PluginDrawingIdArgs.extend({\n name: z.string().trim().min(1),\n})\nexport const PluginDrawingIdsArgs = z\n .object({ drawingIds: z.array(z.string().min(1)).min(1).max(100) })\n .strict()\nexport const PluginDrawingCreateOptions = z\n .object({\n position: PluginSheetPosition.optional(),\n scale: z.number().finite().positive().optional(),\n })\n .strict()\nexport type PluginDrawingCreateOptions = z.infer<\n typeof PluginDrawingCreateOptions\n>\nexport const PluginDrawingCreateArgs = PluginDrawingCreateOptions.extend({\n viewId: z.string().min(1),\n})\nexport type PluginDrawingCreateArgs = z.infer<typeof PluginDrawingCreateArgs>\nexport type PluginDrawingIdArgs = z.infer<typeof PluginDrawingIdArgs>\nexport type PluginDrawingNameArgs = z.infer<typeof PluginDrawingNameArgs>\nexport type PluginDrawingIdsArgs = z.infer<typeof PluginDrawingIdsArgs>\nexport const PluginDrawingScaleArgs = PluginDrawingIdArgs.extend({\n scale: z.number().finite().positive(),\n})\nexport type PluginDrawingScaleArgs = z.infer<typeof PluginDrawingScaleArgs>\nexport const PluginDrawingCropArgs = PluginDrawingIdArgs.extend({\n crop: PluginPlacedViewCrop.nullable(),\n})\nexport type PluginDrawingCropArgs = z.infer<typeof PluginDrawingCropArgs>\nexport const PluginDrawingPresetArgs = PluginDrawingIdArgs.extend({\n presetId: z.string().min(1),\n})\nexport type PluginDrawingPresetArgs = z.infer<typeof PluginDrawingPresetArgs>\n\n/** Architectural scale is a denominator (100 means 1:100); positions are Present canvas units. */\nexport const PluginDrawing = PluginPlacedView.extend({\n id: z.string(),\n presetId: z.string().nullable(),\n kind: z.enum([\"plan\", \"sitePlan\", \"threeD\", \"elevation\"]),\n direction: z.enum([\"north\", \"east\", \"south\", \"west\"]).nullable(),\n})\nexport type PluginDrawing = z.infer<typeof PluginDrawing>\n\nconst color = z.string().regex(/^#[0-9a-fA-F]{6}$/)\nconst weight = z.number().finite().min(0).max(2.11)\nconst opacity = z.number().finite().min(0).max(100)\nconst pattern = z\n .object({\n id: z.string().min(1).optional(),\n scale: z.number().finite().min(0.25).max(4).optional(),\n color: color.optional(),\n background: color.optional(),\n weight: weight.optional(),\n })\n .strict()\nconst face = z\n .object({\n fontStyle: z.enum([\"400\", \"500\", \"700\", \"italic\"]).optional(),\n fontSize: z.enum([\"12px\", \"16px\", \"24px\", \"2.5\", \"3.5\", \"5\"]).optional(),\n color: color.optional(),\n })\n .strict()\nconst part = z\n .object({\n show: z.boolean().optional(),\n lineStyle: PluginViewLineStyle.optional(),\n weight: weight.optional(),\n })\n .strict()\nconst title = z\n .object({\n show: z.boolean().optional(),\n text: z.string().max(2000).optional(),\n })\n .strict()\nconst material = z\n .object({\n show: z.boolean().optional(),\n color: color.optional(),\n pattern: pattern.optional(),\n })\n .strict()\n\n/**\n * Vector-plan row patch. Only controls returned by getStyles are accepted for that row.\n * Paper weights are mm; opacity is 0–100. Nested values merge, never replace the whole row.\n * Presets apply to kernel plans only; other drawings use the legacy category patch.\n */\nexport const PluginDrawingVectorStylePatch = z\n .object({\n hide: z.boolean().optional(),\n lineStyle: PluginViewLineStyle.optional(),\n lineColor: color.optional(),\n cutWidth: weight.optional(),\n projectionWidth: weight.optional(),\n weight: weight.optional(),\n fillMode: z.string().optional(),\n fillColor: color.optional(),\n opacity: opacity.optional(),\n pattern: pattern.optional(),\n mask: z.boolean().optional(),\n materials: z\n .object({\n brick: material.optional(),\n glass: material.optional(),\n concrete: material.optional(),\n metalFrame: material.optional(),\n woodFrame: material.optional(),\n plaster: material.optional(),\n paint: material.optional(),\n insulation: material.optional(),\n })\n .strict()\n .optional(),\n components: z\n .object({\n frame: z.boolean().optional(),\n leaf: z.boolean().optional(),\n jambs: z.boolean().optional(),\n glazing: z.boolean().optional(),\n track: z.boolean().optional(),\n operation: z.boolean().optional(),\n operationStyle: PluginViewLineStyle.optional(),\n })\n .strict()\n .optional(),\n tag: z\n .object({\n show: z.boolean().optional(),\n fontFamily: z.string().min(1).optional(),\n fontStyle: z.enum([\"400\", \"500\", \"700\", \"italic\"]).optional(),\n fontSize: z.enum([\"12px\", \"16px\", \"24px\"]).optional(),\n frame: z.enum([\"pill\", \"hexagon\"]).optional(),\n color: color.optional(),\n opacity: opacity.optional(),\n outline: weight.optional(),\n })\n .strict()\n .optional(),\n text: z\n .object({\n show: z.boolean().optional(),\n labels: z.boolean().optional(),\n areas: z.boolean().optional(),\n department: z.boolean().optional(),\n fontFamily: z.string().min(1).optional(),\n color: color.optional(),\n fontSize: z.enum([\"12px\", \"16px\", \"24px\"]).optional(),\n label: face.optional(),\n metrics: face.optional(),\n title: face.optional(),\n scale: face.optional(),\n })\n .strict()\n .optional(),\n detail: z\n .object({ show: z.boolean().optional(), weight: weight.optional() })\n .strict()\n .optional(),\n belowCut: part.optional(),\n aboveCut: part.optional(),\n underlay: part.optional(),\n walkLine: part.optional(),\n breakLine: part.optional(),\n overlay: part.optional(),\n title: title.optional(),\n proposal: title.optional(),\n building: title.optional(),\n scale: z\n .object({\n show: z.boolean().optional(),\n format: z.enum([\"automatic\", \"ratio\", \"text\"]).optional(),\n })\n .strict()\n .optional(),\n scaleBar: z\n .object({\n show: z.boolean().optional(),\n format: z.enum([\"divided\", \"simple\"]).optional(),\n length: z.enum([\"1\", \"2\", \"5\", \"10\"]).optional(),\n })\n .strict()\n .optional(),\n extension: z\n .object({\n show: z.boolean().optional(),\n length: z.enum([\"underTitle\", \"fullWidth\"]).optional(),\n weight: weight.optional(),\n color: color.optional(),\n })\n .strict()\n .optional(),\n position: z.enum([\"bottomLeft\", \"bottomCentre\", \"topLeft\"]).optional(),\n })\n .strict()\nexport const PluginDrawingStylePatch = z.union([\n PluginDrawingVectorStylePatch,\n PluginPlacedViewStylePatch,\n])\nexport type PluginDrawingStylePatch = z.infer<typeof PluginDrawingStylePatch>\nexport const PluginDrawingStylesArgs = PluginDrawingIdArgs.extend({\n category: z.string().min(1),\n patch: PluginDrawingStylePatch,\n})\nexport type PluginDrawingStylesArgs = z.infer<typeof PluginDrawingStylesArgs>\n\n/** One selectable value exposed by a native drawing style control. */\nexport const PluginDrawingStyleControlOption = z.object({\n label: z.string(),\n value: z.string(),\n})\nexport type PluginDrawingStyleControlOption = z.infer<\n typeof PluginDrawingStyleControlOption\n>\n\n/** Numeric bounds exposed by a native drawing style control. */\nexport const PluginDrawingStyleControlRange = z.object({\n min: z.number().finite(),\n max: z.number().finite(),\n step: z.number().finite().positive(),\n})\nexport type PluginDrawingStyleControlRange = z.infer<\n typeof PluginDrawingStyleControlRange\n>\n\n/** Description of one editable control returned by `drawings.getStyles`. */\nexport const PluginDrawingStyleControl = z.object({\n key: z.string(),\n kind: z.string(),\n options: z.array(PluginDrawingStyleControlOption).optional(),\n range: PluginDrawingStyleControlRange.optional(),\n})\nexport type PluginDrawingStyleControl = z.infer<\n typeof PluginDrawingStyleControl\n>\n\n/**\n * Read values differ from write patches: legacy renderers use -1 for an unset opacity and\n * include the read-only `disable` flag. These values must not be echoed as an update patch.\n */\nexport const PluginDrawingStyleValues = z.union([\n PluginDrawingVectorStylePatch,\n z\n .object({\n ...PluginPlacedViewStylePatch.shape,\n disable: z.boolean().optional(),\n opacity: z.number().finite().min(-1).max(100).optional(),\n mapOpacity: z.number().finite().min(-1).max(100).optional(),\n buildingOpacity: z.number().finite().min(-1).max(100).optional(),\n })\n .strict(),\n])\nexport type PluginDrawingStyleValues = z.infer<typeof PluginDrawingStyleValues>\n\n/** Read-only effective settings and editable controls for one drawing category. */\nexport const PluginDrawingStyleCategory = z.object({\n category: z.string(),\n absent: z.boolean(),\n settings: PluginDrawingStyleValues,\n controls: z.array(PluginDrawingStyleControl),\n})\nexport type PluginDrawingStyleCategory = z.infer<\n typeof PluginDrawingStyleCategory\n>\n\n/** Effective style categories and their native editing capabilities. */\nexport const PluginDrawingStyles = z.object({\n categories: z.array(PluginDrawingStyleCategory),\n})\nexport type PluginDrawingStyles = z.infer<typeof PluginDrawingStyles>\nexport const PluginDrawingStatus = z.object({\n drawingId: z.string(),\n generation: z.enum([\"ready\", \"updating\", \"missing\"]),\n assetSave: z.enum([\"uploading\", \"retrying\", \"uploaded\", \"missing\"]),\n layout: z.enum([\"pending\", \"ready\", \"unsupported\"]),\n exportReady: z.boolean(),\n warnings: z.array(z.string()),\n})\nexport type PluginDrawingStatus = z.infer<typeof PluginDrawingStatus>\nexport const PluginDrawingStatusArgs = PluginDrawingIdArgs.extend({\n waitMs: z.number().finite().int().min(0).max(30000).optional(),\n})\nexport type PluginDrawingStatusArgs = z.infer<typeof PluginDrawingStatusArgs>\nexport const PluginDrawingExportFormat = z.enum([\"pdf\", \"svg\", \"png\", \"jpg\"])\nexport type PluginDrawingExportFormat = z.infer<\n typeof PluginDrawingExportFormat\n>\n/** Renderer-specific operations and formats supported by a drawing. */\nexport const PluginDrawingCapabilities = z.object({\n renderer: z.enum([\"vector\", \"hybrid\", \"raster\", \"unknown\"]),\n presets: z.boolean(),\n labelLayout: z.boolean(),\n architecturalScale: z.boolean(),\n standardScales: z.array(\n z.object({ label: z.string(), value: z.number().finite().positive() }),\n ),\n exportFormats: z.array(PluginDrawingExportFormat),\n})\nexport type PluginDrawingCapabilities = z.infer<\n typeof PluginDrawingCapabilities\n>\nexport const PluginDrawingExportOptions = z\n .object({\n fileName: z.string().trim().min(1).max(200).optional(),\n resolution: PluginPresentationExportResolution.optional(),\n download: z.boolean().optional(),\n })\n .strict()\nexport type PluginDrawingExportOptions = z.infer<\n typeof PluginDrawingExportOptions\n>\nexport const PluginDrawingExportArgs = PluginDrawingIdArgs.extend({\n format: PluginDrawingExportFormat,\n ...PluginDrawingExportOptions.shape,\n})\nexport type PluginDrawingExportArgs = z.infer<typeof PluginDrawingExportArgs>\nexport const PluginDrawingExportResult = z.object({\n files: z.array(\n z.object({\n fileName: z.string(),\n mimeType: z.string(),\n byteLength: z.number(),\n dataBase64: z.string().optional(),\n }),\n ),\n downloadStarted: z.boolean(),\n warnings: z.array(z.string()),\n})\nexport type PluginDrawingExportResult = z.infer<\n typeof PluginDrawingExportResult\n>\n\nexport const PluginDrawingLabelArgs = z\n .object({ labelId: z.string().min(1) })\n .strict()\nexport type PluginDrawingLabelArgs = z.infer<typeof PluginDrawingLabelArgs>\nexport const PluginDrawingLabelMoveArgs = PluginDrawingLabelArgs.extend({\n position: PluginSheetPosition,\n})\nexport type PluginDrawingLabelMoveArgs = z.infer<\n typeof PluginDrawingLabelMoveArgs\n>\nexport const PluginDrawingLabelHiddenArgs = PluginDrawingLabelArgs.extend({\n hidden: z.boolean(),\n})\nexport type PluginDrawingLabelHiddenArgs = z.infer<\n typeof PluginDrawingLabelHiddenArgs\n>\nexport const PluginDrawingLabelResetArgs = PluginDrawingIdArgs.extend({\n scope: z.enum([\"scale\", \"all\"]).optional(),\n})\nexport type PluginDrawingLabelResetArgs = z.infer<\n typeof PluginDrawingLabelResetArgs\n>\n\n/** Collision-layout diagnostics for drawing labels, including actionable scale suggestions. */\nexport const PluginDrawingLabelLayout = z.object({\n pending: z.boolean(),\n pinned: z.number().int().nonnegative(),\n unplaced: z.array(\n z.object({\n labelId: z.string(),\n name: z.string(),\n reason: z.string(),\n suggestedScale: z.number().finite().positive().nullable(),\n }),\n ),\n duplicatesMerged: z.number().int().nonnegative(),\n rowsDropped: z.object({\n department: z.number().int().nonnegative(),\n area: z.number().int().nonnegative(),\n wrapped: z.number().int().nonnegative(),\n }),\n})\nexport type PluginDrawingLabelLayout = z.infer<typeof PluginDrawingLabelLayout>\n\n/** Label ids are text-shape ids returned by list; positions are drawing-local canvas units. */\nexport abstract class PluginDrawingLabelsApi {\n /**\n * List every text label on a drawing, including hidden labels.\n * @examplePrompt List all labels in this drawing\n */\n public abstract list(\n drawingId: string,\n ): PluginApiReturn<PluginPlacedViewLabel[]>\n /**\n * Read collision diagnostics, suggested scales and pin counts without hiding failed labels.\n * @examplePrompt Check this drawing for overlapping labels\n */\n public abstract getLayout(\n drawingId: string,\n ): PluginApiReturn<PluginDrawingLabelLayout>\n /** Move a label; room labels retain the chosen position at the current architectural scale. */\n public abstract move(\n labelId: string,\n position: z.infer<typeof PluginSheetPosition>,\n ): PluginApiReturn<PluginPlacedViewLabel>\n /** Set user visibility, independently of layout/crop visibility. */\n public abstract setHidden(\n labelId: string,\n hidden: boolean,\n ): PluginApiReturn<PluginPlacedViewLabel>\n /** Pin an unclean room label at its best available position. May wait up to ten seconds for fitting. */\n public abstract place(labelId: string): PluginApiReturn<boolean>\n /** Clear pins at this scale (default) or every scale. Returns the number cleared. */\n public abstract reset(\n drawingId: string,\n options?: { scope?: \"scale\" | \"all\" },\n ): PluginApiReturn<number>\n}\n\n/**\n * First-class drawing deliverables on the Present canvas. Present must be open for every method;\n * no method changes tabs. Native shape history/sync are reused. Creating does not wait for upload:\n * inspect getStatus; \"uploaded\" describes the asset, not a server acknowledgement of document sync.\n * A drawing needs no sheet. Legacy placedViews/sheets.place continue to address the same shape ids.\n */\nexport abstract class PluginPresentationDrawingsApi {\n public abstract labels: PluginDrawingLabelsApi\n /**\n * Generate a saved source view on the canvas. Options use page coordinates and a standard scale denominator.\n * @examplePrompt Create a drawing from the ground floor view\n */\n public abstract create(\n viewId: string,\n options?: PluginDrawingCreateOptions,\n ): PluginApiReturn<PluginDrawing>\n /**\n * List drawing deliverables on the current Present page.\n * @examplePrompt List all drawings in this presentation\n */\n public abstract list(): PluginApiReturn<PluginDrawing[]>\n /** Read a drawing by its stable id; null if it no longer exists. */\n public abstract get(drawingId: string): PluginApiReturn<PluginDrawing | null>\n /** Copy a drawing and its annotations with native duplication; returns the new drawing. */\n public abstract copy(drawingId: string): PluginApiReturn<PluginDrawing>\n /** Rename the drawing title without renaming its source saved view. */\n public abstract rename(\n drawingId: string,\n name: string,\n ): PluginApiReturn<PluginDrawing>\n /** Delete a drawing and its children as one Present undo step. */\n public abstract delete(drawingId: string): PluginApiReturn<void>\n /** Regenerate specified drawings. Reports actual changed assets, not merely submitted ids. */\n public abstract updateContent(\n drawingIds: string[],\n ): PluginApiReturn<{ drawingId: string; updated: boolean }[]>\n /**\n * Read actual renderer and supported operations; presets are kernel-plan-only.\n * @examplePrompt What editing capabilities does this drawing support?\n */\n public abstract getCapabilities(\n drawingId: string,\n ): PluginApiReturn<PluginDrawingCapabilities>\n /** Wait at most waitMs (0 by default) for export readiness and upload; returns current status on timeout. */\n public abstract getStatus(\n drawingId: string,\n options?: { waitMs?: number },\n ): PluginApiReturn<PluginDrawingStatus>\n /** Set a standard architectural scale; perspective/isometric drawings reject this. */\n public abstract setScale(\n drawingId: string,\n scale: number,\n ): PluginApiReturn<PluginDrawing>\n /** Set normalized crop corners (0–1), or null to remove the crop. */\n public abstract setCrop(\n drawingId: string,\n crop: z.infer<typeof PluginPlacedViewCrop> | null,\n ): PluginApiReturn<PluginDrawing>\n /**\n * Read effective categories/controls before editing; absent categories reject writes.\n * @examplePrompt Show the editable styles for this drawing\n */\n public abstract getStyles(\n drawingId: string,\n ): PluginApiReturn<PluginDrawingStyles>\n /** Merge a typed category patch as one undo step. Invalid/unsupported fields reject the whole call. */\n public abstract updateStyles(\n drawingId: string,\n category: string,\n patch: PluginDrawingStylePatch,\n ): PluginApiReturn<PluginDrawingStyles>\n /**\n * Bind a kernel plan to a preset and clear declared overrides; local title text is retained.\n * @examplePrompt Apply the presentation plan preset to this drawing\n */\n public abstract applyPreset(\n drawingId: string,\n presetId: string,\n ): PluginApiReturn<PluginDrawing>\n /** Reset declared overrides to the bound preset, preserving local title/proposal/building text. */\n public abstract resetOverrides(\n drawingId: string,\n ): PluginApiReturn<PluginDrawing>\n /** List pattern ids accepted by the native hatch catalog. */\n public abstract listPatterns(): PluginApiReturn<\n { id: string; label: string; group: string }[]\n >\n /** List available font families from the native catalog (including DEFAULT). */\n public abstract listFonts(): PluginApiReturn<string[]>\n /**\n * Export the drawing including labels/annotations, no sheet required. PDF size follows its canvas bounds.\n * download:true starts a browser download and returns metadata only (never send large base64 to an agent).\n * Otherwise files contain dataBase64. downloadStarted means initiated, not proof of an OS-saved file.\n * SVG/PDF preserve vector content; raster fallbacks remain raster. Layout warnings are returned.\n */\n public abstract export(\n drawingId: string,\n format: PluginDrawingExportFormat,\n options?: PluginDrawingExportOptions,\n ): PluginApiReturn<PluginDrawingExportResult>\n}\n","import * as z from \"zod\"\nimport type { PluginApiReturn } from \"../../types\"\n\n/** Presets are document-local. Their ids are stable across reload; built-ins are immutable. */\nexport const PluginViewPreset = z.object({\n id: z.string(),\n name: z.string(),\n builtIn: z.boolean(),\n revision: z.number().int().nonnegative(),\n})\nexport type PluginViewPreset = z.infer<typeof PluginViewPreset>\nexport const PluginViewPresetIdArgs = z\n .object({ presetId: z.string().min(1) })\n .strict()\nexport type PluginViewPresetIdArgs = z.infer<typeof PluginViewPresetIdArgs>\nexport const PluginViewPresetCreateArgs = z\n .object({ name: z.string().trim().min(1), fromDrawingId: z.string().min(1) })\n .strict()\nexport type PluginViewPresetCreateArgs = z.infer<\n typeof PluginViewPresetCreateArgs\n>\nexport const PluginViewPresetRenameArgs = PluginViewPresetIdArgs.extend({\n name: z.string().trim().min(1),\n})\nexport type PluginViewPresetRenameArgs = z.infer<\n typeof PluginViewPresetRenameArgs\n>\nexport const PluginViewPresetUpdateArgs = PluginViewPresetIdArgs.extend({\n fromDrawingId: z.string().min(1),\n})\nexport type PluginViewPresetUpdateArgs = z.infer<\n typeof PluginViewPresetUpdateArgs\n>\nexport const PluginViewPresetDeleteArgs = PluginViewPresetIdArgs.extend({\n replacementId: z.string().min(1),\n})\nexport type PluginViewPresetDeleteArgs = z.infer<\n typeof PluginViewPresetDeleteArgs\n>\n\n/**\n * Reusable kernel-plan styles. Requires Present open. Catalog writes are native non-undoable\n * document metadata; binding/override writes use Present history. Local title text is excluded.\n */\nexport abstract class PluginPresentationViewPresetsApi {\n /**\n * List built-ins first, then custom presets by name.\n * @examplePrompt List the available drawing style presets\n */\n public abstract list(): PluginApiReturn<PluginViewPreset[]>\n /** Read a preset, or null when missing. */\n public abstract get(\n presetId: string,\n ): PluginApiReturn<PluginViewPreset | null>\n /**\n * Capture a drawing's effective styles and bind it to the new preset. Names must be unique.\n * @examplePrompt Save this drawing's styles as a new preset\n */\n public abstract create(\n name: string,\n options: { fromDrawingId: string },\n ): PluginApiReturn<PluginViewPreset>\n /** Copy a preset with a native unique name, without changing drawing bindings. */\n public abstract copy(presetId: string): PluginApiReturn<PluginViewPreset>\n /** Rename a custom preset. Built-ins reject this operation. */\n public abstract rename(\n presetId: string,\n name: string,\n ): PluginApiReturn<PluginViewPreset>\n /**\n * Update a custom preset from a drawing bound to it; refresh all bound drawings.\n * @examplePrompt Update this preset from the current drawing styles\n */\n public abstract update(\n presetId: string,\n options: { fromDrawingId: string },\n ): PluginApiReturn<PluginViewPreset>\n /** Delete a custom preset, rebinding its drawings to a different existing replacement. */\n public abstract delete(\n presetId: string,\n options: { replacementId: string },\n ): PluginApiReturn<void>\n}\n","import { PluginApiReturn } from \"../../types\"\nimport { PluginPresentationViewsApi } from \"./views\"\nimport { PluginPresentationAIInspirationApi } from \"./aiInspiration\"\nimport { PluginPresentationSheetsApi } from \"./sheets\"\nimport { PluginPresentationPlacedViewsApi } from \"./placedViews\"\nimport { PluginPresentationDiagramsApi } from \"./diagrams\"\nimport { PluginPresentationDrawingsApi } from \"./drawings\"\nimport { PluginPresentationViewPresetsApi } from \"./viewPresets\"\nimport { PluginPresentationAnnotateApi } from \"./annotate\"\nimport { PluginPresentationShapesApi } from \"./shapes\"\nimport { PluginPresentationTablesApi } from \"./tables\"\nimport { PluginPresentationSlideshowApi } from \"./slideshow\"\nimport { PluginPresentationImportApi } from \"./import\"\nimport {\n type PluginPresentationExportResult,\n type PluginPresentationExportDownloadResult,\n type PluginPresentationExportFormat,\n type PluginPresentationExportOptions,\n} from \"./export\"\n\n/**\n * Presentation APIs — prepare and assemble presentations.\n *\n * Reads describe the artifacts a presentation is built from (saved views, layout\n * sheets); writes assemble them (place views/diagrams on sheets, import\n * references, capture/save views). Present-mode AI Inspiration also lives here.\n *\n * Several members require **Present mode to be open** (the documentation editor):\n * sheets, diagram placement, and import.\n *\n * - {@linkcode PluginPresentationApi.views} — Saved 2D/3D views (list, get, capture, activate, create)\n * - {@linkcode PluginPresentationApi.sheets} — Layout sheets (list, get, create, place views)\n * - {@linkcode PluginPresentationApi.placedViews} — Views already placed on the canvas (move, scale, crop)\n * - {@linkcode PluginPresentationApi.diagrams} — Generate program/site diagram sheets, place diagram images\n * - {@linkcode PluginPresentationApi.tables} — Place data tables on sheets\n * - {@linkcode PluginPresentationApi.slideshow} — Run the Present-mode slideshow\n * - {@linkcode PluginPresentationApi.import} — Import reference images/PDFs onto the canvas\n * - {@linkcode PluginPresentationApi.aiInspiration} — Present-mode AI image/video generation\n *\n * Accessed via `snaptrude.presentation`.\n */\nexport abstract class PluginPresentationApi {\n /** Saved 2D/3D views. See {@linkcode PluginPresentationViewsApi}. */\n public abstract views: PluginPresentationViewsApi\n /** Drawing deliverables independent of sheet composition; Present must be open. */\n public abstract drawings: PluginPresentationDrawingsApi\n /** Reusable kernel-plan styles stored in the Present document. */\n public abstract viewPresets: PluginPresentationViewPresetsApi\n /** Layout sheets — list/get/create and place views (Present mode). See {@linkcode PluginPresentationSheetsApi}. */\n public abstract sheets: PluginPresentationSheetsApi\n /** Placed views — layout of the view shapes on the canvas: move/scale/setScale/setCrop (Present mode). See {@linkcode PluginPresentationPlacedViewsApi}. */\n public abstract placedViews: PluginPresentationPlacedViewsApi\n /** Program/adjacency/site diagrams — place images on sheets. See {@linkcode PluginPresentationDiagramsApi}. */\n public abstract diagrams: PluginPresentationDiagramsApi\n /** Annotations on Present-mode sheets — text/arrows/notes/geo shapes. See {@linkcode PluginPresentationAnnotateApi}. */\n public abstract annotate: PluginPresentationAnnotateApi\n /** Plugin-owned keyed shapes — upsert/remove/list so reruns update instead of duplicating. See {@linkcode PluginPresentationShapesApi}. */\n public abstract shapes: PluginPresentationShapesApi\n /** Data tables on Present sheets — place string rows as a table shape. See {@linkcode PluginPresentationTablesApi}. */\n public abstract tables: PluginPresentationTablesApi\n /** The Present-mode slideshow — start/stop/getState. See {@linkcode PluginPresentationSlideshowApi}. */\n public abstract slideshow: PluginPresentationSlideshowApi\n /** Import reference images/PDFs onto the Present canvas. See {@linkcode PluginPresentationImportApi}. */\n public abstract import: PluginPresentationImportApi\n /**\n * Present-mode AI Inspiration image/video generation. See\n * {@linkcode PluginPresentationAIInspirationApi}.\n */\n public abstract aiInspiration: PluginPresentationAIInspirationApi\n\n /**\n * Export the presentation's layout sheets to a downloadable file (PDF / PNG /\n * JPG). Return base64 by default; `download: true` initiates browser downloads\n * and returns metadata without base64. Requires Present mode to be open.\n *\n * @param format - Output format: `\"pdf\"`, `\"png\"`, or `\"jpg\"`.\n * @param options - Optional `sheetIds` (all sheets when omitted), `combine`\n * (PDF only — one multi-page file vs one per sheet, default `true`),\n * `resolution` (raster target size for `png`/`jpg`), `fileName`, and `download`.\n * @returns A `files` array (one entry for a combined PDF, one per sheet otherwise).\n * Downloads return {@linkcode PluginPresentationExportDownloadResult}; otherwise\n * {@linkcode PluginPresentationExportResult}. Download initiation does not prove\n * the browser saved the file; multiple downloads may require user permission.\n * @throws If Present mode is not open, a `sheetId` is unknown, or a sheet\n * fails to serialize/encode.\n *\n * @examplePrompt Export the presentation as a PDF\n * @examplePrompt Download all sheets as a single PDF\n * @examplePrompt Export sheet 1 as a PNG\n * @examplePrompt Save the presentation sheets as JPG images\n * @examplePrompt Export these sheets to PDF at high resolution\n *\n * # Example\n * ```ts\n * const { files } = await snaptrude.presentation.export(\"pdf\")\n * const download = await snaptrude.presentation.export(\"pdf\", { download: true })\n * ```\n */\n public abstract export(\n format: PluginPresentationExportFormat,\n options?: PluginPresentationExportOptions & { download?: false },\n ): PluginApiReturn<PluginPresentationExportResult>\n public abstract export(\n format: PluginPresentationExportFormat,\n options: PluginPresentationExportOptions & { download: true },\n ): PluginApiReturn<PluginPresentationExportDownloadResult>\n public abstract export(\n format: PluginPresentationExportFormat,\n options?: PluginPresentationExportOptions,\n ): PluginApiReturn<\n PluginPresentationExportResult | PluginPresentationExportDownloadResult\n >\n\n constructor() {}\n}\n\nexport * from \"./views\"\nexport * from \"./sheets\"\nexport * from \"./placedViews\"\nexport * from \"./diagrams\"\nexport * from \"./annotate\"\nexport * from \"./shapes\"\nexport * from \"./tables\"\nexport * from \"./slideshow\"\nexport * from \"./import\"\nexport * from \"./aiInspiration\"\nexport * from \"./export\"\nexport * from \"./drawings\"\nexport * from \"./viewPresets\"\n","import { PluginApiReturn } from \"../../types\"\n\n/**\n * Analysis sunpath — the sun-path diagram overlay.\n *\n * The sun-path diagram draws the sun's annual/daily trajectory arcs over the\n * scene for the project's geographic location, so daylight behaviour can be\n * read directly off the model. Toggling it is an **undoable** action (it goes\n * through the command stack, so `core.history.undo` reverts it).\n *\n * The overlay is computed for the project's geo-location. Enabling **throws**\n * when the project has no geo-located site/terrain — there is no fallback\n * location. Set the site location first (Import terrain / site context);\n * read it via `program.site.getLocation`.\n *\n * Accessed via `snaptrude.analysis.sunpath`.\n */\nexport abstract class PluginAnalysisSunpathApi {\n constructor() {}\n\n /**\n * Turn the sun-path diagram overlay ON.\n *\n * Undoable: the toggle is committed through the command stack. A no-op\n * (returns `true`) when the overlay is already active.\n *\n * @returns `true` — the overlay is active after the call.\n * @throws When the project has no geo-located site/terrain (no location to\n * compute the sun trajectory for).\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Show the sun path diagram\n * @examplePrompt Turn on the sunpath overlay\n * @examplePrompt Visualize the sun's trajectory over my building\n *\n * # Example\n * ```ts\n * await snaptrude.analysis.sunpath.enable()\n * const active = await snaptrude.analysis.sunpath.isActive() // true\n * ```\n */\n public abstract enable(): PluginApiReturn<boolean>\n\n /**\n * Turn the sun-path diagram overlay OFF.\n *\n * Undoable: the toggle is committed through the command stack. A no-op\n * (returns `false`) when the overlay is already off.\n *\n * @returns `false` — the overlay is inactive after the call.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Hide the sun path diagram\n * @examplePrompt Turn off the sunpath overlay\n * @examplePrompt Remove the sun trajectory arcs from the scene\n *\n * # Example\n * ```ts\n * await snaptrude.analysis.sunpath.disable()\n * ```\n */\n public abstract disable(): PluginApiReturn<boolean>\n\n /**\n * Whether the sun-path diagram overlay is currently active.\n *\n * A pure read — never mutates, never throws.\n *\n * @returns `true` when the overlay is showing.\n *\n * @examplePrompt Is the sun path diagram on?\n * @examplePrompt Check whether the sunpath overlay is active\n * @examplePrompt Am I looking at the sun trajectory right now?\n *\n * # Example\n * ```ts\n * if (!(await snaptrude.analysis.sunpath.isActive())) {\n * await snaptrude.analysis.sunpath.enable()\n * }\n * ```\n */\n public abstract isActive(): PluginApiReturn<boolean>\n}\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * Analysis shadows — real-time sun shadows in the scene.\n *\n * Shadows are cast for the sun position at a specific date and time at the\n * project's geographic location. The date-time crosses this API as an **ISO\n * 8601 date-time string** in the project's local time (e.g.\n * `\"2026-06-21T14:30\"`); the engine snaps the time **down** to its half-hour\n * grid (`14:59` → `14:30`); seconds, if provided, are discarded. The\n * timezone is derived from the site location by the engine — do not append\n * a UTC offset.\n *\n * Methods that position the sun **throw** when the project has no geo-located\n * site/terrain — there is no fallback location. Set the site location first;\n * read it via `program.site.getLocation`.\n *\n * Accessed via `snaptrude.analysis.shadows`.\n */\nexport abstract class PluginAnalysisShadowsApi {\n constructor() {}\n\n /**\n * Turn real-time sun shadows ON, optionally at a specific date and time.\n *\n * Without `options.dateTime` the shadows use the current stored sun\n * position; with it, the sun is moved first (same as\n * {@linkcode PluginAnalysisShadowsApi.setDateTime}). Enabling shadows\n * resets any active heatmap analysis (`sunlightHours` / `illuminance`) —\n * they are mutually exclusive in the product.\n *\n * @param options - Optional `dateTime`: ISO 8601 local date-time to\n * position the sun at (snapped to the engine's half-hour grid).\n * @returns `true` — shadows are enabled after the call.\n * @throws When the project has no geo-located site/terrain.\n * @throws When `options.dateTime` is not a parseable ISO date-time.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Turn on shadows\n * @examplePrompt Show the shadows at 3pm on June 21st\n * @examplePrompt Enable sun shadows for the summer solstice afternoon\n *\n * # Example\n * ```ts\n * await snaptrude.analysis.shadows.enable({ dateTime: \"2026-06-21T15:00\" })\n * ```\n */\n public abstract enable(\n options?: PluginAnalysisShadowsEnableOptions,\n ): PluginApiReturn<boolean>\n\n /**\n * Turn real-time sun shadows OFF.\n *\n * A no-op (returns `false`) when shadows are already off. The stored sun\n * date-time is kept for the next enable.\n *\n * @returns `false` — shadows are disabled after the call.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Turn off shadows\n * @examplePrompt Hide the sun shadows\n * @examplePrompt Disable shadow rendering in the scene\n *\n * # Example\n * ```ts\n * await snaptrude.analysis.shadows.disable()\n * ```\n */\n public abstract disable(): PluginApiReturn<boolean>\n\n /**\n * Whether real-time sun shadows are currently enabled.\n *\n * A pure read — never mutates, never throws.\n *\n * @returns `true` when shadows are rendering.\n *\n * @examplePrompt Are shadows on?\n * @examplePrompt Check whether sun shadows are enabled\n * @examplePrompt Is the scene showing shadows right now?\n *\n * # Example\n * ```ts\n * const on = await snaptrude.analysis.shadows.isEnabled()\n * ```\n */\n public abstract isEnabled(): PluginApiReturn<boolean>\n\n /**\n * Move the sun to a specific date and time.\n *\n * Takes an ISO 8601 date-time string in the project's local time; the\n * engine snaps the time down to its half-hour grid and re-renders the shadows\n * (when they are enabled). The engine stores no year — only the month, day,\n * and time are applied, and the returned date-time always carries the\n * **current year** (setting `\"2020-12-21T09:30\"` returns\n * `\"2026-12-21T09:30\"` in 2026). Paired with\n * {@linkcode PluginAnalysisShadowsApi.getDateTime}.\n *\n * @param dateTime - ISO 8601 local date-time, e.g. `\"2026-12-21T09:30\"`.\n * @returns A {@linkcode PluginAnalysisShadowsDateTimeResult} — the applied\n * ISO date-time after half-hour snapping.\n * @throws When the project has no geo-located site/terrain.\n * @throws When `dateTime` is not a parseable ISO date-time.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Set the shadow time to 9:30 am on the winter solstice\n * @examplePrompt Move the sun to noon on March 1st\n * @examplePrompt Change the shadow study date to December 21\n *\n * # Example\n * ```ts\n * const applied = await snaptrude.analysis.shadows.setDateTime(\"2026-12-21T09:30\")\n * console.log(applied) // \"2026-12-21T09:30\" (snapped to the half-hour grid)\n * ```\n */\n public abstract setDateTime(\n dateTime: string,\n ): PluginApiReturn<PluginAnalysisShadowsDateTimeResult>\n\n /**\n * Get the sun's current date and time.\n *\n * Returns the stored sun position as an ISO 8601 local date-time string —\n * the value shadows render at (whether or not they are enabled). The engine\n * stores no year, so the returned string always carries the **current\n * year**, whatever year was passed to\n * {@linkcode PluginAnalysisShadowsApi.setDateTime} (its pair).\n *\n * @returns A {@linkcode PluginAnalysisShadowsDateTimeResult} — the current\n * ISO local date-time of the sun.\n *\n * @examplePrompt What date and time are the shadows set to?\n * @examplePrompt Get the current sun position date\n * @examplePrompt Which time of day is the shadow study showing?\n *\n * # Example\n * ```ts\n * const dateTime = await snaptrude.analysis.shadows.getDateTime()\n * ```\n */\n public abstract getDateTime(): PluginApiReturn<PluginAnalysisShadowsDateTimeResult>\n}\n\n/**\n * Options for {@linkcode PluginAnalysisShadowsApi.enable}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `dateTime` | `string \\| undefined` | ISO 8601 local date-time to position the sun at (snapped to the half-hour grid) |\n */\nexport const PluginAnalysisShadowsEnableOptions = z.object({\n dateTime: z.string().optional(),\n})\nexport type PluginAnalysisShadowsEnableOptions = z.infer<\n typeof PluginAnalysisShadowsEnableOptions\n>\n\n/**\n * Arguments for {@linkcode PluginAnalysisShadowsApi.setDateTime}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `dateTime` | `string` | ISO 8601 local date-time, e.g. `\"2026-06-21T14:30\"` |\n */\nexport const PluginAnalysisShadowsSetDateTimeArgs = z.object({\n dateTime: z.string(),\n})\nexport type PluginAnalysisShadowsSetDateTimeArgs = z.infer<\n typeof PluginAnalysisShadowsSetDateTimeArgs\n>\n\n/**\n * Result of {@linkcode PluginAnalysisShadowsApi.setDateTime} /\n * {@linkcode PluginAnalysisShadowsApi.getDateTime} — the sun's ISO 8601\n * local date-time (minute precision, on the engine's half-hour grid).\n */\nexport const PluginAnalysisShadowsDateTimeResult = z.string()\nexport type PluginAnalysisShadowsDateTimeResult = z.infer<\n typeof PluginAnalysisShadowsDateTimeResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * Analysis sunlightHours — the direct-sunlight-hours heatmap study.\n *\n * Computes, for every space surface (Room or Department Mass — generic\n * Masses are not enough), how many hours of direct sunlight\n * it receives over a date range, and renders the result as a heatmap on the\n * scene. The computation is an **asynchronous backend job**:\n *\n * 1. {@linkcode PluginAnalysisSunlightHoursApi.compute} starts the job and\n * returns immediately.\n * 2. Poll {@linkcode PluginAnalysisSunlightHoursApi.get} until `status` is\n * `\"active\"` (heatmap rendered) — a run typically takes minutes. There is\n * no completion event; polling is the pattern.\n * 3. {@linkcode PluginAnalysisSunlightHoursApi.cancel} aborts an in-flight\n * run; {@linkcode PluginAnalysisSunlightHoursApi.reset} clears a rendered\n * heatmap.\n *\n * Heatmaps are invalidated by scene-mutating edits — re-run the study after\n * changing the model. Dates cross as ISO 8601 date strings (`\"YYYY-MM-DD\"`).\n *\n * Accessed via `snaptrude.analysis.sunlightHours`.\n */\nexport abstract class PluginAnalysisSunlightHoursApi {\n constructor() {}\n\n /**\n * Start a direct-sunlight-hours run for a date range.\n *\n * Starts the backend job and **returns immediately** — it does not wait\n * for the heatmap. Poll {@linkcode PluginAnalysisSunlightHoursApi.get}\n * until `status` is `\"active\"`. Starting a new run while one is in flight\n * replaces it. Shadows and the heatmap analyses are mutually exclusive in\n * the product: starting a run disables real-time shadows and resets/cancels\n * any {@linkcode PluginAnalysisIlluminanceApi} (`analysis.illuminance`)\n * study.\n *\n * @param startDate - Start of the study range, ISO date `\"YYYY-MM-DD\"`.\n * @param endDate - End of the study range, ISO date `\"YYYY-MM-DD\"` (on or\n * after `startDate`).\n * @returns A {@linkcode PluginAnalysisComputeResult} — `{ success: true }`\n * when the job was started. The `{ success: false, error }` arm is\n * reserved — not yet emitted (start failures currently throw).\n * @throws When the project has no geo-located site/terrain (no location to\n * compute sun exposure for — there is no fallback location).\n * @throws When the scene has no space (Room or Department Mass) to analyse\n * or the editor is not in the 3D view.\n * @throws When a date is not a parseable ISO date or the range is inverted.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Run a sunlight hours analysis for June\n * @examplePrompt Compute direct sunlight hours between March and September\n * @examplePrompt How much sun does my building get over the summer?\n *\n * # Example\n * ```ts\n * const { success } = await snaptrude.analysis.sunlightHours.compute(\n * \"2026-06-01\",\n * \"2026-06-30\",\n * )\n * // poll until the heatmap is rendered\n * let job = await snaptrude.analysis.sunlightHours.get()\n * while (job?.status === \"running\") {\n * await new Promise((r) => setTimeout(r, 5000))\n * job = await snaptrude.analysis.sunlightHours.get()\n * }\n * ```\n */\n public abstract compute(\n startDate: string,\n endDate: string,\n ): PluginApiReturn<PluginAnalysisComputeResult>\n\n /**\n * Get the state of the sunlight-hours study.\n *\n * The polling read for the async job started by\n * {@linkcode PluginAnalysisSunlightHoursApi.compute}. `status` values:\n *\n * | Status | Meaning |\n * |---|---|\n * | `\"running\"` | A run is in flight — keep polling |\n * | `\"active\"` | The heatmap is rendered on the scene |\n * | `\"inactive\"` | A previous run exists but its heatmap is not showing |\n *\n * @returns A {@linkcode PluginAnalysisJobStateResult} with the `status` and\n * the run's ISO `startDate`/`endDate`, or `null` when no run result is\n * available (the study never ran, was cancelled, or the last run failed).\n *\n * @examplePrompt Is the sunlight analysis done?\n * @examplePrompt Check the status of the sunlight hours run\n * @examplePrompt What date range was the sunlight heatmap computed for?\n *\n * # Example\n * ```ts\n * const job = await snaptrude.analysis.sunlightHours.get()\n * if (job?.status === \"active\") console.log(job.startDate, job.endDate)\n * ```\n */\n public abstract get(): PluginApiReturn<PluginAnalysisJobStateResult>\n\n /**\n * Cancel the in-flight sunlight-hours run.\n *\n * Aborts the backend job. A no-op (returns `false`) when nothing is\n * running.\n *\n * @returns `true` when a run was cancelled, `false` when nothing was\n * running.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Cancel the sunlight analysis\n * @examplePrompt Stop the running sunlight hours computation\n * @examplePrompt Abort the sun study\n *\n * # Example\n * ```ts\n * await snaptrude.analysis.sunlightHours.cancel()\n * ```\n */\n public abstract cancel(): PluginApiReturn<boolean>\n\n /**\n * Clear the sunlight-hours heatmap from the scene.\n *\n * Un-applies the rendered heatmap and restores the normal material view.\n * A no-op (returns `false`) when no heatmap is showing. Does not cancel an\n * in-flight run — use {@linkcode PluginAnalysisSunlightHoursApi.cancel}.\n *\n * @returns `true` when a heatmap was cleared, `false` when none was\n * showing.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Clear the sunlight heatmap\n * @examplePrompt Remove the sun hours colours from the model\n * @examplePrompt Reset the sunlight analysis view\n *\n * # Example\n * ```ts\n * await snaptrude.analysis.sunlightHours.reset()\n * ```\n */\n public abstract reset(): PluginApiReturn<boolean>\n}\n\n/**\n * Result of starting an analysis job\n * ({@linkcode PluginAnalysisSunlightHoursApi.compute} /\n * {@linkcode PluginAnalysisIlluminanceApi.compute}).\n *\n * The `success: false` arm is **reserved — not yet emitted**: the current\n * host always returns `{ success: true }` (start failures throw instead); a\n * failure indicator may be added additively later.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `success` | `boolean` | `true` when the backend job was started |\n * | `error` | `string \\| undefined` | Failure reason when `success` is `false` (reserved — not yet emitted) |\n */\nexport const PluginAnalysisComputeResult = z.object({\n success: z.boolean(),\n error: z.string().optional(),\n})\nexport type PluginAnalysisComputeResult = z.infer<\n typeof PluginAnalysisComputeResult\n>\n\n/**\n * The status of an analysis heatmap job.\n *\n * | Value | Meaning |\n * |---|---|\n * | `\"running\"` | A run is in flight — keep polling |\n * | `\"active\"` | The heatmap is rendered on the scene |\n * | `\"inactive\"` | A previous run exists but its heatmap is not showing |\n */\nexport const PluginAnalysisJobStatus = z.enum([\n \"running\",\n \"active\",\n \"inactive\",\n])\nexport type PluginAnalysisJobStatus = z.infer<typeof PluginAnalysisJobStatus>\n\n/**\n * The state of an analysis heatmap job.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `status` | {@linkcode PluginAnalysisJobStatus} | `\"running\"` \\| `\"active\"` \\| `\"inactive\"` |\n * | `startDate` | `string` | ISO date (`\"YYYY-MM-DD\"`) the run was computed from |\n * | `endDate` | `string` | ISO date (`\"YYYY-MM-DD\"`) the run was computed to |\n */\nexport const PluginAnalysisJobState = z.object({\n status: PluginAnalysisJobStatus,\n startDate: z.string(),\n endDate: z.string(),\n})\nexport type PluginAnalysisJobState = z.infer<typeof PluginAnalysisJobState>\n\n/**\n * Result of {@linkcode PluginAnalysisSunlightHoursApi.get} /\n * {@linkcode PluginAnalysisIlluminanceApi.get} — the job state, or `null`\n * when no run result is available (the study never ran, was cancelled, or\n * the last run failed).\n */\nexport const PluginAnalysisJobStateResult = PluginAnalysisJobState.nullable()\nexport type PluginAnalysisJobStateResult = z.infer<\n typeof PluginAnalysisJobStateResult\n>\n","import { PluginApiReturn } from \"../../types\"\nimport {\n PluginAnalysisComputeResult,\n PluginAnalysisJobStateResult,\n} from \"./sunlightHours\"\n\n/**\n * Analysis illuminance — the daylight illuminance (lux) heatmap study.\n *\n * Computes interior daylight illuminance over a date range and renders the\n * result as a heatmap on the scene. **Pro-gated**: every method that starts\n * a run throws when the workspace is not on a Pro plan.\n *\n * Like {@linkcode PluginAnalysisSunlightHoursApi} (`analysis.sunlightHours`),\n * this is an **asynchronous backend job**:\n *\n * 1. {@linkcode PluginAnalysisIlluminanceApi.compute} starts the job and\n * returns immediately.\n * 2. Poll {@linkcode PluginAnalysisIlluminanceApi.get} until `status` is\n * `\"active\"` (heatmap rendered) — a run typically takes minutes. There is\n * no completion event; polling is the pattern.\n * 3. {@linkcode PluginAnalysisIlluminanceApi.cancel} aborts an in-flight\n * run; {@linkcode PluginAnalysisIlluminanceApi.reset} clears a rendered\n * heatmap.\n *\n * Illuminance analyses BIM slab and roof objects (floors, roofs, …) — walls\n * or bare Masses are not enough — and requires the 3D view. Heatmaps are invalidated by\n * scene-mutating edits. Dates cross as ISO 8601 date strings\n * (`\"YYYY-MM-DD\"`).\n *\n * Accessed via `snaptrude.analysis.illuminance`.\n */\nexport abstract class PluginAnalysisIlluminanceApi {\n constructor() {}\n\n /**\n * Start a daylight-illuminance run for a date range.\n *\n * Starts the backend job and **returns immediately** — it does not wait\n * for the heatmap. Poll {@linkcode PluginAnalysisIlluminanceApi.get} until\n * `status` is `\"active\"`. Starting a new run while one is in flight\n * replaces it. Shadows and the heatmap analyses are mutually exclusive in\n * the product: starting a run disables real-time shadows and resets/cancels\n * any {@linkcode PluginAnalysisSunlightHoursApi}\n * (`analysis.sunlightHours`) study.\n *\n * @param startDate - Start of the study range, ISO date `\"YYYY-MM-DD\"`.\n * @param endDate - End of the study range, ISO date `\"YYYY-MM-DD\"` (on or\n * after `startDate`).\n * @returns A {@linkcode PluginAnalysisComputeResult} — `{ success: true }`\n * when the job was started. The `{ success: false, error }` arm is\n * reserved — not yet emitted (start failures currently throw).\n * @throws When the workspace is not on a **Pro** plan (illuminance is a\n * Pro feature).\n * @throws When the project has no geo-located site/terrain (no location to\n * compute daylight for — there is no fallback location).\n * @throws When the scene has no slab or roof BIM object to analyse (walls\n * or bare Masses are not enough) or the editor is not in the 3D view.\n * @throws When a date is not a parseable ISO date or the range is inverted.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Run a daylight illuminance analysis for December\n * @examplePrompt Compute the lux levels inside the building for winter\n * @examplePrompt How much daylight do the interior floors get?\n *\n * # Example\n * ```ts\n * const { success } = await snaptrude.analysis.illuminance.compute(\n * \"2026-12-01\",\n * \"2026-12-31\",\n * )\n * // poll until the heatmap is rendered\n * let job = await snaptrude.analysis.illuminance.get()\n * while (job?.status === \"running\") {\n * await new Promise((r) => setTimeout(r, 5000))\n * job = await snaptrude.analysis.illuminance.get()\n * }\n * ```\n */\n public abstract compute(\n startDate: string,\n endDate: string,\n ): PluginApiReturn<PluginAnalysisComputeResult>\n\n /**\n * Get the state of the illuminance study.\n *\n * The polling read for the async job started by\n * {@linkcode PluginAnalysisIlluminanceApi.compute}. `status` values:\n *\n * | Status | Meaning |\n * |---|---|\n * | `\"running\"` | A run is in flight — keep polling |\n * | `\"active\"` | The heatmap is rendered on the scene |\n * | `\"inactive\"` | A previous run exists but its heatmap is not showing |\n *\n * @returns A {@linkcode PluginAnalysisJobStateResult} with the `status` and\n * the run's ISO `startDate`/`endDate`, or `null` when no run result is\n * available (the study never ran, was cancelled, or the last run failed).\n *\n * @examplePrompt Is the illuminance analysis done?\n * @examplePrompt Check the status of the daylight lux run\n * @examplePrompt What date range was the illuminance heatmap computed for?\n *\n * # Example\n * ```ts\n * const job = await snaptrude.analysis.illuminance.get()\n * if (job?.status === \"active\") console.log(job.startDate, job.endDate)\n * ```\n */\n public abstract get(): PluginApiReturn<PluginAnalysisJobStateResult>\n\n /**\n * Cancel the in-flight illuminance run.\n *\n * Aborts the backend job. A no-op (returns `false`) when nothing is\n * running.\n *\n * @returns `true` when a run was cancelled, `false` when nothing was\n * running.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Cancel the illuminance analysis\n * @examplePrompt Stop the running daylight computation\n * @examplePrompt Abort the lux study\n *\n * # Example\n * ```ts\n * await snaptrude.analysis.illuminance.cancel()\n * ```\n */\n public abstract cancel(): PluginApiReturn<boolean>\n\n /**\n * Clear the illuminance heatmap from the scene.\n *\n * Un-applies the rendered heatmap and restores the normal material view.\n * A no-op (returns `false`) when no heatmap is showing. Does not cancel an\n * in-flight run — use {@linkcode PluginAnalysisIlluminanceApi.cancel}.\n *\n * @returns `true` when a heatmap was cleared, `false` when none was\n * showing.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Clear the illuminance heatmap\n * @examplePrompt Remove the daylight lux colours from the model\n * @examplePrompt Reset the illuminance analysis view\n *\n * # Example\n * ```ts\n * await snaptrude.analysis.illuminance.reset()\n * ```\n */\n public abstract reset(): PluginApiReturn<boolean>\n}\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport { ComponentHandle, Vec3Components } from \"../../handles\"\n\n/**\n * Analysis heatmaps — render plugin-computed scalar data as a heatmap.\n *\n * Unlike the built-in studies ({@linkcode PluginAnalysisSunlightHoursApi},\n * {@linkcode PluginAnalysisIlluminanceApi}) where the host computes the\n * values, here the **plugin brings its own numbers** and the host renders\n * them. Two render modes:\n *\n * - {@linkcode PluginAnalysisHeatmapsApi.renderSpaces} — one flat colour per\n * space, painted on the space's top face (occupancy, energy use, any\n * per-room metric).\n * - {@linkcode PluginAnalysisHeatmapsApi.renderGrid} — a coloured grid mesh\n * from point samples in world coordinates (wind, noise, microclimate — any\n * field sampled over an area).\n * - {@linkcode PluginAnalysisHeatmapsApi.renderField} — arbitrary planar\n * polygon cells (or pre-tessellated meshes), one scalar value each — for\n * non-square analysis meshes straight from a solver.\n *\n * Shared semantics:\n *\n * - **Named overlays** — every render call targets a named overlay\n * (`options.name`; a shared default overlay when omitted). Rendering an\n * existing name replaces that overlay only; the newly rendered overlay\n * becomes the visible one — **exactly one overlay is visible at a time**.\n * Up to 16 overlays can be registered; manage them (list / show / hide /\n * remove) via {@linkcode PluginAnalysisHeatmapsApi.overlays}.\n * - **Legend** — the host renders a legend card from\n * {@linkcode PluginAnalysisHeatmapOptions} (title, unit, min/max, colour\n * segments). Values outside `[min, max]` **clamp to the end colours** and\n * the legend shows `<` / `>` markers at the clamped ends.\n * - **Hover tooltip** — pass `options.hover: true` to any render call and,\n * while that overlay is visible, hovering a cell shows a tooltip with the\n * cell's `value` (and its `meta` payload, when the cell carries one).\n * - **Ephemeral** — the heatmap is a transient overlay: never persisted,\n * never saved with views, and auto-cleared when a scene-mutating edit\n * invalidates it. Re-render after changing the model.\n *\n * Accessed via `snaptrude.analysis.heatmaps`.\n */\nexport abstract class PluginAnalysisHeatmapsApi {\n /**\n * Manage the named heatmap overlays created by the render calls —\n * list / show / hide / remove. See\n * {@linkcode PluginAnalysisHeatmapOverlaysApi}.\n */\n public abstract overlays: PluginAnalysisHeatmapOverlaysApi\n\n constructor() {}\n\n /**\n * Render a per-space heatmap — one flat colour per space's top face.\n *\n * Each entry maps a space to a scalar value; the value is mapped through\n * the colour ramp (see {@linkcode PluginAnalysisHeatmapOptions}) and the\n * space's top face is painted that single flat colour. Renders into the\n * overlay named `options.name` (the default overlay when omitted),\n * replacing that overlay only and making it the visible one. Spaces whose top\n * face is not flat (pitched/sloped tops) are **skipped with a console\n * warning** — the rest of the heatmap still renders.\n *\n * The heatmap is ephemeral: it is auto-cleared by scene-mutating edits\n * and is never persisted or saved with views.\n *\n * @param entries - One {@linkcode PluginAnalysisHeatmapSpaceEntry} per\n * space: the space's handle and its scalar value.\n * @param options - {@linkcode PluginAnalysisHeatmapOptions} — legend\n * title/unit, explicit min/max range (auto-derived from the data when\n * omitted), and the colour ramp.\n * @returns A {@linkcode PluginAnalysisHeatmapsRenderResult} —\n * `{ success: true }` once the heatmap is rendered.\n * @throws When `entries` is empty or a handle does not resolve to a space.\n * @throws When no space had a flat top face to paint (every entry was\n * skipped).\n * @throws When `options.min` is greater than `options.max`.\n * @throws When a colour in `options.colors` is not a 6-digit hex colour.\n * @throws When `options.name` is a new overlay name and the 16-overlay\n * cap is reached.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Color each room by its occupancy count\n * @examplePrompt Show my energy simulation results as a heatmap per space\n * @examplePrompt Paint the rooms red to blue by temperature\n *\n * # Example\n * ```ts\n * const spaces = await snaptrude.design.query.listSpaces()\n * await snaptrude.analysis.heatmaps.renderSpaces(\n * spaces.map((space) => ({ space, value: occupancy[space] ?? 0 })),\n * { title: \"Occupancy\", unit: \"people\", min: 0, max: 50 },\n * )\n * ```\n */\n public abstract renderSpaces(\n entries: PluginAnalysisHeatmapSpaceEntry[],\n options?: PluginAnalysisHeatmapOptions,\n ): PluginApiReturn<PluginAnalysisHeatmapsRenderResult>\n\n /**\n * Render a grid heatmap — a coloured mesh from point samples.\n *\n * Each cell is a scalar sample at a **world-coordinate** position\n * (**y is up** — a ground-level field sits at `y = 0`); the host builds\n * one square cell of edge `cellSize` per sample — in the same Snaptrude\n * internal units as `position` (convert real-world lengths via\n * {@linkcode PluginUnitsApi.convert}) — coloured by mapping the value\n * through the colour ramp\n * (see {@linkcode PluginAnalysisHeatmapOptions}). This is the mode for\n * field data — wind, noise, microclimate — sampled over an area. Renders\n * into the overlay named `options.name` (the default overlay when\n * omitted), replacing that overlay only and making it the visible one.\n *\n * The heatmap is ephemeral: it is auto-cleared by scene-mutating edits\n * and is never persisted or saved with views.\n *\n * @param cells - One {@linkcode PluginAnalysisHeatmapGridCell} per\n * sample: a world-coordinate `position` (y up) and its scalar `value`.\n * @param cellSize - Edge length of each (square) cell, in the same\n * Snaptrude internal units as `position` (see\n * {@linkcode PluginUnitsApi.convert}). Must be a positive, finite number.\n * @param options - {@linkcode PluginAnalysisHeatmapOptions} — legend\n * title/unit, explicit min/max range (auto-derived from the data when\n * omitted), and the colour ramp.\n * @returns A {@linkcode PluginAnalysisHeatmapsRenderResult} —\n * `{ success: true }` once the heatmap is rendered.\n * @throws When `cells` is empty or `cellSize` is not a positive, finite number.\n * @throws When `options.min` is greater than `options.max`.\n * @throws When a colour in `options.colors` is not a 6-digit hex colour.\n * @throws When `options.name` is a new overlay name and the 16-overlay\n * cap is reached.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Overlay my CFD wind simulation results on the site\n * @examplePrompt Render a noise-level grid around the building\n * @examplePrompt Show these sampled microclimate values as a colored grid\n *\n * # Example\n * ```ts\n * // World coordinates are y-up: a ground-level field varies in x/z at y = 0.\n * await snaptrude.analysis.heatmaps.renderGrid(\n * samples.map((s) => ({ position: { x: s.x, y: 0, z: s.z }, value: s.windSpeed })),\n * 2, // 2-unit square cells\n * { title: \"Wind speed\", unit: \"m/s\", colors: [\"#0000ff\", \"#00ff00\", \"#ff0000\"] },\n * )\n * ```\n */\n public abstract renderGrid(\n cells: PluginAnalysisHeatmapGridCell[],\n cellSize: number,\n options?: PluginAnalysisHeatmapOptions,\n ): PluginApiReturn<PluginAnalysisHeatmapsRenderResult>\n\n /**\n * Render a grid heatmap on an **arbitrarily oriented plane** — a façade,\n * a section cut, any flat surface.\n *\n * The same point-sample model as\n * {@linkcode PluginAnalysisHeatmapsApi.renderGrid}, generalised: cells lie\n * on the plane whose direction is given by `normal` (for a façade, the\n * outward wall normal). Each cell is a square of edge `cellSize` centred\n * on its **world-coordinate** `position` and oriented in that plane; the\n * host derives the in-plane axes from the normal and offsets each cell a\n * hair along it so the heatmap never z-fights the surface it annotates.\n * Cell positions should lie on (or very near) the surface being\n * annotated — the host does not project them. Renders into the overlay\n * named `options.name` (the default overlay when omitted), replacing\n * that overlay only and making it the visible one.\n *\n * This is the mode for per-façade data — solar irradiance, glare, panel\n * yield, wind pressure — sampled over a vertical or sloped face. For\n * horizontal ground fields prefer\n * {@linkcode PluginAnalysisHeatmapsApi.renderGrid}, which is this call\n * with `normal = { x: 0, y: 1, z: 0 }`.\n *\n * The heatmap is ephemeral: it is auto-cleared by scene-mutating edits\n * and is never persisted or saved with views.\n *\n * @param cells - One {@linkcode PluginAnalysisHeatmapGridCell} per\n * sample: a world-coordinate `position` on the surface plane (y up)\n * and its scalar `value`.\n * @param cellSize - Edge length of each (square) cell, in the same\n * Snaptrude internal units as `position` (see\n * {@linkcode PluginUnitsApi.convert}). Must be a positive, finite number.\n * @param normal - The plane's normal direction as `{ x, y, z }` (for a\n * façade, the outward wall normal — read it via\n * `core.geom.query.face.getNormal` on the wall's face). Any non-zero\n * length; the host normalises it.\n * @param options - {@linkcode PluginAnalysisHeatmapOptions} — legend\n * title/unit, explicit min/max range (auto-derived from the data when\n * omitted), and the colour ramp.\n * @returns A {@linkcode PluginAnalysisHeatmapsRenderResult} —\n * `{ success: true }` once the heatmap is rendered.\n * @throws When `cells` is empty, `cellSize` is not a positive finite\n * number, or `normal` has zero length.\n * @throws When `options.min` is greater than `options.max`.\n * @throws When a colour in `options.colors` is not a 6-digit hex colour.\n * @throws When `options.name` is a new overlay name and the 16-overlay\n * cap is reached.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Paint the solar irradiance results on the south façade\n * @examplePrompt Show my daylight values as a heatmap on this wall\n * @examplePrompt Render panel-level PV yield on the building face\n * @examplePrompt Overlay wind pressure on the tower's west elevation\n *\n * # Example\n * ```ts\n * // Samples lie on a wall plane facing +X; colour each 1-unit cell by irradiance.\n * await snaptrude.analysis.heatmaps.renderSurfaceGrid(\n * samples.map((s) => ({ position: { x: wallX, y: s.y, z: s.z }, value: s.irradiance })),\n * 1,\n * { x: 1, y: 0, z: 0 }, // façade normal\n * { title: \"Irradiance\", unit: \"kWh/m²\", colors: [\"#0000ff\", \"#ffff00\", \"#ff0000\"] },\n * )\n * ```\n */\n public abstract renderSurfaceGrid(\n cells: PluginAnalysisHeatmapGridCell[],\n cellSize: number,\n normal: Vec3Components,\n options?: PluginAnalysisHeatmapOptions,\n ): PluginApiReturn<PluginAnalysisHeatmapsRenderResult>\n\n /**\n * Render a heatmap from **arbitrary cell geometry** — planar polygons or\n * pre-tessellated meshes, one scalar value each.\n *\n * The free-form sibling of the grid calls: instead of square cells, each\n * {@linkcode PluginAnalysisHeatmapFieldCell} brings its own footprint in\n * **world coordinates** (y up) — either a planar `polygon` ring (3+\n * points, any orientation; the host triangulates it) or a pre-tessellated\n * `vertices` + `indices` mesh (triangle list). This is the mode for\n * analysis meshes straight from a solver: FEA/CFD cells, Voronoi regions,\n * per-panel façade patches — anything not on a square grid. Each cell's\n * `value` is mapped through the colour scale\n * (see {@linkcode PluginAnalysisHeatmapOptions}, including\n * `options.scale`); an optional `meta` payload is stored with the overlay\n * for later features. Renders into the overlay named `options.name` (the\n * default overlay when omitted), replacing that overlay only and making\n * it the visible one.\n *\n * Degenerate polygon cells (collinear points, zero area) are **skipped\n * with a console warning** — the rest of the heatmap still renders.\n *\n * The heatmap is ephemeral: it is auto-cleared by scene-mutating edits\n * and is never persisted or saved with views.\n *\n * @param cells - One {@linkcode PluginAnalysisHeatmapFieldCell} per cell:\n * a planar `polygon` ring OR a `vertices`/`indices` triangle mesh, plus\n * its scalar `value` and optional `meta`.\n * @param options - {@linkcode PluginAnalysisHeatmapOptions} — legend\n * title/unit, min/max range, colour ramp, and the colour `scale`\n * (bands / threshold / categorical).\n * @returns A {@linkcode PluginAnalysisHeatmapsRenderResult} —\n * `{ success: true }` once the heatmap is rendered.\n * @throws When `cells` is empty, a polygon has fewer than 3 points, a\n * pre-tessellated cell's `indices` length is not a multiple of 3, or an\n * index is out of range of its `vertices`.\n * @throws When every polygon cell was degenerate (nothing renderable).\n * @throws When a `categorical` scale has no class for a cell's value.\n * @throws When `options.min` is greater than `options.max`, or a colour\n * is not a 6-digit hex colour.\n * @throws When `options.name` is a new overlay name and the 16-overlay\n * cap is reached.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Overlay my FEA stress results on their solver mesh\n * @examplePrompt Color these Voronoi regions by footfall\n * @examplePrompt Paint each façade panel polygon by its PV yield\n * @examplePrompt Render pass/fail daylight compliance per zone polygon\n *\n * # Example\n * ```ts\n * // Two triangular cells at ground level, coloured pass/fail at 300 lux.\n * await snaptrude.analysis.heatmaps.renderField(\n * [\n * { polygon: [{ x: 0, y: 0, z: 0 }, { x: 4, y: 0, z: 0 }, { x: 0, y: 0, z: 4 }], value: 420 },\n * { polygon: [{ x: 4, y: 0, z: 4 }, { x: 0, y: 0, z: 4 }, { x: 4, y: 0, z: 0 }], value: 180 },\n * ],\n * { title: \"Daylight\", unit: \"lux\", scale: { type: \"threshold\", threshold: 300 } },\n * )\n * ```\n */\n public abstract renderField(\n cells: PluginAnalysisHeatmapFieldCell[],\n options?: PluginAnalysisHeatmapOptions,\n ): PluginApiReturn<PluginAnalysisHeatmapsRenderResult>\n\n /**\n * Clear every plugin heatmap overlay from the scene.\n *\n * Removes **all** registered overlays (equivalent to\n * {@linkcode PluginAnalysisHeatmapOverlaysApi.removeAll}) and closes the\n * legend. A no-op (returns `false`) when no plugin heatmap exists. Does\n * not touch the built-in study heatmaps (`sunlightHours` /\n * `illuminance`) — they have their own `reset`.\n *\n * @returns `true` when at least one overlay was cleared, `false` when\n * none existed.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Clear my custom heatmap\n * @examplePrompt Remove the wind overlay from the model\n * @examplePrompt Reset the plugin heatmap view\n *\n * # Example\n * ```ts\n * await snaptrude.analysis.heatmaps.reset()\n * ```\n */\n public abstract reset(): PluginApiReturn<boolean>\n\n /**\n * Is a plugin heatmap currently showing?\n *\n * `true` between a successful render and the next\n * {@linkcode PluginAnalysisHeatmapsApi.reset} — or the scene-mutating\n * edit that auto-clears it (heatmaps are ephemeral).\n *\n * @returns `true` when a plugin heatmap is rendered on the scene.\n *\n * @examplePrompt Is my heatmap still showing?\n * @examplePrompt Check whether the custom heatmap is active\n * @examplePrompt Do I need to re-render the wind overlay?\n *\n * # Example\n * ```ts\n * if (!(await snaptrude.analysis.heatmaps.isActive())) {\n * await snaptrude.analysis.heatmaps.renderGrid(cells, 2)\n * }\n * ```\n */\n public abstract isActive(): PluginApiReturn<boolean>\n}\n\n/**\n * Heatmap overlay management — the named overlays created by the\n * `analysis.heatmaps` render calls.\n *\n * Every render call registers (or replaces) an overlay under\n * `options.name` — a shared default overlay when omitted. Overlays are\n * in-session only: never persisted, never saved with views, and **all**\n * of them are cleared together when a scene-mutating edit invalidates\n * them. At most 16 overlays can be registered at once, and **exactly one\n * overlay is visible at a time** — showing one hides the others, and the\n * legend always describes the visible overlay.\n *\n * Accessed via `snaptrude.analysis.heatmaps.overlays`.\n */\nexport abstract class PluginAnalysisHeatmapOverlaysApi {\n constructor() {}\n\n /**\n * List the registered heatmap overlays.\n *\n * Returns every overlay's `name`, `kind` (which render call produced\n * it: `\"spaces\"`, `\"grid\"`, `\"surfaceGrid\"` or `\"field\"`) and whether it is the\n * currently visible one. At most one overlay has `visible: true`.\n *\n * @returns A {@linkcode PluginAnalysisHeatmapOverlaysListResult} —\n * `{ overlays: [{ name, visible, kind }] }`, empty when nothing is\n * registered.\n *\n * @examplePrompt Which heatmap overlays do I have?\n * @examplePrompt List my rendered analysis overlays\n * @examplePrompt Check which heatmap is currently visible\n *\n * # Example\n * ```ts\n * const { overlays } = await snaptrude.analysis.heatmaps.overlays.list()\n * const visible = overlays.find((o) => o.visible)\n * ```\n */\n public abstract list(): PluginApiReturn<PluginAnalysisHeatmapOverlaysListResult>\n\n /**\n * Make a registered overlay the visible one.\n *\n * Enables the named overlay's meshes, hides whichever overlay was\n * visible before, and swaps the legend to the shown overlay's. A no-op\n * returning `false` when no overlay of that name exists.\n *\n * @param name - The overlay's name (`options.name` at render time).\n * @returns `true` when the overlay is now visible, `false` when no\n * overlay of that name exists.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Switch back to my wind heatmap\n * @examplePrompt Show the occupancy overlay\n * @examplePrompt Flip between my two analysis overlays\n *\n * # Example\n * ```ts\n * await snaptrude.analysis.heatmaps.overlays.show(\"wind\")\n * ```\n */\n public abstract show(name: string): PluginApiReturn<boolean>\n\n /**\n * Hide an overlay without removing it.\n *\n * Disables the named overlay's meshes; when it was the visible one its\n * legend closes too (nothing visible → no legend). The overlay stays\n * registered and can be brought back with\n * {@linkcode PluginAnalysisHeatmapOverlaysApi.show}. A no-op returning\n * `false` when no overlay of that name exists.\n *\n * @param name - The overlay's name (`options.name` at render time).\n * @returns `true` when the overlay exists (and is now hidden), `false`\n * when no overlay of that name exists.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Hide the wind overlay but keep it around\n * @examplePrompt Temporarily hide my heatmap\n * @examplePrompt Turn off the occupancy colours without deleting them\n *\n * # Example\n * ```ts\n * await snaptrude.analysis.heatmaps.overlays.hide(\"wind\")\n * ```\n */\n public abstract hide(name: string): PluginApiReturn<boolean>\n\n /**\n * Remove an overlay — dispose its meshes and forget it.\n *\n * Frees the overlay's slot toward the 16-overlay cap; when it was the\n * visible one its legend closes. A no-op returning `false` when no\n * overlay of that name exists.\n *\n * @param name - The overlay's name (`options.name` at render time).\n * @returns `true` when the overlay was removed, `false` when no overlay\n * of that name exists.\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Delete the wind overlay\n * @examplePrompt Remove my occupancy heatmap for good\n * @examplePrompt Free up an overlay slot\n *\n * # Example\n * ```ts\n * await snaptrude.analysis.heatmaps.overlays.remove(\"wind\")\n * ```\n */\n public abstract remove(name: string): PluginApiReturn<boolean>\n\n /**\n * Remove every registered overlay and close the legend.\n *\n * The bulk form of {@linkcode PluginAnalysisHeatmapOverlaysApi.remove};\n * `analysis.heatmaps.reset()` is this call under its historical name.\n *\n * @returns The number of overlays removed (`0` when none existed).\n * @throws When plugin writes are disabled.\n *\n * @examplePrompt Clear all my heatmap overlays\n * @examplePrompt Remove every analysis overlay from the scene\n * @examplePrompt Start over with a clean set of overlays\n *\n * # Example\n * ```ts\n * const removed = await snaptrude.analysis.heatmaps.overlays.removeAll()\n * ```\n */\n public abstract removeAll(): PluginApiReturn<number>\n}\n\n/**\n * One space's entry in {@linkcode PluginAnalysisHeatmapsApi.renderSpaces}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `space` | {@linkcode ComponentHandle} | Handle of the space to colour |\n * | `value` | `number` | The scalar value mapped to a colour |\n */\nexport const PluginAnalysisHeatmapSpaceEntry = z.object({\n space: ComponentHandle,\n value: z.number().finite(),\n})\nexport type PluginAnalysisHeatmapSpaceEntry = z.infer<\n typeof PluginAnalysisHeatmapSpaceEntry\n>\n\n/**\n * One grid sample in {@linkcode PluginAnalysisHeatmapsApi.renderGrid}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `position` | `{ x, y, z }` | Cell centre in **world coordinates** (y is up) |\n * | `value` | `number` | The scalar value mapped to a colour |\n * | `meta` | `unknown \\| undefined` | Optional payload stored with the overlay — shown in the hover tooltip when `options.hover` is on |\n */\nexport const PluginAnalysisHeatmapGridCell = z.object({\n position: Vec3Components,\n value: z.number().finite(),\n meta: z.unknown().optional(),\n})\nexport type PluginAnalysisHeatmapGridCell = z.infer<\n typeof PluginAnalysisHeatmapGridCell\n>\n\n/** A 6-digit hex colour like `\"#0000ff\"` (the `#` is optional). */\nconst HexColor = z\n .string()\n .regex(/^#?[0-9a-fA-F]{6}$/, 'expected a 6-digit hex colour like \"#0000ff\"')\n\n/**\n * One cell in {@linkcode PluginAnalysisHeatmapsApi.renderField} — either a\n * planar polygon ring the host triangulates, or a pre-tessellated triangle\n * mesh. All coordinates are **world coordinates** (y up).\n *\n * Polygon form:\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `polygon` | `{ x, y, z }[]` | Planar ring of 3+ points (any orientation; no self-intersections) |\n * | `value` | `number` | The scalar value mapped to a colour |\n * | `meta` | `unknown \\| undefined` | Optional payload stored with the overlay |\n *\n * Pre-tessellated form:\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `vertices` | `{ x, y, z }[]` | Mesh vertices (3+) |\n * | `indices` | `number[]` | Triangle list into `vertices` — length a multiple of 3, every index in range |\n * | `value` | `number` | The scalar value mapped to a colour |\n * | `meta` | `unknown \\| undefined` | Optional payload stored with the overlay |\n */\nexport const PluginAnalysisHeatmapFieldCell = z.union([\n z.object({\n polygon: z.array(Vec3Components).min(3),\n value: z.number().finite(),\n meta: z.unknown().optional(),\n }),\n z.object({\n vertices: z.array(Vec3Components).min(3),\n indices: z.array(z.number().int().nonnegative()).min(3),\n value: z.number().finite(),\n meta: z.unknown().optional(),\n }),\n])\nexport type PluginAnalysisHeatmapFieldCell = z.infer<\n typeof PluginAnalysisHeatmapFieldCell\n>\n\n/**\n * The colour scale for a heatmap render — how values become colours and how\n * the legend reads. Three kinds (discriminated by `type`):\n *\n * - **`bands`** — the default: the colour ramp quantised into `count`\n * discrete bands (2–64, default 11). The rendered mesh and legend always\n * use discrete bands; \"continuous\" is simply a high `count`.\n * - **`threshold`** — binary pass/fail at a cut-off `threshold`: values\n * `>= threshold` get `passColor`, the rest `failColor`; the legend shows\n * the two labelled swatches.\n * - **`categorical`** — exact-match classes: every cell value must equal\n * one class's `value` (anything else is a validation error); the legend\n * shows one labelled swatch per class.\n *\n * | Kind | Property | Type | Description |\n * |---|---|---|---|\n * | `bands` | `count` | `number \\| undefined` | Number of discrete bands, 2–64 (default 11) |\n * | `threshold` | `threshold` | `number` | Values `>= threshold` pass |\n * | `threshold` | `passColor` | `string \\| undefined` | Hex colour for passing values (host default green) |\n * | `threshold` | `failColor` | `string \\| undefined` | Hex colour for failing values (host default red) |\n * | `threshold` | `passLabel` | `string \\| undefined` | Legend label for the pass swatch (default `≥ threshold`) |\n * | `threshold` | `failLabel` | `string \\| undefined` | Legend label for the fail swatch (default `< threshold`) |\n * | `categorical` | `classes` | `{ value, label, color }[]` | Exact-match classes: scalar `value`, legend `label`, hex `color` |\n */\nexport const PluginAnalysisHeatmapScale = z.discriminatedUnion(\"type\", [\n z.object({\n type: z.literal(\"bands\"),\n count: z.number().int().min(2).max(64).optional(),\n }),\n z.object({\n type: z.literal(\"threshold\"),\n threshold: z.number().finite(),\n passColor: HexColor.optional(),\n failColor: HexColor.optional(),\n passLabel: z.string().optional(),\n failLabel: z.string().optional(),\n }),\n z.object({\n type: z.literal(\"categorical\"),\n classes: z\n .array(\n z.object({\n value: z.number().finite(),\n label: z.string(),\n color: HexColor,\n }),\n )\n .min(1),\n }),\n])\nexport type PluginAnalysisHeatmapScale = z.infer<\n typeof PluginAnalysisHeatmapScale\n>\n\n/**\n * Display options shared by both heatmap render modes.\n *\n * The colour ramp maps values to colours: `colors` is an **ordered list of\n * hex colour stops, low → high** (e.g. `[\"#0000ff\", \"#ff0000\"]`). When\n * omitted, the host's default blue → cyan → green → yellow → red ramp is used. Values outside\n * `[min, max]` clamp to the end colours; the legend marks the clamped ends\n * with `<` / `>`. `min`/`max` are auto-derived from the data when omitted.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string \\| undefined` | Overlay name to render into (see {@linkcode PluginAnalysisHeatmapOverlaysApi}); a shared default overlay when omitted |\n * | `title` | `string \\| undefined` | Legend title |\n * | `unit` | `string \\| undefined` | Unit label shown on the legend (e.g. `\"m/s\"`) |\n * | `min` | `number \\| undefined` | Value mapped to the first colour stop; data minimum when omitted |\n * | `max` | `number \\| undefined` | Value mapped to the last colour stop; data maximum when omitted |\n * | `colors` | `string[] \\| undefined` | Ordered 6-digit hex colour stops (e.g. `\"#0000ff\"`), low → high; host blue → cyan → green → yellow → red default |\n * | `scale` | {@linkcode PluginAnalysisHeatmapScale}` \\| undefined` | Colour scale: discrete `bands` (count 2–64, default 11), binary `threshold`, or exact-match `categorical` |\n * | `hover` | `boolean \\| undefined` | Show a tooltip with the hovered cell's `value` (+ its `meta`, when given) while this overlay is visible. Default `false` |\n */\nexport const PluginAnalysisHeatmapOptions = z.object({\n name: z.string().min(1).max(64).optional(),\n title: z.string().optional(),\n unit: z.string().optional(),\n min: z.number().finite().optional(),\n max: z.number().finite().optional(),\n colors: z.array(HexColor).min(1).optional(),\n scale: PluginAnalysisHeatmapScale.optional(),\n hover: z.boolean().optional(),\n})\nexport type PluginAnalysisHeatmapOptions = z.infer<\n typeof PluginAnalysisHeatmapOptions\n>\n\n/**\n * Result of {@linkcode PluginAnalysisHeatmapsApi.renderSpaces} /\n * {@linkcode PluginAnalysisHeatmapsApi.renderGrid} — render failures throw,\n * so a returned value always carries `success: true`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `success` | `true` | The heatmap is rendered on the scene |\n */\nexport const PluginAnalysisHeatmapsRenderResult = z.object({\n success: z.literal(true),\n})\nexport type PluginAnalysisHeatmapsRenderResult = z.infer<\n typeof PluginAnalysisHeatmapsRenderResult\n>\n\n/**\n * One overlay row in {@linkcode PluginAnalysisHeatmapOverlaysApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | The overlay's name (`options.name` at render time) |\n * | `visible` | `boolean` | Whether this is the (single) visible overlay |\n * | `kind` | `\"spaces\" \\| \"grid\" \\| \"surfaceGrid\" \\| \"field\"` | Which render call produced it |\n */\nexport const PluginAnalysisHeatmapOverlayInfo = z.object({\n name: z.string(),\n visible: z.boolean(),\n kind: z.enum([\"spaces\", \"grid\", \"surfaceGrid\", \"field\"]),\n})\nexport type PluginAnalysisHeatmapOverlayInfo = z.infer<\n typeof PluginAnalysisHeatmapOverlayInfo\n>\n\n/**\n * Result of {@linkcode PluginAnalysisHeatmapOverlaysApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `overlays` | {@linkcode PluginAnalysisHeatmapOverlayInfo}`[]` | Every registered overlay; at most one has `visible: true` |\n */\nexport const PluginAnalysisHeatmapOverlaysListResult = z.object({\n overlays: z.array(PluginAnalysisHeatmapOverlayInfo),\n})\nexport type PluginAnalysisHeatmapOverlaysListResult = z.infer<\n typeof PluginAnalysisHeatmapOverlaysListResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * Analysis weather — read the project site's typical-year weather series.\n *\n * Returns the hourly weather rows (temperature, humidity, wind, solar\n * irradiance) of the EPW file the sustainability analyses compute against,\n * resolved from the project's geo-located site. This is a **synchronous,\n * paged read** of an already-resolved weather file — not a job: call\n * {@linkcode PluginAnalysisWeatherApi.getSeries} for a date range, then follow\n * `nextCursor` until it is `null`.\n *\n * The weather file is resolved by the **same** code path the daylight /\n * sunlight-hours analyses use, so the `source.weatherFileId` here matches the\n * provenance a {@linkcode PluginAnalysisDaylightApi} run echoes. Dates cross as\n * ISO 8601 date strings (`\"YYYY-MM-DD\"`); each row's `timestamp` is a local\n * ISO 8601 string **with** its UTC offset, and `utcOffsetMinutes` restates the\n * offset so consumers never re-derive it.\n *\n * Reads never throw for \"no data\": when the project has no geo-located site\n * (no weather file to resolve) the host surfaces that per its read contract.\n *\n * Accessed via `snaptrude.analysis.weather`.\n */\nexport abstract class PluginAnalysisWeatherApi {\n constructor() {}\n\n /**\n * Get the hourly weather series for a date range, paged.\n *\n * Returns the resolved weather file's provenance (`source`) plus the weather\n * `rows` for the requested range. The page size is backend-chosen; when\n * `nextCursor` is a string, pass it back as `args.cursor` to fetch the next\n * page, and stop when it is `null`.\n *\n * @param args - {@linkcode PluginWeatherGetSeriesArgs}: the inclusive\n * `startDate`/`endDate` ISO dates, an optional `interval` (`\"hourly\"` is\n * the only v1 value), and an optional `cursor` for paging.\n * @returns A {@linkcode PluginWeatherSeriesResult} — the resolved weather\n * `source`, the `rows` for this page, and `nextCursor` (`null` at the end).\n *\n * @examplePrompt Get the weather series for June at my site\n * @examplePrompt Pull the hourly temperature and irradiance for this location\n * @examplePrompt What EPW weather file is my project using?\n *\n * # Example\n * ```ts\n * let cursor: string | undefined = undefined\n * const rows = []\n * do {\n * const page = await snaptrude.analysis.weather.getSeries({\n * startDate: \"2026-06-01\",\n * endDate: \"2026-06-30\",\n * cursor,\n * })\n * rows.push(...page.rows)\n * cursor = page.nextCursor ?? undefined\n * } while (cursor)\n * ```\n */\n public abstract getSeries(\n args: PluginWeatherGetSeriesArgs,\n ): PluginApiReturn<PluginWeatherSeriesResult>\n}\n\n/**\n * How the weather file was matched to the project site.\n *\n * | Value | Meaning |\n * |---|---|\n * | `\"nearest-station\"` | Auto-resolved to the closest weather station |\n * | `\"user-upload\"` | A weather file the user uploaded (`core.io.import.epw`) |\n * | `\"project-override\"` | A station pinned for the project regardless of distance |\n */\nexport const PluginWeatherSelectionMethod = z.enum([\n \"nearest-station\",\n \"user-upload\",\n \"project-override\",\n])\nexport type PluginWeatherSelectionMethod = z.infer<\n typeof PluginWeatherSelectionMethod\n>\n\n/**\n * Provenance of the resolved weather file — where the numbers came from.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `weatherFileId` | `string` | Catalog id (same key as `core.io.import.epw`'s `weatherFileId`) |\n * | `station` | `string` | Station/file name, e.g. `\"USA_IL_Chicago-OHare.Intl.AP.725300_TMY3\"` |\n * | `sourceId` | `string` | WMO/station identifier from the EPW header |\n * | `distanceKm` | `number \\| null` | Station → project site distance, km; `null` for pinned project-override files |\n * | `selectionMethod` | {@linkcode PluginWeatherSelectionMethod} | How the file was matched |\n * | `checksum` | `string` | sha256 of the EPW file — the provenance key |\n * | `windReferenceHeightM` | `number` | Anemometer height the wind rows are valid at |\n */\nexport const PluginWeatherSourceMeta = z.object({\n weatherFileId: z.string(),\n station: z.string(),\n sourceId: z.string(),\n distanceKm: z.number().nullable(),\n selectionMethod: PluginWeatherSelectionMethod,\n checksum: z.string(),\n windReferenceHeightM: z.number(),\n})\nexport type PluginWeatherSourceMeta = z.infer<typeof PluginWeatherSourceMeta>\n\n/**\n * One hourly weather reading.\n *\n * `flags` is empty for a clean measured/typical value; known flag strings are\n * `\"missing\"`, `\"substituted\"`, and `\"interpolated\"` (kept as `string[]` so the\n * backend can add flag kinds without a contract break).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `timestamp` | `string` | Local ISO 8601 with offset, e.g. `\"2026-06-21T14:00:00+05:30\"` |\n * | `utcOffsetMinutes` | `number` | UTC offset in minutes (restates the timestamp's offset) |\n * | `dryBulbC` | `number` | Dry-bulb temperature, °C |\n * | `relativeHumidityPercent` | `number` | Relative humidity, % |\n * | `windSpeedMps` | `number` | Wind speed, m/s (at `source.windReferenceHeightM`) |\n * | `ghiWm2` | `number` | Global horizontal irradiance, W/m² |\n * | `dniWm2` | `number` | Direct normal irradiance, W/m² |\n * | `dhiWm2` | `number` | Diffuse horizontal irradiance, W/m² |\n * | `flags` | `string[]` | Quality flags; `[]` = clean value |\n */\nexport const PluginWeatherRow = z.object({\n timestamp: z.string(),\n utcOffsetMinutes: z.number(),\n dryBulbC: z.number(),\n relativeHumidityPercent: z.number(),\n windSpeedMps: z.number(),\n ghiWm2: z.number(),\n dniWm2: z.number(),\n dhiWm2: z.number(),\n flags: z.array(z.string()),\n})\nexport type PluginWeatherRow = z.infer<typeof PluginWeatherRow>\n\n/**\n * Arguments for {@linkcode PluginAnalysisWeatherApi.getSeries}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `startDate` | `string` | Inclusive range start, ISO `\"YYYY-MM-DD\"` |\n * | `endDate` | `string` | Inclusive range end, ISO `\"YYYY-MM-DD\"` |\n * | `interval` | `\"hourly\"`? | Sampling interval; `\"hourly\"` is the only v1 value |\n * | `cursor` | `string`? | Paging cursor from a prior page's `nextCursor` |\n */\nexport const PluginWeatherGetSeriesArgs = z.object({\n startDate: z.string(),\n endDate: z.string(),\n interval: z.enum([\"hourly\"]).optional(),\n cursor: z.string().optional(),\n})\nexport type PluginWeatherGetSeriesArgs = z.infer<\n typeof PluginWeatherGetSeriesArgs\n>\n\n/**\n * Result of {@linkcode PluginAnalysisWeatherApi.getSeries} — one page.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `source` | {@linkcode PluginWeatherSourceMeta} | The resolved weather file's provenance |\n * | `rows` | {@linkcode PluginWeatherRow}`[]` | Weather rows for this page |\n * | `nextCursor` | `string \\| null` | Pass back as `args.cursor`; `null` = last page |\n * | `warnings` | `string[]`? | Degradation notices (e.g. `\"override lookup failed; served nearest-station\"`); omitted when the page is clean |\n */\nexport const PluginWeatherSeriesResult = z.object({\n source: PluginWeatherSourceMeta,\n rows: z.array(PluginWeatherRow),\n nextCursor: z.string().nullable(),\n warnings: z.array(z.string()).optional(),\n})\nexport type PluginWeatherSeriesResult = z.infer<\n typeof PluginWeatherSeriesResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport { PluginWeatherSourceMeta } from \"./weather\"\n\n/**\n * Analysis solar — sample solar exposure at caller-chosen points.\n *\n * Unlike the heatmap studies (which raster the model's own surfaces), this\n * samples solar exposure at **points you supply** — an instant (sun visible? /\n * irradiance now) or aggregated over a date range (shaded fraction / cumulative\n * irradiance). Positions are **world plan-space** coordinates, the same units\n * and frame as plugin geometry reads; the backend echoes them verbatim and\n * never re-projects.\n *\n * Raw numbers only: this renders no heatmap, mutates no geometry, and writes no\n * save commands — visualization is the plugin's job. Large grids run as a job\n * (`status: \"running\"` until complete); every result carries `modelRevision` +\n * `inputHash` so the caller can mark it stale after a geometry change.\n *\n * Accessed via `snaptrude.analysis.solar`.\n */\nexport abstract class PluginAnalysisSolarApi {\n constructor() {}\n\n /**\n * Sample solar exposure at a set of points.\n *\n * Provide **exactly one** timing mode: `dateTime` (a single instant →\n * `mode: \"instant\"`, results in `W/m²`), or `startDate` + `endDate` (a range\n * → `mode: \"range\"`, results aggregated in `kWh/m²`). `results` is paged —\n * follow `nextCursor` until it is `null`. Each point's optional `normal`\n * gives plane-of-array irradiance; omit it for a horizontal sample.\n *\n * @param args - {@linkcode PluginSolarSampleGridArgs}: the `points`, exactly\n * one timing mode (`dateTime` XOR `startDate`+`endDate`), and an optional\n * `includeHourlyArtifact` to attach a downloadable per-hour series.\n * @returns A {@linkcode PluginSolarSampleGridResult} — the run `status`,\n * `mode`/`units`, provenance (`weather`, `modelRevision`, `inputHash`),\n * this page of `results`, an optional `hourlyArtifact`, and `nextCursor`.\n * @throws When the project has no geo-located site (no location to sample\n * solar exposure for).\n *\n * @examplePrompt What's the irradiance at these facade points at noon on June 21?\n * @examplePrompt How shaded are these balcony points across the summer?\n * @examplePrompt Sample direct sun visibility at these roof points right now\n *\n * # Example\n * ```ts\n * const grid = await snaptrude.analysis.solar.sampleGrid({\n * points: [\n * { id: \"a\", position: [0, 3, 0] },\n * { id: \"b\", position: [5, 3, 0], normal: [0, 0, 1] },\n * ],\n * dateTime: \"2026-06-21T12:00:00+05:30\",\n * })\n * for (const r of grid.results) {\n * console.log(r.id, r.directSunVisible, r.totalIrradiance, grid.units)\n * }\n * ```\n */\n public abstract sampleGrid(\n args: PluginSolarSampleGridArgs,\n ): PluginApiReturn<PluginSolarSampleGridResult>\n}\n\n/**\n * Status of a {@linkcode PluginAnalysisSolarApi.sampleGrid} run.\n *\n * | Value | Meaning |\n * |---|---|\n * | `\"running\"` | The job is in flight — poll again |\n * | `\"complete\"` | Results are final |\n * | `\"cancelled\"` | The run was cancelled |\n * | `\"failed\"` | The run failed |\n */\nexport const PluginSolarRunStatus = z.enum([\n \"running\",\n \"complete\",\n \"cancelled\",\n \"failed\",\n])\nexport type PluginSolarRunStatus = z.infer<typeof PluginSolarRunStatus>\n\n/**\n * Timing mode of the run — chosen by which timing args were supplied.\n *\n * | Value | Meaning |\n * |---|---|\n * | `\"instant\"` | Single `dateTime` — irradiance in `W/m²` |\n * | `\"range\"` | `startDate`+`endDate` — irradiance aggregated in `kWh/m²` |\n */\nexport const PluginSolarSampleMode = z.enum([\"instant\", \"range\"])\nexport type PluginSolarSampleMode = z.infer<typeof PluginSolarSampleMode>\n\n/**\n * Irradiance units of the results — `W/m²` (instant) or `kWh/m²` (range).\n */\nexport const PluginSolarUnits = z.enum([\"W/m2\", \"kWh/m2\"])\nexport type PluginSolarUnits = z.infer<typeof PluginSolarUnits>\n\n/**\n * Downloadable per-hour artifact (present only when `includeHourlyArtifact`).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `url` | `string` | Signed download URL |\n * | `format` | `\"parquet\" \\| \"csv-gz\"` | File format |\n * | `expiresAt` | `string` | ISO 8601 expiry of the signed URL |\n */\nexport const PluginSolarHourlyArtifact = z.object({\n url: z.string(),\n format: z.enum([\"parquet\", \"csv-gz\"]),\n expiresAt: z.string(),\n})\nexport type PluginSolarHourlyArtifact = z.infer<\n typeof PluginSolarHourlyArtifact\n>\n\n/**\n * A caller-supplied point to sample.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Caller-chosen id, echoed back verbatim on the result |\n * | `position` | `[number, number, number]` | World plan-space `[x, y, z]` |\n * | `normal` | `[number, number, number]`? | Surface normal for plane-of-array; omit = horizontal |\n */\nexport const PluginSolarSamplePoint = z.object({\n id: z.string(),\n position: z.tuple([z.number(), z.number(), z.number()]),\n normal: z.tuple([z.number(), z.number(), z.number()]).optional(),\n})\nexport type PluginSolarSamplePoint = z.infer<typeof PluginSolarSamplePoint>\n\n/**\n * Arguments for {@linkcode PluginAnalysisSolarApi.sampleGrid}.\n *\n * Provide **exactly one** timing mode: `dateTime` (instant) XOR\n * `startDate`+`endDate` (range).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `points` | {@linkcode PluginSolarSamplePoint}`[]` | Points to sample |\n * | `dateTime` | `string`? | Instant mode: local ISO 8601 with offset |\n * | `startDate` | `string`? | Range mode: inclusive start `\"YYYY-MM-DD\"` |\n * | `endDate` | `string`? | Range mode: inclusive end `\"YYYY-MM-DD\"` |\n * | `includeHourlyArtifact` | `boolean`? | Attach a downloadable per-hour series |\n */\nexport const PluginSolarSampleGridArgs = z\n .object({\n points: z.array(PluginSolarSamplePoint),\n dateTime: z.string().optional(),\n startDate: z.string().optional(),\n endDate: z.string().optional(),\n includeHourlyArtifact: z.boolean().optional(),\n })\n .superRefine((a, ctx) => {\n const hasInstant = a.dateTime !== undefined\n const hasStart = a.startDate !== undefined\n const hasEnd = a.endDate !== undefined\n const hasRange = hasStart || hasEnd\n if (hasInstant === hasRange) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message:\n \"provide exactly one timing mode: dateTime (instant), or startDate+endDate (range)\",\n })\n }\n if (hasRange && !(hasStart && hasEnd)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: \"range mode requires both startDate and endDate\",\n })\n }\n })\nexport type PluginSolarSampleGridArgs = z.infer<\n typeof PluginSolarSampleGridArgs\n>\n\n/**\n * One sampled point's result. Irradiance fields are in the enclosing result's\n * `units` and are omitted when the backend did not compute them.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | The point's caller-chosen id |\n * | `directSunVisible` | `boolean` | Instant mode: sun disc visible from the point |\n * | `shadeFraction` | `number` | `0`..`1`; range mode: fraction of sun-up hours shaded |\n * | `directIrradiance` | `number`? | Direct component, in the result's `units` |\n * | `diffuseIrradiance` | `number`? | Diffuse component, in the result's `units` |\n * | `totalIrradiance` | `number`? | Total (direct + diffuse), in the result's `units` |\n */\nexport const PluginSolarSampleResult = z.object({\n id: z.string(),\n directSunVisible: z.boolean(),\n shadeFraction: z.number(),\n directIrradiance: z.number().optional(),\n diffuseIrradiance: z.number().optional(),\n totalIrradiance: z.number().optional(),\n})\nexport type PluginSolarSampleResult = z.infer<typeof PluginSolarSampleResult>\n\n/**\n * Result of {@linkcode PluginAnalysisSolarApi.sampleGrid} — one page.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `status` | {@linkcode PluginSolarRunStatus} | Run status |\n * | `mode` | {@linkcode PluginSolarSampleMode} | `\"instant\"` or `\"range\"` |\n * | `coordinateSystem` | `\"world-plan-space\"` | Echo of the input frame |\n * | `modelRevision` | `string` | Geometry revision sampled against (staleness key) |\n * | `inputHash` | `string` | Hash of points+dates+geometry (staleness key) |\n * | `units` | {@linkcode PluginSolarUnits} | `\"W/m2\"` (instant) or `\"kWh/m2\"` (range) |\n * | `sunUpHours` | `number` | Sun-up hours over the sampled window |\n * | `weather` | {@linkcode PluginWeatherSourceMeta} \\| `null` | Resolved weather file, or `null` |\n * | `weatherQuality` | `string[]` | Deduped quality flags present in the consumed rows |\n * | `results` | {@linkcode PluginSolarSampleResult}`[]` | This page of per-point results |\n * | `hourlyArtifact` | {@linkcode PluginSolarHourlyArtifact} \\| `null` | Per-hour download, or `null` |\n * | `nextCursor` | `string \\| null` | Next page cursor; `null` = last page |\n */\nexport const PluginSolarSampleGridResult = z.object({\n status: PluginSolarRunStatus,\n mode: PluginSolarSampleMode,\n coordinateSystem: z.literal(\"world-plan-space\"),\n modelRevision: z.string(),\n inputHash: z.string(),\n units: PluginSolarUnits,\n sunUpHours: z.number(),\n weather: PluginWeatherSourceMeta.nullable(),\n weatherQuality: z.array(z.string()),\n results: z.array(PluginSolarSampleResult),\n hourlyArtifact: PluginSolarHourlyArtifact.nullable(),\n nextCursor: z.string().nullable(),\n})\nexport type PluginSolarSampleGridResult = z.infer<\n typeof PluginSolarSampleGridResult\n>\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\nimport { PluginWeatherSourceMeta } from \"./weather\"\n\n/**\n * Analysis daylight — standards-grade annual daylight metrics (IES LM-83).\n *\n * Runs an annual daylight simulation over per-space sensor grids and returns\n * **numeric** per-sensor and aggregate metrics — no textures, no heatmap, no\n * model mutation. Two metrics are supported: **ASE** (Annual Sunlight\n * Exposure) and **sDA** (Spatial Daylight Autonomy, sDA300/50%).\n *\n * **Run semantics.** `metrics: [\"ASE\"]` runs **synchronously** — `compute`\n * returns the full result directly, no `runId`. Any request including\n * `\"sDA\"` (alone or with ASE) runs **asynchronously**: `compute` submits the\n * job and immediately returns `{ status: \"running\", runId, sensors: [],\n * aggregates: [] }`; call {@linkcode PluginAnalysisDaylightApi.poll} with the\n * `runId` until `status` is `\"complete\"` (or `\"failed\"`).\n *\n * **Blind operation.** sDA is computed WITH dynamic blind operation per the\n * IES LM-83 2% rule: windows are grouped by storey + facade orientation, and\n * for every occupied hour blind groups close (default 5% diffuse\n * transmittance) until no more than 2% of the analysis points receive direct\n * sun. This API **operates the blinds for you** — sDA without blind operation\n * is not LM-83-compliant, so there is no switch to turn it off. The run's\n * `provenance.blinds` reports the groups, their closed hours, and any hours\n * where no combination satisfies the rule.\n *\n * **Surface optics.** The optional `optics` arg overrides surface optics.\n * Resolution order per surface: per-material (`optics.materials`, keyed by\n * material **name** as shown in Snaptrude) → per-category\n * (`optics.categories`) → LM-83/convention defaults:\n *\n * | Category | Default | Property |\n * |---|---|---|\n * | `wall` | `0.5` | reflectance |\n * | `ceiling` | `0.7` | reflectance |\n * | `floor` | `0.2` | reflectance |\n * | `furniture` | `0.5` | reflectance |\n * | `glazing` | `0.65` | transmittance (Tvis) |\n * | `context` | `0.2` | reflectance |\n * | blinds (closed) | `0.05` | diffuse transmittance |\n *\n * Glazing takes visible **transmittance** (Tvis) exactly as manufacturers\n * quote it; the backend converts it to Radiance glass transmissivity\n * internally.\n *\n * Every result carries a full `provenance` echo (weather file, thresholds,\n * grid, location, resolved optics, blind operation, warnings, excluded\n * spaces) plus `modelRevision` + `inputHash` for staleness. Positions are\n * **world plan-space** coordinates.\n *\n * `compute` is write-gated and Pro-gated host-side, exactly like\n * {@linkcode PluginAnalysisIlluminanceApi.compute}.\n *\n * Accessed via `snaptrude.analysis.daylight`.\n */\nexport abstract class PluginAnalysisDaylightApi {\n constructor() {}\n\n /**\n * Compute annual daylight metrics for the model's spaces.\n *\n * Sensors are laid out per-space from the `grid` spec; request the metrics\n * you want in `metrics` (`[\"ASE\"]`, `[\"sDA\"]`, or both). ASE-only runs are\n * **synchronous** and return the full result directly. Any run including\n * `\"sDA\"` is **asynchronous**: this returns `{ status: \"running\", runId,\n * sensors: [], aggregates: [] }` immediately — pass the `runId` to\n * {@linkcode PluginAnalysisDaylightApi.poll} until the run completes.\n * sDA is computed with LM-83 dynamic blind operation (see the class doc);\n * pass `optics` to override surface reflectances / glazing transmittance.\n *\n * @param args - {@linkcode PluginDaylightComputeArgs}: the `standard`\n * (`\"IES-LM-83-23\"`), the `metrics` to compute, an optional occupancy\n * `schedule`, optional surface `optics` overrides, and the sensor `grid`\n * spec.\n * @returns A {@linkcode PluginDaylightResults} — per-sensor `sensors`,\n * scope-rolled `aggregates`, the full `provenance` echo, `nextCursor`\n * (`null` when all sensors fit in one page), and `runId` on\n * asynchronous (sDA) runs.\n * @throws When the workspace is not on a **Pro** plan.\n * @throws When plugin writes are disabled.\n * @throws When the project has no geo-located site (no location to compute\n * daylight for) or no spaces to analyse.\n *\n * @examplePrompt Run an LM-83 ASE daylight analysis on all my spaces\n * @examplePrompt Which rooms fail annual sunlight exposure?\n * @examplePrompt Compute ASE with a 0.6m sensor grid at desk height\n * @examplePrompt Run an LM-83 sDA analysis with blinds operated\n * @examplePrompt Which spaces meet sDA300/50%?\n * @examplePrompt Compute sDA with 70% reflective ceilings and Tvis 0.6 glazing\n *\n * # Example\n * ```ts\n * let res = await snaptrude.analysis.daylight.compute({\n * standard: \"IES-LM-83-23\",\n * metrics: [\"sDA\", \"ASE\"],\n * grid: { spacingM: 0.6, workplaneHeightM: 0.76, boundaryOffsetM: 0.5 },\n * optics: { materials: { \"Glass - Clear\": { transmittance: 0.6 } } },\n * })\n * const runId = res.runId\n * while (res.status === \"running\" && runId) {\n * await new Promise((r) => setTimeout(r, 5000))\n * res = await snaptrude.analysis.daylight.poll({ runId })\n * }\n * const failing = res.aggregates.filter(\n * (a) => a.scope === \"space\" && (a.sdaPercent ?? 0) < 55,\n * )\n * ```\n */\n public abstract compute(\n args: PluginDaylightComputeArgs,\n ): PluginApiReturn<PluginDaylightResults>\n\n /**\n * Poll an asynchronous daylight run started by\n * {@linkcode PluginAnalysisDaylightApi.compute}.\n *\n * Returns the same {@linkcode PluginDaylightResults} shape as `compute`:\n * `status: \"running\"` while the job is in flight (empty `sensors` /\n * `aggregates`), `\"complete\"` with the full data once done. Poll every few\n * seconds — an annual sDA simulation takes minutes, not milliseconds.\n * Completed results page sensors exactly like a synchronous run: follow\n * `nextCursor` via the `cursor` arg.\n *\n * @param args - {@linkcode PluginDaylightPollArgs}: the `runId` returned by\n * `compute`, plus an optional sensor-page `cursor`.\n * @returns A {@linkcode PluginDaylightResults} for the run.\n * @throws When the `runId` is unknown, or when the run failed (surfaced as\n * a typed `OPERATION_FAILED`).\n *\n * @examplePrompt Check whether my sDA run has finished\n * @examplePrompt Get the results of the daylight run\n */\n public abstract poll(\n args: PluginDaylightPollArgs,\n ): PluginApiReturn<PluginDaylightResults>\n}\n\n/**\n * A daylight metric to compute. `\"ASE\"` (Annual Sunlight Exposure) runs\n * synchronously; any request including `\"sDA\"` (Spatial Daylight Autonomy,\n * sDA300/50% with LM-83 dynamic blind operation) runs asynchronously —\n * `compute` returns a `runId` to pass to\n * {@linkcode PluginAnalysisDaylightApi.poll}.\n */\nexport const PluginDaylightMetric = z.enum([\"ASE\", \"sDA\"])\nexport type PluginDaylightMetric = z.infer<typeof PluginDaylightMetric>\n\n/**\n * Status of a daylight run. ASE-only runs are synchronous, so their result is\n * `\"complete\"`; runs including sDA return `\"running\"` from `compute` and\n * progress to `\"complete\"` (or `\"failed\"`) via\n * {@linkcode PluginAnalysisDaylightApi.poll}.\n */\nexport const PluginDaylightRunStatus = z.enum([\n \"running\",\n \"complete\",\n \"cancelled\",\n \"failed\",\n])\nexport type PluginDaylightRunStatus = z.infer<typeof PluginDaylightRunStatus>\n\n/**\n * Sensor-grid spec: spacing, workplane height, and boundary inset (all metres).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `spacingM` | `number` | Sensor spacing |\n * | `workplaneHeightM` | `number` | Sensor height above the floor |\n * | `boundaryOffsetM` | `number` | Inset from the space boundary |\n */\nexport const PluginDaylightGrid = z.object({\n spacingM: z.number(),\n workplaneHeightM: z.number(),\n boundaryOffsetM: z.number(),\n})\nexport type PluginDaylightGrid = z.infer<typeof PluginDaylightGrid>\n\n/**\n * Optional occupancy schedule (local hours). Defaults to the LM-83 default\n * (8am–6pm) when omitted.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `startHour` | `number` | Occupied-period start hour, local (`0`..`24`) |\n * | `endHour` | `number` | Occupied-period end hour, local (`0`..`24`) |\n */\nexport const PluginDaylightSchedule = z.object({\n startHour: z.number(),\n endHour: z.number(),\n})\nexport type PluginDaylightSchedule = z.infer<typeof PluginDaylightSchedule>\n\n/**\n * Opaque surface optics: visible reflectance (`0`..`1`).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `reflectance` | `number` | Visible reflectance (`0`..`1`) |\n */\nexport const PluginDaylightOpaqueOptics = z.object({\n reflectance: z.number().min(0).max(1),\n})\nexport type PluginDaylightOpaqueOptics = z.infer<\n typeof PluginDaylightOpaqueOptics\n>\n\n/**\n * Glazing optics: visible **transmittance** (Tvis, `0`..`1`) exactly as\n * manufacturers quote it. The backend converts it to Radiance glass\n * transmissivity internally — never pre-convert.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `transmittance` | `number` | Visible transmittance Tvis (`0`..`1`) |\n */\nexport const PluginDaylightGlazingOptics = z.object({\n transmittance: z.number().min(0).max(1),\n})\nexport type PluginDaylightGlazingOptics = z.infer<\n typeof PluginDaylightGlazingOptics\n>\n\n/**\n * Optics for one material: {@linkcode PluginDaylightOpaqueOptics}\n * (`reflectance`) for opaque surfaces, or\n * {@linkcode PluginDaylightGlazingOptics} (`transmittance`) for glazing.\n */\nexport const PluginDaylightMaterialOptics = z.union([\n PluginDaylightOpaqueOptics,\n PluginDaylightGlazingOptics,\n])\nexport type PluginDaylightMaterialOptics = z.infer<\n typeof PluginDaylightMaterialOptics\n>\n\n/**\n * Per-category optics overrides. Categories cover every surface in the\n * model; omitted categories use the LM-83/convention defaults (wall `0.5`,\n * ceiling `0.7`, floor `0.2`, furniture `0.5`, glazing Tvis `0.65`,\n * context `0.2`).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `wall` | {@linkcode PluginDaylightOpaqueOptics}? | Walls, columns, beams, doors, frames (default `0.5`) |\n * | `ceiling` | {@linkcode PluginDaylightOpaqueOptics}? | Down-facing slab faces (default `0.7`) |\n * | `floor` | {@linkcode PluginDaylightOpaqueOptics}? | Up-facing slab faces (default `0.2`) |\n * | `furniture` | {@linkcode PluginDaylightOpaqueOptics}? | Furniture (default `0.5`) |\n * | `glazing` | {@linkcode PluginDaylightGlazingOptics}? | Window/door glass (default Tvis `0.65`) |\n * | `context` | {@linkcode PluginDaylightOpaqueOptics}? | Terrain / context masses (default `0.2`) |\n */\nexport const PluginDaylightCategoryDefaults = z.object({\n wall: PluginDaylightOpaqueOptics.optional(),\n ceiling: PluginDaylightOpaqueOptics.optional(),\n floor: PluginDaylightOpaqueOptics.optional(),\n furniture: PluginDaylightOpaqueOptics.optional(),\n glazing: PluginDaylightGlazingOptics.optional(),\n context: PluginDaylightOpaqueOptics.optional(),\n})\nexport type PluginDaylightCategoryDefaults = z.infer<\n typeof PluginDaylightCategoryDefaults\n>\n\n/**\n * Closed-blind optics: diffuse transmittance of the closed state. Defaults to\n * the LM-83 `0.05` (5% diffuse) when omitted.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `transmittance` | `number` | Closed-blind diffuse transmittance (`0`..`1`) |\n */\nexport const PluginDaylightBlindsOptics = z.object({\n transmittance: z.number().min(0).max(1),\n})\nexport type PluginDaylightBlindsOptics = z.infer<\n typeof PluginDaylightBlindsOptics\n>\n\n/**\n * Surface-optics overrides for a daylight run. Resolution order per surface:\n * `materials` (by material name) → `categories` → LM-83/convention defaults.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `materials` | `Record<string,` {@linkcode PluginDaylightMaterialOptics}`>`? | Keyed by material **name** as shown in Snaptrude (the same names `design.query.geometry.getTriangulatedMeshes` returns in `materialIds`); applies wherever the material appears and wins over category defaults |\n * | `categories` | {@linkcode PluginDaylightCategoryDefaults}? | Per-category defaults override |\n * | `blinds` | {@linkcode PluginDaylightBlindsOptics}? | Closed-blind state (default `0.05` diffuse) |\n */\nexport const PluginDaylightOptics = z.object({\n materials: z.record(z.string(), PluginDaylightMaterialOptics).optional(),\n categories: PluginDaylightCategoryDefaults.optional(),\n blinds: PluginDaylightBlindsOptics.optional(),\n})\nexport type PluginDaylightOptics = z.infer<typeof PluginDaylightOptics>\n\n/**\n * Arguments for {@linkcode PluginAnalysisDaylightApi.compute}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `standard` | `\"IES-LM-83-23\"` | Daylight standard; only supported value |\n * | `metrics` | {@linkcode PluginDaylightMetric}`[]` | Metrics to compute (`[\"ASE\"]` synchronous; including `\"sDA\"` asynchronous) |\n * | `schedule` | {@linkcode PluginDaylightSchedule}? | Occupancy hours (default 8am–6pm) |\n * | `grid` | {@linkcode PluginDaylightGrid} | Sensor-grid spec |\n * | `optics` | {@linkcode PluginDaylightOptics}? | Surface-optics overrides (per-material → per-category → LM-83 defaults) |\n */\nexport const PluginDaylightComputeArgs = z.object({\n standard: z.literal(\"IES-LM-83-23\"),\n metrics: z.array(PluginDaylightMetric).min(1),\n schedule: PluginDaylightSchedule.optional(),\n grid: PluginDaylightGrid,\n optics: PluginDaylightOptics.optional(),\n})\nexport type PluginDaylightComputeArgs = z.infer<\n typeof PluginDaylightComputeArgs\n>\n\n/**\n * Arguments for {@linkcode PluginAnalysisDaylightApi.poll}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `runId` | `string` | The run id returned by `compute` |\n * | `cursor` | `string`? | Sensor-page cursor from a previous page's `nextCursor` |\n */\nexport const PluginDaylightPollArgs = z.object({\n runId: z.string(),\n cursor: z.string().optional(),\n})\nexport type PluginDaylightPollArgs = z.infer<typeof PluginDaylightPollArgs>\n\n/**\n * One sensor's record. Positions are world plan-space `[x, y, z]`. Metric\n * fields are present when the corresponding metric was requested: the ASE\n * pair for `\"ASE\"`, `da300Percent`/`sdaPass` for `\"sDA\"`.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `sensorId` | `string` | Sensor id |\n * | `position` | `[number, number, number]` | World plan-space `[x, y, z]` |\n * | `spaceId` | `string` | `ComponentHandle` id of the containing space |\n * | `storeyId` | `string` | Storey the sensor is on |\n * | `areaWeightM2` | `number` | Floor area this sensor represents (m²) |\n * | `occupiedHours` | `number` | Occupied hours per the schedule |\n * | `directSunHoursAtOrAbove1000Lux` | `number`? | ASE numerator (shades ignored per LM-83); present when ASE requested |\n * | `aseFail` | `boolean`? | Direct-sun hours > 250 (fails ASE); present when ASE requested |\n * | `da300Percent` | `number`? | % of occupied hours at or above 300 lux, blinds operated; present when sDA requested |\n * | `sdaPass` | `boolean`? | `da300Percent >= 50`; present when sDA requested |\n */\nexport const PluginDaylightSensorRecord = z.object({\n sensorId: z.string(),\n position: z.tuple([z.number(), z.number(), z.number()]),\n spaceId: z.string(),\n storeyId: z.string(),\n areaWeightM2: z.number(),\n occupiedHours: z.number(),\n directSunHoursAtOrAbove1000Lux: z.number().optional(),\n aseFail: z.boolean().optional(),\n da300Percent: z.number().optional(),\n sdaPass: z.boolean().optional(),\n})\nexport type PluginDaylightSensorRecord = z.infer<\n typeof PluginDaylightSensorRecord\n>\n\n/**\n * Scope of a rolled-up aggregate.\n *\n * | Value | Meaning |\n * |---|---|\n * | `\"project\"` | Whole project (aggregate `id` is `null`) |\n * | `\"storey\"` | One storey (`id` = storey id) |\n * | `\"space\"` | One space (`id` = space `ComponentHandle` id) |\n */\nexport const PluginDaylightAggregateScope = z.enum([\n \"project\",\n \"storey\",\n \"space\",\n])\nexport type PluginDaylightAggregateScope = z.infer<\n typeof PluginDaylightAggregateScope\n>\n\n/**\n * An area-weighted rollup for a scope. Metric fields are present when the\n * corresponding metric was requested.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `scope` | {@linkcode PluginDaylightAggregateScope} | `\"project\"` \\| `\"storey\"` \\| `\"space\"` |\n * | `id` | `string \\| null` | Scope id; `null` for project scope |\n * | `analysisAreaM2` | `number` | Analysed floor area (m²) |\n * | `asePercent` | `number`? | Area-weighted % of the analysis area failing ASE; present when ASE requested |\n * | `sdaPercent` | `number`? | Area-weighted % of the analysis area passing sDA300/50%; present when sDA requested |\n */\nexport const PluginDaylightAggregate = z.object({\n scope: PluginDaylightAggregateScope,\n id: z.string().nullable(),\n analysisAreaM2: z.number(),\n asePercent: z.number().optional(),\n sdaPercent: z.number().optional(),\n})\nexport type PluginDaylightAggregate = z.infer<typeof PluginDaylightAggregate>\n\n/**\n * Full provenance echo of a daylight run — a **loose** record: the known keys\n * below are typed (all optional), and any additional backend-supplied keys pass\n * through unmodified. `weather` is the resolved {@linkcode PluginWeatherSourceMeta}\n * (or `null`); `surfaceReflectances` maps material/family-default key →\n * reflectance so callers can tell per-material values from defaults.\n */\nexport const PluginDaylightProvenance = z\n .object({\n engine: z.string(),\n engineVersion: z.string(),\n standardProfile: z.string(),\n thresholds: z\n .object({ aseLux: z.number(), aseHours: z.number() })\n .passthrough(),\n weather: PluginWeatherSourceMeta.nullable(),\n occupancySchedule: z\n .object({ id: z.string(), hoursPerYear: z.number() })\n .passthrough(),\n grid: PluginDaylightGrid,\n location: z\n .object({\n latitude: z.number(),\n longitude: z.number(),\n timezone: z.string(),\n trueNorthDeg: z.number(),\n })\n .passthrough(),\n surfaceReflectances: z.record(z.string(), z.number()),\n modelRevision: z.string(),\n inputHash: z.string(),\n warnings: z.array(z.string()),\n excludedSpaces: z.array(\n z.object({ spaceId: z.string(), reason: z.string() }),\n ),\n failedGeometry: z.array(\n z.object({ componentId: z.string(), reason: z.string() }),\n ),\n })\n .partial()\n .passthrough()\nexport type PluginDaylightProvenance = z.infer<typeof PluginDaylightProvenance>\n\n/**\n * Result of {@linkcode PluginAnalysisDaylightApi.compute} and\n * {@linkcode PluginAnalysisDaylightApi.poll}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `status` | {@linkcode PluginDaylightRunStatus} | `\"complete\"` on synchronous (ASE-only) runs; `\"running\"` then `\"complete\"`/`\"failed\"` on sDA runs |\n * | `runId` | `string`? | Run id for {@linkcode PluginAnalysisDaylightApi.poll}; present on asynchronous (sDA) runs |\n * | `sensors` | {@linkcode PluginDaylightSensorRecord}`[]` | Per-sensor records (paged; empty while `\"running\"`) |\n * | `aggregates` | {@linkcode PluginDaylightAggregate}`[]` | Scope rollups (all scopes, not paged; empty while `\"running\"`) |\n * | `provenance` | {@linkcode PluginDaylightProvenance} | Full run provenance echo |\n * | `nextCursor` | `string \\| null` | Next sensor page cursor; `null` = last page |\n */\nexport const PluginDaylightResults = z.object({\n status: PluginDaylightRunStatus,\n runId: z.string().optional(),\n sensors: z.array(PluginDaylightSensorRecord),\n aggregates: z.array(PluginDaylightAggregate),\n provenance: PluginDaylightProvenance,\n nextCursor: z.string().nullable(),\n})\nexport type PluginDaylightResults = z.infer<typeof PluginDaylightResults>\n","import { PluginAnalysisSunpathApi } from \"./sunpath\"\nimport { PluginAnalysisShadowsApi } from \"./shadows\"\nimport { PluginAnalysisSunlightHoursApi } from \"./sunlightHours\"\nimport { PluginAnalysisIlluminanceApi } from \"./illuminance\"\nimport { PluginAnalysisHeatmapsApi } from \"./heatmaps\"\nimport { PluginAnalysisWeatherApi } from \"./weather\"\nimport { PluginAnalysisSolarApi } from \"./solar\"\nimport { PluginAnalysisDaylightApi } from \"./daylight\"\n\n/**\n * Sustainability-analysis APIs.\n *\n * Analysis is the environmental-performance layer: sun and daylight studies\n * computed for the project's geo-located site. The synchronous visual\n * overlays (sun-path diagram, real-time shadows) toggle instantly; the\n * heatmap studies (direct sunlight hours, daylight illuminance) are\n * asynchronous backend jobs — start them, poll their state, and cancel or\n * clear them. Shadows and the heatmap analyses are **mutually exclusive**\n * (enabling one resets the other), exactly as in the sustainability sidebar.\n * Site inputs (location, true north) are read via `program.site`.\n *\n * - {@linkcode PluginAnalysisApi.sunpath} — Toggle & query the sun-path diagram overlay\n * - {@linkcode PluginAnalysisApi.shadows} — Toggle real-time shadows and scrub the sun's date-time\n * - {@linkcode PluginAnalysisApi.sunlightHours} — Direct-sunlight-hours heatmap job (start / poll / cancel / clear)\n * - {@linkcode PluginAnalysisApi.illuminance} — Daylight-illuminance heatmap job, Pro-gated (start / poll / cancel / clear)\n * - {@linkcode PluginAnalysisApi.heatmaps} — Render plugin-computed scalar data as a heatmap (per-space / grid)\n * - {@linkcode PluginAnalysisApi.weather} — Read the site's typical-year weather series (numeric, paged)\n * - {@linkcode PluginAnalysisApi.solar} — Sample solar exposure at caller-chosen points (numeric)\n * - {@linkcode PluginAnalysisApi.daylight} — Standards-grade annual daylight metrics (IES LM-83, numeric)\n *\n * Accessed via `snaptrude.analysis`.\n */\nexport abstract class PluginAnalysisApi {\n /** Sun-path diagram overlay — enable/disable/isActive. See {@linkcode PluginAnalysisSunpathApi}. */\n public abstract sunpath: PluginAnalysisSunpathApi\n /** Real-time sun shadows + sun date-time. See {@linkcode PluginAnalysisShadowsApi}. */\n public abstract shadows: PluginAnalysisShadowsApi\n /** Direct-sunlight-hours heatmap job. See {@linkcode PluginAnalysisSunlightHoursApi}. */\n public abstract sunlightHours: PluginAnalysisSunlightHoursApi\n /** Daylight-illuminance heatmap job (Pro). See {@linkcode PluginAnalysisIlluminanceApi}. */\n public abstract illuminance: PluginAnalysisIlluminanceApi\n /** Plugin-supplied scalar-data heatmaps. See {@linkcode PluginAnalysisHeatmapsApi}. */\n public abstract heatmaps: PluginAnalysisHeatmapsApi\n /** Site typical-year weather series (numeric, paged). See {@linkcode PluginAnalysisWeatherApi}. */\n public abstract weather: PluginAnalysisWeatherApi\n /** Per-point solar exposure sampling (numeric). See {@linkcode PluginAnalysisSolarApi}. */\n public abstract solar: PluginAnalysisSolarApi\n /** IES LM-83 annual daylight metrics (numeric). See {@linkcode PluginAnalysisDaylightApi}. */\n public abstract daylight: PluginAnalysisDaylightApi\n\n constructor() {}\n}\n\nexport * from \"./sunpath\"\nexport * from \"./shadows\"\nexport * from \"./sunlightHours\"\nexport * from \"./illuminance\"\nexport * from \"./heatmaps\"\nexport * from \"./weather\"\nexport * from \"./solar\"\nexport * from \"./daylight\"\n","import * as z from \"zod\"\nimport { PluginApiReturn } from \"../../types\"\n\n/**\n * `snaptrude.workspace.*` — the dashboard surface: projects and teams outside the\n * open canvas.\n *\n * This namespace reads and creates the containers a design lives in — the\n * project itself and the team it belongs to — mirroring the Snaptrude dashboard.\n * Teams are **read-only** here (no create/invite/delete in v1, a deliberate\n * safety decision); there are no folders in v1. A `projectId` is the project's\n * floorkey. Plan limits are enforced by the backend — a limit rejection surfaces\n * as a normal host error. It also carries the workspace-level mode switches for\n * Present mode ({@linkcode PluginWorkspaceApi.openPresentMode} /\n * {@linkcode PluginWorkspaceApi.closePresentMode}) and Program mode\n * ({@linkcode PluginWorkspaceApi.openProgramMode} /\n * {@linkcode PluginWorkspaceApi.closeProgramMode}).\n *\n * - {@linkcode PluginWorkspaceApi.projects} — Create, copy, list, read & rename projects\n * - {@linkcode PluginWorkspaceApi.teams} — Read teams and their members\n * - {@linkcode PluginWorkspaceApi.openPresentMode} / {@linkcode PluginWorkspaceApi.closePresentMode} — Open/close the Present-mode documentation editor\n * - {@linkcode PluginWorkspaceApi.openProgramMode} / {@linkcode PluginWorkspaceApi.closeProgramMode} — Open/close the Program tab (the spreadsheet the `program.spreadsheet.*` calls run in)\n */\nexport abstract class PluginWorkspaceApi {\n /** Projects — create, copy, list, read & rename. See {@linkcode PluginWorkspaceProjectsApi}. */\n public abstract projects: PluginWorkspaceProjectsApi\n /** Teams — read teams and their members. See {@linkcode PluginWorkspaceTeamsApi}. */\n public abstract teams: PluginWorkspaceTeamsApi\n\n /**\n * Open Present mode — the documentation editor the `presentation.*`\n * namespaces operate on.\n *\n * Switches the workspace into Present mode and resolves only once the\n * Present canvas has finished mounting (waits up to ~15s; a mount that\n * never completes is refused with a typed `PRECONDITION_FAILED`), so a\n * `presentation.*` call issued right after this resolves finds Present\n * mode open. Idempotent — resolves immediately when Present mode is\n * already open. Write-gated: switching the mode under the user's feet is\n * approval-controlled by design.\n *\n * @throws `PRECONDITION_FAILED` if the Present canvas does not mount within\n * the timeout.\n *\n * @examplePrompt Open present mode\n * @examplePrompt Switch to the presentation editor\n * @examplePrompt Open the sheets so I can lay out views\n *\n * # Example\n * ```ts\n * await snaptrude.workspace.openPresentMode()\n * const { sheets } = await snaptrude.presentation.sheets.list()\n * ```\n */\n public abstract openPresentMode(): PluginApiReturn<void>\n\n /**\n * Close Present mode and return to the modeling canvas.\n *\n * Idempotent — a no-op when Present mode is not open. Write-gated, like\n * {@linkcode PluginWorkspaceApi.openPresentMode}.\n *\n * @examplePrompt Close present mode\n * @examplePrompt Exit the presentation editor\n * @examplePrompt Go back to the modeling canvas\n *\n * # Example\n * ```ts\n * await snaptrude.workspace.closePresentMode()\n * ```\n */\n public abstract closePresentMode(): PluginApiReturn<void>\n\n /**\n * Open Program mode — the Program tab the `program.spreadsheet.*` calls run in.\n *\n * Runs the top bar's own Program-button sequence (remember the preference,\n * open or re-link the paired Program window) and resolves only once that tab\n * has connected to the editor over its channel, so a `program.spreadsheet.*`\n * call issued right after this resolves finds the tab open. The open happens\n * outside a user gesture, so the browser may block the pop-up; the editor\n * then shows its \"Allow pop-up to continue\" modal and this call keeps waiting\n * (up to ~90s) for the user to confirm. Idempotent — resolves immediately\n * when the Program tab is already connected, and is a no-op when the plugin is\n * itself running in the Program tab. Write-gated.\n *\n * @throws `PRECONDITION_FAILED` if the user dismisses the pop-up modal or the\n * Program tab does not connect within the timeout.\n *\n * @examplePrompt Open program mode\n * @examplePrompt Open the Program tab so I can build a custom sheet\n * @examplePrompt Switch to the program spreadsheet\n *\n * # Example\n * ```ts\n * await snaptrude.workspace.openProgramMode()\n * const { sheets } = await snaptrude.program.spreadsheet.listSheets()\n * ```\n */\n public abstract openProgramMode(): PluginApiReturn<void>\n\n /**\n * Close the Program tab.\n *\n * Idempotent — a no-op when no Program tab is connected. Write-gated, like\n * {@linkcode PluginWorkspaceApi.openProgramMode}.\n *\n * @throws `PRECONDITION_FAILED` if the tab is still connected after ~15s.\n *\n * @examplePrompt Close program mode\n * @examplePrompt Close the Program tab\n *\n * # Example\n * ```ts\n * await snaptrude.workspace.closeProgramMode()\n * ```\n */\n public abstract closeProgramMode(): PluginApiReturn<void>\n\n constructor() {}\n}\n\n/**\n * `workspace.projects.*` — create, copy, list, read & rename projects.\n *\n * A **project** is a single Snaptrude model, identified by its `projectId`\n * (floorkey). Reads never throw for a miss ({@linkcode PluginWorkspaceProjectsApi.get}\n * returns `null`). Create/copy/rename go through the backend, which enforces plan\n * limits — a rejection surfaces as a normal host error.\n *\n * Accessed via `snaptrude.workspace.projects`.\n */\nexport abstract class PluginWorkspaceProjectsApi {\n constructor() {}\n\n /**\n * Create a new, empty BIM project.\n *\n * Creates the project on the backend and returns its `projectId` (floorkey).\n * Pass `options.teamId` to create it inside a team (otherwise it lands in the\n * user's personal workspace) and `options.unit` to set the default length unit.\n *\n * @param name - Display name of the new project.\n * @param options - Optional `unit` (default length unit) and `teamId` (owning team).\n * @returns The new project's `projectId`.\n * @throws If the backend rejects the create (e.g. a plan project limit).\n *\n * @examplePrompt Create a new project called Tower Study\n * @examplePrompt Start a new project in the Acme team\n * @examplePrompt Make a fresh project using millimeters\n * @examplePrompt Create an empty project for me to work in\n *\n * # Example\n * ```ts\n * const { projectId } = await snaptrude.workspace.projects.create(\"Tower Study\", {\n * teamId: \"team_1\",\n * unit: \"mm\",\n * })\n * ```\n */\n public abstract create(\n name: string,\n options?: { unit?: string; teamId?: string },\n ): PluginApiReturn<PluginWorkspaceProjectsCreateResult>\n\n /**\n * Copy the **current** project into a new project — the dashboard's \"Save As\".\n *\n * Duplicates the project that is currently open into a new project with the\n * given name, and returns the new `projectId`. Pass `options.teamId` to place\n * the copy in a specific team.\n *\n * @param name - Display name for the copy.\n * @param options - Optional `teamId` (team to place the copy in).\n * @returns The new project's `projectId`.\n * @throws If the backend rejects the copy (e.g. a plan project limit).\n *\n * @examplePrompt Save this project as a new copy called Tower Study v2\n * @examplePrompt Duplicate the current project into the Acme team\n * @examplePrompt Make a copy of this project so I can try changes\n *\n * # Example\n * ```ts\n * const { projectId } = await snaptrude.workspace.projects.copy(\"Tower Study v2\")\n * ```\n */\n public abstract copy(\n name: string,\n options?: { teamId?: string },\n ): PluginApiReturn<PluginWorkspaceProjectsCopyResult>\n\n /**\n * List the projects the user can access.\n *\n * Returns a {@linkcode PluginProjectRef} per project. Pass `options.teamId` to\n * scope the list to a single team; omit it for the user's accessible projects.\n *\n * @param options - Optional `teamId` filter.\n * @returns A {@linkcode PluginWorkspaceProjectsListResult} with a `projects`\n * array (empty when there are none).\n *\n * @examplePrompt List all my projects\n * @examplePrompt Show the projects in the Acme team\n * @examplePrompt How many projects do I have?\n * @examplePrompt What projects can I open?\n *\n * # Example\n * ```ts\n * const { projects } = await snaptrude.workspace.projects.list({ teamId: \"team_1\" })\n * for (const p of projects) console.log(p.id, p.name)\n * ```\n */\n public abstract list(\n options?: { teamId?: string },\n ): PluginApiReturn<PluginWorkspaceProjectsListResult>\n\n /**\n * Get a single project by id.\n *\n * @param projectId - The project's `projectId` (floorkey).\n * @returns The matching {@linkcode PluginProjectRef}, or `null` if no project\n * has that id / the user cannot access it.\n *\n * @examplePrompt Get the project with this id\n * @examplePrompt Look up a project by its floorkey\n * @examplePrompt What is the name of this project?\n *\n * # Example\n * ```ts\n * const project = await snaptrude.workspace.projects.get(\"floorkey_123\")\n * if (project) console.log(project.name)\n * ```\n */\n public abstract get(\n projectId: string,\n ): PluginApiReturn<PluginWorkspaceProjectsGetResult>\n\n /**\n * Rename a project.\n *\n * @param projectId - The project's `projectId` (floorkey).\n * @param name - The project's new display name.\n * @returns The renamed project's `projectId`.\n * @throws If no project has the given id or the rename is rejected.\n *\n * @examplePrompt Rename this project to Tower Study Final\n * @examplePrompt Change the name of project floorkey_123\n * @examplePrompt Give this project a new name\n *\n * # Example\n * ```ts\n * const { projectId } = await snaptrude.workspace.projects.rename(\n * \"floorkey_123\",\n * \"Tower Study Final\",\n * )\n * ```\n */\n public abstract rename(\n projectId: string,\n name: string,\n ): PluginApiReturn<PluginWorkspaceProjectsRenameResult>\n}\n\n/**\n * `workspace.teams.*` — read teams and their members.\n *\n * A **team** is a shared workspace that owns projects and members. **Read-only**\n * in v1: there is no create/invite/delete (a deliberate safety decision). Reads\n * never throw for a miss ({@linkcode PluginWorkspaceTeamsApi.get} returns `null`).\n *\n * Accessed via `snaptrude.workspace.teams`.\n */\nexport abstract class PluginWorkspaceTeamsApi {\n constructor() {}\n\n /**\n * List the teams the user belongs to.\n *\n * @returns A {@linkcode PluginWorkspaceTeamsListResult} with a `teams` array\n * (empty when the user belongs to none).\n *\n * @examplePrompt List all my teams\n * @examplePrompt Which teams am I a member of?\n * @examplePrompt Show every team I can access\n *\n * # Example\n * ```ts\n * const { teams } = await snaptrude.workspace.teams.list()\n * for (const t of teams) console.log(t.id, t.name)\n * ```\n */\n public abstract list(): PluginApiReturn<PluginWorkspaceTeamsListResult>\n\n /**\n * Get a single team by id.\n *\n * @param teamId - The id of the team to read.\n * @returns The matching {@linkcode PluginTeamRef}, or `null` if no team has that\n * id / the user is not a member.\n *\n * @examplePrompt Get the team with id team_1\n * @examplePrompt Look up a team by its id\n * @examplePrompt What is the name of this team?\n *\n * # Example\n * ```ts\n * const team = await snaptrude.workspace.teams.get(\"team_1\")\n * if (team) console.log(team.name)\n * ```\n */\n public abstract get(\n teamId: string,\n ): PluginApiReturn<PluginWorkspaceTeamsGetResult>\n\n /**\n * List the members of a team.\n *\n * @param teamId - The id of the team whose members to list.\n * @returns A {@linkcode PluginWorkspaceTeamsListMembersResult} with a `members`\n * array (empty when the team has none / is missing).\n *\n * @examplePrompt List the members of the Acme team\n * @examplePrompt Who is on team_1?\n * @examplePrompt How many people are in this team?\n *\n * # Example\n * ```ts\n * const { members } = await snaptrude.workspace.teams.listMembers(\"team_1\")\n * for (const m of members) console.log(m.name, m.email)\n * ```\n */\n public abstract listMembers(\n teamId: string,\n ): PluginApiReturn<PluginWorkspaceTeamsListMembersResult>\n}\n\n// ---------------------------------------------------------------------------\n// DTOs — conservative, never raw backend payloads\n// ---------------------------------------------------------------------------\n\n/**\n * A project reference — a conservative projection of a project, never the raw\n * backend payload.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | The project's `projectId` (floorkey) |\n * | `name` | `string` | Display name |\n * | `teamId` | `string?` | Owning team id (absent for personal projects) |\n * | `createdAt` | `string?` | Creation timestamp (ISO 8601) when known |\n * | `modifiedAt` | `string?` | Last-modified timestamp (ISO 8601) when known |\n */\nexport const PluginProjectRef = z.object({\n id: z.string(),\n name: z.string(),\n teamId: z.string().optional(),\n createdAt: z.string().optional(),\n modifiedAt: z.string().optional(),\n})\nexport type PluginProjectRef = z.infer<typeof PluginProjectRef>\n\n/**\n * A team reference — a conservative projection of a team, never the raw backend\n * payload.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Stable team id |\n * | `name` | `string` | Display name |\n * | `role` | `string?` | The user's role in the team when known |\n */\nexport const PluginTeamRef = z.object({\n id: z.string(),\n name: z.string(),\n role: z.string().optional(),\n})\nexport type PluginTeamRef = z.infer<typeof PluginTeamRef>\n\n/**\n * A team member reference — a conservative projection of a member, never the raw\n * backend payload.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `id` | `string` | Stable user id |\n * | `name` | `string` | Display name |\n * | `email` | `string?` | Email address when known |\n * | `role` | `string?` | The member's role in the team when known |\n */\nexport const PluginTeamMemberRef = z.object({\n id: z.string(),\n name: z.string(),\n email: z.string().optional(),\n role: z.string().optional(),\n})\nexport type PluginTeamMemberRef = z.infer<typeof PluginTeamMemberRef>\n\n// ---------------------------------------------------------------------------\n// projects — args + results\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for {@linkcode PluginWorkspaceProjectsApi.create} (options flattened).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | Display name of the new project |\n * | `unit` | `string?` | Default length unit |\n * | `teamId` | `string?` | Owning team id (personal workspace if omitted) |\n */\nexport const PluginWorkspaceProjectsCreateArgs = z.object({\n name: z.string(),\n unit: z.string().optional(),\n teamId: z.string().optional(),\n})\nexport type PluginWorkspaceProjectsCreateArgs = z.infer<\n typeof PluginWorkspaceProjectsCreateArgs\n>\n\n/**\n * Result of {@linkcode PluginWorkspaceProjectsApi.create}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `projectId` | `string` | The id (floorkey) of the created project |\n */\nexport const PluginWorkspaceProjectsCreateResult = z.object({\n projectId: z.string(),\n})\nexport type PluginWorkspaceProjectsCreateResult = z.infer<\n typeof PluginWorkspaceProjectsCreateResult\n>\n\n/**\n * Arguments for {@linkcode PluginWorkspaceProjectsApi.copy} (options flattened).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `name` | `string` | Display name for the copy |\n * | `teamId` | `string?` | Team to place the copy in |\n */\nexport const PluginWorkspaceProjectsCopyArgs = z.object({\n name: z.string(),\n teamId: z.string().optional(),\n})\nexport type PluginWorkspaceProjectsCopyArgs = z.infer<\n typeof PluginWorkspaceProjectsCopyArgs\n>\n\n/**\n * Result of {@linkcode PluginWorkspaceProjectsApi.copy}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `projectId` | `string` | The id (floorkey) of the copied project |\n */\nexport const PluginWorkspaceProjectsCopyResult = z.object({\n projectId: z.string(),\n})\nexport type PluginWorkspaceProjectsCopyResult = z.infer<\n typeof PluginWorkspaceProjectsCopyResult\n>\n\n/**\n * Arguments for {@linkcode PluginWorkspaceProjectsApi.list} (options flattened).\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `teamId` | `string?` | Scope the list to this team; omit for all accessible projects |\n */\nexport const PluginWorkspaceProjectsListArgs = z.object({\n teamId: z.string().optional(),\n})\nexport type PluginWorkspaceProjectsListArgs = z.infer<\n typeof PluginWorkspaceProjectsListArgs\n>\n\n/**\n * Result of {@linkcode PluginWorkspaceProjectsApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `projects` | {@linkcode PluginProjectRef}`[]` | Accessible projects (empty when none) |\n */\nexport const PluginWorkspaceProjectsListResult = z.object({\n projects: z.array(PluginProjectRef),\n})\nexport type PluginWorkspaceProjectsListResult = z.infer<\n typeof PluginWorkspaceProjectsListResult\n>\n\n/**\n * Arguments for {@linkcode PluginWorkspaceProjectsApi.get}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `projectId` | `string` | The project's `projectId` (floorkey) |\n */\nexport const PluginWorkspaceProjectsGetArgs = z.object({\n projectId: z.string(),\n})\nexport type PluginWorkspaceProjectsGetArgs = z.infer<\n typeof PluginWorkspaceProjectsGetArgs\n>\n\n/**\n * Result of {@linkcode PluginWorkspaceProjectsApi.get} — the project, or `null`\n * when no project has the given id.\n */\nexport const PluginWorkspaceProjectsGetResult = PluginProjectRef.nullable()\nexport type PluginWorkspaceProjectsGetResult = z.infer<\n typeof PluginWorkspaceProjectsGetResult\n>\n\n/**\n * Arguments for {@linkcode PluginWorkspaceProjectsApi.rename}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `projectId` | `string` | The project's `projectId` (floorkey) |\n * | `name` | `string` | The project's new display name |\n */\nexport const PluginWorkspaceProjectsRenameArgs = z.object({\n projectId: z.string(),\n name: z.string(),\n})\nexport type PluginWorkspaceProjectsRenameArgs = z.infer<\n typeof PluginWorkspaceProjectsRenameArgs\n>\n\n/**\n * Result of {@linkcode PluginWorkspaceProjectsApi.rename}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `projectId` | `string` | The id (floorkey) of the renamed project |\n */\nexport const PluginWorkspaceProjectsRenameResult = z.object({\n projectId: z.string(),\n})\nexport type PluginWorkspaceProjectsRenameResult = z.infer<\n typeof PluginWorkspaceProjectsRenameResult\n>\n\n// ---------------------------------------------------------------------------\n// teams — args + results\n// ---------------------------------------------------------------------------\n\n/**\n * Result of {@linkcode PluginWorkspaceTeamsApi.list}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `teams` | {@linkcode PluginTeamRef}`[]` | The user's teams (empty when none) |\n */\nexport const PluginWorkspaceTeamsListResult = z.object({\n teams: z.array(PluginTeamRef),\n})\nexport type PluginWorkspaceTeamsListResult = z.infer<\n typeof PluginWorkspaceTeamsListResult\n>\n\n/**\n * Arguments for {@linkcode PluginWorkspaceTeamsApi.get}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `teamId` | `string` | The id of the team to read |\n */\nexport const PluginWorkspaceTeamsGetArgs = z.object({\n teamId: z.string(),\n})\nexport type PluginWorkspaceTeamsGetArgs = z.infer<\n typeof PluginWorkspaceTeamsGetArgs\n>\n\n/**\n * Result of {@linkcode PluginWorkspaceTeamsApi.get} — the team, or `null` when no\n * team has the given id.\n */\nexport const PluginWorkspaceTeamsGetResult = PluginTeamRef.nullable()\nexport type PluginWorkspaceTeamsGetResult = z.infer<\n typeof PluginWorkspaceTeamsGetResult\n>\n\n/**\n * Arguments for {@linkcode PluginWorkspaceTeamsApi.listMembers}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `teamId` | `string` | The id of the team whose members to list |\n */\nexport const PluginWorkspaceTeamsListMembersArgs = z.object({\n teamId: z.string(),\n})\nexport type PluginWorkspaceTeamsListMembersArgs = z.infer<\n typeof PluginWorkspaceTeamsListMembersArgs\n>\n\n/**\n * Result of {@linkcode PluginWorkspaceTeamsApi.listMembers}.\n *\n * | Property | Type | Description |\n * |---|---|---|\n * | `members` | {@linkcode PluginTeamMemberRef}`[]` | The team's members (empty when none) |\n */\nexport const PluginWorkspaceTeamsListMembersResult = z.object({\n members: z.array(PluginTeamMemberRef),\n})\nexport type PluginWorkspaceTeamsListMembersResult = z.infer<\n typeof PluginWorkspaceTeamsListMembersResult\n>\n","import { PluginCoreApi } from \"./core\"\nimport { PluginDesignApi } from \"./design\"\nimport { PluginEntityApi } from \"./entity\"\nimport { PluginProgramApi } from \"./program\"\nimport { PluginPresentationApi } from \"./presentation\"\nimport { PluginAnalysisApi } from \"./analysis\"\nimport { PluginWorkspaceApi } from \"./workspace\"\n\n/**\n * Root API surface for Snaptrude plugins.\n *\n * Access all plugin capabilities through the namespaced properties:\n *\n * - {@linkcode PluginApi.core} — Math, geometry kernel, history, units, zoom, project, comments (infrastructure)\n * - {@linkcode PluginApi.design} — Author, query & edit BIM geometry\n * - {@linkcode PluginApi.entity} — CRUD operations on Snaptrude entities (spaces, stories)\n * - {@linkcode PluginApi.program} — Program-mode planning reads (departments, metrics)\n * - {@linkcode PluginApi.presentation} — Presentation preparation (views, AI Inspiration)\n * - {@linkcode PluginApi.analysis} — Sustainability analysis (sunpath, shadows, sunlight/illuminance heatmaps)\n * - {@linkcode PluginApi.workspace} — Dashboard surface: projects and teams\n */\nexport abstract class PluginApi {\n /** Core math, geometry, history, and units primitives. See {@linkcode PluginCoreApi}. */\n public abstract core: PluginCoreApi\n /** Author, query & edit BIM geometry. See {@linkcode PluginDesignApi}. */\n public abstract design: PluginDesignApi\n /** CRUD operations on Snaptrude entities. See {@linkcode PluginEntityApi}. */\n public abstract entity: PluginEntityApi\n /** Program-mode planning reads — departments and area metrics. See {@linkcode PluginProgramApi}. */\n public abstract program: PluginProgramApi\n /** Presentation preparation reads — saved views. See {@linkcode PluginPresentationApi}. */\n public abstract presentation: PluginPresentationApi\n /** Sustainability analysis — sun & daylight studies. See {@linkcode PluginAnalysisApi}. */\n public abstract analysis: PluginAnalysisApi\n /** Dashboard surface — projects and teams. See {@linkcode PluginWorkspaceApi}. */\n public abstract workspace: PluginWorkspaceApi\n\n constructor() {}\n}\n\nexport * from \"./core\"\nexport * from \"./design\"\nexport * from \"./entity\"\nexport * from \"./program\"\nexport * from \"./presentation\"\nexport * from \"./analysis\"\nexport * from \"./workspace\"\n","/**\n * The closed error-code registry — the single source of truth for every error\n * that crosses the plugin wire (host → worker) or is minted client-side.\n *\n * APPEND-ONLY: codes are a published contract; never rename or remove one.\n * Handle-resolution failures deliberately collapse to ONE code\n * (`HANDLE_INVALID`) so error responses cannot be used as an existence /\n * ownership oracle (PLUGIN_HANDLE_MIGRATION_HLD §6). `HANDLE_KIND_MISMATCH`\n * stays distinct because the kind is information the caller already holds.\n */\nexport const PLUGIN_ERROR_CODES = [\n \"VALIDATION\",\n \"INVALID_RPC_FORMAT\",\n \"METHOD_NOT_FOUND\",\n \"METHOD_NOT_PERMITTED\",\n \"HANDLE_INVALID\",\n \"HANDLE_KIND_MISMATCH\",\n \"RESOURCE_QUOTA_EXCEEDED\",\n \"RATE_LIMITED\",\n \"PRECONDITION_FAILED\",\n \"OPERATION_FAILED\",\n \"TIMEOUT\",\n \"CALL_TIMEOUT\",\n \"TRANSPORT_LOST\",\n \"PLUGIN_NOT_READY\",\n \"PLUGIN_TERMINATED\",\n \"RESULT_NOT_SERIALIZABLE\",\n \"RESULT_TOO_LARGE\",\n \"BATCH_PARTIAL\",\n \"STORY_UPDATE_FAILED\",\n \"STORY_HEIGHT_REJECTED\",\n \"NO_ACTIVE_STRUCTURE\",\n \"NO_ELIGIBLE_ELEMENTS\",\n \"PROPOSAL_SWITCH_BLOCKED\",\n \"INTERNAL\",\n \"UNKNOWN\",\n] as const\n\nexport type PluginErrorCode = (typeof PLUGIN_ERROR_CODES)[number]\n\n/**\n * A known code, or a string outside the union — a plugin built against an\n * older SDK may receive codes a newer host appended. Keeps autocomplete for\n * known codes while staying open on the wire (forward compatibility).\n */\nexport type WirePluginErrorCode = PluginErrorCode | (string & {})\n\nexport type PluginErrorCategory =\n | \"validation\"\n | \"not_found\"\n | \"permission\"\n | \"handle\"\n | \"quota\"\n | \"timeout\"\n | \"transport\"\n | \"lifecycle\"\n | \"execution\"\n | \"internal\"\n\n/**\n * Client-side metadata for a code. NOT carried on the wire — every field is a\n * pure function of the code, and this table ships inside plugin-core, so both\n * endpoints derive it locally.\n */\nexport interface CodeMeta {\n /** Family of the code; picks the `PluginError` sugar subclass. */\n category: PluginErrorCategory\n /** Fallback `hint` when the throw site provides none. */\n hintTemplate?: string\n}\n\nexport const CODE_META: Record<PluginErrorCode, CodeMeta> = {\n VALIDATION: {\n category: \"validation\",\n hintTemplate: \"Check details.issues for the failing argument.\",\n },\n INVALID_RPC_FORMAT: { category: \"validation\" },\n METHOD_NOT_FOUND: {\n category: \"not_found\",\n hintTemplate: \"Check details.didYouMean for a close match.\",\n },\n METHOD_NOT_PERMITTED: { category: \"permission\" },\n HANDLE_INVALID: {\n category: \"handle\",\n hintTemplate: \"Re-query the entity; do not cache handles across undo/reload.\",\n },\n HANDLE_KIND_MISMATCH: { category: \"handle\" },\n RESOURCE_QUOTA_EXCEEDED: {\n category: \"quota\",\n hintTemplate: \"Release unused handles (core.handles.release) or use scopes.\",\n },\n RATE_LIMITED: {\n category: \"quota\",\n hintTemplate: \"Wait details.retryAfterMs before retrying.\",\n },\n PRECONDITION_FAILED: { category: \"execution\" },\n OPERATION_FAILED: { category: \"execution\" },\n TIMEOUT: { category: \"timeout\" },\n CALL_TIMEOUT: { category: \"timeout\" },\n TRANSPORT_LOST: { category: \"transport\" },\n PLUGIN_NOT_READY: { category: \"lifecycle\" },\n PLUGIN_TERMINATED: { category: \"lifecycle\" },\n RESULT_NOT_SERIALIZABLE: { category: \"execution\" },\n RESULT_TOO_LARGE: { category: \"execution\" },\n BATCH_PARTIAL: { category: \"execution\" },\n STORY_UPDATE_FAILED: {\n category: \"execution\",\n hintTemplate: \"The storey update could not be applied; re-read the storey and retry.\",\n },\n STORY_HEIGHT_REJECTED: {\n category: \"execution\",\n hintTemplate: \"The requested height was out of range; pick a value within the storey's limits.\",\n },\n NO_ACTIVE_STRUCTURE: {\n category: \"execution\",\n hintTemplate: \"Activate a structure before running this operation.\",\n },\n NO_ELIGIBLE_ELEMENTS: {\n category: \"execution\",\n hintTemplate: \"The storey has no elements eligible for this operation.\",\n },\n PROPOSAL_SWITCH_BLOCKED: {\n category: \"execution\",\n hintTemplate: \"Wait for the in-canvas agent run to finish, then retry.\",\n },\n INTERNAL: {\n category: \"internal\",\n hintTemplate: \"Host-side fault. Report with errorId.\",\n },\n UNKNOWN: { category: \"internal\" },\n}\n\n/** Narrow an arbitrary string to a member of the closed code union. */\nexport function isPluginErrorCode(value: string): value is PluginErrorCode {\n return (PLUGIN_ERROR_CODES as readonly string[]).includes(value)\n}\n","import type { WirePluginErrorCode } from \"./codes\"\n\n/**\n * Structured error payload carried on the `{success: false}` branch of the RPC\n * wire result, next to the legacy `error: string` field (additive — old\n * clients ignore it, old hosts never set it).\n *\n * Errors cross the worker boundary as THIS plain object, never as a thrown\n * class: Comlink's throw transfer strips subclasses and custom fields and\n * leaks host stacks. plugin-client reconstructs a typed `PluginError` from it\n * (`fromEnvelope`).\n */\nexport const ERROR_ENVELOPE_VERSION = 1 as const\n\nexport interface ErrorEnvelope {\n envelopeVersion: typeof ERROR_ENVELOPE_VERSION\n code: WirePluginErrorCode\n /**\n * Human-readable failure description. For INTERNAL/UNKNOWN this is a GENERIC\n * message — the real message and stack stay host-side, logged under\n * `errorId` (the worker is untrusted; internals must not leak).\n */\n message: string\n /**\n * Per-occurrence id. For INTERNAL the host logs the real message and stack\n * under this id (the sanitized message would otherwise be an undebuggable\n * dead end). Other codes carry the id only to identify the occurrence in\n * bug reports — the host does not log them. Client-minted ids carry a `c-`\n * prefix.\n */\n errorId: string\n /** Structured, JSON-safe, code-specific data (issues / retryAfterMs / didYouMean / handles…). */\n details?: Record<string, unknown>\n /**\n * Wire id of the single primary offending handle, when the failure is\n * handle-scoped. Multiple offenders go in `details.handles: string[]`.\n * May echo ONLY ids the caller itself supplied (anti-oracle rule).\n */\n handle?: string\n /** Dotted method path of the failed call (e.g. \"design.boolean.union\"). */\n methodPath?: string\n /** Machine-actionable fix suggestion (written for LLM plugin authors). */\n hint?: string\n}\n\n/**\n * Structural guard for a wire value claiming to be an `ErrorEnvelope`.\n *\n * Checks shape only — deliberately does NOT reject codes outside the known\n * union, so envelopes from a newer host still pass (forward compatibility).\n */\nexport function isErrorEnvelope(value: unknown): value is ErrorEnvelope {\n if (typeof value !== \"object\" || value === null) return false\n const record = value as Record<string, unknown>\n return (\n record.envelopeVersion === ERROR_ENVELOPE_VERSION &&\n typeof record.code === \"string\" &&\n typeof record.message === \"string\" &&\n typeof record.errorId === \"string\"\n )\n}\n\nlet idCounter = 0\n\n/**\n * Mint a per-occurrence error id. Uses `crypto.randomUUID` when available\n * (browser main thread and workers both have it); falls back to a\n * counter+random suffix — uniqueness within a session is all that is needed.\n */\nexport function newErrorId(prefix = \"\"): string {\n const cryptoApi = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto\n if (cryptoApi?.randomUUID) return `${prefix}${cryptoApi.randomUUID()}`\n idCounter += 1\n return `${prefix}${Date.now().toString(36)}-${idCounter}-${Math.random().toString(36).slice(2, 10)}`\n}\n","import { CODE_META, isPluginErrorCode } from \"./codes\"\nimport type { PluginErrorCategory, PluginErrorCode, WirePluginErrorCode } from \"./codes\"\nimport { ERROR_ENVELOPE_VERSION, newErrorId } from \"./envelope\"\nimport type { ErrorEnvelope } from \"./envelope\"\n\n/**\n * Cross-copy brand: a plugin bundle may contain its OWN copy of this module,\n * in which case `instanceof` fails across the two class identities.\n * `Symbol.for` resolves to the same symbol in every copy within a realm, so\n * `PluginError.is()` keeps working. Canonical check = `.is()` + `code`.\n */\nconst BRAND = Symbol.for(\"snaptrude.pluginError\")\n\ninterface Branded {\n [BRAND]?: boolean\n}\n\n/**\n * Typed error every failed plugin API call rejects with.\n *\n * Rehydrated client-side from the wire `ErrorEnvelope` (`fromEnvelope`) — the\n * class itself never crosses the worker boundary. Branch on the stable string\n * `code`; use `PluginError.is()` rather than `instanceof` (a bundled duplicate\n * SDK copy breaks `instanceof`, never the brand).\n *\n * @example\n * ```ts\n * try {\n * await snaptrude.design.boolean.union(handles)\n * } catch (e) {\n * if (PluginError.is(e) && e.code === \"RATE_LIMITED\") {\n * await sleep(e.details?.retryAfterMs as number)\n * } else {\n * throw e\n * }\n * }\n * ```\n */\nexport class PluginError extends Error {\n readonly code: WirePluginErrorCode\n readonly errorId: string\n readonly details?: Record<string, unknown>\n readonly handle?: string\n readonly methodPath?: string\n readonly hint?: string\n\n constructor(envelope: ErrorEnvelope) {\n super(envelope.message)\n this.name = new.target.name\n this.code = envelope.code\n this.errorId = envelope.errorId\n this.details = envelope.details\n this.handle = envelope.handle\n this.methodPath = envelope.methodPath\n this.hint = envelope.hint ?? hintFor(envelope.code)\n ;(this as Branded)[BRAND] = true\n }\n\n /** Canonical check — survives bundled duplicate SDK copies where `instanceof` fails. */\n static is(value: unknown): value is PluginError {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as Branded)[BRAND] === true\n )\n }\n\n /** Derived (not wire-carried) family of the code; unknown codes map to \"internal\". */\n get category(): PluginErrorCategory {\n return isPluginErrorCode(this.code) ? CODE_META[this.code].category : \"internal\"\n }\n}\n\n// Category sugar — same brand, so `PluginError.is()` matches all of them.\n// Convenience for single-SDK-copy plugins; docs teach `.is()` + `code`.\nexport class PluginValidationError extends PluginError {}\nexport class PluginNotFoundError extends PluginError {}\nexport class PluginPermissionError extends PluginError {}\nexport class PluginHandleError extends PluginError {}\nexport class PluginQuotaError extends PluginError {}\nexport class PluginTimeoutError extends PluginError {}\nexport class PluginTransportError extends PluginError {}\nexport class PluginLifecycleError extends PluginError {}\nexport class PluginExecutionError extends PluginError {}\nexport class PluginInternalError extends PluginError {}\n\ntype PluginErrorConstructor = new (envelope: ErrorEnvelope) => PluginError\n\nconst CATEGORY_CLASS: Record<PluginErrorCategory, PluginErrorConstructor> = {\n validation: PluginValidationError,\n not_found: PluginNotFoundError,\n permission: PluginPermissionError,\n handle: PluginHandleError,\n quota: PluginQuotaError,\n timeout: PluginTimeoutError,\n transport: PluginTransportError,\n lifecycle: PluginLifecycleError,\n execution: PluginExecutionError,\n internal: PluginInternalError,\n}\n\nfunction hintFor(code: WirePluginErrorCode): string | undefined {\n return isPluginErrorCode(code) ? CODE_META[code].hintTemplate : undefined\n}\n\n/**\n * Rehydrate a wire envelope into the typed error the plugin catches.\n * A code outside the known union (newer host) constructs the base\n * `PluginError` with the code preserved verbatim.\n */\nexport function fromEnvelope(envelope: ErrorEnvelope): PluginError {\n if (!isPluginErrorCode(envelope.code)) return new PluginError(envelope)\n const ErrorClass = CATEGORY_CLASS[CODE_META[envelope.code].category]\n return new ErrorClass(envelope)\n}\n\n/**\n * Build an envelope for a CLIENT-detected failure (transport rejection,\n * legacy host without `errorInfo`). Host-originated envelopes are built by\n * the host's `toErrorEnvelope` mapper, never by this.\n */\nexport function makeClientEnvelope(\n code: PluginErrorCode,\n message: string,\n extra?: Partial<Pick<ErrorEnvelope, \"details\" | \"handle\" | \"methodPath\" | \"hint\">>\n): ErrorEnvelope {\n return {\n envelopeVersion: ERROR_ENVELOPE_VERSION,\n code,\n message,\n errorId: newErrorId(\"c-\"),\n ...extra,\n }\n}\n"],"mappings":"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","names":["z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","record"]}
|