1
$\begingroup$

How to set child object world scale using python ? (With the least amount of math possible)

You can read the value I try to change from:

obj.matrix_world.to_scale()

I want to modify this value to an other. Like obj.matrix_world.ItsScale = (5,6,2)

$\endgroup$
2
  • $\begingroup$ What do you mean by "world scale"? Along global axes? $\endgroup$
    – TLousky
    Aug 25, 2015 at 6:35
  • $\begingroup$ Yes it would be similar to "obj.matrix_world.to_scale() = Vector((5,3,2))" $\endgroup$ Aug 25, 2015 at 9:26

3 Answers 3

4
$\begingroup$

To set a child's scale to a certain scale in world-space, you can do:

import bpy
from mathutils import Matrix

ob = bpy.context.object # child

nmat = ob.matrix_world.normalized()

target_scale = (2, 2, 2)

smat = Matrix()
for i in range(3):
    smat[i][i] = target_scale[i]

# note: order is important!
ob.matrix_world = nmat * smat

The change in scale will not affect the parent (or grand-parents) in any way. The child's children will do on the other hand.

If you want to change the scale of child and parent, traverse all the way up to the root object (which may be a grand-grand-...-parent) and change its scale:

import bpy
from mathutils import Matrix

ob = bpy.context.object
while ob.parent:
    ob = ob.parent
print("Root object:", ob.name)

ob.scale = (2, 2, 2)

Note that this will affect all children of the root object.

If you want to change scale of root and child1 only...

     root
      /\
     /  \
    /    \
child1  child2

... then it's a bit trickier, because you need to change the scale of the objects you want to exclude (they are affected by root). Here's a script that can be configured to ~~~set the selected object to the given target scale~~~ (I think it actually changes objects proportionally to the local scale, and fails if non-uniform scaling is involved), while preserving object locations:

import bpy
from mathutils import Matrix

ob = bpy.context.object
start = ob
orig_scale = start.scale
target_scale = (2, 2, 2)
target_factor = tuple(map(lambda a, b: a / b, target_scale, orig_scale))

visited = [start]
while ob.parent:
    ob = ob.parent
    visited.append(ob)
root = ob    

orig_loc = {}
def store_loc_recursive(obs):
    for ob in obs:
        orig_loc[ob.name] = ob.matrix_world.to_translation()
        if ob.children:
            store_loc_recursive(ob.children)
store_loc_recursive(root.children)

other = []
other.extend(start.children)

for child in root.children:
    if child not in visited and child not in other:
        other.append(child)

root.scale = tuple(map(lambda a,b: a * b, root.scale, target_factor))

for ob in other:
    ob.scale = tuple(map(lambda a, b: a / b, ob.scale, target_factor))

bpy.context.scene.update()

def restore_loc_recursive(obs):
    for ob in obs:
        ob.matrix_world.translation = orig_loc[ob.name]
        if ob.children:
            restore_loc_recursive(ob.children)
restore_loc_recursive(root.children)

Note that the scale property of the selected object will not change. Thus, repetitive runs will grow all objects on the path from selected to root more and more. Nonetheless, the change in scale is correct - if you started at dimensions (1,1,1) and set a target scale of (2,2,2) in the script, then all objects from selected up to root will be doubled in size.

A better approach might be to un-parent temporarily, apply scale changes, then re-parent. But I noticed that even Blender's built-in Clear Parent (Keep Transform) fails to preserve non-uniformly scaled objects...

$\endgroup$
2
$\begingroup$

You can use a recursive function to find all the parents an object has, then multiply the object's matrix by all of the parents' matrices, then get the scale:

import bpy

def get_parents( o, parents ):
    if o.parent: 
        parents.append( o.parent )
        get_parents( o.parent, parents )

    return parents

o = bpy.context.object
parents = get_parents( o, [] )

# Calc cumulative matrix:
global_matrix = o.matrix_world
for p in parents: global_matrix *= p.matrix_world

global_scale     = global_matrix.to_scale()
new_global_scale = ( 5, 7, 9 )

scale_ratio = [ ng / g for ng, g in zip( new_global_scale, global_scale  ) ]

o.scale = [ axis * ratio for axis, ratio in zip( o.scale, scale_ratio ) ]
$\endgroup$
5
  • $\begingroup$ Perhaps I expressed myself poorly. But I'm not looking for the "global_matrix". I want to modify his value to an other. Like global_matrix.scale = (5,6,2) $\endgroup$ Aug 26, 2015 at 8:45
  • $\begingroup$ @IssanouKamardineIK, well that's easy. Once you have the global matrix scale, you can update the object's scale to reflect the desired global scale by finding the ratio between the current and desired global scale (updated my code above to reflect this) $\endgroup$
    – TLousky
    Aug 26, 2015 at 11:19
  • $\begingroup$ nice ! but in my case it works better without this line # for p in parents: global_matrix *= p.matrix_world $\endgroup$ Aug 26, 2015 at 11:35
  • $\begingroup$ This means you're not taking into account the parent objects' scale though $\endgroup$
    – TLousky
    Aug 26, 2015 at 12:23
  • $\begingroup$ Looks overly complicated give that my script (first code block) appears to work too? Please test if mine it behaves the same in more complex setups! $\endgroup$
    – CodeManX
    Aug 26, 2015 at 19:34
0
$\begingroup$

The simpler way is to set the scale property of the object :

obj.scale = Vector((1., 1., 1.))
$\endgroup$
4
  • $\begingroup$ Dont forget that the obj is a child and the parent scale will change. parent scale will not change "obj.scale". So "obj.scale" will not corresponding to the obj.matrix_world "scale". word scale not = to local scale (obj.scale) $\endgroup$ Aug 25, 2015 at 12:29
  • 1
    $\begingroup$ Please be more explicit. What is the difference between local and world scale to you ? $\endgroup$ Aug 25, 2015 at 13:16
  • $\begingroup$ "obj.matrix_world.to_scale()" this is global. And "obj.scale" is local when obj is a child $\endgroup$ Aug 25, 2015 at 13:53
  • $\begingroup$ Maybe you could try something like (parent.matrix_world * child.matrix_world).to_scale() ? Is that what you need ? $\endgroup$ Aug 25, 2015 at 14:14

You must log in to answer this question.

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