Resetting the viewport camera roll in Python

I’ve been working on a plugin for a while that automatically sets up the UV of an object the way I need them, which is with the texture being rotated down so to speak? The texture is a text, so I need it to be readable and upright.

And I nearly got everything working, except for the rotation part. I split the object by every triangle, then create orientations that let me rotate the camera to every one of the triangles, then I project from view on every single one of them and merge them back together.

What messes it up is that the viewport camera always has a bit of a roll to it after using view_axis(type='TOP', align_active=True) and I need to roll it back to a “global 0” (don’t know how to put it) so it is upright, and the UV projected from view is also upright.

I tried calculating the roll from a Quaternion using some formula from the web, and then rolling the viewport with bpy.ops.view3d.view_roll(), but it also doesn’t work, and the roll isn’t even 0.0 when calculating it with the same formula.

view_rot = context.space_data.region_3d.view_rotation
view_rot = mathutils.Quaternion((view_rot[0], view_rot[1], view_rot[2], view_rot[3]))
roll = math.atan2(2.0 * (view_rot[3] * view_rot[2] + view_rot[0] * view_rot[1]) , 1.0 - 2.0 * (view_rot[1] * view_rot[1] + view_rot[2] * view_rot[2]))
bpy.ops.view3d.view_roll(angle = roll)

This is how I try to do it, and frankly it doesn’t work. Maybe there is a better solution for that?

If you’re just trying to zero out your camera roll there’s a million ways to do this, but the easiest to understand is probably the following:

from mathutils import Euler
from math import radians

rv3d = context.space_data.region_3d
old_rot = rv3d.view_rotation
rv3d.view_rotation = Euler((old_rot[0], radians(0), old_rot[2])).to_quaternion()

just remember that a viewport camera doesn’t rotate from where the ‘camera’ is located, it rotates from an invisible orbit point X units in front of the camera (where X depends on your zoom level), so zero’ing out the roll might not have the desired outcome.


Yup, doing that doesn’t have the desired outcome at all and just flips the camera somewhere else.
Does a regular camera also rotate that way? Is there any other solution to the problem?

Nice, rot is Quaternion though. You can turn it to euler first and then reconstruct the rotation but exclude the roll.

import bpy
from mathutils import Euler
from math import radians

class ResetRoll(bpy.types.Operator):
    bl_idname = 'view.reset_roll'    
    bl_label = 'reset roll'
    def execute(self, context):
        rv3d = context.space_data.region_3d
        rot = rv3d.view_rotation.to_euler()
        rv3d.view_rotation = Euler((rot[0], 0, rot[2])).to_quaternion()
        return {'FINISHED'}


It actually works nearly perfectly now! Although sometimes the roll is just 180 degrees around, which means the text ends up being upside down, and I can’t find the condition when that happens to just roll it over accordingly.

You’ve just discovered gimbal lock, an unavoidable downside of using Euler rotation instead of quaternions :slight_smile:

1 Like

Would there be any way around this? Is it even possible to do?

1 Like

Not with Euler rotation, it’s a fundamental mathematical limitation. If you can find a way to get that code working with quaternions, then you could

1 Like

@Deiran @joseph

I just remembered this addon to rotate canvas.


Thanks! As I can see, this addon rotates the camera by a value and not to a value, I assume that’s how it avoids the “gimbal lock”? Can you give me any hints how to implement this?

I didn’t want to mention it since you didn’t ask- but now that you do, i find it odd that you want to unwrap UVs but we’re sitting here talking about how to rotate cameras :slight_smile:
why not skip the middle man and just unwrap the faces?


I need every face to have a separate texture with a text on it, and the text has to be upright, so it is always readable when standing close to it and not randomly rotated. Is there some way to unwrap faces so that the UV is always “upright”?

sure, as long as there is some algorithmic way of thinking of the problem, there is always an algorithmic solution to it. If you think about it, a face already has a local 2D space relative to itself (it’s just a flat plane after all)- it’s just a matter of transforming the 3D points into 2D coordinates (which you can easily do by creating a matrix from the face’s normal, one of the edge vectors, and the cross product of the two.


Would it be possible to get in contact with you? Can you perhaps help me with that, for a price, of course?

unfortunately I don’t have a ton of free time at the moment, but I can give you a quick pseudo-code version of what I would do:

For each face in a bmesh:

  • find the loop that has the lowest Z location, that’s how we’ll determine “right side up”. we’ll call this the “bottom loop”
  • create a matrix using the face normal as the Z axis, the 'bottom loop" vector as the X axis, and the Y axis would be the cross product of the X and Z axis. all of these vectors should be normalized, obviously. This is our “unwrap matrix”

And then for each uvloop on a face:

  • Set the uv coordinate to be the uvloop.vert location transformed by the “unwrap matrix”. UV coordinates are Vector2’s, where the vert locations are in 3d space- just remember that you can throw away the Z axis coordinate after the transformation because we transformed everything to a 2D plane. You will also want to normalize the transformed UV coordinate to fit everything into 0,1 space.

At this point you should have each face unwrapped and ‘right side up’, and fit to a texture. You may have additional logic that is needed depending on your specific criteria but that’s the jist of it.

As a side note- this is actually how camera projection works as well, but rather than using the face as the projection plane which I’ve described, it uses the camera’s view plane. Pretty much all of the other steps are the same.

1 Like