Skip to content

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).

ValueEntity
"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.

FieldTypeKeeps components where
typesPluginEntityType[]entity kind ∈ types (ignored by per-type list* — the method fixes the kind)
storeysnumber[]storey/floor level ∈ storeys (negative = basement)
buildingIdsstring[]building ∈ buildingIds
structureIdsstring[]structure ∈ structureIds
labelstringlabel (room_type ?? mesh label) === label
massTypesPluginMassType[]mass-type ∈ (masses only)
spaceTypesPluginSpaceType[]space-type ∈ (spaces only)
departmentIdPluginDepartmentIddepartment ===
isSelectedbooleancurrent selection state ===
isLockedbooleanlock flag ===
isHiddenbooleanuser-hidden flag ===
withinBBoxComponentsthe entity's bounding box intersects the region
overlapsComponentHandlethe 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.
idsComponentHandle[]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.

PropertyTypeDescription
idstringThe entity id
typePluginEntityTypeEntity kind
labelstringroom_type ?? mesh label ?? ""
storeynumberStorey/floor level
buildingIdstringOwning building
structureIdstringOwning structure
massTypestringMass-type (masses only)
spaceTypePluginSpaceTypeSpace-type (spaces only)
areaClassPluginAreaClassArea classification (spaces only)
departmentIdstringDepartment (masses only)
buildingTypestringAssigned 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)
isLockedbooleanLock flag
isHiddenbooleanUser-hidden flag
isSelectedbooleanCurrent selection state
boundingBoxBBoxComponentsWorld-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.

PropertyTypeDescription
areanumber (optional)Bottom-face area
volumenumber (optional)Solid volume (curved solids and families: see below)
lengthnumber (optional)Running length (linear elements)
thicknessnumber (optional)Panel Thickness — walls (straight orthogonal only) and slabs/floors/roofs/ceilings
heightnumber (optional)Panel Height — walls only
boundingBoxBBoxComponentsWorld-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.

PropertyTypeDescription
staircaseTypestringType token (straight, dogLegged, lShaped, …)
staircasePresetstringPreset token
staircaseHeightnumberTotal rise
stepsnumberStep count
risernumberRiser height
treadnumberTread depth
widthnumberFlight width
depthnumberFlight depth
baseOffsetnumberBase offset from the storey
isStoreyHeightUnlockedbooleanWhether height is decoupled from the storey
isStaircaseHeightUnlockedbooleanWhether height is decoupled from steps × riser
storeynumberStorey/floor level
wellSizenumber?Well size (well-based presets only)
landingWidthnumber?Landing width (landing-based presets only)
flightStartDistAfterTurnnumber?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.

kindvalues
"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.

opFields
"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:

TypeShapeMeaning
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 typeFields
"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:
  • 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 list
    • filter: 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:
  • Returns: ComponentHandle[] — Wall handles
ts
const groundFloorWalls = await snaptrude.design.query.listWalls({ storeys: [0] });

listSpaces(filter?) ​

List all spaces (room masses), filtered. See listWalls.

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.


listDoors(filter?) ​

List all doors, filtered.


listWindows(filter?) ​

List all windows, filtered.


listFloors(filter?) ​

List all floors, filtered.


listSlabs(filter?) ​

List all slabs, filtered.


listRoofs(filter?) ​

List all roofs, filtered.


listColumns(filter?) ​

List all columns, filtered.


listBeams(filter?) ​

List all beams, filtered.


listCeilings(filter?) ​

List all ceilings, filtered.


listStaircases(filter?) ​

List all staircases, filtered.


listFurniture(filter?) ​

List all furniture, filtered.


listReferenceLines(filter?) ​

List all reference lines, filtered.


listMullions(filter?) ​

List all curtain-wall mullions, filtered.


listGrids(filter?) ​

List all structural grids (grid groups), filtered.


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, or null if 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 — true if it exists, otherwise false

getEntityType(component) ​

Get an entity's PluginEntityType.

  • Parameters:
    • component: ComponentHandle — The entity to read
  • Returns: PluginEntityType | null — The kind, or null if 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, or null if 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, or null if 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 / Height

getBoundingBox(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, or null if the set is empty / none resolve
  • Throws: VALIDATION if 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 — null when 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, or null when 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 read
    • options (optional): { frame?: "local" | "world" } — "local" (default) or "world"
  • Returns: BrepHandle | null — The brep, or null if 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, or null if 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, or null if 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 triangulate
    • options: { includeMaterialIds?: boolean, includeFaceIndices?: boolean } (optional) — includeMaterialIds adds the per-triangle material names; includeFaceIndices adds the per-triangle B-rep face indices
  • Returns: { meshes: PluginTriangulatedMesh[] } — one record { id, positions, indices, materialIds?, faceIds? } per requested component
  • Throws: HANDLE_INVALID if any component handle is gone/forged; VALIDATION if 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 read
    • properties: 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:

ValueReturn TypeDescription
"id"stringUnique space identifier
"type"stringComponent type
"room_type"stringRoom type classification
"name"stringDisplay 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:

ValueReturn TypeDescription
"area"numberBottom face area (from areas_bottomFace)
"breadth"numberBreadth dimension
"depth"numberDepth dimension
"height"numberHeight dimension
"massType"string | nullMass type classification
"spaceType"PluginSpaceType | nullSpace type classification (Room, Balcony, etc.)
"areaClass"PluginAreaClassEffective area classification ("NET", "GROSS", or "EXCLUDED"), resolving any per-space override then the space-type default. Never null.
"storey"number | nullStory the space belongs to
"departmentId"string | nullAssigned department ID
"departmentName"stringAssigned department name
"departmentColor"stringAssigned department color (hex/CSS)

Derived from mesh:

ValueReturn TypeDescription
"position"Vec3ComponentsLocal position relative to parent
"absolutePosition"Vec3ComponentsAbsolute position in the scene
"rotation"Vec3ComponentsEuler rotation angles (radians)
"rotationQuaternion"QuatComponents | nullQuaternion rotation, or null if unset

Derived from brep:

ValueReturn TypeDescription
"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, or null if 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:

  • faceIndices is 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 via geometry.getBrep on its component instead.
  • 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.
  • adjacentSpaces comes from the same adjacency data as program.adjacency.getMatrix. sharedEdgeLength is always 0 (that data exposes no edge length), and sharedSurface is the common boundary wall only when one is found geometrically (else null).
  • Parameters:
    • space: ComponentHandle — The space (room mass) to read
  • Returns: PluginSpaceEnclosure | null — null when space is not a space (room mass) or has no extractable plan footprint
  • Throws: HANDLE_INVALID if 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 ​
PropertyTypeDescription
spaceComponentHandleThe queried space
surfacesPluginEnclosureSurface[]Floor + ceiling + bounding walls, as a flat list (any of the three may be absent when no plan match is found)
adjacentSpacesPluginAdjacentSpace[]Neighbouring spaces from the adjacency data ([] when adjacency was never computed)
PluginEnclosureSurface ​
PropertyTypeDescription
role"floor" | "ceiling" | "wall"The surface's role in the enclosure
componentComponentHandleThe wall / slab / roof entity
faceIndicesnumber[]B-rep face indices facing the space. Always [] in geometric v1
isExternalbooleanNo 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 ​
PropertyTypeDescription
spaceComponentHandleThe neighbouring space
sharedSurfaceComponentHandle | nullThe common boundary wall when one is found geometrically, else null (open-plan / non-physical adjacency)
sharedEdgeLengthnumberPlan-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.

ValueReturn TypeDescription
"curve"CurveHandleThe 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.

PropertyTypeDescription
curveCurveHandle (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 ID
    • properties: 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, so getGraph() 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.floor and region.slab are always null. 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.owners to getProperties and measure for 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.

typePropertiesDescription
"line"start, endA straight edge
"arc"start, end, centre, axisTravels from start to end around centre in the right-hand direction of axis

All four are Vec3Components in world-space Snaptrude units.

PluginDesignGraphNode ​

PropertyTypeDescription
idPluginDesignGraphElementIdNode identity
positionVec3ComponentsWorld position
ownersComponentHandle[]Every component whose outline meets here

PluginDesignGraphEdge ​

PropertyTypeDescription
idPluginDesignGraphElementIdEdge identity
curvePluginDesignGraphCurveLine or arc geometry
startNodeIdPluginDesignGraphElementIdEndpoint, referencing nodes
endNodeIdPluginDesignGraphElementIdEndpoint, referencing nodes
ownersComponentHandle[]One owner = exterior edge; two = party wall between those spaces
wallComponentHandle | nullAlways null in this host

PluginDesignGraphBoundaryEdge ​

PropertyTypeDescription
edgeIdPluginDesignGraphElementIdThe edge this boundary step uses
direction"forward" | "reverse"Whether the boundary follows the edge from its start node to its end node

PluginDesignGraphRegion ​

PropertyTypeDescription
idPluginDesignGraphElementIdRegion identity
boundary{ outer: PluginDesignGraphBoundaryEdge[], holes: PluginDesignGraphBoundaryEdge[][] }Ordered, closed, directed edge loops
ownersComponentHandle[]The space this region belongs to
floorComponentHandle | nullAlways null in this host
slabComponentHandle | nullAlways null in this host

Boundary loops are loops of edges, not points, because graph subdivision can split one region curve into several edges.

PluginDesignGraphSnapshot ​

PropertyTypeDescription
proposalIdstringIdentity token for the snapshot
nodesPluginDesignGraphNode[]Every node
edgesPluginDesignGraphEdge[]Every edge
regionsPluginDesignGraphRegion[]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 — never null; 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 ​

PropertyTypeDescription
originVec3ComponentsWhere the ray starts
directionVec3ComponentsNormalised host-side, so its magnitude is ignored. A zero vector is a validation error
maxDistancenumber (optional)How far to look. Defaults to the diagonal of bounds() (10 000 when that is null)

PluginSpatialHit ​

PropertyTypeDescription
entityComponentHandleWhat was hit
typePluginEntityTypeIts entity kind
distancenumberDistance along the ray from origin
pointVec3ComponentsWhere the ray meets the surface

PluginSpatialRayResult ​

PropertyTypeDescription
hitsPluginSpatialHit[]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 rays
    • options.types: PluginEntityType[] (optional) — restrict the candidate set
    • options.limit: number (optional, default 1) — maximum hits reported per ray, 1 to 16
  • Returns: PluginSpatialRayResult[] — one entry per input ray, in input order
  • Throws: VALIDATION if a direction is the zero vector, the ray list is empty or over 2000, or limit is 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 — null when 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.

ValueReturn TypeDescription
"angle"numberWorld rotation about +Y, in degrees
"props"PluginGridLinePropsGroup-level default line properties
"storeyVisibility"PluginGridStoreyVisibilityActive storey + storeys where the grid is hidden in 2D
"wings"PluginGridWingRecord[]Full wing/line structure with world-space curve handles (the heavy read)
"bounds"BBoxComponentsWorld-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.

PropertyTypeDescription
style"SOLID" | "DASHED" | "DOT_DASH"Line style
weightnumberLine weight
colorstringLine color as a #rrggbb hex string
showStartbooleanWhether the start-end tag bubble shows
showEndbooleanWhether the end-end tag bubble shows

PluginGridWingRecord ​

One wing of a grid — a family of parallel lines sharing a local frame.

PropertyTypeDescription
spacingnumberNominal spacing between adjacent lines (Snaptrude units)
tagStartstringFirst 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
linesPluginGridLineRecord[]The wing's lines, in wing order (index = the line positional address)

PluginGridLineRecord ​

One grid line within a wing.

PropertyTypeDescription
curveCurveHandleThe line's geometry in world space (line or arc; read coordinates via core.geom.curve)
tagstringThe line's grid tag (e.g. "A", "3")
visiblebooleanPersisted per-line visibility
propsPluginGridLinePropsEffective visual properties (group defaults merged with this line's overrides)
overridesPartial<PluginGridLineProps> (optional)This line's raw overrides, when any exist

PluginGridStoreyVisibility ​

Per-storey 2D-plan visibility of a grid.

PropertyTypeDescription
activeStoreynumberThe storey the grid belongs to
disabledOnStoreysnumber[]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 ID
    • properties: 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:

CodeThrown byWhendetails
HANDLE_INVALIDgetLabel, getEntityRef, listChildren, getHost, getProperties, geometry.*, spaces.getFootprintThe component handle cannot be resolved—
HANDLE_INVALIDall list*filter.overlaps names an unresolvable id — this fails loudly rather than silently returning []—
HANDLE_INVALIDspaces.getThe id does not resolve to a live space/mass (not-found and wrong-kind collapse to the one code)—
HANDLE_INVALIDreferenceLines.getThe id is unknown or does not belong to a reference line—
HANDLE_INVALIDgrids.getThe id is unknown or does not belong to a grid group—
PRECONDITION_FAILEDspaces.getA brep-derived property (planPoints, innerPlanPoints) is requested on a space without B-rep geometry; or the not-yet-wired profile/innerProfiles properties are requestedproperty — the offending property, handles — the space
VALIDATIONgeometry.getTriangulatedMeshesThe 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).