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 unitLength-derived valuesForce-derived values
Metricmeters (m)kilonewtons (kN)
Englishinches (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

STAAD.Pro Application Configuration dialog showing the Base Unit setting under General, with Metric selected

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:

QuantityMetricEnglish
Length, displacement, section dimensionmin
Force, reaction, axial forcekNkip
MomentkN·mkip·in
StresskN/m²kip/in² (ksi)
Section aream²in²
Section modulusm³in³
Moment of inertiam⁴in⁴
Distributed forcekN/mkip/in
DensitykN/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 readRatio English / Metric
Coordinates, displacements39.370
Reactions, member end forces0.22481
Moments8.8507
Section area (Ax, Ay, Az)1550.003
Iy, Iz2 402 510
Modulus of elasticity1.4504e-4
Material density3.684e-6
UDL magnitude5.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.22 invites the reader to guess; one that says 236.22 in does 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