Base Units: the units OpenStaad returns to you
Every number OpenSTAAD returns to your script — a node coordinate, a member end force, a displacement, a section area — comes back in the model's base units, a unit your script did not choose and cannot change.
Getting this wrong is silent. A beam reported as 236.22 is not a 236 m span; it is 236.22 in, or
6 m, in a model whose base unit happens to be English. Nothing raises, nothing warns — the design
check just comes out wrong.
This page is only about base units, the get side. For the units of values you send, see Input Units; for how the two fit together, see Unit Systems.
The short answer
| Base unit | Length-derived values | Force-derived values |
|---|---|---|
| Metric | meters (m) | kilonewtons (kN) |
| English | inches (in) | kilopounds (kip) |
That is the whole rule for reads. Everything else in this page is a consequence of it.
Note the two easy mistakes: the English force unit is the kip, not the pound — and the metric
length unit is the meter, not the millimeter. A displacement of 0.012 in a metric model is
12 mm, not 0.012 mm.
Base unit is not stored in the file

Base unit is not a .std file property. It's set in STAAD.Pro's own Application
Configuration → General → Base Unit dialog (pictured above), and it applies to whatever file is
opened next — it is read once, when the file is opened, not stored inside it.
A model built and tested entirely with STAAD.Pro configured for English can have a .std file
that contains no mention of Metric anywhere — only UNIT lines from SetInputUnits() calls, which
are a separate, per-command setting (see Input Units):
UNIT INCHES KIP
JOINT COORDINATES
1 0 0 0; 2 120 0 0; 3 240 0 0;
...
UNIT METER KN
...
UNIT INCHES KIP
Close that file, switch Application Configuration → General → Base Unit to Metric, and
reopen the same, unmodified .std file: GetBaseUnit() immediately starts returning "Metric",
and every previously-in-inches value — node coordinates, load magnitudes — comes back converted to
meters and kN, with no edit to the file at all.
Two people can open the identical file with different STAAD installs configured differently and get
different values back from every Get* call — same file, same geometry, different numbers. This
makes GetBaseUnit() not safely cacheable across sessions or machines, and not something a
script can infer from the file itself without calling the API. Any pipeline that assumes "this model
is an English-unit model" because that's how it was built is wrong; it depends on whoever's STAAD
instance opens it next.
Always call GetBaseUnit() at the start of a script; never assume it from how the model was
authored.
Reading the base unit
GetBaseUnit() returns the string "Metric" or "English" for the currently open .STD:
from openstaad import ops
s = ops.connect()
print(s.GetBaseUnit()) # -> "Metric" or "English"
Underneath, STAAD returns the integer 1 for English and 2 for Metric; the library maps it to the
string so you never have to remember which is which.
Any script that will run on somebody else's model — or on your own model from a machine configured differently — should read this on the first line and branch on it. A script that assumes metric is a script that works until the day it doesn't.
Can I get returned values in a different unit than the base unit?
No. OpenSTAAD has no setting that changes the units of returned values: SetInputUnits() only
affects what you send, and the base unit is set by the STAAD.Pro application, not by your script.
If you need a value in another unit, convert it yourself as soon as it arrives — see
A conversion pattern that holds up below.
Derived quantities
Only length and force are primitive. Everything else is built from them, which means every derived unit changes with the base unit too:
| Quantity | Metric | English |
|---|---|---|
| Length, displacement, section dimension | m | in |
| Force, reaction, axial force | kN | kip |
| Moment | kN·m | kip·in |
| Stress | kN/m² | kip/in² (ksi) |
| Section area | m² | in² |
| Section modulus | m³ | in³ |
| Moment of inertia | m⁴ | in⁴ |
| Distributed force | kN/m | kip/in |
| Density | kN/m³ | kip/in³ |
The practical consequence: a conversion factor of 0.0254 is not enough. An area needs 0.0254²,
an inertia needs 0.0254⁴, and a stress needs force / length². Converting each quantity with the
same length factor is a common and expensive bug.
These factors were checked by reading the same unmodified model with English and then Metric base
units (STAAD.Pro 2025 25.0.1.424, openstaad 0.0.15). The ratio English / Metric matched the table:
| Quantity read | Ratio English / Metric |
|---|---|
| Coordinates, displacements | 39.370 |
| Reactions, member end forces | 0.22481 |
| Moments | 8.8507 |
Section area (Ax, Ay, Az) | 1550.003 |
Iy, Iz | 2 402 510 |
| Modulus of elasticity | 1.4504e-4 |
| Material density | 3.684e-6 |
| UDL magnitude | 5.710e-3 |
Not everything follows the base unit. Rotations, Poisson's ratio and the thermal expansion
coefficient came back identical under both. Section modulus was not verified, because no function
returns it directly. The torsional constant returned by GetBeamProperty was the one exception
we could not explain: it did not follow the 39.37⁴ factor (0.3 in⁴ against 1.181e-7 m⁴, a
ratio of about 2 539 564 instead of 2 402 510), so do not convert it with the inertia factor
without checking it against your own model.
A conversion pattern that holds up
Read the base unit once, derive every factor from the two primitives, and never hand-write a conversion at the call site:
from openstaad import ops
IN_TO_M = 0.0254 # exact
KIP_TO_KN = 4.4482216152605 # exact
class BaseUnits:
"""Factors converting OpenStaad's base-unit values into m / kN."""
def __init__(self, base_unit: str):
if base_unit == "Metric":
self.length, self.force = 1.0, 1.0
elif base_unit == "English":
self.length, self.force = IN_TO_M, KIP_TO_KN
else:
raise ValueError(f"unknown base unit: {base_unit!r}")
# Everything below is derived, so it can never drift out of sync.
@property
def area(self):
return self.length ** 2
@property
def inertia(self):
return self.length ** 4
@property
def moment(self):
return self.force * self.length
@property
def stress(self):
return self.force / self.length ** 2
@property
def dist_force(self):
return self.force / self.length
s = ops.connect()
u = BaseUnits(s.GetBaseUnit())
x, y, z = s.GetNodeCoordinates(1)
print(f"node 1: ({x * u.length:.3f}, {y * u.length:.3f}, {z * u.length:.3f}) m")
# end = 0 (start) or 1 (end); last argument is 0 global / 1 local
fx, fy, fz, mx, my, mz = s.GetMemberEndForces(1, 0, 1, 1)
print(f"axial = {fx * u.force:.2f} kN")
print(f"moment = {mz * u.moment:.2f} kN·m")
Two habits make this robust:
- Convert at the boundary. Turn base-unit values into your working units the moment they arrive, and keep everything downstream in one system. Mixed-unit values flowing through a calculation are nearly impossible to debug after the fact.
- Label your output. Print the unit next to the number. A report that says
236.22invites the reader to guess; one that says236.22 indoes not.
Checklist
- Read
GetBaseUnit()once at startup, every run — never assume it from how the model was authored or from a previous run. It's an application setting, not a file property. - Convert returned values at the boundary and label every printed number with its unit.
- Derive area, inertia, moment and stress factors from the length and force factors; never reuse the length factor alone.
- If a script runs on shared models or teammates' machines, don't assume
GetBaseUnit()will be the same on every machine — check STAAD's Application Configuration if results look suspiciously off by a unit factor.
Further reading
- Unit Systems — how base units and input units work together.
- Input Units — the units of every value you send.
- Get Base Unit — the function reference.
- Units in STAAD.Pro — Bentley's own description of the base unit setting.