9
$\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$

7 Answers 7

6
$\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$
2
  • 1
    $\begingroup$ As a matter of fact, thats exactly what my script does :P $\endgroup$
    – gandalf3
    Dec 4, 2014 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$
    – Weaver
    Dec 4, 2014 at 23:03
6
$\begingroup$

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

import bpy

# If you want to automatically select all the linked objects:
# bpy.ops.object.select_linked(type='OBDATA')

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

# select objects again
for obj in selected:
    obj.select_set(True)
    # reset scale
    obj.scale = (1, 1, 1)
# link selected to active
bpy.ops.object.make_links_data(type='OBDATA')
$\endgroup$
0
2
$\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$
7
  • $\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, 2014 at 2:51
  • $\begingroup$ Dunno what you wanna have, added 3 more code snippets for testing. $\endgroup$
    – CodeManX
    Feb 15, 2014 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$ Feb 15, 2014 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, 2014 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, 2014 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$
2
  • $\begingroup$ I still need to do some further testing. $\endgroup$
    – Vader
    Feb 16, 2014 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, 2014 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$
1
$\begingroup$
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

    bpy.ops.object.make_links_data(type='OBDATA')
    bpy.ops.object.select_all(action='DESELECT')

Bit of topic but may be helpful ... groups object by name ... selects a prototype ... applies transform ... links all instances in a group to group's prototype

$\endgroup$
0
1
$\begingroup$

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"]
__license__     = "GPL"
__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)
$\endgroup$

You must log in to answer this question.

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