Parent Behavior on a "Script" Form



  • Hi,

    I managed to simulate a parenting behavior in the xpresso node editor.
    You can see it in this illustration video below:
    https://www.dropbox.com/s/isw8bibg6r8u3ot/c4d270_parenting_in_script_form.mp4?dl=0

    It works well. As you can see, the child follows the parent even though it is not parented in the hierarchy.
    My problem is I want it to be in a script form (executes only once) rather than on a Xpresso editor (executes every time or live)

    Here is my code so far. It is the same logic I used in the node editor.

    import c4d
    from c4d import gui
    # Main function
    def main():
        parent      = doc.SearchObject("parent")
        child       = doc.SearchObject("child")
        
        child_parent_wrd_mat = child.GetUpMg() # Since there are no direct parent. This will be the default world matrix
        parent_wrd_mat       = parent.GetMg()
        child_offset_mat     = ~parent.GetMg() * child.GetMg()
        
        overall_mat   = child_parent_wrd_mat * parent_wrd_mat * child_offset_mat
        
        # Rotating the Parent
        parent[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Y] += 0.5
        
        # This should also move the child based on the parent's rotation but it does not
        # Same thing happens if I move the rotating parent before retreiving the matrices. 
        # Again, I'm not sure how to implement it on a script. Only on a node. 
        
        child.SetMg(overall_mat)
        
        c4d.EventAdd()
    
    # Execute main()
    if __name__=='__main__':
        main()
    

    You can also check the C4D File with the Xpresso Parent Behavior here:
    https://www.dropbox.com/s/isw8bibg6r8u3ot/c4d270_parenting_in_script_form.mp4?dl=0

    Regards,
    Ben



  • That the current code doesn't work is obvious (after you change the "parent", you do not retrieve its matrix again so the change is not reflected in the matrix you apply to the "child").

    That the code doesn't work either when you change the parent first is due to the matrices that you multiply. overall_mat contains parent_wrd_mat which is the parent's world matrix. It also contains child_offset_mat which contains the parent's inverted world matrix. Multiplying a matrix with its own inverse results in the unity matrix which amounts to no transformation at all.

    So essentially you have multiplied away the parent's contribution to the overall_mat.

    What you need to do is to make the explicit transformation "in the middle":

    import c4d
    from c4d import gui
    
    def main():
        parent      = doc.SearchObject("parent")
        child       = doc.SearchObject("child")
        
        transformToParentSystem = ~parent.GetMg()
    
        # Rotating the Parent
        parent[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_X] += c4d.utils.DegToRad(45.0)
        parent[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_X] += 123
        parent[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_Y] += -10
        parent[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_Z] += 2
    
        transformFromParentSystem = parent.GetMg()
        
        overall_mat = transformFromParentSystem * transformToParentSystem * child.GetMg()
    
        child.SetMg(overall_mat)
        c4d.EventAdd()
    
    if __name__=='__main__':
        main()
    

    Here, you first remember the transformation into the parent system in transformToParentSystem. Then you can make the desired transformation to the parent object. Then you remember the transformation out of the parent system in transformFromParentSystem. These two remembered matrices are now not the inverse of each other, as you have changed the parent in between!

    Now, the overall matrix for the child is composed from: its own matrix, which is then transformed into the parent's system (before the change), which is then transformed out of the parent's system (after the change).



  • @Cairyn

    Thanks for the detailed explanation. It works as expected.



  • @Cairyn

    Apologies. I might have spoken too soon.

    Sorry to trouble you again, but I'm having problem using the script if the a child has a hierarchical parent.
    You can see the problem here:
    https://www.dropbox.com/s/1fjsx1tigsxncdb/c4d270_parenting_in_script_form02.mp4?dl=0

    By a successful parenting script, the red and blue spline should match. But as you can see, it does not.
    You can also check the revised illustration file here:
    https://www.dropbox.com/s/p2kz00lp5c4oo9c/c4d270_parenting_in_script_form02.c4d?dl=0

    I have revised the code to include the child's hierarchical parent. But its not working as expected.

    import c4d
    from c4d import gui
    
    def main():
        parent      = doc.SearchObject("COG_con")
        child       = doc.SearchObject("wrist_IK_L_con")
    
        transformToParentSystem = ~parent.GetMg()
        transform_To_Pseudo_Parent_System = ~child.GetUpMg()
        # Rotating the Parent
        parent[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_X] = 10
        parent[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_X] = 0.5
    
        transformFromParentSystem = parent.GetMg()
        transform_From_Pseudo_Parent_System =  child.GetUpMg()
        pseduo_parent_local_space = ~parent.GetMg() * child.GetUpMg()
    
    
        overall_mat =   transform_From_Pseudo_Parent_System * transform_To_Pseudo_Parent_System * transformFromParentSystem  * transformToParentSystem * child.GetMg()
    
        child.SetMg(overall_mat)
        c4d.EventAdd()
    
    if __name__=='__main__':
        main()
    


  • @bentraje The script is fine (the original one I gave you). You do not need all the parent Mgs that you inserted because GetMg() is already giving you the global matrix, which includes the object's own local matrix as well as all the parent matrices.

    Your problem is something completely different: You have a PSR constraint on the child's parent. Switch that off, and my original script works fine.

    The reason is: (think about it first, as exercise, then return here...)

    1. The script changes COG_con
    2. Then it transfers the matrix to the child wrist_IK_L_con
    3. Everything is fine so far
    4. Now EventAdd() will re-evaluate the scene
    5. Especially the PSR constraint
    6. Which happens to refer to an object (wrist_IK_L_con_grp_COG_con_space_switch) that is a child of the "parent"
    7. And you just changed the parent.
    8. So, ultimately you have applied the parent's matrix to the child, and then AGAIN through the PSR constraint which naturally affects the child too. What you get is that the child is now transformed twice.

    TL;DR the logic in your scene cannot work. I don't know what you want to achieve here so I can't advise any further.



  • @Cairyn

    Thanks for the response.

    RE: Switch that off, and my original script works fine.
    Yes, I forgot to mention the PSR Constraint.
    Unfortunately, I can't switch it off as it is a part of the rig (i.e. space switching feature).

    I also tried the following:

    1. Turn Off PSR Constraint
    2. Execute Script. Works as expected
    3. Turn On PSR Constraint. And the object is thrown again out of position.

    RE: transformed twice.
    That makes sense. Thanks for the rundown.
    I was able to come up with a solution to bypass/negate it.

    Here's what I added at the end of the script

    child.SetMg(overall_mat)
    doc.ExecutePasses(None, True, True,True,c4d.BUILDFLAGS_0) # Applies PSR at script run time 
    c4d.EventAdd()
    child.SetMg(overall_mat) # Reapply Matrix basically bypass/negating the double transform
    

    It seems to work as expected, at least for now in my use case.
    Wdythink? I'm feeling I'm missing something out.

    RE: I don't know what you want to achieve
    I'm creating a script that can mirror/flip a pose.
    Think of a run cycle that needs basically 8 poses to read as one full cycle. But with the mirror/flip pose I can create only 4 poses and mirror the rest.

    I have the mirroring done. My problem is only when the mirror plane is rotated. That's why I need to have the "parenting" to happen only once (i..e at script time) since the parenting at run time is handle by various xpresso/PSR/expressions.



  • @bentraje said in Parent Behavior on a "Script" Form:

    RE: Switch that off, and my original script works fine.
    Yes, I forgot to mention the PSR Constraint.
    Unfortunately, I can't switch it off as it is a part of the rig (i.e. space switching feature).

    I didn't mean "switch it off forever" but "switch it off to see what I mean" ;-)

    I also tried the following:

    1. Turn Off PSR Constraint
    2. Execute Script. Works as expected
    3. Turn On PSR Constraint. And the object is thrown again out of position.

    Yes, because you still have changed your parent and you still take the parent's child as constraint. The moment you switch the constraint on again, you apply the transformation a second time, this time indirectly through the constraint (or rather, the constraint's target's parent).

    RE: transformed twice.
    Here's what I added at the end of the script

    child.SetMg(overall_mat)
    doc.ExecutePasses(None, True, True,True,c4d.BUILDFLAGS_0) # Applies PSR at script run time 
    c4d.EventAdd()
    child.SetMg(overall_mat) # Reapply Matrix basically bypass/negating the double transform
    

    Yes... if you apply the PSR tag first, by using ExecutePasses, SetMg will take that change to the parent into account already, which cancels out the double transformation. (You don't need the first line btw, setting the child's matrix once will do.)

    In the context of a script, this is okay to do. Personally, I find it a bit convoluted but I don't feel like digging for another solution.

    Wdythink? I'm feeling I'm missing something out.

    No, it's okay for what it does. I'd have to spend a few hours going through the usecase to fully understand the mirroring process with active constraints, so, just go ahead with your current approach.

    RE: I don't know what you want to achieve
    I'm creating a script that can mirror/flip a pose.

    Ah. Don't the built-in mirror functions suffice? I thought there were some (but I don't animate so often so maybe I'm just mistaken).



  • @Cairyn

    Thanks for the confirmation. I'll just continue with the revised script until I find a bug (lol).

    RE: Ah. Don't the built-in mirror functions suffice?
    Not really. Correct me if I'm wrong, The built-in mirror tool 1) mirrors only the objects itself and not the mirror plane 2) when it mirrors, it creates additional copy. might be good when you are modelling and rigging, but not on animating.

    Again, thank you for the detailed responses. Appreciate it alot.

    Have a great day ahead!


Log in to reply