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$
  • $\begingroup$ What do you mean by "world scale"? Along global axes? $\endgroup$ – TLousky Aug 25 '15 at 6:35
  • $\begingroup$ Yes it would be similar to "obj.matrix_world.to_scale() = Vector((5,3,2))" $\endgroup$ – Issanou Kamardine IK Aug 25 '15 at 9:26
3
$\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...

| improve this answer | |
$\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 ) ]
| improve this answer | |
$\endgroup$
  • $\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$ – Issanou Kamardine IK Aug 26 '15 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 '15 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$ – Issanou Kamardine IK Aug 26 '15 at 11:35
  • $\begingroup$ This means you're not taking into account the parent objects' scale though $\endgroup$ – TLousky Aug 26 '15 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 '15 at 19:34
0
$\begingroup$

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

obj.scale = Vector((1., 1., 1.))
| improve this answer | |
$\endgroup$
  • $\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$ – Issanou Kamardine IK Aug 25 '15 at 12:29
  • 1
    $\begingroup$ Please be more explicit. What is the difference between local and world scale to you ? $\endgroup$ – Jonathan Chemla Aug 25 '15 at 13:16
  • $\begingroup$ "obj.matrix_world.to_scale()" this is global. And "obj.scale" is local when obj is a child $\endgroup$ – Issanou Kamardine IK Aug 25 '15 at 13:53
  • $\begingroup$ Maybe you could try something like (parent.matrix_world * child.matrix_world).to_scale() ? Is that what you need ? $\endgroup$ – Jonathan Chemla Aug 25 '15 at 14:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.