Based on this answer, Rotate UV by specific angle e.g. 30deg in Python script in backgroud mode

enter image description here

you can do a rotation of the UV mapping, then enter the rotation angle, so far everything is ok.

The problem arises when working with images of rectangular and non-square proportions.

This function will not take into account the appearance of this image, so the rotation will deform the image.

Obviously this does not happen in the uv editor.

It seems that this can be compensated for by obtaining the aspect ratio of the image. But I think it is an incorrect or inconvenient practice.

The question is therefore whether it is possible to obtain a correct UV rotation without knowing the aspect ratio of the image.

Here I attach the .blend example of the problem


1 Answer 1


UV editor uses the aspect ratio.

enter image description here Closing the image in the UV editor on LHS demonstrates that the UV editor is using the aspect ratio of the image when rotating. The LHS rotation result is equivalent to running script result in question, or with unit aspect ratio below.

Instead of our normal 2D rotation matrix,

$$R=\left[ \begin{array}{ccc} \cos \theta & \sin \theta& 0 \\ -\sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \\ \end{array} \right]$$

can allow for a non uniform aspect ratio $\rho$

$$R=\left[ \begin{array}{ccc} \cos \theta & \dfrac{\sin \theta}{\rho } & 0 \\ -\rho \sin\theta & \cos \theta& 0 \\ 0 & 0 & 1 \\ \end{array} \right]$$

Here is a an object mode script using numpy and the $XY$ dimension of the object (for example, if imported using image as planes) to calculate $\rho$ which will emulate the rotation as shown on RHS of gif. Setting p = 1 will be same as shown on LHS.

import bpy
from mathutils import Matrix, Vector
import numpy as np

ob = bpy.context.object
me = ob.data
uvlayer = me.uv_layers.active

pivot = Vector((0.5, 0.5))
angle = np.radians(30)
# aspect ratio
p = ob.dimensions.y / ob.dimensions.x
# aspect rotate
R = Matrix((
            (np.cos(angle), np.sin(angle) / p),
            (-p * np.sin(angle), np.cos(angle)),
uvs = np.empty(2 * len(me.loops))
uvlayer.data.foreach_get("uv", uvs)

# shear rotate about origin, translate to pivot
uvs = np.dot(
        uvs.reshape((-1, 2)) - pivot, 
        R) + pivot
# write the new UV's back
uvlayer.data.foreach_set("uv", uvs.ravel())
# update mesh to display changes.

enter image description here Result on image imported from "Images as Planes" addon. Note positive rotation is clockwise, where-as blender uses CCW (simply change sign of angle to change)

  • 2
    $\begingroup$ This answer is great and works great. Really a useful answer $\endgroup$
    – Noob Cat
    Commented Feb 27, 2021 at 21:24
  • 1
    $\begingroup$ This deserves a 'wow', if you ask me. Missed it. Trying to work it through.. you can call np.dot on a mathutils.Matrix? Should I not be surprised by that? $\endgroup$
    – Robin Betts
    Commented Jul 1, 2021 at 16:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .