# How to rotate UV and preserve the correct aspect ratio?

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

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

Clearly the UV editor uses the aspect ratio.

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))
# 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.
me.update()


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)

• This answer is great and works great. Really a useful answer – Noob Cat Feb 27 at 21:24