UI reference
A module draws nothing itself. build_ui returns a tree of plain dicts, and the app creates
every element from it, setting all text as text. This page lists every node type the Buckling host renders and every
property it reads, with an example of each.
Every example below is a statement node = {…}: put the dict in a children list. All of
them were rendered in the app's builder to check them. A node with a type the host does not know renders
as a red dashed box reading unsupported node: ‹type›, and properties the host does not read are ignored.
How build_ui and check are called#
- When the module opens, the host loads
main.pyand callsbuild_ui({}, [], []).checkdoes not run yet. So read every input with a default,inputs.get("fy", ""), and draw results only when there are some. - On a
run-checkaction, or when a field changes, the host collects the form's inputs and callscheck(inputs)with the model of the moment behind the capabilities. - Then it calls
build_ui(inputs, result, calcLines)with the same inputs and whatevercheckreturned under"result"and"calcLines"(each[]when missing), and redraws the form. What the user typed is kept across the redraw: a field'sdefaultonly fills a field that was not on screen before.
check returns {"result": [rows], "calcLines": [lines]}: rows as in result
and lines as in calc. Anything else it needs to show (a curve, a table) it keeps in a
module-level variable for build_ui to read, as the tutorial and every
reference module do: both calls run in the same Python session, one after the other. The session ends when the module
view closes, or the worker restarts.
Keep capability calls in check. When the module first opens, no model has reached the sandbox yet, so a
build_ui that called getModel would get no nodes.
An exception in either function shows in the module's error box, with its Python traceback; in the builder its line
is marked. Raise a ValueError with a sentence the user can act on ("fy must be a positive stress
(MPa)"), or return a result row with "status": "over" when the check itself fails.
Inputs#
inputs is a dict from each rendered field's id to its value:
inputType | Value in inputs |
|---|---|
"number" | a float; "" when blank; the typed text if it is not a number |
"integer" | an int (the text is cut at the first non-digit); "" when blank |
"boolean" | True or False |
"enum" | the chosen option's value, a string |
"string" (the default) | the text |
Only fields on screen are included; a field inside a collapsed section or a hidden
tab still counts. In the tutorial's checks this came back as
{"code": "AISI S100-16", "t": 1.5, "nh": 6, "Le": 0, "offer": true, "h": 250, "ref": "P1"}. A blank
number arrives as "", so read numbers through a helper such as the starter's num.
Actions#
Buckling acts on two:
"run-check": a button or menu item with this action runscheck."change": sent by the host when any field changes; it also runscheck. A module whose check is slow should say so in its form, since every change runs it.
Any other action (a button's default "click", a bar chart's "chartclick") does nothing in
Buckling. check is not told which button was pressed, only the inputs; to offer two behaviours, use a
field (a boolean or an enum), as the
Rounded C / Z module's Offer it to the analysis box
does.
Layout nodes#
panel#
The box everything sits in; build_ui normally returns one. Reads title and
children.
node = {"type": "panel", "title": "Purlin check", "children": [
{"type": "text", "value": "Everything a module shows sits inside one panel."},
]}
section#
A titled group the user can fold and unfold by its title bar. Reads title, children, and
collapsed (start folded). Its fields count as inputs even when folded.
node = {"type": "section", "title": "Mesh and material", "collapsed": True, "children": [
{"type": "field", "id": "nh", "label": "Web elements", "inputType": "integer", "default": 6},
]}
row#
Children side by side. cols is a number of equal columns (default: one per child) or a list of
proportions, [2, 1] for two thirds and one third. Rows do not wrap, so keep them to two to four short
fields for phones.
node = {"type": "row", "cols": [2, 1], "children": [
{"type": "field", "id": "h", "label": "h (mm)", "inputType": "number", "default": 200},
{"type": "field", "id": "t", "label": "t (mm)", "inputType": "number", "default": 1.5},
]}
tabs#
A tab strip. Its children must be tab nodes; the first is open, and
the open tab is remembered by its label across redraws.
node = {"type": "tabs", "children": [
{"type": "tab", "label": "Results", "children": [{"type": "text", "value": "Result rows here."}]},
{"type": "tab", "label": "Working", "children": [{"type": "text", "value": "Calc lines here."}]},
]}
tab#
One page of a tabs node: label and children. Outside
tabs it renders its children as a plain block.
Form nodes#
field#
An input with its label above it. Reads:
| Property | Meaning |
|---|---|
id | The key in inputs. Unique within the module. |
label | The text above it (the id when missing). Put the unit here: "Le (mm)". |
inputType | "number", "integer", "boolean" (a checkbox beside its label), "enum" (a drop-down) or "string" (the default). |
default | The initial value: a number, text, True/False, or an option's value. |
options | For enum: strings, or {"value": …, "label": …} dicts. |
min, max, step | For number and integer: passed to the browser's number input (they guide the spinner; they do not stop a typed value, so check ranges in check). |
help | A small line of help under the input. |
unit | Not displayed in Buckling: its units adapter is the identity, and fields carry units in their labels. Leave it out. |
A number:
node = {"type": "field", "id": "Le", "label": "Le (mm), 0: continuously braced",
"inputType": "number", "default": 0, "min": 0, "step": 100,
"help": "The effective length for global buckling."}
A choice, from plain strings or value/label pairs (inputs["code"] is "nzs" when the second
is chosen):
node = {"type": "field", "id": "code", "label": "Code", "inputType": "enum",
"options": ["AISI S100-16", {"value": "nzs", "label": "AS/NZS 4600:2018"}],
"default": "AISI S100-16"}
A checkbox:
node = {"type": "field", "id": "offer", "label": "Offer it to the analysis",
"inputType": "boolean", "default": True}
Text (the default input type):
node = {"type": "field", "id": "ref", "label": "Member reference", "default": "P1"}
To start a field with the analysis's value, leave it blank and fall back to the model in check
(fy in the tutorial): build_ui cannot know the model when the module opens.
button#
Reads id, label, action and variant. Give it
"action": "run-check"; see actions. Buttons are disabled while a check runs.
node = {"type": "button", "id": "run", "label": "Run", "action": "run-check"}
"variant": "secondary" draws an outlined button:
node = {"type": "button", "id": "again", "label": "Run again", "action": "run-check",
"variant": "secondary"}
menu#
A drop-down menu button. Reads label and children, which are
menuitem nodes.
node = {"type": "menu", "label": "More", "children": [
{"type": "menuitem", "id": "rerun", "label": "Run again", "action": "run-check",
"shortcut": "R"},
{"type": "menuitem", "separator": True},
{"type": "menuitem", "id": "later", "label": "Coming later", "disabled": True},
]}
menuitem#
One entry of a menu: id, label, action,
shortcut (text shown at the right; no key is bound), disabled, or
"separator": True for a dividing line. An item with "action": "run-check" runs the check;
other actions do nothing in Buckling.
Output nodes#
text#
A paragraph: value. (A tone is accepted but not coloured in Buckling.)
node = {"type": "text", "value": "Units: N, mm, MPa. Leave fy blank to use the analysis's."}
badge#
A small label: value, and tone "ok" (green), "warn" (amber) or
"fail" (red).
node = {"type": "row", "children": [
{"type": "badge", "value": "OK", "tone": "ok"},
{"type": "badge", "value": "Check the lips", "tone": "warn"},
{"type": "badge", "value": "Fails", "tone": "fail"},
]}
result#
Label and value rows: items, each {"label", "value", "unit"?, "status"?}. These are the rows
check returns as "result", and the rows a worked example
matches by label. status "ok" colours the value green and "over" red; other
values are not coloured in Buckling. Numbers are shown to 12 significant figures, which hides MicroPython's
last-digit float noise without changing the value; round for display yourself (the starter's sig).
node = {"type": "result", "items": [
{"label": "Pn", "value": 86.308, "unit": "kN", "status": "ok"},
{"label": "Utilisation", "value": 1.12, "status": "over"},
{"label": "Governs", "value": "local"},
]}
table#
columns: [{"key", "label"}]; rows: dicts keyed by column key. A
missing key is a blank cell. Wide tables scroll sideways on a phone; keep them to a few columns.
node = {"type": "table",
"columns": [{"key": "t", "label": "t (mm)"}, {"key": "A", "label": "A (mm²)"},
{"key": "ok", "label": ""}],
"rows": [{"t": 1.2, "A": 420, "ok": ""}, {"t": 1.5, "A": 525, "ok": "meets"}]}
calc#
Calc lines: lines, each {"standard"?, "year"?, "clause"?, "label"?, "eq"}. The reference
(standard:year clause) shows small above the label, and eq under it. A Copy button
copies all the lines as plain text. These are the lines check returns as "calcLines". A
tex key is accepted (Studio typesets it) but Buckling shows eq as plain text: write it
readably, with Unicode (λ, √, ×, ≤, ²).
node = {"type": "calc", "lines": [
{"standard": "AISI S100", "year": "2016", "clause": "§E3",
"label": "Local buckling", "eq": "λl = √(Pne/Pcrl) = 2.8868 > 0.776"},
{"label": "Squash load", "eq": "Py = A fy = 525 × 450 = 236.25 kN"},
]}
Charts#
chart#
Drawn by the host as SVG, in the app's colours. chart picks the kind: "bar" (the
default), "line" or "scatter". All kinds read title and id.
Bar charts#
Horizontal bars, one per entry in bars: {"label", "value", "valueLabel"?, "tone"?, "id"?, "sub"?}.
threshold draws a dashed line with thresholdLabel, and then colours each bar by whether it is
over it (red) or not; tone overrides that: "ok", "warn", "over",
"bad" or "accent". max fixes the scale (default: the largest value or the
threshold); labelWidth the room for labels (150). A bar with an id is clickable, which does
nothing in Buckling.
node = {"type": "chart", "chart": "bar", "title": "Utilisation",
"threshold": 1, "thresholdLabel": "limit",
"bars": [{"label": "Local", "value": 0.82},
{"label": "Distortional", "value": 1.07},
{"label": "Global", "value": 0.4, "valueLabel": "0.40 (braced)"}]}
Line and scatter charts#
| Property | Meaning |
|---|---|
series | [{"label", "points": [{"x", "y"}, …], "color"?}]. Points are dicts with x and y, not pairs. color is any CSS colour; by default the series take the app's four series colours in turn. |
xScale, yScale | "linear" (default) or "log". A log axis gets decade ticks (10, 100, 1000) and unlabelled 2 and 5 ticks, and drops values at or below zero with a note (2 points outside the logarithmic axis range ignored). |
ticks | Labelled ticks on linear axes, at 1, 2, 5 × 10ⁿ steps. Log axes always have ticks. |
grid | Gridlines at the ticks. |
legend | A legend row under the chart, from the series labels. |
markers | A dot on every point of a line chart (a scatter chart always has them). |
annotations | [{"x", "y", "label", "tone"?}]: marked, labelled points, such as minima; tone "ok", "warn" or "fail" (default: the accent colour). |
hover | A readout of the nearest point (t = 1.5 mm @ 60,0.4) as the pointer moves, or the finger drags, over the plot. |
xLabel, yLabel | Axis titles. |
xMin, xMax, yMin, yMax | Fix the range. Without them a linear axis always includes 0, and a log axis spans the data. |
height | The drawing height in chart units (default 300, for a width of 720). |
A signature-style curve on log axes, with a marked minimum (threshold applies to bar charts only):
node = {"type": "chart", "chart": "line", "title": "Signature curve",
"xScale": "log", "yScale": "log", "grid": True, "legend": True, "hover": True,
"xLabel": "half-wavelength (mm)", "yLabel": "Pcr / Py",
"series": [{"label": "t = 1.5 mm", "points": [
{"x": 20, "y": 2.1}, {"x": 60, "y": 0.4}, {"x": 115, "y": 0.22},
{"x": 300, "y": 0.9}, {"x": 1300, "y": 0.59}, {"x": 5000, "y": 0.12}]}],
"annotations": [{"x": 115, "y": 0.22, "label": "local 0.22", "tone": "ok"}]}
A scatter chart with linear ticks and a grid:
node = {"type": "chart", "chart": "scatter", "title": "Pcrl / Py against t",
"ticks": True, "grid": True, "xLabel": "t (mm)", "yLabel": "Pcrl / Py",
"series": [{"label": "local", "points": [
{"x": 1.0, "y": 0.10}, {"x": 1.5, "y": 0.22}, {"x": 2.0, "y": 0.40}]}]}
For a signature curve of the section, buckling.signature draws
it exactly as the app does.
Drawings#
drawing#
Geometry with equal scales on both axes, fitted to the space, y upwards ("flipY": False for y
downwards). height (default 320) limits the scale. The drawing is then stretched to the full width of the
module, so a drawing much taller than it is wide becomes very tall on screen: keep drawings landscape, and draw a cross
section with buckling.section. items:
| kind | Properties |
|---|---|
"polyline", "polygon" | points [[x, y], …]; width (stroke width); dash; fill: True for a light accent fill, or a CSS colour; tone. |
"circle" | at [x, y], r in drawing units; tone. |
"text" | at, text, anchor ("start", "middle", "end"). |
"dimension" | from, to, text: a line with its text at the middle. |
In this version a drawing item's tone is written with its prefix:
"tone-ok", "tone-warn", "tone-fail" or "tone-accent". A plain
"ok" is accepted but not coloured.
node = {"type": "drawing", "height": 200, "items": [
{"kind": "polyline", "points": [[0, 0], [30, 0], [30, 40], [90, 40], [90, 0], [120, 0]],
"width": 2},
{"kind": "circle", "at": [60, 26], "r": 2, "tone": "tone-accent"},
{"kind": "text", "at": [64, 24], "text": "centroid"},
{"kind": "dimension", "from": [30, -8], "to": [90, -8], "text": "60"},
]}
A filled polygon and a dashed line:
node = {"type": "drawing", "height": 120, "items": [
{"kind": "polygon", "points": [[0, 0], [100, 0], [100, 10], [0, 10]], "fill": True,
"tone": "tone-ok"},
{"kind": "polyline", "points": [[0, 20], [100, 20]], "dash": True, "tone": "tone-warn"},
]}
colorBy colours each segment of every polyline by a value per vertex, on a blue, neutral, red ramp from
−max|v| to +max|v| (for example a stress along a plate): {"values": [v₀, v₁, …]}.
node = {"type": "drawing", "height": 120,
"items": [{"kind": "polyline", "points": [[0, 0], [50, 20], [100, 20], [150, 0]]}],
"colorBy": {"values": [-40, -10, 10, 40]}}
Buckling's own nodes#
Two node types are added by the Buckling host and drawn by the app's own code, so they look exactly like the app's section card and signature chart, in both themes. Studio does not have them.
buckling.section#
The cross-section, drawn like the app's section card: title, model (a model in the
model shape; default: the section in the analysis), and stress
(True colours the elements by their node stresses, as the app's stress view does). A model that fails
validation shows its reason in place of the drawing. Without a model the drawing follows the analysis: when
the user accepts a proposal (or undoes it) it is redrawn.
node = {"type": "buckling.section", "title": "The section in the analysis", "stress": True}
With a model of the module's own, such as a section it generated (node numbers are shown 1-based, as in the app):
node = {"type": "buckling.section", "title": "A proposed plate", "model": {
"mats": [{"id": 1, "ex": 200000, "ey": 200000, "vx": 0.3, "vy": 0.3, "g": 76923}],
"nodes": [{"x": 0, "z": 0, "free": [1, 1, 1, 1], "stress": 1},
{"x": 50, "z": 0, "free": [1, 1, 1, 1], "stress": 1},
{"x": 100, "z": 0, "free": [1, 1, 1, 1], "stress": 1}],
"elems": [{"i": 0, "j": 1, "t": 2, "mat": 1}, {"i": 1, "j": 2, "t": 2, "mat": 1}],
"springs": [], "constraints": []}}
buckling.signature#
A signature curve, drawn by the app's chart: title, curve as [[length, lf], …]
(exactly what signature returns), minima to mark, each with an
index into curve (or a length) and an optional kind ("L",
"D", "G", "O") that labels the marker, and the axis titles xlabel and
ylabel (default load factor λ; note the lower-case keys). Lengths must be positive. With fewer
than two points it shows No signature curve yet: run the module.
node = {"type": "buckling.signature", "title": "Signature curve",
"curve": [[20, 2.1], [60, 0.4], [115, 0.22], [300, 0.9], [1300, 0.59], [5000, 0.12]],
"minima": [{"index": 2, "kind": "L"}, {"index": 4, "kind": "D"},
{"index": 5, "kind": "G"}],
"xlabel": "half-wavelength (mm)", "ylabel": "Pcr / Py"}
Colours and sizes#
The host takes every colour from the app's theme, so a module follows the light and dark themes and the ◐ switch
with no work. Inputs and buttons are at least 48 px tall on touch screens. Do not put colours in labels or values; use
status, tone and color where a node offers them.