Benchmark

Which 3D file formats record a unit of length, and which leave it to be guessed

Does a mesh file say what its numbers mean, and what breaks when it does not?

Measured 2026-09-07

Result

Of five mesh formats written from one 100 mm cube, only 3MF puts a unit in its bytes: the model part opens with unit="millimeter". STL, OBJ, PLY and GLB store a bare number and nothing else. glTF is a special case, because its specification declares the number to mean metres even though no field says so; a cube authored in millimetres therefore arrives 1000 times too large in tooling that follows the spec, and at the right size in a slicer that assumes millimetres. STL, OBJ and PLY have no such rule at all, so the importing program picks one.

Cube authored in metres, written as 0.1

FormatNumber in the fileUnit stated in the bytesUnit implied by the specResult if mm assumed (mm)Result if m assumed (mm)
GLB0.1Nometers0.1100.0
STL0.1Nonone0.1100.0
OBJ0.1Nonone0.1100.0
PLY0.1Nonone0.1100.0
3MF0.1Yesmillimeter0.1100.0

True size is 100 mm in every row. A reader that assumes millimetres produces a 0.1 mm cube from all five files, which is the 1000 times too small case people meet most often.

Cube authored in millimetres, written as 100

FormatNumber in the fileUnit stated in the bytesUnit implied by the specResult if mm assumed (mm)Result if m assumed (mm)
GLB100.0Nometers100.0100,000.0
STL100.0Nonone100.0100,000.0
OBJ100.0Nonone100.0100,000.0
PLY100.0Nonone100.0100,000.0
3MF100.0Yesmillimeter100.0100,000.0

The same five files, authored the other way round. Now assuming metres is what breaks, and it breaks by the same factor of 1000 in the other direction.

What the unit evidence column in the raw data contains

The published JSON records, per file, exactly what was found when the container was opened: the top level key list of the GLB JSON chunk, the literal contents of the STL 80 byte header, the leading comment lines of OBJ and PLY, and the model element of the 3MF archive. That is included so the negative results are checkable. Saying a format does not store a unit is a claim about absence, and absence is only believable if you can see where it was looked for.

Why the glTF row is not the same kind of no as the others

GLB has no unit field, so the first column reads the same as STL, OBJ and PLY. The difference is that the glTF specification fixes the meaning: distances are metres. Tooling written to the spec therefore has a correct answer available to it without the file saying anything, which is why a millimetre authored GLB tends to arrive 1000 times too large rather than at an arbitrary size. For STL, OBJ and PLY there is no correct answer to reach for, and the importer's default is the whole of the behaviour.

How this was measured

One axis aligned cube, physically 100 mm on its longest edge, authored twice: once with the value written as 0.1 (metres) and once as 100 (millimetres). Each version was written to all five formats, read back, and separately opened as a raw container: the GLB JSON chunk was parsed out of the binary header, the STL header was read as its first 80 bytes, OBJ and PLY comment lines were read as text, and the 3MF archive was opened as a ZIP with its model part searched for a unit attribute.

Software and versions
trimesh 5.1.0, numpy 2.5.2
Date measured
2026-09-07
Script
scripts/measure-unit-declarations.py
Raw data
unit-declarations-2026-09-07.json

This data is published under CC BY 4.0. Reuse the numbers freely, with a link back so a reader can check the method.

What this does not show

Stated so the result is not read wider than it goes.

  • slicer import behaviour (no Cura or PrusaSlicer on this machine)
  • DCC exporter behaviour (no Blender on this machine)
  • FBX and STEP (trimesh cannot write them)

Where this is used

PrusaSlicer , Blender , Online 3D Viewer