Appearance
Design Query
Read-only enumeration and inspection of scene-committed BIM entities. No query mutates the scene. Accessed via snaptrude.design.query.
Every list* method returns a snapshot ComponentHandle[] ([], never null) filtered by the shared PluginEntityFilter; per-entity reads take a ComponentHandle and return primitive records / child handles, or null when the entity is gone. Resolve details of a returned handle with getProperties / measure / design.query.geometry.*.
Entity Queries
Discovery and inspection of BIM entities by kind, plus identity, hierarchy, property, and metric reads. Accessed via snaptrude.design.query.
Types
PluginEntityType
The kind of a BIM entity — the discovery/filter vocabulary for the whole design.query.* surface. Values are clean lowerCamel tokens; the host maps each to the engine's internal type (whose raw strings are case-inconsistent — never surfaced here).
| Value | Entity |
|---|---|
"wall" | Wall |
"space" | Space (a room mass) |
"mass" | Generic mass (non-room) |
"door" | Door |
"window" | Window |
"floor" | Floor |
"slab" | Slab |
"roof" | Roof |
"column" | Column |
"beam" | Beam |
"ceiling" | Ceiling |
"staircase" | Staircase |
"furniture" | Furniture |
"referenceLine" | Reference line |
"curtainWall" | Parametric curtain wall |
"mullion" | Curtain-wall mullion |
"panel" | Curtain-wall panel |
"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 |
"grid" | Structural grid group |
PluginEntityFilter
The single filter shared by every design.query.list* method, so filtering is identical across all BIM object types. Every field is optional and AND-combined: a component must satisfy all provided fields. Plural fields are arrays (match any of); singular fields are scalars. A field that names a type-specific dimension (massTypes/spaceTypes/departmentId) excludes components that lack that dimension.
Unknown keys are rejected. A key the filter does not define would otherwise be dropped silently, and a dropped key means no filtering — the whole scene comes back looking like a legitimate answer (listSpaces({ storey: 2 }) — the field is storeys: [2] — once returned every space in the project). Any unrecognized key now fails with a VALIDATION error naming the key, the nearest match, and the full allowed set. Note the plurals: storeys, types, buildingIds, structureIds, massTypes, spaceTypes, ids.
A defined key whose value is explicitly undefined is legal and means not provided — { departmentId: undefined } places no department constraint, so it matches everything. Omit the key, or give it a real value, when you mean to narrow.
| Field | Type | Keeps components where |
|---|---|---|
types | PluginEntityType[] | entity kind ∈ types (ignored by per-type list* — the method fixes the kind) |
storeys | number[] | storey/floor level ∈ storeys (negative = basement) |
buildingIds | string[] | building ∈ buildingIds |
structureIds | string[] | structure ∈ structureIds |
label | string | label (room_type ?? mesh label) === label |
massTypes | PluginMassType[] | mass-type ∈ (masses only) |
spaceTypes | PluginSpaceType[] | space-type ∈ (spaces only) |
departmentId | PluginDepartmentId | department === |
isSelected | boolean | current selection state === |
isLocked | boolean | lock flag === |
isHidden | boolean | user-hidden flag === |
within | BBoxComponents | the entity's bounding box intersects the region |
overlaps | 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. |
ids | ComponentHandle[] | id ∈ ids (restrict to a known set) |
PluginEntityProperties
The common property record returned by getProperties — a partial object (a field is present only when it applies to the entity). Type-specific and geometry reads live on measure and design.query.geometry.*, keeping this record generic across every type.
| Property | Type | Description |
|---|---|---|
id | string | The entity id |
type | PluginEntityType | Entity kind |
label | string | room_type ?? mesh label ?? "" |
storey | number | Storey/floor level |
buildingId | string | Owning building |
structureId | string | Owning structure |
massType | string | Mass-type (masses only) |
spaceType | PluginSpaceType | Space-type (spaces only) |
areaClass | PluginAreaClass | Area classification (spaces only) |
departmentId | string | Department (masses only) |
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) |
isLocked | boolean | Lock flag |
isHidden | boolean | User-hidden flag |
isSelected | boolean | Current selection state |
boundingBox | BBoxComponents | World-space AABB |
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 Adjacency. |
PluginEntityMeasurements
The multi-metric bundle returned by measure. boundingBox is always present; the numeric metrics appear only when the entity's type computes them (area: floors/masses/slabs/roofs/beams/columns; volume: all; length: walls/beams). Values are in Snaptrude units — convert with snaptrude.core.units.convert(value, from, to).
thickness and height are the Object Properties panel's dimension reads — the same getMeshDimensions engine seam the panel and the design.update.wall / .slab / .floor / .roof / .ceiling write side use, in engine units. thickness is present for walls (the plan thickness — straight orthogonal walls only, exactly when the panel shows it) and for the slab family (the vertical extent the panel calls Thickness); height for walls only.
| Property | Type | Description |
|---|---|---|
area | number (optional) | Bottom-face area |
volume | number (optional) | Solid volume (curved solids and families: see below) |
length | number (optional) | Running length (linear elements) |
thickness | number (optional) | Panel Thickness — walls (straight orthogonal only) and slabs/floors/roofs/ceilings |
height | number (optional) | Panel Height — walls only |
boundingBox | BBoxComponents | World-space AABB (always present) |
volume of a solid with curved faces is re-integrated off its exact surfaces, so it agrees with core.geom.query.brep.getVolume; a planar solid reads the stored bill-of-quantities volume. A parametric family answers its declared takeoff; one that declares none answers the sum of its kernel parts (exact) and, when every mesh part is closed, its mesh parts (from their triangles, so a round mesh part reads about 1 % low). If any mesh part is open, no mesh part counts. Overlapping parts count twice, like the bill of quantities.
PluginStaircaseParams
The parametric fields of a staircase, returned by getStaircaseParams. Dimensions are in Snaptrude (engine) units — the same units the design.update.staircase write side takes. The three type-specific fields (wellSize / landingWidth / flightStartDistAfterTurn) are omitted when the preset doesn't use them.
| Property | Type | Description |
|---|---|---|
staircaseType | string | Type token (straight, dogLegged, lShaped, …) |
staircasePreset | string | Preset token |
staircaseHeight | number | Total rise |
steps | number | Step count |
riser | number | Riser height |
tread | number | Tread depth |
width | number | Flight width |
depth | number | Flight depth |
baseOffset | number | Base offset from the storey |
isStoreyHeightUnlocked | boolean | Whether height is decoupled from the storey |
isStaircaseHeightUnlocked | boolean | Whether height is decoupled from steps × riser |
storey | number | Storey/floor level |
wellSize | number? | Well size (well-based presets only) |
landingWidth | number? | Landing width (landing-based presets only) |
flightStartDistAfterTurn | number? | Flight start distance after a turn (turn presets only) |
MassParametersRecord
A mass's creation recipe — the constructor kind, a schema version, the values that constructor was called with, and the kernel operations applied on top. Returned by getParameters and writable through design.update.parameters. Points and axes come back in world coordinates; lengths are in Snaptrude (engine) units.
kind | values |
|---|---|
"sphere" | centre, radius |
"cylinder" | base, axis, radius, height |
"cone" | base, axis, baseRadius, topRadius, height |
"torus" | centre, axis, majorRadius, minorRadius |
"extrusion" | contour, direction, amount, twistInDegrees?, topScale?, levels? |
"revolution" | contour, axisOrigin, axisDirection, angleInDegrees |
"loft" | sections, options? |
"sweep" | profile, path, options? |
"sweepAlongCurve" | profile, path (CurveRecord[]), options? |
Every variant also carries an optional operations: OperationRecord[] — the fillets, chamfers, shells and offsets applied to that recipe's solid, in order. kind stays the base constructor even after operations: a shelled, filleted sphere is still { kind: "sphere", operations: [shell, fillet] }, so a patch of radius edits the sphere and a patch of operations edits the blends.
version is 1 for every kind in this release (operations is an additive key, not a new version). The record is pure JSON — plain objects, numbers and arrays, with one { x, y, z } point shape everywhere.
An optional key is absent until it is first set, and a record that never had one round-trips byte for byte: a straight extrusion carries exactly contour, direction, amount. Twisting or tapering it adds twistInDegrees (degrees, positive turning the top clockwise seen from the far end of direction), topScale (a ratio) and the section count levels — the kind stays "extrusion". levels is host-owned: the host resolves and stamps it, re-resolves it when an edit changes the angle, and refuses an edit that changes it (see design.update.parameters). A loft's options may carry smoothing, a sweep-along-a-curve's may carry orientation; a sweep carried by a rail records nothing at all, because a record with no rail in it would rebuild a different solid.
OperationRecord
One kernel operation applied to the base recipe's solid. input pins the topology the operation was authored against — TopologyCounts, i.e. { faceCount, edgeCount } — so a base edit that changes the topology is refused rather than mis-applied.
op | Fields |
|---|---|
"fillet" | edges: EdgeSelector[], radius, input, endRadius? or radii? |
"chamfer" | edges: EdgeSelector[], distance, input |
"shell" | openFaces: FaceSelector[], thickness, input |
"offset" | distance, input — the whole solid; nothing is selected |
Selectors are topology, not coordinates — that is what lets a scalar edit of the base (which moves every edge) replay the operations, and what makes a reorder or a topology change trip instead of mis-applying:
| Type | Shape | Meaning |
|---|---|---|
EdgeSelector | { index, faces: [number, number], curve, at? } | Index in brep.listEdges order at authoring time, the indices of the two faces it lies between, and its curve kind ("line" / "arc" / "circle" / "spline") |
FaceSelector | { index, kind, edgeCount, at? } | Index in brep.listFaces order, its surface kind, and the number of edges in its outer loop |
at is the tiebreak, written only where the other three cannot separate the selection — two faces of the same kind and loop size, a spline patch, two edges of one curve kind between the same two faces (the two half-arcs of a split rim). It is a ratio per axis: the item's position inside the solid's own bounding box, 0..1, never a length. A record written before it existed parses and verifies unchanged.
A fillet's variable blend rides the same record: endRadius (a length) or radii (2..32 rows of { position, radius }, position a ratio of arc length along the blended chain), exactly as brepFromFillet took them — so a law is replayed on every rebuild, and either form names exactly one edge.
Selectors are read-only: to blend a different edge, re-select it on the new shape and re-apply the operation with core.geom.create.brepFromFillet. Editing an operation's own scalar (a radius, a thickness) is a normal design.update.parameters patch.
Caps: 8 operations per recipe, 64 edges per fillet/chamfer, 32 open faces per shell.
ContourRecord
The cross-section shape inside an extrusion, revolution, loft or sweep record: { outer: ProfileRecord, holes: ProfileRecord[] }, where a ProfileRecord is { curves: CurveRecord[] } — one closed boundary loop as an ordered list of curves.
CurveRecord type | Fields |
|---|---|
"line" | start, end |
"arc" | start, end, centre, axis |
"circle" | centre, axis, radius |
"splineThrough" | fitVersion, throughPoints — the authored through-points are the recipe |
"splinePoles" | degree, knots, mults, poles — an exact NURBS definition |
The two spline forms are either/or, never both: a curve authored through points (via splineFromPoints / profileFromSplinePoints) carries splineThrough — patch the points and the fit re-runs — and one with no through-point provenance carries splinePoles and is re-minted verbatim: authored from control vertices via splineFromControlPoints, or read back off an edge.
Functions
listEntities(filter?)
List all BIM entities in the project matching the filter.
- Parameters:
filter:PluginEntityFilter(optional) — The shared entity filter
- Returns:
ComponentHandle[]— Matching entities ([]if none)
ts
const doorsAndWindows = await snaptrude.design.query.listEntities({
types: ["door", "window"],
storeys: [0]
});listByType(type, filter?)
List entities of a single PluginEntityType, plus filter.
- Parameters:
type:PluginEntityType— The entity kind to listfilter:PluginEntityFilter(optional) — The shared entity filter
- Returns:
ComponentHandle[]— Matching entities
ts
const lockedSlabs = await snaptrude.design.query.listByType("slab", { isLocked: true });listWalls(filter?)
List all walls, filtered. The types field of the filter is ignored (this method fixes the kind).
- Parameters:
filter:PluginEntityFilter(optional)
- Returns:
ComponentHandle[]— Wall handles
ts
const groundFloorWalls = await snaptrude.design.query.listWalls({ storeys: [0] });listSpaces(filter?)
List all spaces (room masses), filtered. See listWalls.
- Parameters:
filter:PluginEntityFilter(optional)
- Returns:
ComponentHandle[]
ts
// Which spaces sit on a bigger floor-plate space? Exact plan intersection —
// fully-inside, boundary-nested, and partially-overhanging spaces all match;
// outside and merely edge-touching spaces don't, and the plate itself is excluded.
const [plate] = await snaptrude.design.query.listSpaces({ label: "Bigger floorplate" });
const onPlate = await snaptrude.design.query.listSpaces({ overlaps: plate });listMasses(filter?)
List all generic masses (non-room masses), filtered.
- Parameters:
filter:PluginEntityFilter(optional)
- Returns:
ComponentHandle[]
listDoors(filter?)
List all doors, filtered.
- Parameters:
filter:PluginEntityFilter(optional)
- Returns:
ComponentHandle[]
listWindows(filter?)
List all windows, filtered.
- Parameters:
filter:PluginEntityFilter(optional)
- Returns:
ComponentHandle[]
listFloors(filter?)
List all floors, filtered.
- Parameters:
filter:PluginEntityFilter(optional)
- Returns:
ComponentHandle[]
listSlabs(filter?)
List all slabs, filtered.
- Parameters:
filter:PluginEntityFilter(optional)
- Returns:
ComponentHandle[]
listRoofs(filter?)
List all roofs, filtered.
- Parameters:
filter:PluginEntityFilter(optional)
- Returns:
ComponentHandle[]
listColumns(filter?)
List all columns, filtered.
- Parameters:
filter:PluginEntityFilter(optional)
- Returns:
ComponentHandle[]
listBeams(filter?)
List all beams, filtered.
- Parameters:
filter:PluginEntityFilter(optional)
- Returns:
ComponentHandle[]
listCeilings(filter?)
List all ceilings, filtered.
- Parameters:
filter:PluginEntityFilter(optional)
- Returns:
ComponentHandle[]
listStaircases(filter?)
List all staircases, filtered.
- Parameters:
filter:PluginEntityFilter(optional)
- Returns:
ComponentHandle[]
listFurniture(filter?)
List all furniture, filtered.
- Parameters:
filter:PluginEntityFilter(optional)
- Returns:
ComponentHandle[]
listReferenceLines(filter?)
List all reference lines, filtered.
- Parameters:
filter:PluginEntityFilter(optional)
- Returns:
ComponentHandle[]
listMullions(filter?)
List all curtain-wall mullions, filtered.
- Parameters:
filter:PluginEntityFilter(optional)
- Returns:
ComponentHandle[]
listGrids(filter?)
List all structural grids (grid groups), filtered.
- Parameters:
filter:PluginEntityFilter(optional)
- Returns:
ComponentHandle[]
getById(id)
Look an entity up by id — returns the same handle if it still resolves live, else null. A cheap validity probe (distinct from exists).
- Parameters:
id:ComponentHandle— The entity id to look up
- Returns:
ComponentHandle | null— The handle, ornullif it no longer resolves
exists(id)
Test whether an entity id resolves to a live scene entity.
- Parameters:
id:ComponentHandle— The entity id to test
- Returns:
boolean—trueif it exists, otherwisefalse
getEntityType(component)
Get an entity's PluginEntityType.
- Parameters:
component:ComponentHandle— The entity to read
- Returns:
PluginEntityType | null— The kind, ornullif the entity is gone / its type is unknown
getLabel(component)
Get an entity's human label (room_type for spaces/masses, else the mesh label; "" when unlabelled). The write side is design.update (setLabel).
- Parameters:
component:ComponentHandle— The entity to read
- Returns:
string— The label
getEntityRef(component)
Get a serializable, persistable reference to an entity. Under the all-handle model a ComponentHandle already IS the ref (raw Component.id), so this is the identity passthrough — provided for API completeness.
- Parameters:
component:ComponentHandle— The entity
- Returns:
ComponentHandle— The entity's handle
listChildren(component)
List an entity's direct child entities.
- Parameters:
component:ComponentHandle— The parent entity
- Returns:
ComponentHandle[]— Child handles ([]if none)
getHost(component)
Get an entity's host/parent entity (e.g. the wall a door is cut into).
This is how you recover a host wall after design.create.door / window / doors / windows replaced it: cutting an opening rebuilds the wall as a new entity, so the handle you passed as hostWall stops resolving — ask the opening for its current host instead.
- Parameters:
component:ComponentHandle— The entity
- Returns:
ComponentHandle | null— The host, ornullif top-level
getProperties(component)
Read an entity's common properties as a PluginEntityProperties record (only applicable fields are present). For spaces/masses this includes adjacency — the per-entity adjacency read (bulk form: program.adjacency.getMatrix). For walls and the slab family (slab/floor/roof/ceiling) this includes buildingType — the assigned building type NAME the panel's type dropdown shows, matching the names from design.types.list(kind).
- Parameters:
component:ComponentHandle— The entity to read
- Returns:
PluginEntityProperties— The property record
ts
const [wall] = await snaptrude.design.query.listWalls();
const props = await snaptrude.design.query.getProperties(wall);
console.log(props.type, props.storey, props.buildingType);measure(component)
Measure an entity — the multi-metric bundle (area/volume/length + bbox). For walls this also carries the panel's thickness (straight orthogonal walls only) and height; for the slab family (slab/floor/roof/ceiling) the panel's thickness — the read side of design.update.wall / .slab / .floor / .roof / .ceiling, in engine units.
- Parameters:
component:ComponentHandle— The entity to measure
- Returns:
PluginEntityMeasurements| null— The metrics, ornullif the entity is gone
ts
const [space] = await snaptrude.design.query.listSpaces();
const m = await snaptrude.design.query.measure(space);
console.log(m?.area, m?.volume);
const [wall] = await snaptrude.design.query.listWalls();
const wm = await snaptrude.design.query.measure(wall);
console.log(wm?.thickness, wm?.height); // the panel's Thickness / HeightgetBoundingBox(components)
Get the union axis-aligned bounding box enclosing a set of entities.
- Parameters:
components:ComponentHandle[] | ComponentHandle— The entities to enclose. A single handle is accepted as well as an array.
- Returns:
BBoxComponents | null— The union box, ornullif the set is empty / none resolve - Throws:
VALIDATIONif the argument is neither a handle nor an array of handles
ts
const walls = await snaptrude.design.query.listWalls();
const box = await snaptrude.design.query.getBoundingBox(walls);
// One entity — no array needed
const [space] = await snaptrude.design.query.listSpaces({ storeys: [1] });
const spaceBox = await snaptrude.design.query.getBoundingBox(space);A bare id used to return null
Before this was fixed, passing a single handle walked the id string character by character, resolved nothing, and returned null for every entity. If you are on an older host, wrap the handle in an array.
getStaircaseParams(staircase)
Read the parametric fields of a staircase. The write counterpart is design.update.staircase; a read → write round-trips exactly.
- Parameters:
staircase:ComponentHandle— The staircase to read
- Returns:
PluginStaircaseParams| null—nullwhen the handle is gone or is not a staircase
ts
const [stair] = await snaptrude.design.query.listStaircases();
const params = stair ? await snaptrude.design.query.getStaircaseParams(stair) : null;
console.log(params?.steps, params?.riser);getParameters(component)
Read a mass's creation-parameter record — the recipe it was built from (kind plus the values that constructor was called with, plus any kernel operations applied on top). Present on masses committed from a core.geom.create brep constructor that carries a recipe: the four primitives (brepFromSphere, brepFromCylinder, brepFromCone, brepFromTorus), extrusions (twisted and tapered ones included), revolutions, lofts and both sweeps.
A fillet, chamfer, shell or offset of a recorded solid keeps the record: kind stays the base constructor and the step is appended to operations, so a rounded column reads back as a cylinder with one fillet — and both halves are writable through design.update.parameters.
null for everything else — drawn masses, imported geometry, booleans, split parts, patches, pieces cut by a face, a sweep carried by a rail, brepFromFaces / brepFromMesh, and anything built on a source that carried no record — and for a mass whose record no longer matches what is on screen (a later free-form edit dropped the recipe, or the mass carries a baked scale — undo the scale to get the record back). A record is never reported unless it is still the truth, so null is a real answer, not a fault; it is also exactly the case design.update.parameters refuses with PRECONDITION_FAILED.
This is the strongest self-check after authoring or editing a curved solid: it echoes the exact numbers back. Corroborate it with core.geom.query.face.getSurfaceKind (a faceted fake reports every face "plane"), measure and core.geom.query.brep.getVolume — a record that only agrees with itself proves nothing.
Points and axes come back in world coordinates. Pure read — nothing is created or modified.
- Parameters:
component:ComponentHandle— The mass to read
- Returns:
MassParametersRecord| null— The record, ornullwhen the handle is gone, is not a mass, carries no recipe, or the recipe is no longer the truth
ts
const [mass] = await snaptrude.design.query.listMasses({ isSelected: true });
const params = await snaptrude.design.query.getParameters(mass);
if (params?.kind === "sphere") console.log("radius:", params.values.radius);
if (params?.operations?.length) {
console.log("chain:", params.operations.map((op) => op.op).join(" → ")); // "shell → fillet"
}
if (params === null) console.log("this mass has no editable recipe");Geometry
Read the geometry of scene BIM entities. Accessed via snaptrude.design.query.geometry.
This is the bridge from the BIM/scene layer (design.*, keyed by ComponentHandle) into the geometry kernel (core.geom.query.*, keyed by value/topology handles). getBrep mints a BrepHandle for a component's boundary-representation mesh; getBottomContour mints a ContourHandle for its footprint outline; getCenterline mints a CurveHandle for a wall's core curve. getTriangulatedMeshes is the handle-free exception: it returns { meshes } — the triangulated render meshes as plain world-space arrays, wrapped in an object, never bare.
Returned handles are session-ephemeral and go stale on undo/redo/re-topologize — re-fetch a fresh one after edits.
Functions
getBrep(component, options?)
Get the boundary-representation (BREP) mesh of a scene component.
By default the brep is in the component's own local frame (vertices relative to the mesh origin, exactly as stored) — unchanged from every 0.9.x release. Pass { frame: "world" } to get the same solid placed where it sits in the scene, matching getBottomContour, getTriangulatedMeshes and design.query.measure. Use "world" whenever the handle will be combined with other scene geometry or committed back beside its source: booleans between two scene masses, fillet / chamfer / shell / offset on a scene mass followed by massFromBrep, or comparing vertices with a bounding box. A world-frame handle from a parametrically live mass also carries that mass's recipe, so an operation result keeps an editable record.
- Parameters:
component:ComponentHandle— The scene component to readoptions(optional):{ frame?: "local" | "world" }—"local"(default) or"world"
- Returns:
BrepHandle | null— The brep, ornullif the component has no brep geometry
ts
const brep = await snaptrude.design.query.geometry.getBrep(component);
if (brep) {
const faces = await snaptrude.core.geom.query.brep.listFaces(brep);
}getBottomContour(component)
Get the bottom-face contour of a component — the footprint outline (outer profile + holes) of its lowest horizontal face, as a reusable ContourHandle.
Extracted from the component's B-rep; robust to inverted face normals. Read its geometry via snaptrude.core.geom.query.contour.*, or feed it back into design.create.* / core.geom.*.
- Parameters:
component:ComponentHandle— The scene component to read
- Returns:
ContourHandle | null— The footprint contour, ornullif the component has no extractable bottom face (e.g. it carries no B-rep)
ts
const [wall] = await snaptrude.design.query.listWalls();
const contour = await snaptrude.design.query.geometry.getBottomContour(wall);
if (contour) {
const area = await snaptrude.core.geom.query.contour.getArea(contour);
}getCenterline(component)
Get the centerline (core curve) of a wall — the single curve running down the middle of the wall, as a reusable CurveHandle (a Line for a straight wall, an Arc for a curved wall).
For a parametric wall this is its stored core curve; otherwise it is derived from the wall's B-rep bottom face. Read its geometry via snaptrude.core.geom.query.curve.*, or feed it into design.create.*.
Only walls have a centerline. As with all get* reads it never throws: a non-wall component, or a wall with no derivable centerline, yields null.
- Parameters:
component:ComponentHandle— The scene component to read (only walls yield a centerline)
- Returns:
CurveHandle | null— The centerline curve, ornullif the component is not a wall or the wall has no derivable centerline (e.g. it carries no B-rep or has an unsupported profile)
ts
const [wall] = await snaptrude.design.query.listWalls();
const centerline = await snaptrude.design.query.geometry.getCenterline(wall);
if (centerline) {
const length = await snaptrude.core.geom.query.curve.getLength(centerline);
}getTriangulatedMeshes(components, options?)
Get the triangulated render meshes of scene components as plain, serializable arrays — flat vertex positions ([x0, y0, z0, x1, …]) and triangle indices (three per triangle), one record per requested component, in input order. The result is { meshes }, not a bare array — destructure it before iterating.
Unlike the other geometry.* reads this does not mint kernel handles — the raw arrays cross the boundary directly, ready for export, custom analysis, or an external renderer. Positions are always world-space (instance/transform baked in; there is no local-coordinates mode), in plan units — no conversion needed — and raw doubles, unrounded.
With includeMaterialIds: true each record also carries materialIds: one entry per triangle (indices.length / 3 entries) holding the painted material's name (a MaterialHandle token), or null for unpainted/default faces.
With includeFaceIndices: true each record also carries faceIds: one entry per triangle (input order) holding the B-rep face index owning that triangle — the same ids as getBrep faces — or null where there is no provenance. Non-B-rep meshes (Revit imports, generic models, furniture) yield all-null rather than an error.
A resolvable component with no readable mesh geometry yields { id, positions: [], indices: [] } — nothing is silently omitted; an unresolvable handle throws HANDLE_INVALID instead.
The call is capped at 500 000 triangles total. Over the cap it throws a VALIDATION error whose message includes the actual total — split the component list into smaller batches and call once per batch.
- Parameters:
components:ComponentHandle[]— The scene components to triangulateoptions:{ includeMaterialIds?: boolean, includeFaceIndices?: boolean }(optional) —includeMaterialIdsadds the per-triangle material names;includeFaceIndicesadds the per-triangle B-rep face indices
- Returns:
{ meshes: PluginTriangulatedMesh[] }— one record{ id, positions, indices, materialIds?, faceIds? }per requested component - Throws:
HANDLE_INVALIDif any component handle is gone/forged;VALIDATIONif the combined mesh exceeds 500 000 triangles
ts
const spaces = await snaptrude.design.query.listSpaces();
const { meshes } = await snaptrude.design.query.geometry.getTriangulatedMeshes(spaces, {
includeMaterialIds: true
});
for (const mesh of meshes) {
const triangleCount = mesh.indices.length / 3;
console.log(mesh.id, triangleCount, mesh.materialIds?.[0]);
}Spaces
Space-specific reads (a space is a room mass). Accessed via snaptrude.design.query.spaces.
Only the space queries feasible against the engine today are exposed here. Full, persisted space↔wall topology does not exist in the engine yet — getEnclosure fills the gap with a geometric best-effort v1 (floor/ceiling/walls + adjacency derived from plan geometry). listAdjacent stays deferred because the shipped surfaces already answer it with value semantics: per-space via the adjacency field of getProperties, in bulk via program.adjacency.getMatrix.
Functions
get(space, properties)
Get selected properties of a space — the selective multi-property read. Only the properties listed in properties are returned; unlisted properties are undefined in the result. This is the canonical home of the read formerly at entity.space.get (now removed). Adjacency is not part of this read — it is an entity property: read it via the adjacency field of getProperties.
- Parameters:
space:ComponentHandle— The space (room mass) to readproperties:PluginDesignQuerySpacesGetProperty[]— Property names to retrieve (value tables below).
- Returns:
PluginDesignQuerySpacesGetResult— Partial record containing only the requested properties - Throws: If the space does not exist or the component is not a space/mass
ts
const [space] = await snaptrude.design.query.listSpaces();
const info = await snaptrude.design.query.spaces.get(space, ["name", "area", "position"]);
console.log(info.name, info.area, info.position);Available properties (PluginDesignQuerySpacesGetProperty):
Basic:
| Value | Return Type | Description |
|---|---|---|
"id" | string | Unique space identifier |
"type" | string | Component type |
"room_type" | string | Room type classification |
"name" | string | Display name of the space — the same label getLabel / getProperties().label return (room_type, then the mesh metadata label). undefined only when the space carries no label at all. |
Derived from parametric data:
| Value | Return Type | Description |
|---|---|---|
"area" | number | Bottom face area (from areas_bottomFace) |
"breadth" | number | Breadth dimension |
"depth" | number | Depth dimension |
"height" | number | Height dimension |
"massType" | string | null | Mass type classification |
"spaceType" | PluginSpaceType | null | Space type classification (Room, Balcony, etc.) |
"areaClass" | PluginAreaClass | Effective area classification ("NET", "GROSS", or "EXCLUDED"), resolving any per-space override then the space-type default. Never null. |
"storey" | number | null | Story the space belongs to |
"departmentId" | string | null | Assigned department ID |
"departmentName" | string | Assigned department name |
"departmentColor" | string | Assigned department color (hex/CSS) |
Derived from mesh:
| Value | Return Type | Description |
|---|---|---|
"position" | Vec3Components | Local position relative to parent |
"absolutePosition" | Vec3Components | Absolute position in the scene |
"rotation" | Vec3Components | Euler rotation angles (radians) |
"rotationQuaternion" | QuatComponents | null | Quaternion rotation, or null if unset |
Derived from brep:
| Value | Return Type | Description |
|---|---|---|
"planPoints" | Vec3Components[] | Bottom outer profile points in world space with y = 0 (2D plan). No closing duplicate point. |
"profile" | CurveHandle[] | Bottom outer profile curves in world space, as opaque curve handles. Read coordinates via the core.geom.query.curve read API. |
"innerProfiles" | CurveHandle[][] | Inner (hole) profile curve loops in world space — one loop per hole, [] when the space has none. Each curve is an opaque handle. |
"innerPlanPoints" | Vec3Components[][] | Inner (hole) profile points in world space with y = 0 — one loop per hole, no closing duplicate, [] when none. |
getFootprint(space)
Get the footprint of a space — its bottom-face contour (outer profile + holes) as a reusable ContourHandle. Equivalent to getBottomContour narrowed to spaces.
- Parameters:
space:ComponentHandle— The space (room mass) to read
- Returns:
ContourHandle | null— The footprint contour, ornullif the space has no extractable footprint
ts
const [space] = await snaptrude.design.query.listSpaces();
const footprint = await snaptrude.design.query.spaces.getFootprint(space);getEnclosure(space)
Get the enclosure of a space — the floor and ceiling that cap it, the bounding walls around it (with each wall's door/window openings), and the neighbouring spaces it shares a boundary with.
Geometric v1 — best effort
The engine has no persisted space→surface topology, so this read is derived purely from plan geometry: the space footprint is matched against the plan footprints/baselines of the slabs and walls on the same storey. Consequences a caller must account for:
faceIndicesis always[]. The triangle→B-rep-face provenance that would populate it is a separate engine capability that does not exist yet — read a surface's full geometry viageometry.getBrepon itscomponentinstead.- After heavy edits (move / stretch / boolean) or on imported models the plan match can miss or mis-attribute a surface — treat the result as advisory.
adjacentSpacescomes from the same adjacency data asprogram.adjacency.getMatrix.sharedEdgeLengthis always0(that data exposes no edge length), andsharedSurfaceis the common boundary wall only when one is found geometrically (elsenull).
- Parameters:
space:ComponentHandle— The space (room mass) to read
- Returns:
PluginSpaceEnclosure| null—nullwhenspaceis not a space (room mass) or has no extractable plan footprint - Throws:
HANDLE_INVALIDif the space handle is gone/forged - Read-only; never mutates the model.
ts
const [space] = await snaptrude.design.query.listSpaces();
const enclosure = await snaptrude.design.query.spaces.getEnclosure(space);
if (enclosure) {
const walls = enclosure.surfaces.filter((s) => s.role === "wall");
const external = walls.filter((s) => s.isExternal);
console.log(`${walls.length} walls, ${external.length} external`);
}PluginSpaceEnclosure
| Property | Type | Description |
|---|---|---|
space | ComponentHandle | The queried space |
surfaces | PluginEnclosureSurface[] | Floor + ceiling + bounding walls, as a flat list (any of the three may be absent when no plan match is found) |
adjacentSpaces | PluginAdjacentSpace[] | Neighbouring spaces from the adjacency data ([] when adjacency was never computed) |
PluginEnclosureSurface
| Property | Type | Description |
|---|---|---|
role | "floor" | "ceiling" | "wall" | The surface's role in the enclosure |
component | ComponentHandle | The wall / slab / roof entity |
faceIndices | number[] | B-rep face indices facing the space. Always [] in geometric v1 |
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 |
openings | { kind, component }[] | Doors/windows/voids in the surface (walls only; [] for floor/ceiling in v1). kind ∈ "door" | "window" | "void", component is a ComponentHandle |
PluginAdjacentSpace
| Property | Type | Description |
|---|---|---|
space | ComponentHandle | The neighbouring space |
sharedSurface | ComponentHandle | null | The common boundary wall when one is found geometrically, else null (open-plan / non-physical adjacency) |
sharedEdgeLength | number | Plan-unit length of the shared boundary — always 0 in geometric v1 |
Reference Lines
Reference-line-specific reads. Accessed via snaptrude.design.query.referenceLines.
A reference line is a 2D guide line (or arc) in the scene, typically used for grid lines and alignment guides. Enumerate reference lines with listReferenceLines; read an individual line's properties here. This is the canonical home of the read formerly at entity.referenceLine.get (now deprecated).
Types
PluginReferenceLineGetProperty
Available properties that can be queried on a reference line via referenceLines.get.
| Value | Return Type | Description |
|---|---|---|
"curve" | CurveHandle | The curve geometry of the reference line, as an opaque handle |
PluginReferenceLineGetResult
A partial object — only the properties that were requested in properties will be present.
| Property | Type | Description |
|---|---|---|
curve | CurveHandle (optional) | The curve geometry of the reference line, as an opaque handle |
Functions
get(referenceLineId, properties)
Get properties of a reference line by its ID. Only the properties listed in properties are returned — unlisted properties will be undefined in the result.
- Parameters:
referenceLineId:string— The unique reference line IDproperties:PluginReferenceLineGetProperty[]— Array of property names to retrieve
- Returns:
PluginReferenceLineGetResult— A partial object containing only the requested properties - Throws: If the reference line does not exist
ts
const [refLine] = await snaptrude.design.query.listReferenceLines();
const result = await snaptrude.design.query.referenceLines.get(refLine, ["curve"]);
// result.curve is an opaque CurveHandle; read its coordinates via
// `snaptrude.core.geom.query.curve`.Design graph
The project's planar adjacency graph, read as a detached snapshot. Accessed via snaptrude.design.query.getGraph().
The graph is the merged bottom-face outline of every room/department space (slabs and roofs contribute their edges too), maintained live as the model changes. It is a shared-edge graph, which is what makes it worth reading instead of re-deriving geometry: two regions that name the same edgeId are exactly adjacent — no tolerance, no polygon clipping — and an edge with a single owner is an exterior edge. Node degree gives junctions for free.
The snapshot holds plain records only: no live scene or graph objects, so changing the result cannot change the project. All geometry is in world-space Snaptrude units.
Scope and limits, so nothing here has to be guessed:
- Never
null. The graph always exists in this host, sogetGraph()always returns a snapshot; a model with nothing in the graph returns{ proposalId, nodes: [], edges: [], regions: [] }. - Entities predating the graph are absent. Anything created in this build — by a plugin, by an agent worker, or by hand — enters the graph immediately. Legacy geometry saved before it existed does not. An empty snapshot on a populated model means "fall back to footprints", not "nothing is built".
edge.wall,region.floorandregion.slabare alwaysnull. This graph binds no such components; the fields exist so the contract matches and are reported honestly rather than filled in.- No semantics. Join
region.owners/edge.ownerstogetPropertiesandmeasurefor label, storey, area. - A region whose boundary cannot be fully resolved back to graph edges is omitted rather than reported half-built; the rest of the snapshot is unaffected.
Repeated calls between edits are free — the snapshot is memoized on the graph's mutation counter.
Types
PluginDesignGraphElementId
A non-empty string. Opaque identity of one element in a snapshot, stable across snapshots within a project. Treat it only as a reference between records in one snapshot.
PluginDesignGraphCurve
A discriminated union on type.
type | Properties | Description |
|---|---|---|
"line" | start, end | A straight edge |
"arc" | start, end, centre, axis | Travels from start to end around centre in the right-hand direction of axis |
All four are Vec3Components in world-space Snaptrude units.
PluginDesignGraphNode
| Property | Type | Description |
|---|---|---|
id | PluginDesignGraphElementId | Node identity |
position | Vec3Components | World position |
owners | ComponentHandle[] | Every component whose outline meets here |
PluginDesignGraphEdge
| Property | Type | Description |
|---|---|---|
id | PluginDesignGraphElementId | Edge identity |
curve | PluginDesignGraphCurve | Line or arc geometry |
startNodeId | PluginDesignGraphElementId | Endpoint, referencing nodes |
endNodeId | PluginDesignGraphElementId | Endpoint, referencing nodes |
owners | ComponentHandle[] | One owner = exterior edge; two = party wall between those spaces |
wall | ComponentHandle | null | Always null in this host |
PluginDesignGraphBoundaryEdge
| Property | Type | Description |
|---|---|---|
edgeId | PluginDesignGraphElementId | The edge this boundary step uses |
direction | "forward" | "reverse" | Whether the boundary follows the edge from its start node to its end node |
PluginDesignGraphRegion
| Property | Type | Description |
|---|---|---|
id | PluginDesignGraphElementId | Region identity |
boundary | { outer: PluginDesignGraphBoundaryEdge[], holes: PluginDesignGraphBoundaryEdge[][] } | Ordered, closed, directed edge loops |
owners | ComponentHandle[] | The space this region belongs to |
floor | ComponentHandle | null | Always null in this host |
slab | ComponentHandle | null | Always null in this host |
Boundary loops are loops of edges, not points, because graph subdivision can split one region curve into several edges.
PluginDesignGraphSnapshot
| Property | Type | Description |
|---|---|---|
proposalId | string | Identity token for the snapshot |
nodes | PluginDesignGraphNode[] | Every node |
edges | PluginDesignGraphEdge[] | Every edge |
regions | PluginDesignGraphRegion[] | Every resolvable enclosed region |
Functions
getGraph()
Get a detached snapshot of the design graph. Read-only: no scene change, no history entry.
- Parameters: none
- Returns:
PluginDesignGraphSnapshot— nevernull; an empty graph is a snapshot with empty arrays
ts
const graph = await snaptrude.design.query.getGraph();
const nodesById = new Map(graph.nodes.map((n) => [n.id, n]));
// Exterior edges of the model: exactly the edges with a single owner.
const exterior = graph.edges.filter((e) => e.owners.length === 1);
// Which spaces touch which, and along which edge.
for (const edge of graph.edges) {
if (edge.owners.length === 2) {
const [a, b] = edge.owners;
console.log(a, b, nodesById.get(edge.startNodeId)?.position);
}
}Spatial queries
Ray casting and world bounds. Accessed via snaptrude.design.query.spatial.
These answer the two questions the filter-based reads cannot: what is along this line, and how big is the world. A ray is the way to ask about outlook, clearance and what a facade faces — cast one outward from a facade and read the first thing it meets.
Candidates are the same entities listEntities enumerates, minus what a viewer cannot see: user-hidden and disabled components are skipped, because a ray stopping on invisible geometry reports a clearance nobody in the model can see. Restrict the set further with options.types.
All coordinates and lengths are world-space Snaptrude units. Triangle tests run against the scene's acceleration structure, so a full-scene cast is cheap; the cost that matters is the number of rays.
Types
PluginSpatialRay
| Property | Type | Description |
|---|---|---|
origin | Vec3Components | Where the ray starts |
direction | Vec3Components | Normalised host-side, so its magnitude is ignored. A zero vector is a validation error |
maxDistance | number (optional) | How far to look. Defaults to the diagonal of bounds() (10 000 when that is null) |
PluginSpatialHit
| Property | Type | Description |
|---|---|---|
entity | ComponentHandle | What was hit |
type | PluginEntityType | Its entity kind |
distance | number | Distance along the ray from origin |
point | Vec3Components | Where the ray meets the surface |
PluginSpatialRayResult
| Property | Type | Description |
|---|---|---|
hits | PluginSpatialHit[] | Nearest first, at most limit long. A miss is [] |
Functions
castRays(rays, options?)
Cast rays against the project's visible geometry.
- Parameters:
rays:PluginSpatialRay[]— 1 to 2000 raysoptions.types:PluginEntityType[](optional) — restrict the candidate setoptions.limit:number(optional, default1) — maximum hits reported per ray, 1 to 16
- Returns:
PluginSpatialRayResult[]— one entry per input ray, in input order - Throws:
VALIDATIONif a direction is the zero vector, the ray list is empty or over 2000, orlimitis outside 1–16
ts
// How far is it from this facade point to the nearest thing in front of it?
const [result] = await snaptrude.design.query.spatial.castRays([
{ origin: { x: 0, y: 3, z: 0 }, direction: { x: 0, y: 0, z: 1 }, maxDistance: 120 }
]);
const nearest = result.hits[0]; // undefined when the ray hits nothing
if (nearest) console.log(nearest.type, nearest.distance);bounds()
World axis-aligned bounding box over everything castRays could hit.
- Parameters: none
- Returns:
{ min: Vec3Components, max: Vec3Components } | null—nullwhen there is no visible geometry
ts
const box = await snaptrude.design.query.spatial.bounds();Grids
Structural-grid-specific reads. Accessed via snaptrude.design.query.grids.
A grid (grid group) is a structural/documentation grid: one or more wings — families of parallel lines sharing a local frame — each holding tagged grid lines (straight lines, or a lone arc). Enumerate grids with listGrids; read an individual grid's structure here.
Lines have no stable identity of their own — mutating APIs address them positionally as { wing, line } index pairs into the wings arrays returned by grids.get. Structural edits (adding/removing lines, count changes) re-index lines; re-query after mutating.
Units: angle is in degrees (positive = CCW about +Y); spacing, span, bounds, and curve coordinates are in Snaptrude units, world space.
Types
PluginGridGetProperty
Available properties that can be queried on a grid via grids.get.
| Value | Return Type | Description |
|---|---|---|
"angle" | number | World rotation about +Y, in degrees |
"props" | PluginGridLineProps | Group-level default line properties |
"storeyVisibility" | PluginGridStoreyVisibility | Active storey + storeys where the grid is hidden in 2D |
"wings" | PluginGridWingRecord[] | Full wing/line structure with world-space curve handles (the heavy read) |
"bounds" | BBoxComponents | World-space axis-aligned bounding box of the grid geometry |
PluginGridLineProps
Visual properties of grid lines — the group's defaults and any per-line overrides share this shape.
| Property | Type | Description |
|---|---|---|
style | "SOLID" | "DASHED" | "DOT_DASH" | Line style |
weight | number | Line weight |
color | string | Line color as a #rrggbb hex string |
showStart | boolean | Whether the start-end tag bubble shows |
showEnd | boolean | Whether the end-end tag bubble shows |
PluginGridWingRecord
One wing of a grid — a family of parallel lines sharing a local frame.
| Property | Type | Description |
|---|---|---|
spacing | number | Nominal spacing between adjacent lines (Snaptrude units) |
tagStart | string | First tag of the wing's deterministic tag sequence |
span | { min: number; max: number } (optional) | Explicit family length along the wing's line axis, when baked |
lines | PluginGridLineRecord[] | The wing's lines, in wing order (index = the line positional address) |
PluginGridLineRecord
One grid line within a wing.
| Property | Type | Description |
|---|---|---|
curve | CurveHandle | The line's geometry in world space (line or arc; read coordinates via core.geom.curve) |
tag | string | The line's grid tag (e.g. "A", "3") |
visible | boolean | Persisted per-line visibility |
props | PluginGridLineProps | Effective visual properties (group defaults merged with this line's overrides) |
overrides | Partial<PluginGridLineProps> (optional) | This line's raw overrides, when any exist |
PluginGridStoreyVisibility
Per-storey 2D-plan visibility of a grid.
| Property | Type | Description |
|---|---|---|
activeStorey | number | The storey the grid belongs to |
disabledOnStoreys | number[] | Storeys on whose 2D plan the grid is hidden |
Functions
get(gridId, properties)
Get properties of a grid by its ID. Only the properties listed in properties are returned — unlisted properties will be undefined in the result. wings is the heavy read (it registers a world-space CurveHandle per line); request it only when the geometry is needed.
- Parameters:
gridId:string— The unique grid group IDproperties:PluginGridGetProperty[]— Array of property names to retrieve
- Returns:
PluginGridGetResult— A partial object containing only the requested properties - Throws: If the grid does not exist
ts
const [grid] = await snaptrude.design.query.listGrids();
const { wings } = await snaptrude.design.query.grids.get(grid, ["wings"]);
// wings[0].lines[0].curve is an opaque CurveHandle in world space;
// read its coordinates via `snaptrude.core.geom.curve`.Errors
Failed calls reject with a typed PluginError — see Error Handling. Conditions specific to this namespace:
| Code | Thrown by | When | details |
|---|---|---|---|
HANDLE_INVALID | getLabel, getEntityRef, listChildren, getHost, getProperties, geometry.*, spaces.getFootprint | The component handle cannot be resolved | — |
HANDLE_INVALID | all list* | filter.overlaps names an unresolvable id — this fails loudly rather than silently returning [] | — |
HANDLE_INVALID | spaces.get | The id does not resolve to a live space/mass (not-found and wrong-kind collapse to the one code) | — |
HANDLE_INVALID | referenceLines.get | The id is unknown or does not belong to a reference line | — |
HANDLE_INVALID | grids.get | The id is unknown or does not belong to a grid group | — |
PRECONDITION_FAILED | spaces.get | A brep-derived property (planPoints, innerPlanPoints) is requested on a space without B-rep geometry; or the not-yet-wired profile/innerProfiles properties are requested | property — the offending property, handles — the space |
VALIDATION | geometry.getTriangulatedMeshes | The requested components total more than 500 000 triangles — the message includes the actual total; split the component list and call per batch | — |
The probe-style reads are deliberately total: getById/getEntityType/measure/getStaircaseParams/getParameters return null and exists returns false for a gone id, getBoundingBox skips unresolvable members (null only when none resolve), and the geometry.* / getFootprint reads return null for a resolvable component that lacks the requested geometry (no B-rep, no bottom face, no derivable centerline) — none of these throw (getTriangulatedMeshes likewise returns { id, positions: [], indices: [] } rather than throwing for a resolvable component with no readable mesh).