# Apply transforms to linked objects?

## Note:

Vader's solution set me on the right track, but I ended up using this, as it goes a bit faster with large numbers of objects.

## Original question:

Is it possible to directly apply transforms on linked object data? Blender does not seem to let you do this. It results in an error: Cannot apply to a multiuser: object, object2, etc. aborting

That makes sense, but I should be able to apply the scale to all of the objects, right?

For instance, I have three cubes which are linked, but have different scales:

I realize that because the mesh data is linked, they all have to be the same size once the scale is applied.

I tried selecting them all with the smallest cube active, then applying the scale.

The expected result is that they all become the size of the smallest cube with scales of 1,1,1:

However, it just throws the same error instead:

Is there some way to do this without unlinking everything and then relinking?

Update: There seems to have been some confusion about if this was possible or not, so I'll quote my comment below to help clarify what I wanted:

I only want the active object to remain the same size, I realize the other objects will have to become the same size as the active object. I don't see why that would be impossible..

Hmm, it might be slower than the elegant-er scripts, but there's a way to do this without scripting at all.

Starting with the objects selected and the one you want to keep the size of active:

1. U -> Object and Data, to make everything single user
2. ⎈ CtrlA -> Scale or whatever you want to apply
3. ⎈ CtrlL -> Object Data, to link all objects to the active's data.

Optional: Save file and then File->Revert to shed any excess mesh data.

There's a second method which avoids making extra duplicate objects, but you need to make every selectable object sharing the data is one that you want to nuke into submission.

1. Select the 'master' object you want to duplicate
2. U -> Object and Data, to make it single user
3. ⎈ CtrlA -> Scale or whatever you want to apply
4. Select any of the objects that you want to relink to the master
5. ShiftL -> Object Data, to select everything that's still linked to the prior data.
6. Shift-Select the object to copy data from
7. ⎈ CtrlL -> Object Data, to link all objects to the active's data.

If you have to do this more than about once or twice ever, though, I'd recommend gandalf3's script instead :3.

• As a matter of fact, thats exactly what my script does :P – gandalf3 Dec 4 '14 at 19:04
• I didn't expect to get reselected for this o.o I was just looking at the answers and thinking "you can do that with the keyboard". ... And I often have a "script first, ask questions later" mentality ... about everything BUT blender, since I still haven't made heads or tails out of blender's API. Thanks for doing the keyboardkeys thing, I hadn't figured that out yet. – StarWeaver Dec 4 '14 at 23:03

Vader's solution works, but I think this modified version is slightly more efficient because it only unlinks the active object:

import bpy

selected=[]
# de-select all selected objects and add to array:
for obj in bpy.context.selected_editable_objects:
selected.append(obj)
obj.select = False
# reselect active object
bpy.context.object.select = True
# Make single user and apply scale
bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True, obdata=True, material=True, texture=True, animation=True)
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)

# select objects again
for obj in selected:
obj.select = True
# reset scale
obj.scale = 1,1,1
# link selected to active


I tried something fancy

import bpy
from mathutils import Matrix

me_done = []
for ob in bpy.context.selected_editable_objects:
me = ob.data

if me not in me_done:
me_done.append(me)
mat = Matrix()
mat[0][0], mat[1][1], mat[2][2] = ob.matrix_world.to_scale()
print(mat)
me.transform(mat)

ob.matrix_world = ob.matrix_world.normalized()


But I was totally off track, what you seem to want is basically to set the scale of all objects to 1,1,1, which is as easy as:

for ob in bpy.context.selected_editable_objects:
ob.matrix_world = ob.matrix_world.normalized()


Variant 1:

import bpy
from mathutils import Matrix

ob_active = bpy.context.object
mat = Matrix()
mat[0][0], mat[1][1], mat[2][2] = ob_active.matrix_world.to_scale()
ob_active.data.transform(mat)
ob_active.matrix_world = ob_active.matrix_world.normalized() * mat.inverted()

for ob in bpy.context.selected_editable_objects:
if ob == ob_active:
continue

ob.matrix_world = ob.matrix_world.normalized() * mat.inverted()


Variant 2:

import bpy
from mathutils import Matrix

ob_active = bpy.context.object
mat = Matrix()
mat[0][0], mat[1][1], mat[2][2] = ob_active.matrix_world.to_scale()
ob_active.data.transform(mat)
ob_active.matrix_world = ob_active.matrix_world.normalized()

for ob in bpy.context.selected_editable_objects:
if ob == ob_active:
continue

ob.matrix_world = ob.matrix_world.normalized() * mat.inverted()


Variant 3:

import bpy
from mathutils import Matrix

ob_active = bpy.context.object
mat = Matrix()
mat[0][0], mat[1][1], mat[2][2] = ob_active.matrix_world.to_scale()
ob_active.data.transform(mat)
ob_active.matrix_world = ob_active.matrix_world.normalized()

for ob in bpy.context.selected_editable_objects:
if ob == ob_active:
continue

ob.matrix_world = ob.matrix_world.normalized()

• Is there a way to keep the size of the object the same, applying the scale of the active object to the linked mesh data? – gandalf3 Feb 15 '14 at 2:51
• Dunno what you wanna have, added 3 more code snippets for testing. – CodeManX Feb 15 '14 at 3:39
• I think gandalf3 wants to have their size visisually unchanged, while appling their individual scale all same to the active one. If so, I doubt that it would be possible. :S I think that's why it doesn't allow you to do this. – Leon Cheung Feb 15 '14 at 5:01
• @LeonCheung I only want the active object to remain the same size, I realize the other objects will have to become the same size as the active object. I don't see why that would be impossible.. – gandalf3 Feb 15 '14 at 8:44
• @LeonCheung I can confirm this behavior. Also gandlaf3, if this wold to work as you want it to, it would defeat the purpose of the links. – Vader Feb 15 '14 at 13:23

This is my python implementation of this problem. Here is how it works:

• the input of the script is the current selection

• for each item in selection un-link it and apply the scale

• after doing that with all object still selected, link them again

import bpy

# unlink, apply scale
for obj in bpy.context.selected_editable_objects:
bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True, obdata=True, material=True, texture=True, animation=True)
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)

# link all together

• I still need to do some further testing. – Vader Feb 16 '14 at 0:09
• The reason why my method is faster for large amounts of objects is because it only unlinks the active object, applies the scale, and the links the other (still linked) objects to the active object instead of unlinking and applying the scale to each object individually. Method three does not apply the scale and does not maintain the links between the objects. – gandalf3 Feb 16 '14 at 1:10

I had pretty much the same problem, except on a larger scale. I didn't want to select one multi-user object at a time, and I also wanted to preserve the relative scale of objects.

This script loops through your selection, unlinks any multi-user object, applies scale, rescales the other objects using the mesh, then relinks them.

"""apply_scale_to_multi-user_object.py: \
Loops through selection and applies the scale to multi-user objects. Relative size is preserved."""

__author__      = "nik10110"
__credits__     = ["Gandalf3", "Vader"]
__version__     = "0.1.1"

preserve_relative_scale = True

import bpy

# Remember the initial selection and initially active object
active_obj = bpy.context.scene.objects.active
initial_selected=[]
for obj in bpy.context.selected_editable_objects:
initial_selected.append(obj)

# Build an inverse mesh -> objects tree
mesh_owners = {}
for ob in bpy.data.objects:
if ob.type == 'MESH':
mesh_owners.setdefault(ob.data, []).append(ob)

# Apply scale to selected objects (and all objects using the same mesh)
selected = list(initial_selected)
for obj in selected:
if obj.data is not None:
# Remember the initial scale and object
orig_scale = obj.scale.copy()
orig_data = obj.data

# Set active object and deselect the rest
bpy.context.scene.objects.active = obj
bpy.ops.object.select_all(action='DESELECT')
obj.select = True

# Make single user and apply scale
bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True, obdata=True, material=False, texture=False, animation=False)
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)

# Keep the relative scale of the other objects using the same mesh
for o in mesh_owners[orig_data]:
if o != obj:
if o in selected:
# No need to loop through the same mesh again
selected.remove(o)
if preserve_relative_scale:
o.scale.x = o.scale.x/orig_scale.x
o.scale.y = o.scale.y/orig_scale.y
o.scale.z = o.scale.z/orig_scale.z
else:
o.scale = 1,1,1
o.select = True
# Re-link to the scaled mesh

# Reset active object and initial selection
bpy.context.scene.objects.active = active_obj
bpy.ops.object.select_all(action='DESELECT')
for o in initial_selected:
o.select = True


Since I have empty objects mixed into my hierarchies, none of the scripts on this page seemed to do the trick for me (in 2.8 at least), so here's my first script, using frankensteined concepts that can be found on this page, yet written from scratch:

"""reset-scale-of-hierarchy.py: \
Runs through an entire hierarchy and resets the scale to 1 on everything."""

__author__      = "eobet"
__credits__     = ["Gandalf3", "Vader", "nik10110"]
__version__     = "0.1"

import bpy

selected = bpy.context.selected_objects
top_level = []

hierarchy = []
mesh_data = {}
instanced_data = {}

original_active = bpy.context.view_layer.objects.active
original_selection = selected

def resetHierarchy():
for h in hierarchy:
bpy.ops.object.select_all(action='DESELECT')
h.select_set(state = True)

if h.type == 'MESH':
# here is where we identify instances
if len(mesh_data[h.data]) > 1:
# check if we already reset this instance
if h.data in instanced_data:
i = h.data
bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True, obdata=True)
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
h.data = instanced_data.get(i)[0]
else:
# reset and save the mesh if not
original_data = h.data
bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True, obdata=True)
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
instanced_data.setdefault(original_data, []).append(h.data)
else:
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
else:
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)

return

def drillDown(o):
if o.type == 'MESH':
mesh_data.setdefault(o.data, []).append(o)

hierarchy.append(o)

for child in o.children:
drillDown(child)

return

def getTopLevel(o):
if o.parent is not None:
return getTopLevel(o.parent)
else:
return o

# get only top level objects regardless of selection
for object in selected:
top = getTopLevel(object)
if top not in top_level:
top_level.append(top)

# drill down from top level objects only
for object in top_level:
drillDown(object)

# here we reset the scaling on all found objects and found instances
resetHierarchy()

# reset selection to the way it was before we had to do all this crap
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = original_active
for object in original_selection:
object.select_set(state = True)

import bpy

group_names = ("SM_Cube_2", "SM_Cube_1.5", "SM_Cube_0.66")

grouped_items = []

for g in range(0,len(group_names)):
grouped_items.append([])

selected = bpy.context.selected_objects.copy()

bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True, obdata=True)
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)

bpy.ops.object.select_all(action='DESELECT')

for o in selected:
for g in range(0,len(group_names)):
if group_names[g] in o.name:
grouped_items[g].append(o)

for g in grouped_items:

#bpy.context.view_layer.objects.active = g[0]    #2.8+
bpy.context.scene.objects.active = g[0]        #<2.8

for o in g:
#o.select_set(True)                          #2.8+
o.select = True                            #<2.8