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².
| Capability | Gives |
|---|---|
getModel | The section on screen (or the worked example's fixture): materials, nodes, elements, springs, constraints, loads, fy, analysis settings. |
hasResults | Whether the app has a solved curve for the current model. |
getResults | The app's solved curve and its classified minima. |
sectionProps | A, centroid, Ixx, Izz, Ixz, principal axes, J, shear centre, Cw. |
stress | The stress at every node for given actions P, Mxx, Mzz, M11, M22, B. |
firstYield | The first-yield actions Py, Mxx, Mzz, M11, M22, B for a given fy. |
signature | A signature curve (or a general-BC curve), optionally with cFSM curves, and its minima. |
modes | The buckling modes at one length, with their cFSM classification. |
proposeModel | Offer a section to the analysis; the user decides. |
log | A 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]}
}
| Key | Meaning | Units |
|---|---|---|
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. | |
loads | The reference loads from the Loads panel; restrained is the restrained-bending option. | N, N·mm, N·mm² |
fy | The yield stress from the Section or Loads panel. | MPa |
analysis | solution ("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 timesmaxStressis 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):
| Key | Meaning | Units |
|---|---|---|
A | Area | mm² |
xcg, zcg | Centroid | mm |
Ixx, Izz, Ixz | Second moments of area and product, about the centroid | mm⁴ |
thetap | Principal axis angle | degrees |
I11, I22 | Principal second moments | mm⁴ |
J | St Venant torsion constant | mm⁴ |
xs, zs | Shear centre | mm |
Cw | Warping constant | mm⁶ |
B1, B2 | The 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:
| Key | Default | Meaning |
|---|---|---|
model | the current model | Solved with its own node stresses as the reference stress. |
lengths | required | A 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. |
terms | 1 | The number of longitudinal terms, as the app's Longitudinal terms. One is right for S-S; the other end conditions need more. |
cfsm | none | Constrained 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 oflengths.constrained: one list per requested cFSM space, aligned withcurve. 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.indexpoints intocurve. 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.