Base Units: the units OpenStaad actually gives you

Every number that crosses the OpenSTAAD boundary — a node coordinate, a member end force, a displacement, a section area — arrives in a unit that your script did not choose. It is decided by a single property of the model: its base unit.

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.


The short answer

Base unitLength-derived valuesForce-derived values
Metricmeters (m)kilonewtons (kN)
Englishinches (in)kilopounds (kip)

That is the whole rule. 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.


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 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.


Two unit systems, and only one of them governs the API

This is where most of the confusion comes from. A STAAD model carries two independent notions of units:

1. The base unitEnglish or Metric. A property of the model itself, fixed when the file is created. It governs what OpenSTAAD hands to your script.

2. The input units — the UNIT command inside the .STD file: UNIT MMS KN, UNIT FEET KIP, and so on. This is what the text of the model uses and what the STAAD.Pro interface displays. You read it with GetInputUnitForLength() and GetInputUnitForForce(), and change it with SetInputUnits().

Only the first one affects what you read back. The input units are a modeling and display convenience; the API is unaffected by them.

s = ops.connect()

print(s.GetBaseUnit())              # -> "English"
print(s.GetInputUnitForLength())    # -> "Meter"
print(s.GetInputUnitForForce())     # -> "KiloNewton"

x, y, z = s.GetNodeCoordinates(1)   # still inches — base unit wins

A model can perfectly well be authored in UNIT METER KN, display meters everywhere in the interface, and still return inches through OpenSTAAD, because its base unit is English. This is the single most reported surprise in the API, and it is working as designed.

Setting input units does not change it either

s.SetInputUnits(4, 5)               # meter, kilonewton
x, y, z = s.GetNodeCoordinates(1)   # unchanged: still base units

SetInputUnits(), SetInputUnitForLength() and SetInputUnitForForce() change how values are written into and displayed by the model. They are not a conversion switch for the values you read. If you need meters and you have an English model, you convert them yourself — see below.

For reference, the integer codes those setters take:

CodeLengthForce
0InchKilopound
1FeetPound
2(see note)Kilogram
3CentiMeterMetric Ton
4MeterNewton
5MilliMeterKiloNewton
6DeciMeterMegaNewton
7KiloMeterDecaNewton

Code 2 for length is documented inconsistently upstream (it is listed as a duplicate of Feet). If you need it, set it and read it back with GetInputUnitForLength() to confirm what you got.


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 areain²
Section modulusin³
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.


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.

Output units are a different question again

The GetOutputUnitFor* family — GetOutputUnitForForce(), GetOutputUnitForStress(), GetOutputUnitForDisplacement(), and the rest — reports the units STAAD is using for the analysis report. They are useful for labeling report-facing output and for matching what an engineer sees in the results viewer.

Do not assume they describe the numbers the result getters hand you. Treat them as metadata about the report, and keep the base unit as your source of truth for conversion. If your workflow depends on these agreeing, verify it once against a known model on your STAAD version before trusting it.

GetOutputUnitForRotation() is worth calling explicitly rather than assuming: rotation is not derived from length or force, so the base unit does not settle it.


Functions that state their own units

A few functions document a unit in their own signature, and that documentation wins over any general rule. GetIntersectBeamsCount() is the clearest example — its tolerance argument is specified as "meter for Metric and inch for English in Base Unit", i.e. an input expressed in base units, not in the model's input units.

When a function's page names a unit, use it. When it doesn't, assume base units.


Checklist

  • Call GetBaseUnit() at the start of any script that will meet a model you did not build.
  • Remember the pairs: Metric → m, kN and English → in, kip.
  • Derive area, inertia, moment and stress factors from the length and force factors; never reuse the length factor alone.
  • Do not expect SetInputUnits() to change what you read.
  • Convert once, at the boundary, and label every number you print.

Further reading

  • Get Base Unit — the function reference, alongside GetInputUnitForLength, GetInputUnitForForce and the SetInputUnit* setters.
  • Units in STAAD.Pro — Bentley's own description of the base unit setting.