Scripting SDK¶
The @ifc-lite/sdk package is the scriptable BIM API behind ifc-lite eval, ifc-lite run, the viewer's script console, and the extension system. Everything hangs off a single bim object: query entities, read properties and quantities, walk the spatial tree, edit properties, run clashes, and export, all through one typed surface.
The bim API surface¶
The bim object (a BimContext) groups its capabilities into namespaces, plus a set of top-level helpers for the common query paths. The main namespaces:
| Namespace | Purpose |
|---|---|
bim.model |
Loaded-model management (list, active model) |
bim.query() |
Start a fluent entity query chain |
bim.viewer |
Colorize, isolate, hide/show, section, fly-to (drives a running viewer) |
bim.mutate |
Property and attribute edits |
bim.store |
Document-level edits (add/remove entities, positional attributes) |
bim.lens |
Lens visualization presets |
bim.create |
Create IFC elements from scratch |
bim.export |
Export to CSV, glTF, STEP, HBJSON, and more |
bim.clash |
Run clash rules and the discipline matrix |
bim.ids |
IDS validation |
bim.bcf |
BCF topics, comments, viewpoints |
bim.files, bim.schedule, bim.spatial, bim.spaces, bim.drawing, bim.list, bim.bsdd, bim.events, bim.sandbox |
Supporting namespaces (file access, scheduling, spatial ops, space program, 2D drawings, entity tables, bSDD lookups, events, sandboxed sub-scripts) |
On top of the namespaces, BimContext exposes direct helpers for the hot paths, so you rarely reach into internals: bim.query(), bim.entity(ref), bim.properties(ref), bim.quantities(ref), bim.materials(ref), bim.classifications(ref), bim.storeys(), bim.contains(ref), bim.containedIn(ref), bim.storey(ref), bim.path(ref), and more.
// Fluent query
const externalWalls = bim.query()
.byType('IfcWall')
.where('Pset_WallCommon', 'IsExternal', '=', true)
.toArray();
// Direct helpers
const storeys = bim.storeys();
for (const s of storeys) {
console.log(`${s.name}: ${bim.contains(s.ref).length} elements`);
}
Running scripts from the CLI¶
eval — one-liners¶
ifc-lite eval evaluates a single JavaScript expression with bim in scope:
# Count walls
ifc-lite eval model.ifc "bim.query().byType('IfcWall').count()"
# List storey names
ifc-lite eval model.ifc "bim.storeys().map(s => s.name)"
# Per-entity evaluation: --type binds `ref` and `entity` to each match
ifc-lite eval model.ifc "bim.quantity(ref, 'GrossSideArea')" --type IfcWall --limit 3
With --type, the expression runs once per matching entity with ref (the entity reference) and entity in scope; without it, the expression runs once with bim. Add --json for machine-readable output.
run — script files¶
ifc-lite run executes a .js file with bim and console available:
ifc-lite run analysis.js model.ifc
# Stream bim.viewer.* calls to a running `ifc-lite view` instance
ifc-lite run paint.js model.ifc --viewer 3456
const walls = bim.query().byType('IfcWall').toArray();
console.log(`Found ${walls.length} walls`);
for (const wall of walls) {
const psets = bim.properties(wall.ref);
const common = psets.find(p => p.name === 'Pset_WallCommon');
const isExternal = common?.properties.find(p => p.name === 'IsExternal');
console.log(` ${wall.name}: external=${isExternal?.value ?? 'unknown'}`);
}
eval and run are not sandboxed
The CLI eval and run commands execute your code directly in the Node
host (via new Function), with full access to the machine. They are meant
for your own scripts and trusted LLM-generated snippets on your own
files. To run untrusted code, use the sandbox below. Pointing --viewer
at a running viewer streams bim.viewer.* calls to it in real time.
Discovering the API for LLM tooling¶
ifc-lite schema dumps the scriptable API as JSON so an LLM (or you) can discover methods before writing code:
ifc-lite schema # full schema with params and return types
ifc-lite schema --compact # names and descriptions only
The schema covers the scriptable namespaces, model, query, viewer, mutate, store, lens, create, files, schedule, clash, and export, and includes each method's parameter names, return type, and LLM semantic hints (useWhen, task tags). Running ifc-lite schema first is the recommended way to author correct eval/run code. See Using with LLM Terminals for the wider agent workflow.
The sandbox¶
@ifc-lite/sandbox runs scripts in an isolated QuickJS-in-WASM interpreter, the mechanism the viewer and the extension system use to run untrusted code safely. Each sandbox gets a fresh QuickJS context; the bim API is rebuilt inside it, gated by permissions; and TypeScript is transpiled to JavaScript before execution.
import { Sandbox } from '@ifc-lite/sandbox';
const sandbox = new Sandbox(bim, {
permissions: { query: true, mutate: false }, // read-only by default
limits: { memoryBytes: 64 * 1024 * 1024, timeoutMs: 30_000 },
});
await sandbox.init();
const result = await sandbox.eval(`bim.query.create().byType('IfcWall').count()`);
console.log(result.value, result.logs, result.durationMs);
Or reach it through the SDK as bim.sandbox.eval(script, config), which returns the same ScriptResult (value, captured logs, durationMs).
Permissions gate which namespaces the script can touch (model, query, viewer, mutate, store, lens, export, files). The defaults are read-only: mutate and store are off, everything else on. Limits cap resources, defaulting to 64 MB heap, a 30-second timeout, and a 512 KB stack. A namespace whose permission is disabled is simply not built on the sandboxed bim handle, so a script cannot call what it was not granted.
Sandboxed vs. direct¶
| Path | Runtime | Isolation |
|---|---|---|
ifc-lite eval / ifc-lite run |
Node host, new Function |
None, full host access |
bim.sandbox.eval / @ifc-lite/sandbox |
QuickJS-in-WASM | Permissions + memory/CPU/stack limits |
| Extensions | QuickJS-in-WASM | Capability-gated bundle sandbox |
Use the direct path for your own scripts; use the sandbox when the code comes from somewhere you do not fully trust.
Embedding programmatically¶
Create a BimContext yourself with createBimContext. It needs either a local backend (viewer-embedded) or a transport (connected to a running viewer):
import { createBimContext } from '@ifc-lite/sdk';
// Local mode: drive an in-process backend
const bim = createBimContext({ backend: myLocalBackend });
const wallCount = bim.query().byType('IfcWall').count();
// Remote mode: talk to a running viewer over a transport
const remote = createBimContext({ transport: myBroadcastTransport });
remote.viewer.colorize(refs, '#ff0000');
In remote mode bim.viewer.* calls are forwarded to the connected viewer, which is exactly how ifc-lite run script.js model.ifc --viewer <port> works under the hood. For the full type surface, see the @ifc-lite/sdk README and the TypeScript API reference.