"My model imported upside down / sideways / facing the wrong way"
Your mesh loads, but:
- The "front" of the model faces the wrong cardinal direction in-world.
- Roofs, ramps, or angled slabs tip the wrong way (valley instead of peak — upside-down geometry).
- Facing looks fine in Blender and wrong after the
.vmdlcompiles.
Three separate axis traps stack:
-
OBJ compile remaps Y-up → Z-up. Export with
forward_axis='NEGATIVE_Z', up_axis='Y'and keepimport_rotation [0,0,0]in the vmdl. The compiled world frame is:world = (-objZ, -objX, objY) * scale -
Facing convention. Geometry built along Blender +X faces world −Y after import. Yaw +90° turns it to face world +X.
-
Blender rotation sign. Rotating a slab about +Y by a positive angle tips its +X edge down. Guessing the sign ships every roof upside-down (valley instead of peak).
Colliders live in the model's own frame and rotate with the GameObject — author collider specs in the yaw-0 frame, never pre-rotate them to "fix" facing.
Export (Blender):
bpy.ops.wm.obj_export(
filepath=path,
export_materials=True,
forward_axis='NEGATIVE_Z',
up_axis='Y',
)vmdl: leave mesh import rotation at zero:
import_rotation = [ 0, 0, 0 ]Facing at placement — codify once, reuse everywhere:
// Blender +X → world −Y; +90° yaw → face world +X
const float FacingYawOffset = 90f;
float yaw = MathF.Atan2(dir.y, dir.x) * (180f / MathF.PI) + FacingYawOffset;
go.WorldRotation = Rotation.FromYaw(yaw);Angled geometry: before batching roofs/ramps, place one instance and verify the peak/valley visually. If it's inverted, negate the Blender rotation angle — do not "fix" it with import_rotation.
AI-generated meshes often face the opposite way from the Blender-pipeline convention — use atan2(...) − 90 instead of + 90. Verify one placed instance per source pipeline. See ai-generated-models.
s&box's OBJ importer owns the Y-up → Z-up conversion at compile time. Fighting that with import_rotation double-applies a frame change and breaks the facing constants you already tuned. Keeping export axes + zero import rotation means one predictable map (world = (−objZ, −objX, objY)), so a single yaw offset corrects "front" for every model from that pipeline. The roof-valley bug is pure Blender local-axis semantics — positive +Y rotation lowers the +X edge — so the fix is authoring discipline, not an engine remapping.