{
  "measured_on": "2026-09-11",
  "question": "does a mesh file record which axis is up, and what happens when it does not",
  "object": "box measuring 10 x 20 x 30 mm with its minimum corner at the origin, authored Z up so the 30 mm dimension is the height",
  "trimesh_version": "5.1.0",
  "numpy_version": "2.5.2",
  "truth": {
    "extents_mm": [
      10.0,
      20.0,
      30.0
    ],
    "authored_up_axis": "+Z",
    "height_mm": 30.0,
    "tallest_axis": "Z"
  },
  "rows": [
    {
      "format": "glb",
      "axis_field_present_in_bytes": false,
      "axis_evidence": "top level keys=['accessors', 'asset', 'bufferViews', 'buffers', 'meshes', 'nodes', 'scene', 'scenes']; asset=['generator', 'version']; asset.extras present=False; extensionsUsed=none",
      "up_axis_by_spec": "+Y",
      "spec_quote": "glTF defines +Y as up",
      "spec_name": "glTF 2.0, Coordinate System and Units",
      "spec_url": "https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#coordinate-system-and-units",
      "agrees_with_authoring": false,
      "extents_after_roundtrip_mm": [
        10.0,
        20.0,
        30.0
      ],
      "roundtrip_preserved_axes": true,
      "height_if_read_as_authored_mm": 30.0,
      "height_if_y_up_conversion_applied_mm": 20.0,
      "extents_if_y_up_conversion_applied_mm": [
        10.0,
        30.0,
        20.0
      ],
      "height_error_factor": 0.6667
    },
    {
      "format": "stl",
      "axis_field_present_in_bytes": false,
      "axis_evidence": "80 byte header='' (the format defines no other metadata slot)",
      "up_axis_by_spec": null,
      "spec_quote": null,
      "spec_name": null,
      "spec_url": null,
      "agrees_with_authoring": null,
      "extents_after_roundtrip_mm": [
        10.0,
        20.0,
        30.0
      ],
      "roundtrip_preserved_axes": true,
      "height_if_read_as_authored_mm": 30.0,
      "height_if_y_up_conversion_applied_mm": 20.0,
      "extents_if_y_up_conversion_applied_mm": [
        10.0,
        30.0,
        20.0
      ],
      "height_error_factor": 0.6667
    },
    {
      "format": "obj",
      "axis_field_present_in_bytes": false,
      "axis_evidence": "leading comments=['# https://github.com/mikedh/trimesh']",
      "up_axis_by_spec": null,
      "spec_quote": null,
      "spec_name": null,
      "spec_url": null,
      "agrees_with_authoring": null,
      "extents_after_roundtrip_mm": [
        10.0,
        20.0,
        30.0
      ],
      "roundtrip_preserved_axes": true,
      "height_if_read_as_authored_mm": 30.0,
      "height_if_y_up_conversion_applied_mm": 20.0,
      "extents_if_y_up_conversion_applied_mm": [
        10.0,
        30.0,
        20.0
      ],
      "height_error_factor": 0.6667
    },
    {
      "format": "ply",
      "axis_field_present_in_bytes": false,
      "axis_evidence": "comments=['comment https://github.com/mikedh/trimesh']; elements=['element vertex 8', 'element face 12']",
      "up_axis_by_spec": null,
      "spec_quote": null,
      "spec_name": null,
      "spec_url": null,
      "agrees_with_authoring": null,
      "extents_after_roundtrip_mm": [
        10.0,
        20.0,
        30.0
      ],
      "roundtrip_preserved_axes": true,
      "height_if_read_as_authored_mm": 30.0,
      "height_if_y_up_conversion_applied_mm": 20.0,
      "extents_if_y_up_conversion_applied_mm": [
        10.0,
        30.0,
        20.0
      ],
      "height_error_factor": 0.6667
    },
    {
      "format": "3mf",
      "axis_field_present_in_bytes": false,
      "axis_evidence": "3D/3dmodel.model: <model> attributes=['unit', 'xmlns', 'xmlns:b', 'xmlns:m', 'xmlns:p', 'xmlns:s', 'xmlns:sc']",
      "up_axis_by_spec": "+Z",
      "spec_quote": "the z-axis increasing to the top of the output field",
      "spec_name": "3MF Core Specification, coordinate space",
      "spec_url": "https://github.com/3MFConsortium/spec_core",
      "agrees_with_authoring": true,
      "extents_after_roundtrip_mm": [
        10.0,
        20.0,
        30.0
      ],
      "roundtrip_preserved_axes": true,
      "height_if_read_as_authored_mm": 30.0,
      "height_if_y_up_conversion_applied_mm": 20.0,
      "extents_if_y_up_conversion_applied_mm": [
        10.0,
        30.0,
        20.0
      ],
      "height_error_factor": 0.6667
    }
  ],
  "not_measured_here": [
    "FBX and USD, both of which DO record an up axis in the file: FBX in GlobalSettings.UpAxis and USD in the upAxis stage metadatum. trimesh cannot write either, so these are stated from their documentation rather than measured, and they are the counter-example that matters: the fact is recordable, the five formats measured here just do not record it.",
    "STEP, which carries an explicit axis placement per shape, for the same reason.",
    "What any named application does on import. Behaviour differs between Blender, Cura, PrusaSlicer, Unity and Unreal, and each states its own convention in its own documentation."
  ]
}
