Skip to content

Scripting API (window.CivilKit)

CivilKit Studio exposes a small, stable scripting surface on window.CivilKit. It lets you drive the tool from code: load a model, solve it, read the results, and export IFC, without depending on Studio's internals.

This is a v1 in-browser scripting API

window.CivilKit is an in-browser API. It runs where Studio is loaded - the browser devtools console, or a page that embeds Studio. There is no remote / REST endpoint; nothing here talks to a server. The solver and the whole API run client-side.

It is a v1 surface: a curated subset of what Studio can do, intended for the common "load -> solve -> read/export" loop. For the full headless solver (raw JSON model in, JSON results out) see the WASM API Reference.

Quick start

Open Studio, then in the browser console:

js
await CivilKit.loadSample('prattTruss');
await CivilKit.solve();
const ifc = CivilKit.exportModelIfc();
console.log(ifc.slice(0, 200));

CivilKit is a frozen object. Every method throws a clear Error (prefixed CivilKit:) if Studio is not ready yet or the backing feature is missing in the current build - check CivilKit.isReady() first if you are scripting against a freshly-loaded page.

Reference

The methods below are grouped: Model, Query, Edit, Solve & Results, Design, Diagnostics, Export, Events.

CivilKit.version

A version string for the scripting surface itself (currently '1.1'). This is the API contract version, not the app or solver version.

js
CivilKit.version   // '1.1'

CivilKit.isReady()

Returns true once the backing internals exist (Studio has booted and the solver module loaded). Never throws. Use it to gate scripts that run right after page load.

js
if (CivilKit.isReady()) { /* safe to call the rest */ }

CivilKit.getModel()

Returns the current model as a plain, serialisable object (a clone - mutating it does not affect Studio). Same shape as the Model JSON schema.

js
const model = CivilKit.getModel();
model.members.length;

CivilKit.loadModel(model)

Loads a model object into Studio (replacing the current model). Accepts the same shape getModel() returns. Throws if model is not an object.

js
const m = CivilKit.getModel();
m.name = 'Edited copy';
CivilKit.loadModel(m);

CivilKit.loadSample(key)

Loads a built-in sample by its gallery key. Throws on a missing/empty key.

js
CivilKit.loadSample('portalFrame');

Common keys: prattTruss, portalFrame, multiStorey, spaceFrame, cantilever, simplySupported, continuousBeam2, floorSlab, steelPortalReal. (The full list is the Studio sample gallery.)

CivilKit.validate()

Runs the model health check and returns an array of issue objects (each with a code and context). An empty array means no problems were found. Returns a clone.

js
const issues = CivilKit.validate();
if (issues.length) console.warn('Model has issues:', issues);

Query

Read-only access to the model collections. Every method returns a clone, so mutating the result never touches Studio's live state.

CivilKit.getNodes() / getMembers() / getSections()

Return cloned arrays of the model's nodes, members, and sections. Each returns [] if the collection is absent.

js
CivilKit.getNodes().length;
CivilKit.getMembers().map(m => m.id);

CivilKit.getNode(id) / getMember(id)

Return a single node/member by its stable string id (not the engineer num), or null if not found.

js
const n = CivilKit.getNode('n1');   // { id, num, x, y, z, restraint_flags }
const m = CivilKit.getMember('m1'); // { id, num, node_a, node_b, section_id, ... }

Edit

Programmatic model editing. These mutate the model the same way the interactive tools do (new id/num, identical object shape), rebuild the scene, push an undo step, and fire the modelChanged event. Editing invalidates the current results (you must re-solve).

CivilKit.addNode({ x, y, z, restraint? })

Adds a node at {x, y, z} (metres). restraint is an optional 6-bit restraint_flags integer (default 0 = free). Returns the created node.

js
const n = CivilKit.addNode({ x: 0, y: 0, z: 0, restraint: 31 }); // pinned base

CivilKit.addMember({ nodeA, nodeB, section?, material?, memberType? })

Adds a member between two existing node ids. nodeA and nodeB are required (and must differ); section/material default to the model's first of each, memberType defaults to 0 (beam). Returns the created member. Throws if a node id is missing or unknown.

js
const m = CivilKit.addMember({ nodeA: n1.id, nodeB: n2.id });

CivilKit.setSupport(nodeId, preset)

Sets a node's support. preset is a name - 'free' (0), 'pinned' (31), 'roller' (30), 'fixed' (63) - or a raw restraint_flags integer. Returns the updated node.

js
CivilKit.setSupport('n1', 'fixed');
CivilKit.setSupport('n2', 31);       // raw flags also accepted

CivilKit.assignSection(members, name, country?)

Assigns a real catalogue section (by name, e.g. '310UC158') to members - all of them (null), an array of ids, or a single id. The members take the true profile geometry and name, not a placeholder. country is the catalogue code (default 'au'). Returns { assigned, section }. Async - it loads the catalogue.

js
await CivilKit.assignSection(null, '310UC158');          // every member
await CivilKit.assignSection(['m1', 'm2'], '610UB101');  // just these two

CivilKit.meshAll(opt?)

Meshes every detected panel (a bay bounded by four members) into a compatible grid of plate elements in one call - the scripted form of the Mesh all tool. opt: { elementSize?, plane?, thickness?, materialId? }. Returns a summary of what was meshed.

js
CivilKit.meshAll({ plane: 'floor', elementSize: 3 });

Solve & Results

CivilKit.solve()

Runs the analysis using the currently-selected analysis type and load cases (the same path as the Solve button). Returns a Promise that resolves to a compact results summary (see getResultsSummary()). Use getResults() for the full object.

js
const summary = await CivilKit.solve();
// { analType: 'static', loadCases: 1, displacements: 8, reactions: 2, memberCapacities: 12 }

CivilKit.getResults()

Returns the full current results object (a clone), or null if nothing has been solved yet. Shape matches the Results JSON schema.

js
const r = CivilKit.getResults();
r?.load_cases[0].displacements;

CivilKit.getResultsSummary()

A compact summary of the current results, or null if nothing has been solved:

js
CivilKit.getResultsSummary();
// { analType, loadCases, displacements, reactions, memberCapacities }

CivilKit.getMemberUtilisations()

Per-member governing utilisation after a solve - the same numbers as the design table:

js
CivilKit.getMemberUtilisations();
// [{ memberId, gov, govBy, status: 'OK' | 'OVER', code: 'AS4100' | 'AS4600' }, ...]

gov is the governing utilisation ratio (1.0 = at capacity); govBy names the governing clause/term. Returns null if nothing has been solved (or the build has no design check).

CivilKit.getReactions()

Support reactions for the active load case, or null if nothing has been solved. Each entry: { node_id, rx, ry, rz, mx, my, mz } (forces in N, moments in N·m).

js
const r = CivilKit.getReactions();
const totalVertical = r.reduce((s, x) => s + x.ry, 0);

CivilKit.getDisplacements()

Nodal displacements for the active load case, or null if nothing has been solved. Each entry: { node_id, ux, uy, uz, rx, ry, rz } (translations in m, rotations in rad).

js
const d = CivilKit.getDisplacements();
const maxSway = Math.max(...d.map(x => Math.abs(x.ux)));

Code loads & design workflow

Turn the built-in AS/NZS load engines into named load cases, generate the code combinations, and produce the report - the scripted equivalent of the load generators and the Report button. These wrap the same Rust engines the UI uses.

CivilKit.windLoads(opts?)

Applies AS/NZS 1170.2 wind as a lateral load case: computes the site design pressure for the building height, then distributes net along-wind storey forces onto the windward-face nodes. opts: { region?, terrain?, direction?, netCp?, returnPeriodFactor?, caseName? } (defaults: region 'A', terrain '3', direction 'x', netCp 1.3, caseName 'Wind'). The created case is tagged load-type Wind (Wu) so combinations pick it up. Returns the derived pressure and total force.

js
CivilKit.windLoads({ region: 'A', terrain: '3', direction: 'x' });
// { q_Pa: 1167, vDes_ms: 44.1, totalForce_kN: 1261, storeys: 10, caseName: 'Wind' }

CivilKit.seismicLoads(opts?)

Applies AS 1170.4 earthquake as a lateral load case: computes the base shear from the model's self-weight and the site, then distributes it up the storeys (Fx proportional to w·hᵏ). opts: { zone?, soil?, ru?, sp?, period?, direction?, caseName? } - period is estimated from the height if omitted. The case is tagged load-type Seismic (Eu). Returns the base shear and storey forces.

js
CivilKit.seismicLoads({ zone: '0.08', soil: 'De' });
// { baseShear_kN: 9471, weight_kN: 55335, period_T: 1.4, k: 1.45, storeyForces: [...] }

CivilKit.loadCombinations(opts?)

Generates the code load combinations from each load case's type (Dead / Live / Wind / Seismic ...) and stores them on the model. opts: { code? } - 'asnzs' (default), 'asce7' or 'eurocode'. Set the case types first (windLoads / seismicLoads tag their own; give Dead/Live cases a load_case_type). Returns the count and combination names.

js
CivilKit.loadCombinations();
// { count: 7, code: 'asnzs', combinations: ['1.35G', '1.2G+1.5Q', '1.2G+Wu+psiCQ', '0.9G+Wu', 'G+Eu+psiCQ', ...] }

CivilKit.report()

Builds the calculation report as an HTML string - the same content as File → Report (title block, load cases and combinations, member design, results). Solve first so the results sections populate.

js
const html = CivilKit.report();      // e.g. save, print, or open in a new tab

Design

CivilKit.autoDesignConnections()

Auto-designs connections over the active model and load case (AS 4100), stores them on the model, and returns the generated connection objects.

js
await CivilKit.solve();                 // connections need solved forces
const conns = CivilKit.autoDesignConnections();

Export

CivilKit.exportModelIfc()

Returns the full structural model as an IFC4 string (members as IfcBeam / IfcColumn with real section profiles). Opens in Revit / Tekla / ArchiCAD.

js
const ifc = CivilKit.exportModelIfc();

CivilKit.exportJointsIfc()

Returns the connections as an IFC4 string (joint assemblies + the Pset_CivilKit_JointTakeoff property set) - the fabricator/estimator deliverable. Run autoDesignConnections() first if you want joints in it.

js
const jointsIfc = CivilKit.exportJointsIfc();

CivilKit.exportJson()

Returns the current model as a JSON string (the same data as getModel(), serialised - analysis/round-trip ready).

js
const json = CivilKit.exportJson();
// save it, then later: CivilKit.loadModel(JSON.parse(json));

CivilKit.exportDxf()

Returns the model geometry as a DXF (CAD) string (members as LINE entities).

js
const dxf = CivilKit.exportDxf();

Diagnostics

Read-only model-health queries. Unlike the toolbar buttons of the same name, these return the lists (no selection/highlight side effects), so a script can inspect a model before solving.

CivilKit.findInstabilities()

Returns the nodes likely to cause a singular stiffness matrix:

js
CivilKit.findInstabilities();
// [{ nodeId, reason: 'free rotation (...)' | 'collinear truss ...' | 'reaches no support ...' }, ...]

This is a heuristic, not an exhaustive rank-deficiency finder - an empty list does not guarantee the model will solve.

CivilKit.findOrphanNodes()

Returns the nodes connected to no member or plate:

js
CivilKit.findOrphanNodes();
// [{ id, num }, ...]

Events

Subscribe to Studio events from a script. Supported events:

  • 'solved' - fired after a successful solve. The callback receives { analType, dt } (analysis type and solve time in ms).
  • 'modelChanged' - fired after an edit lands an undo step (programmatic or interactive). The callback receives no detail.

CivilKit.on(event, cb)

Registers cb for event. Returns an unsubscribe function. Throws on an unknown event name.

js
const off = CivilKit.on('solved', ({ analType, dt }) => {
  console.log(`solved (${analType}) in ${dt} ms`);
});
// later:
off();
js
CivilKit.on('modelChanged', () => console.log('model edited; results are stale'));

CivilKit.off(event, cb)

Removes a handler previously registered with on(event, cb) (pass the same event and cb). Returns true if a handler was removed. Either off(...) or the function returned by on(...) unsubscribes.

js
function onSolved() { /* ... */ }
CivilKit.on('solved', onSolved);
CivilKit.off('solved', onSolved);

End-to-end example

js
// React to every solve (optional - logs analysis type + time).
CivilKit.on('solved', ({ analType, dt }) => console.log(`solved ${analType} in ${dt} ms`));

// 1. Load a sample and check its health BEFORE touching it.
await CivilKit.loadSample('steelPortalReal');
console.log('Instabilities:', CivilKit.findInstabilities());
console.log('Orphan nodes:', CivilKit.findOrphanNodes());

// 2. Edit the model: add a braced bay between two existing nodes.
const nodes = CivilKit.getNodes();
const base = CivilKit.addNode({ x: 6, y: 0, z: 0, restraint: 31 });  // new pinned base
CivilKit.addMember({ nodeA: nodes[0].id, nodeB: base.id });          // new brace

// 3. Validate, then solve.
const issues = CivilKit.validate();
if (issues.length) console.warn('Model issues:', issues);
const summary = await CivilKit.solve();
console.log('Solved:', summary);

// 4. Read the design results.
const utils = CivilKit.getMemberUtilisations();
const worst = utils.reduce((a, b) => (b.gov > (a?.gov ?? -1) ? b : a), null);
console.log(`Worst member: M${worst.memberId} u=${worst.gov.toFixed(2)} (${worst.status}, ${worst.code})`);
console.log('Reactions:', CivilKit.getReactions());

// 5. Design connections and export the deliverables.
const conns = CivilKit.autoDesignConnections();
console.log(`Designed ${conns.length} connections`);
const modelIfc  = CivilKit.exportModelIfc();
const jointsIfc = CivilKit.exportJointsIfc();
const json      = CivilKit.exportJson();   // re-loadable model snapshot

Stability & scope

This is a deliberately small surface. It wraps Studio's internals so your scripts keep working as the internals change. The much larger window.__fem object also exists, but it is internal and unstable (raw snake_case wasm functions, live State) - do not script against it.