7

I am new to Blender (and to some extent to python). I have pieced together this function that rotates and scales an Object at low level by modifying the matrices:

def rotate_scale_object(inObj, angle, axis, scaling):
        rot_mat = Matrix.Rotation(radians(angle), 4, axis)
        orig_loc, orig_rot, orig_scale = inObj.matrix_world.decompose()
        orig_loc_mat   = Matrix.Translation(orig_loc)
        orig_rot_mat   = orig_rot.to_matrix().to_4x4()
        orig_scale_mat = (Matrix.Scale(scaling,4,(1,0,0)) @ Matrix.Scale(scaling,4,(0,1,0)) @ Matrix.Scale(scaling,4,(0,0,1)))
        # assemble the new matrix
        inObj.matrix_world = orig_loc_mat @ rot_mat @ orig_rot_mat @ orig_scale_mat
        #update_matrices(inObj)
        return inObj

I call it in my script as follow:

obj = bpy.context.active_object
obj = scale_rotate_smooth_color(obj, angle = -90, axis = "X", scaling = 0.01)

It works very well: the object is rotated & scaled as expected. Yet, I am missing a way to "apply" these transformations to the object, as one would do by pressing Ctrl + A and select Apply all transformations.

Is there a way to do this "low level", without resorting to bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)?

amaizel
  • 167
  • 1
  • 11

1 Answers1

21

Mesh transform

Does a child object inherit the matrix from the parent?

Applying the transform.

  • Set the basis matrix value to identity for any or all of location, rotation scale.

  • Transform the mesh and or child objects to reflect the change

For all transforms.

import bpy

context = bpy.context

ob = context.object mb = ob.matrix_basis if hasattr(ob.data, "transform"): ob.data.transform(mb) for c in ob.children: c.matrix_local = mb @ c.matrix_local

ob.matrix_basis.identity()

To apply only rotation.

Similarly will show the process of only applying rotation. Transform the data and children such that the rotation part of matrix basis is identity.

Method below uses the Matrix.decompose to reduce the transform into its three components. The rotation is defined as a quaternion. Another way to decompose is discussed in

How to Decompose and Compose the local transform matrix

import bpy
context = bpy.context
from mathutils import Matrix

ob = context.object mw = ob.matrix_world mb = ob.matrix_basis

loc, rot, scale = mb.decompose()

rotation

T = Matrix.Translation(loc) R = rot.to_matrix().to_4x4() S = Matrix.Diagonal(scale).to_4x4()

if hasattr(ob.data, "transform"): ob.data.transform(R)

for c in ob.children: c.matrix_local = R @ c.matrix_local

ob.matrix_basis = T @ S

For rotation and scale

...:
    ob.data.transform(R @ S)

for c in ob.children: c.matrix_local = (R @ S) @ c.matrix_local

ob.matrix_basis = T

ie set the basis to make identity of applied property, transform mesh and children by the jconverse. eg applying all is transforming by T @ R @ S and setting to I @ I @ I transform & scale T @ I @ S --> I @ R @ S (where I is identity matrix)

Method for all or any

import bpy
from mathutils import Matrix

def apply_transfrom(ob, use_location=False, use_rotation=False, use_scale=False): mb = ob.matrix_basis I = Matrix() loc, rot, scale = mb.decompose()

# rotation
T = Matrix.Translation(loc)
#R = rot.to_matrix().to_4x4()
R = mb.to_3x3().normalized().to_4x4()
S = Matrix.Diagonal(scale).to_4x4()

transform = [I, I, I]
basis = [T, R, S]

def swap(i):
    transform[i], basis[i] = basis[i], transform[i]

if use_location:
    swap(0)
if use_rotation:
    swap(1)
if use_scale:
    swap(2)

M = transform[0] @ transform[1] @ transform[2]
if hasattr(ob.data, "transform"):
    ob.data.transform(M)
for c in ob.children:
    c.matrix_local = M @ c.matrix_local

ob.matrix_basis = basis[0] @ basis[1] @ basis[2]


test call

apply_transfrom(bpy.context.object, use_rotation=True)

Recent answer using Mesh.transform Problem with rotating objects by script

batFINGER
  • 84,216
  • 10
  • 108
  • 233
  • Exactly what I was missing. Thanks a lot. – amaizel Dec 01 '19 at 16:07
  • cheers, prob should have used mult by inverse instead of setting to identity. eg to apply rotation make a rotation matrix, R apply it to mesh me.transform(R), then mult matrix_world by R.inverted() – batFINGER Dec 01 '19 at 16:15
  • This didn't work for me, which is why I asked another question. But it got closed. I wanted to apply rotations to my meshes after adding rotation eulers. In the end I rewrote my code to perform the rotation directly on the meshes instead of using bpy.ops. – DrewTNBD Jun 30 '21 at 11:05
  • 1
    Apologies, fixed and fixed. Probably relates to comment above also. Edited this one re-opened other. – batFINGER Jun 30 '21 at 13:24
  • Nice one! Thanks for the edits! – DrewTNBD Jun 30 '21 at 14:28
  • @batFINGER your 'rotation only' solution worked brilliantly for me. Thanks very much! Your 'for any' solution didn't work however. I tested it in my class and my rotation gets lost. Might be worth looking into for future problem solving, but I'm happy here with just rotation. – DrewTNBD Jul 01 '21 at 12:00
  • Meant to mention was considering an option to identity parent inverse. Can you elaborate on setup that "didn't work" – batFINGER Jul 01 '21 at 12:43
  • @DrewTNBD this one was recently brought to my attention https://blender.stackexchange.com/questions/223800/how-to-decompose-and-compose-the-local-transform-matrix/223804#223804 may be an option to look at for here. – batFINGER Jul 01 '21 at 13:09
  • @batFINGER so what I did was I took your "for any" solution, and I added a self parameter and shamelessly put it into my class. I called it with use_rotation=True and my rotation wasn't applied at all. But when I did it with your "just rotation" solution, it worked fine. But what I am noticing now is that the bounding box for the object doesn't seem to get recalculated. So when I run a object.bound_box call on the rotated object, it still has the pre-rotation values in it. Which really twists my melon! – DrewTNBD Jul 01 '21 at 15:32
  • The two examples in question "rotation only" and "all" when run as above both theoretically are same and testing (so far) glean the same result (consistent with op) for me. Do you get a differing result using as above? Consider re-jigging your question re issue, since it could prob be closed as a dupe of this one now. (my re-opening prior, excludes me from voting to close again) Depending on use, a Mesh.update() or ViewLayer.update() may be required – batFINGER Jul 01 '21 at 15:40
  • to be sure, there is no way to apply scale in edit mode? and get it back, for instance, after having moved the origin? – Uneconscience UneSource Oct 06 '21 at 23:14
  • @batFINGER first of all - thank you for this function which proved to do the job perfectly for me :) If I could just ask you - is it possible to improve it, so it can handle negative values as well? For example I have an object with scale X = -1 to mirror it along X. Big thanks! – Jan Kadeřábek Sep 14 '22 at 13:30