6
$\begingroup$

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:

enter image description here

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.

enter image description here

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

enter image description here

However, it just throws the same error instead:

enter image description here

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

$\endgroup$
4
$\begingroup$

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.

$\endgroup$
  • $\begingroup$ As a matter of fact, thats exactly what my script does :P $\endgroup$ – gandalf3 Dec 4 '14 at 19:04
  • $\begingroup$ 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. $\endgroup$ – StarWeaver Dec 4 '14 at 23:03
4
$\begingroup$

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
bpy.ops.object.make_links_data(type='OBDATA')
$\endgroup$
1
$\begingroup$

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()
$\endgroup$
  • $\begingroup$ 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? $\endgroup$ – gandalf3 Feb 15 '14 at 2:51
  • $\begingroup$ Dunno what you wanna have, added 3 more code snippets for testing. $\endgroup$ – CoDEmanX Feb 15 '14 at 3:39
  • 1
    $\begingroup$ 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. $\endgroup$ – Leon Cheung Feb 15 '14 at 5:01
  • $\begingroup$ @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.. $\endgroup$ – gandalf3 Feb 15 '14 at 8:44
  • $\begingroup$ @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. $\endgroup$ – Vader Feb 15 '14 at 13:23
1
$\begingroup$

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
bpy.ops.object.make_links_data(type='OBDATA')
$\endgroup$
  • $\begingroup$ I still need to do some further testing. $\endgroup$ – Vader Feb 16 '14 at 0:09
  • $\begingroup$ 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. $\endgroup$ – gandalf3 Feb 16 '14 at 1:10
1
$\begingroup$

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"]
__license__     = "MIT"
__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
        bpy.ops.object.make_links_data(type='OBDATA')

# 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
$\endgroup$

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.