0
$\begingroup$

I'm sure I'm not the only one who is a bit annoyed at the current workflow for applying the scale of multi-user objects. We first need to make one copy unique, then apply the scale for that, then re-link the other copies back to that object and hit alt+s to apply their scale values. This can get especially tedious, if the scene has multiple different object types with linked duplicates.

Could we write a script that loops through each object in the scene, and applies their scale if it's not already 1,1,1? And if the script encounters an object with linked duplicates, then it should make one copy unique, apply the scale and then relink everything back like it was before.

I asked chat GPT to write such a script and here's what it came up with. Are we anywhere close for this actually working?

import bpy

# Loop through all objects in the scene
for obj in bpy.context.scene.objects:

    # Check if the object has a scale other than 1
    if obj.scale != (1, 1, 1):

        # Check if the object has a data block associated with it
        if obj.data is not None:

            # Check if the object is a linked duplicate
            if obj.data.is_library_indirect:

                # Make the linked duplicate a single user
                obj.data = obj.data.copy()

            # Apply the scale to the object
            bpy.ops.object.transform_apply(scale=True)

            # If the object was a linked duplicate, relink its object data
            if obj.data.is_library_indirect:
                obj.data = obj.data.library.remap_data(obj.data)
$\endgroup$
2
  • $\begingroup$ How do you want to relink the objects after applying their scales, if the scales were different? Let's say you have a sphere of $r = 1$, then you linked-duplicate it and scale up to scale = 2. Now if you apply the scale of the new sphere, the mesh radius is 2, but this means the first sphere either has to use another mesh, or will also have a big sphere of radius 2. Also, the answer you provide goes way over answering the question, as it changes modifier settings to keep the same visual effect after applying scale. $\endgroup$ Commented Feb 27, 2023 at 16:58
  • $\begingroup$ @MarkusvonBroady I think the proper way to handle linked objects with different scales would be not re-link such objects with each other, but instead only relink copies with the same scale value and create a new linked duplicate set for the object/objects that had a different scale. $\endgroup$
    – user44109
    Commented Feb 27, 2023 at 17:01

1 Answer 1

1
$\begingroup$

Looks like someone already beat me to it, the script provided here worked great for me: https://blenderartists.org/t/apply-scale-to-linked-duplicates/1182294

import bpy

scale_length = bpy.context.scene.unit_settings.scale_length
objects = bpy.context.selected_objects
linkedoblist = {}
latticelist = []
latticedata = []
latticechildren = []

# Runs through modifiers to change values based on object scaling and scene scaling


def modifierfun(var):
    if mod.name == 'Bevel':
        mod.width *= var
    elif mod.name == 'Solidify':
        mod.thickness *= var
    elif mod.name == 'Array':
        mod.constant_offset_displace[0] *= var
        mod.constant_offset_displace[1] *= var
        mod.constant_offset_displace[2] *= var
        mod.merge_threshold *= var
    elif mod.name == 'Boolean':
        mod.double_threshold *= var
    elif mod.name == 'Mirror':
        mod.merge_threshold *= var
    elif mod.name == 'Screw':
        mod.screw_offset *= var
    elif mod.name == 'Wireframe':
        mod.thickness *= var
    elif mod.name == 'Cast':
        mod.radius *= var
    elif mod.name == 'Hook':
        mod.falloff_radius *= var
    elif mod.name == 'Shrinkwrap':
        mod.offset *= var
    elif mod.name == 'Warp':
        mod.falloff_radius *= var
    elif mod.name == 'Wave':
        mod.start_position_x *= var
        mod.start_position_y *= var
        mod.falloff_radius *= var
        mod.height *= var
        mod.width *= var
        mod.narrowness *= var


# Append lattices to a list. Append their children to another list.
for ob in objects:
    if ob.type == 'LATTICE' and ob.children:
        latticelist.append(ob)
        latticedata.append(ob.data)
        latticechildren.append(ob.children[0])
    if ob.type not in ['EMPTY', 'SPEAKER'] and ob.data.users > 1 and ob.type != 'LATTICE':
        linkedoblist[ob] = ob.data

# First pass. Change modifier values to compensate for any object scaling. Make any linked lattice children single users.
for ob in objects:
    var1 = min(ob.scale, key=lambda x: abs(1 - x))
    if latticechildren.count(ob) > 0:
        bpy.ops.object.make_single_user(object=True, obdata=True)
    if ob.modifiers:
        for mod in ob.modifiers:
            modifierfun(var1)

# Clear parenting of lattice children but keep lattice modifier active
for ob in latticechildren:
    bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM')

# Resize all selected objects based on the scene's unit scale
bpy.ops.transform.resize(value=(1.0 * scale_length, 1.0 * scale_length, 1.0 * scale_length))

# Deselect objects we don't want to apply scaling to
for ob in objects:
    if ob.type in ['CAMERA', 'LAMP', 'EMPTY', 'SPEAKER', 'LATTICE']:
        ob.select_set(False)

# Make objects single users and apply scale
bpy.ops.object.make_single_user(object=True, obdata=True)
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)

# Relink duplicate meshes
for ob, data in linkedoblist.items():
    ob.data = data

# Second modifier pass. Change modifier values to be proportional to scene's unit scale.
for ob in objects:
    if ob.modifiers:
        for mod in ob.modifiers:
            modifierfun(scale_length)

# Re-parent children to lattices
for child in latticechildren:
    bpy.ops.object.select_all(action='DESELECT')
    latticeparent = latticelist[latticechildren.index(child)]
    child.select_set(True)
    latticeparent.select_set(True)
    bpy.context.view_layer.objects.active = latticeparent
    bpy.ops.object.parent_set(type='LATTICE')

# Re-link duplicate lattices
for ob in latticelist:
    ob.data = latticedata[latticelist.index(ob)]

# Reset the scene's unit scale back to 1
bpy.context.scene.unit_settings.scale_length = 1

# Reselect original selection
for ob in objects:
    ob.select_set(True)
```
$\endgroup$
2
  • $\begingroup$ This script does answer the question but it also does a lot of other things that you didn't ask for, I think you can strip it down consequently $\endgroup$
    – Gorgious
    Commented Feb 27, 2023 at 16:42
  • $\begingroup$ Yes, it's even better than I hoped for: it even seems to take modifiers into account when doing the scaling. $\endgroup$
    – user44109
    Commented Feb 27, 2023 at 16:59

You must log in to answer this question.

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