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...