Skip to content

Scripting & automation

CivilKit Studio can be driven from code through window.CivilKit - load a model, solve it and read the results without clicking. It runs right in the page: the browser console, or a page that embeds Studio. Nothing to install.

Three ways to run it

The same window.CivilKit API is reachable three ways - use whichever fits:

  • The in-app Console tab - the results panel has a Console tab with a code box, a Run button and ready-made examples. No devtools needed; CivilKit, the raw fem internals, a model snapshot and print() are already in scope, and top-level await works.
  • The browser devtools console - press F12 (or right-click → Inspect) → Console, type CivilKit.version, and if you get a version string back the API is live.
  • Headless, from any agent or CI - drive a real (invisible) Studio, with screenshots, over MCP or a one-shot CLI. See Headless & MCP.

Wait until it's ready

Right after a page load the solver may still be booting. Gate a script with CivilKit.isReady() (it never throws) before calling anything else. The in-app Console already does this for you.

A curated, client-side surface

window.CivilKit is a v1 surface: a stable, curated subset of what Studio can do, aimed at the common "build → load → solve → read" loop. In the browser every call runs client-side (the solver runs in your page); the headless engine runs the identical API in a headless Chromium. There is no remote REST endpoint - the raw, larger internal API lives on window.__fem if you need to go deeper.

The methods you'll use most

A handful of calls cover almost everything. See the full Scripting API reference for the complete list, arguments and return shapes.

MethodWhat it does
loadSample(key)Load a built-in gallery model, e.g. 'prattTruss', 'steelPortalReal'
loadModel(model) / getModel()Load a model object / read the current one (a clone)
validate()Run the model health check; returns a list of issues (empty = clean)
solve()Run the analysis (returns a Promise with a results summary)
assignSection(members, name)Assign a real catalogue section (e.g. '310UC158') to members
windLoads(opts) / seismicLoads(opts)Add AS/NZS 1170.2 wind / AS 1170.4 seismic as load cases
loadCombinations()Generate the code load combinations
report()Build the calc report HTML (same as File → Report)
getResultsSummary()Compact summary of the last solve, or null
getMemberUtilisations()Per-member governing utilisation (the design table numbers)
getReactions() / getDisplacements()Reactions / nodal displacements for the active load case
on(event, cb) / off(event, cb)Subscribe / unsubscribe to 'solved' and 'modelChanged'

Load a sample, solve, read peak utilisation

This is the bread-and-butter loop: pick a model, solve it, and find the hardest-working member.

js
await CivilKit.loadSample('steelPortalReal');
const summary = await CivilKit.solve();
console.log('Solved:', summary);

// Governing utilisation per member (1.0 = at capacity).
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})`);

getMemberUtilisations() returns null if nothing has been solved yet, so always solve() first. Each entry carries gov (the ratio), govBy (the governing clause), status ('OK' / 'OVER') and the design code.

React to every solve (the 'solved' event)

Subscribe to 'solved' to run something automatically each time the model is solved - handy for a live dashboard or a logging script. The callback receives { analType, dt } (analysis type and solve time in ms).

js
const off = CivilKit.on('solved', ({ analType, dt }) => {
  console.log(`solved (${analType}) in ${dt} ms`);
  const utils = CivilKit.getMemberUtilisations() ?? [];
  const overs = utils.filter(u => u.status === 'OVER').length;
  if (overs) console.warn(`${overs} member(s) over capacity`);
});

await CivilKit.solve();   // the handler above fires
off();                    // unsubscribe when you're done

on(...) returns an unsubscribe function; you can also call CivilKit.off('solved', cb) with the same callback. The other event, 'modelChanged', fires after any edit (programmatic or interactive) and is a good cue that the current results are now stale.

Get the model and export it

To capture the current model - for a snapshot, a diff, or re-loading later - read it with getModel() (a clone, so editing it won't touch Studio) or serialise it with exportJson().

js
const model = CivilKit.getModel();
console.log(`${model.members.length} members, ${model.nodes.length} nodes`);

const json = CivilKit.exportJson();        // re-loadable snapshot
// later: CivilKit.loadModel(JSON.parse(json));

For BIM/CAD deliverables there are exportModelIfc(), exportJointsIfc() and exportDxf() - all covered in the reference. For the design numbers themselves, the same utilisations also drive the on-screen analysis and results.

Design a real building end-to-end

The scripting API isn't just "load → solve → read" - it drives the whole design workflow. Given a model (built by hand, imported, or generated), you can assign real catalogue sections, apply the AS/NZS code loads, generate the combinations, solve and check to AS 4100, and produce the report - all in one script.

js
// Real catalogue sections instead of placeholders
await CivilKit.assignSection(columnIds, '310UC158');   // ids from getMembers()
await CivilKit.assignSection(beamIds,   '610UB101');

// Code loads as named cases (the built-in AS/NZS engines)
CivilKit.windLoads({ region: 'A', terrain: '3', direction: 'x' });  // AS/NZS 1170.2
CivilKit.seismicLoads({ zone: '0.08', soil: 'De' });                // AS 1170.4

// Combinations from the case types, then solve + check
CivilKit.loadCombinations();          // -> 1.35G, 1.2G+1.5Q, 1.2G+Wu+psiCQ, G+Eu+psiCQ, ...
const summary = await CivilKit.solve();
console.log(summary.memberCapacities, 'members checked to AS 4100');

// The stamped calc report (title block, combinations, member design, results)
const html = CivilKit.report();

For combinations to come out right, each load case needs a type: windLoads and seismicLoads tag their own (Wind / Seismic); give your Dead and Live cases a load_case_type (0 Dead, 1 Live) when you build the model. This is exactly how examples/office-building.mjs raises and checks a full 10-storey office from a script.

Read more

This page is the friendly tour. Every method, its arguments, return shape and edge cases live in the Scripting API reference.