0
$\begingroup$

I am making a scene procedurally with python, and recently switched to the copy() method instead of duplicate_move(). With duplicate_move, all the children of the object reproduced faithfully and their positions relative to the parent were correct. However, with copy(), the children did not get included, so I had to make a series of nested loops going to the depth I needed for my model (four layers) and manually copy, assign parent, and link to scene with each child. This worked, with the exception that the positions of many of the children were wrong, they appeared to be offset vertically by some amount, but their transform data in the inspector panel appears to be the same.

Before we get into what I may have done wrong in my nested loops, is there any way to automatically do a deep copy of the entire hierarchy of an object using the copy() method?

EDIT: Sorry, forgot to include code. Here is the core of what I am currently doing.

actor_copy = actor.copy()
actor_copy.name = str(ID)
scene.objects.link(actor_copy)
if len(actor.particle_systems)>0:
    ps = actor.particle_systems[0].settings.copy()
    actor_copy.particle_systems[0].settings = ps
for child in actor.children:        
    child_copy = child.copy()
    child_copy.parent = actor_copy
    scene.objects.link(child_copy)        
    if len(child.particle_systems)>0:
        ps = child.particle_systems[0].settings.copy()
        child_copy.particle_systems[0].settings = ps
    for subchild in child.children:
        subchild_copy = subchild.copy()
        subchild_copy.parent = child_copy
        scene.objects.link(subchild_copy)
        if len(subchild.particle_systems)>0:
            ps = subchild.particle_systems[0].settings.copy()
            subchild_copy.particle_systems[0].settings = ps
        for sub2child in subchild.children:
            sub2child_copy = sub2child.copy()
            sub2child_copy.parent = subchild_copy
            scene.objects.link(sub2child_copy)
            if len(sub2child.particle_systems)>0:
                ps = sub2child.particle_systems[0].settings.copy()
                sub2child_copy.particle_systems[0].settings = ps
            for sub3child in sub2child.children:
                sub3child_copy = sub3child.copy()
                sub3child_copy.parent = sub2child_copy
                scene.objects.link(sub3child_copy)
                if len(sub3child.particle_systems)>0:
                    ps = sub3child.particle_systems[0].settings.copy()
                    sub3child_copy.particle_systems[0].settings = ps

Apologies for the hack job, time was short and I'm still hoping this can all be thrown away in favor of the right pre-existing function call. But no, I did not know about matrix_parent_inverse, that sounds very promising, thank you!

$\endgroup$
3
  • $\begingroup$ Without seeing any code it's difficult. Have you set child.matrix_parent_inverse $\endgroup$
    – batFINGER
    Mar 24, 2018 at 17:04
  • $\begingroup$ And... that was it! All I had to do was manually set each child copy's matrix_parent_inverse to the original child's value for same. Thanks! $\endgroup$ Mar 26, 2018 at 3:41
  • $\begingroup$ Please, look up recursion, I'm fairly sure it'll help you. $\endgroup$
    – dr. Sybren
    Aug 27, 2018 at 16:45

2 Answers 2

5
$\begingroup$

Set the matrix_parent_inverse of the copy.

Thought I'd add a supplementary answer, using recursion to get all the children up to a certain level. Without it the code is going to get long and unwieldy for more levels.

import bpy

def copy_ob(ob, parent, collection=bpy.context.scene.collection):
    # copy ob
    copy = ob.copy()
    copy.parent = parent
    copy.matrix_parent_inverse = ob.matrix_parent_inverse.copy()
    # copy particle settings
    for ps in copy.particle_systems:
        ps.settings = ps.settings.copy()
    collection.objects.link(copy)
    return copy


def tree_copy(ob, root_parent, levels=3):
    def recurse(ob, parent, root_parent, depth):
        if depth > levels:
            return
        copy = copy_ob(ob, parent)
        if root_parent is not None:
            copy.parent = root_parent

        for child in ob.children:
            recurse(child, copy, None, depth + 1)

    recurse(ob, ob.parent, root_parent, 0)
    
#test
tree_copy(bpy.context.object, None)
$\endgroup$
0
$\begingroup$

The answer is that copy() does not include the matrix_parent_inverse property of children, and this does not get set automatically just because you say .parent = parent object, so you have to do it yourself manually. My above (still really lazy) code now looks like this:

actor_copy = actor.copy()
actor_copy.name = str(ID)
scene.objects.link(actor_copy)    
if len(actor.particle_systems)>0:
    ps = actor.particle_systems[0].settings.copy()
    actor_copy.particle_systems[0].settings = ps

for child in actor.children:        
    child_copy = child.copy()
    child_copy.parent = actor_copy
    child_copy.matrix_parent_inverse = child.matrix_parent_inverse
    scene.objects.link(child_copy)        
    if len(child.particle_systems)>0:
        ps = child.particle_systems[0].settings.copy()
        child_copy.particle_systems[0].settings = ps

    for subchild in child.children:
        subchild_copy = subchild.copy()
        subchild_copy.parent = child_copy
        subchild_copy.matrix_parent_inverse = subchild.matrix_parent_inverse
        scene.objects.link(subchild_copy)
        if len(subchild.particle_systems)>0:
            ps = subchild.particle_systems[0].settings.copy()
            subchild_copy.particle_systems[0].settings = ps

        for sub2child in subchild.children:
            sub2child_copy = sub2child.copy()
            sub2child_copy.parent = subchild_copy
            sub2child_copy.matrix_parent_inverse = sub2child.matrix_parent_inverse
            scene.objects.link(sub2child_copy)
            if len(sub2child.particle_systems)>0:
                ps = sub2child.particle_systems[0].settings.copy()
                sub2child_copy.particle_systems[0].settings = ps

            for sub3child in sub2child.children:
                sub3child_copy = sub3child.copy()
                sub3child_copy.parent = sub2child_copy
                sub3child_copy.matrix_parent_inverse = sub3child.matrix_parent_inverse
                scene.objects.link(sub3child_copy)
                if len(sub3child.particle_systems)>0:
                    ps = sub3child.particle_systems[0].settings.copy()
                    sub3child_copy.particle_systems[0].settings = ps
$\endgroup$

You must log in to answer this question.

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