Skip to the guide
‹ Back to the app CivilKit Buckling · Guide

Capability reference

Every call a Buckling module can make to the engine and the app, exactly as built: its argument, what it returns, the units, the errors, and an example. Units are N, mm and MPa throughout; moments are N·mm and the bimoment N·mm².

CapabilityGives
getModelThe section on screen (or the worked example's fixture): materials, nodes, elements, springs, constraints, loads, fy, analysis settings.
hasResultsWhether the app has a solved curve for the current model.
getResultsThe app's solved curve and its classified minima.
sectionPropsA, centroid, Ixx, Izz, Ixz, principal axes, J, shear centre, Cw.
stressThe stress at every node for given actions P, Mxx, Mzz, M11, M22, B.
firstYieldThe first-yield actions Py, Mxx, Mzz, M11, M22, B for a given fy.
signatureA signature curve (or a general-BC curve), optionally with cFSM curves, and its minima.
modesThe buckling modes at one length, with their cFSM classification.
proposeModelOffer a section to the analysis; the user decides.
logA line in the command line's report. Free: never declared.

Calling a capability#

import civilkit gives the host module. Each capability takes one argument, a JSON string, and returns a JSON string, as in CivilKit Studio. The exceptions are hasResults, which returns a Python True or False, and log, which returns nothing. A capability with no argument (getModel, getResults) is called with none. The starter's helper does both directions:

def call(name, arg=None):
    f = getattr(civilkit, name)
    return json.loads(f(json.dumps(arg)) if arg is not None else f())

so call("firstYield", {"fy": 450}) returns a dict. Every argument key is optional unless it says otherwise below; an argument of {} (or no argument) takes every default.

Declaring capabilities#

A module lists the capabilities it calls in its manifest:

"capabilities": ["getModel", "firstYield", "stress", "signature"]

A call to a capability that is not listed is refused with Capability 'signature' not declared in the module manifest, which ends that run (or fails that worked example). A name that is declared but that Buckling does not offer (a Studio capability, say) is refused with Capability 'x' not granted. log is the one capability that is always allowed. List what you call and nothing more: the list is what a reviewer reads to see what a module can touch, and the command line reports declared capabilities a run never used.

The model argument#

sectionProps, stress, firstYield, signature and modes work on the current model (the one getModel returns), with its reference stresses, unless the argument carries a model of its own in the model shape. That is how a module solves a variant: a section it built, a copy with other thicknesses, or a copy with other node stresses. A model passed this way must have mats, nodes, elems, springs and constraints (the last two may be empty lists); it is validated first, and refused with the reason (see errors).

Errors end the call#

When a capability refuses (an undeclared call, an invalid model, an engine error), the run stops with that message. In this version a Python try / except around the call does not catch it, so check your arguments before you call: a positive fy, at least two positive lengths, a model with every list present. The message reaches the user in the module's error box, and in a worked example it becomes that example's diff.

The model shape#

getModel returns it, worked-example fixtures are written in it, and proposeModel takes it. It is CUFSM's model as plain data:

{
  "units": "N-mm",
  "mats":   [{"id": 100, "ex": 200000, "ey": 200000, "vx": 0.3, "vy": 0.3, "g": 76923.0769231}],
  "nodes":  [{"x": 60, "z": 40, "free": [1, 1, 1, 1], "stress": 1}, …],
  "elems":  [{"i": 0, "j": 1, "t": 1.5, "mat": 100}, …],
  "springs": [],
  "constraints": [],
  "loads": {"P": 1, "Mxx": 0, "Mzz": 0, "M11": 0, "M22": 0, "B": 0, "restrained": false},
  "fy": 450,
  "analysis": {"solution": "signature", "bc": "S-S", "terms": 1, "neigs": 10, "lengths": [10, …, 5000]}
}
KeyMeaningUnits
mats[]Orthotropic materials: id (CUFSM's mat#), ex, ey, vx, vy, g. The app's materials may carry extra keys (grade, fy); the engine ignores them.MPa
nodes[]x, z: coordinates in the section plane; free: CUFSM's four dof flags (u, v, w, θ), 1 free, 0 fixed; stress: the reference stress at the node, positive in compression (CUFSM's convention).mm, MPa
elems[]Strips: i, j are node indices, 0-based (node 1 in the app is 0 here); t thickness; mat a material id.mm
springs[]Rows of nine: [node_i, node_j (-1 for ground), ku, kv, kw, kq, local (0/1), discrete (0/1), ys (0 to 1)], node indices 0-based.N, mm
constraints[]Rows of five: [node_e, dof_e (1 to 4), coeff, node_k, dof_k], meaning ue = coeff × uk; node indices 0-based.
loadsThe reference loads from the Loads panel; restrained is the restrained-bending option.N, N·mm, N·mm²
fyThe yield stress from the Section or Loads panel.MPa
analysissolution ("signature" or "general"), bc, terms, neigs (eigenvalues per length) and lengths.mm

A model must have at least one element and at most 5000 nodes. Every node needs finite x and z, every element a thickness above zero and a defined material, and every material finite ex, ey, vx, vy and g, with ex, ey and g above zero.

getModel()#

Argument: none. Returns: the model shape, with "units": "N-mm".

In the app: a copy of the model on screen, with the Loads panel's reference loads and fy, and the analysis settings. In a worked example: the example's model fixture, exactly as written (plus units). An example with no model gives {"units": "N-mm"} and nothing else, which is fine for a module that never reads it.

m = json.loads(civilkit.getModel())
n_nodes = len(m["nodes"])
fy = m["fy"]                       # MPa
t = m["elems"][0]["t"]             # mm

hasResults()#

Argument: none. Returns: True when the app has a solved curve for the current model, False otherwise. Always False in a worked example and in the command line, which never solve the fixture.

getResults()#

Argument: none. Returns: the app's solved curve:

{
  "curve": [[L, lf], …],
  "minima": [{"length": 115.18, "lf": 0.2235, "kind": "L", "cls": [G, D, L, O] or null}, …],
  "maxStress": 450
}
  • curve: the length (mm) and the lowest load factor at it, in the app's order.
  • minima: the minima the app marks, each with the family it labels it ("G", "D", "L" or "O") and, when the app has classified it, the cFSM shares in per cent.
  • maxStress: the largest absolute reference stress on the nodes (MPa), or 1 when there is none. A load factor times maxStress is a buckling stress.

With no solved curve (and always in a worked example) it returns {"curve": [], "minima": [], "maxStress": null}. The app's curve was solved under the app's loads, which need not be the loads your module wants; to be sure of the load case, solve your own with signature.

if civilkit.hasResults():
    r = json.loads(civilkit.getResults())
    lowest = min(r["minima"], key=lambda mn: mn["lf"])
    sigma_cr = lowest["lf"] * r["maxStress"]       # MPa

A module that reads results can only prove its no-results path in a worked example. That is by design: a badge earned by reproducing whatever model happened to be open would mean nothing.

sectionProps(json)#

Argument: { "model"?: model }. Returns: the gross section properties of the strip model (CUFSM's grosprop and cutwp):

KeyMeaningUnits
AAreamm²
xcg, zcgCentroidmm
Ixx, Izz, IxzSecond moments of area and product, about the centroidmm⁴
thetapPrincipal axis angledegrees
I11, I22Principal second momentsmm⁴
JSt Venant torsion constantmm⁴
xs, zsShear centremm
CwWarping constantmm⁶
B1, B2The monosymmetry (Wagner) constants β₁, β₂mm

For the default lipped C: A = 525, Ixx = 1 813 408.75, J = 393.75, Cw = 2.6933 × 10⁹.

p = json.loads(civilkit.sectionProps(json.dumps({})))
rx = math.sqrt(p["Ixx"] / p["A"])          # mm

stress(json)#

Argument: { "model"?, "P"?, "Mxx"?, "Mzz"?, "M11"?, "M22"?, "B"?, "restrained"? }; the actions default to 0 and restrained to false. Units: P in N, moments in N·mm, B in N·mm². Returns: { "stress": [σ per node] } in MPa, in node order, positive in compression: the stresses CUFSM's Generate stress writes on the nodes for those actions, added together. restrained is CUFSM's restrained-bending option, the Loads panel's restrained bending about x-z: as the app's note puts it, Mxx and Mzz act about the geometric axes, and with restrained bending about x-z as drawn; M11 and M22 act about the principal axes.

The usual pattern: put the stresses on a copy of the model's nodes, and solve that copy.

st = json.loads(civilkit.stress(json.dumps({"P": 100e3, "Mxx": 5e6})))["stress"]

On the default lipped C, P = 100 kN alone gives 100 000 / 525 = 190.48 MPa at every node; with Mxx = 5 kN·m added the node stresses run from −16.3 to 397.3 MPa.

firstYield(json)#

Argument: { "model"?, "fy": number, "restrained"?: false, "extremeFibre"?: true }. fy (MPa) is required. extremeFibre is the app's extreme-fibre option: yield at the outermost fibre (true) or at the centreline nodes (false). Returns: the action that alone first reaches fy:

{ "Py": 236250, "Mxx": 10772725.2, "Mzz": 4232628.8, "M11": 10772725.2, "M22": 4232628.8, "B": 216189730.5 }

Py in N, the moments in N·mm, B in N·mm² (the numbers are the default lipped C at fy = 450). Py = A fy is the squash load. Refused with fy must be a positive finite number when fy is missing or not positive.

y = json.loads(civilkit.firstYield(json.dumps({"fy": 450})))
Py, My = y["Py"], y["Mxx"]       # N, N·mm

signature(json)#

Argument:

KeyDefaultMeaning
modelthe current modelSolved with its own node stresses as the reference stress.
lengthsrequiredA list of positive lengths (mm): half-wavelengths for S-S, physical member lengths for the other end conditions. Use the model's analysis.lengths, or your own.
bc"S-S"End conditions: "S-S", "C-C", "S-C", "C-F", "C-G" ("F-C" and "G-C" are the same as "C-F" and "C-G"). Case matters.
terms1The number of longitudinal terms, as the app's Longitudinal terms. One is right for S-S; the other end conditions need more.
cfsmnoneConstrained curves to solve as well: any of "G", "D", "L", "O".

Returns:

{
  "curve": [[L, lf], …],
  "constrained": {"L": [lf, …], "D": [lf, …]},
  "minima": [{"index": 4, "length": 111.2, "lf": 100.81}, …]
}
  • curve: each length with its lowest load factor, in the order of lengths.
  • constrained: one list per requested cFSM space, aligned with curve. Empty ({}) when none was asked for.
  • minima: found by the same function the app uses for its own chart, so a module's minima and the drawn curve's can never disagree. Interior minima, plus the last point when the curve is still falling there (global governs at long lengths); for a curve with no minimum at all, its lowest point. index points into curve. Minima are not classified: ask modes at their length.

A load factor multiplies the reference stresses. With the stress at every node set to 1 MPa, it is the buckling stress in MPa; with the stresses of P = Py (from stress), it is Pcr / Py.

s = json.loads(civilkit.signature(json.dumps({
    "lengths": [20 * 1.1 ** k for k in range(60)],
    "cfsm": ["L", "D"]})))
first = s["minima"][0]
local_only = s["constrained"]["L"][first["index"]]

Refused with a length is not a positive finite number for a zero, negative or missing entry, unknown boundary condition "…" for another end condition, and undefined is not iterable … when lengths is missing altogether.

modes(json)#

Argument: { "model"?, "length": number, "bc"?: "S-S", "terms"?: 1, "n"?: 5, "dofs"?: false }. length (mm) is required; n is how many modes to solve. Returns:

{ "modes": [ {"lf": 100.57, "cls": [0.35, 0.43, 99.06, 0.16], "kind": "L"}, … ] }
  • lf: the load factor, lowest first.
  • cls: the cFSM modal classification, the shares of G, D, L and O in per cent (they add to 100).
  • kind: the letter with the largest share.
  • dofs (only with "dofs": true): the mode shape, normalised so its largest in-plane entry is 1. For each longitudinal term, 4 × (number of nodes) values in CUFSM's order: u₁ v₁ … uₙ vₙ (x and longitudinal), then w₁ θ₁ … wₙ θₙ (z and rotation).
md = json.loads(civilkit.modes(json.dumps({"length": 115.18, "n": 3})))["modes"]
kinds = [x["kind"] for x in md]

Refused with a length is not a positive finite number when length is missing or not positive.

proposeModel(json)#

Argument: a model in the model shape. springs and constraints default to empty; loads, fy and analysis are optional and, when present, are applied with the section if the user accepts it. Returns: {"accepted": false, "pending": true, "error": null} for a valid model, or {"accepted": false, "pending": false, "error": "the model has no elements"} for an invalid one.

The host validates the model, and the app shows ‹module› proposes a section: N nodes, M elements. with Preview, Use it and Dismiss (see section proposals). Nothing is applied until the user presses Use it, so accepted is always false: the module cannot tell what the user chose. Each call makes one proposal; an invalid one is reported to the user as ‹module› proposed an invalid section: ‹reason›, and does not stop the run. In a worked example and in the command line, proposals are recorded in the report and never applied.

r = json.loads(civilkit.proposeModel(json.dumps(model)))

Node free flags default to [1, 1, 1, 1] and stress to 1 when the app takes the section. The Rounded C / Z section module is the worked pattern.

log(msg)#

Argument: a string. Returns: nothing. Never needs declaring.

civilkit.log("lengths: %d" % len(ls))

The command line prints each message as a log: line, and lists it in the JSON report's run.logs; print() output appears there under stdout:. In the app, log messages and printed output are not shown in this version: put what the user should see in result rows or calc lines.

CivilKit Buckling runs cufsm-rs, a Rust port of CUFSM, the finite strip software by Benjamin W. Schafer and co-workers (constrained FSM with Sándor Ádány), compiled to WebAssembly. An analysis tool, not a design certificate: check what you use.