0
$\begingroup$

I'm trying to linked-duplicate an object with nested sub-objects.

I've rewritten bpy.ops.duplicate_move_linked() as a function without bpy.ops so it should be faster since it doesn't refresh the GUI, but unfortunately it still gets exponentially slower as more objects are added, and when its done the scene memory is only 613MiB.

For the test script below (to run on default scene) it first makes a bunch of 100 nested cubes, and renames to my nested object, which is the one it then tries to linked-duplicate many times.

import bpy

### Function to duplicate an object and all its children, maintaining the parenting structure
def duplicate_recursive(obj, collection):
    ### Create a new object with the same mesh data as the original object
    new_obj = bpy.data.objects.new(name=obj.name + "_duplicate", object_data=obj.data)

    ### Link the new object to the same collection as the original object
    collection.objects.link(new_obj)

    ### Set the new object's location and scale to the original object's location and scale
    new_obj.location = obj.location
    new_obj.scale = obj.scale
    new_obj.rotation_euler = obj.rotation_euler

    ### Duplicate all children and maintain the parent-child relationship
    for child in obj.children:
        new_child = duplicate_recursive(child, collection)
        new_child.parent = new_obj  # Maintain the parenting structure
        new_child.matrix_parent_inverse = child.matrix_parent_inverse.copy()
    
    return new_obj




### Test ###
if __name__ == '__main__':
    bpy.context.preferences.edit.use_global_undo = False

    # for first execution / scene setup: create nested object with 100 sub-objects
    cube = bpy.data.objects.get("Cube")

    if cube:
        cube.name = "My Nested Object"
        previous_cube = cube
        collection = cube.users_collection[0]
    
        for i in range(0,100):
            print('duplicating cube', i, '/', 100)
            duplicated_cube = duplicate_recursive(previous_cube, collection)
            duplicated_cube.parent = previous_cube
            previous_cube = duplicated_cube

    print("Done setting up scene / nested cubes\n\n\n\n")

    # at this point scene contains 8 verts and 103 objects




    # function under test - create linked duplicates of nested object, without bpy.ops.duplicate_move_linked()
    # WATCH OUTPUT IN CONSOLE
    
    my_object = bpy.data.objects.get("My Nested Object")
    if my_object:
        collection = cube.users_collection[0]
    
        for i in range(0, 300):
            print('duplicating object', i, '/', 300)
            duplicated_cube = duplicate_recursive(my_object, collection)
        
    # gets exponentially slower as more objects are added, even though no bpy.ops are
    # used and the memory is still low... WHY?

    bpy.context.preferences.edit.use_global_undo = True

As done here, I have tried disabling global undo, and also view layer exclude or hide render until it is finished with no luck. I assume it is to do with the depsgraph updating, since the slowdown seems proportional to the number of objects as more get added.

Any way to fix?

$\endgroup$

1 Answer 1

1
$\begingroup$

You will not get performance improvements from rewriting the non-python bpy.ops operators. You are creating 30k cubes and every other cube created takes more time depending on how much already are in the bpy.data.objects. The main performance hit is the bpy.types.Object.children which takes ~49% of the time, it is written in python as tuple(child for child in bpy.data.objects if child.parent == obj), bpy.data.objects.new: ~19% and collection.objects.link: ~19%.

$\endgroup$
4
  • $\begingroup$ Side question: how did you measure that? $\endgroup$
    – Lauloque
    Commented Jun 11 at 12:48
  • 1
    $\begingroup$ cProfile and snakeviz. But the results within a function are not detailed so you need to wrap Blender API calls into their own functions. $\endgroup$
    – unwave
    Commented Jun 11 at 15:30
  • $\begingroup$ Ok, my question was more 'why', it shouldn't get slower and slower to create more objects, what is it doing that's not just allocating more memory and filling it? Surely like appending to an array but in C fashion.. a pointer/address lookup should not depend on the length of the existing data. I know it seems trivial but whatever the underlying cause is is affecting my scripts performance on many fronts, whether I've added too many textures, objects, or keyframes to the overall blend file, everything gets slower and slower $\endgroup$
    – mugwortz
    Commented Jun 12 at 1:41
  • $\begingroup$ Also, I was not aware of what ops are C based and python based, I'll bare this in mind in future. Does this mean C based ops only trigger a GUI update when complete? $\endgroup$
    – mugwortz
    Commented Jun 12 at 1:45

You must log in to answer this question.

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