the symptom, in your words

"Blender OBJ import puts Y-up rotation in object matrix, not vertex coords"

✓ verified on engine 26.07.08elane: Getting art inposted

▸ SYMPTOM

After a headless Blender wm.obj_import(..., up_axis='Y'), per-vertex logic that reads raw .co.z as "vertical" silently uses the wrong axis. A Z-plane split computed on raw vertex coords keeps everything on one side — the "vertical" range it sees is tiny because it's actually reading the file's horizontal Z, not the true world-up Z.

▸ CAUSE

A headless Blender OBJ import with up_axis='Y' leaves the mesh's local vertex coords (bmesh.verts[i].co, mesh.vertices[i].co) in the file's Y-up frame. The Y→Z-up rotation is stored in the object's matrix_world, not baked into the vertices.

So obj.matrix_world @ v.co gives the true Z-up world coordinate, but raw .co.z reads the file's Z (a horizontal axis in the original coordinate space).

▸ FIX

Right after import, apply the object transform so mesh-local coords become world coords (Z-up):

snippet
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)

After this, .co values are in the true Z-up world frame. The exporter's up_axis='Y' re-converts back to the file frame on the way out, so orientation still round-trips correctly.

Alternatively, transform verts manually: world_co = obj.matrix_world @ v.co — but applying the transform is simpler for scripts that do extensive per-vertex work.

▸ WHY IT WORKS

transform_apply bakes the object's matrix_world into the mesh data, multiplying every vertex by the rotation/scale/translation. After this, matrix_world becomes identity and the vertex coords are in world space, where Z is up — matching what per-vertex scripts expect.

Verified on engine 26.07.08e: seen in a real project.
s&box moves fast; an undated fix is a liability. Spot a stale detail?

Want to know when new guides or fixes drop? Join the community to help build this out. Report gotchas, flag outdated fixes, or just lurk.

Join the Discord