Joints from a text file


#1

Has anyone ever written code (COFFEE, C++, Python) that creates joints from a text file?
Parsing the text data and using it to create the joints is pretty simple.
But I’m having trouble figuring out how to handle the nested tree branches when adding the joints to the OM. And I’d like to see how other people would approach this problem.

Searching the internet on this turned up nothing helpful at all. :hmm:

Thanks,
-ScottA


#2

Shouldn’t the text file already define the hierarchy? If not, how do you determine how the joint tree is supposed to look like? And if yes, isn’t that already the answer to your question?


#3

I’m having a hard time with joints that are on the same level as other joints. But in different branches.

Suppose I’ve parsed my text file into a list of tuples where each tuple has the joint’s name & the joint’s level like this:
*To keep this short. I’ve only shown the arm joints for the right side.
joints = [ (‘Hips’, 0), (‘spine’, 1) , (chest, 2), (‘RightCollar’, 3), (‘RightShldr’, 4) (‘RightHand’, 5), (‘RightThumb1’, 6), (RightThumb2, 7), (‘RightIndex1’, 6), (‘RightIndex2’, 7), ('RightMid1, 6 ), ('RightMid2, 7) ]

Notice how once we get to the fingers. Some of the joints are on the same level…But not in the same branch.
I keep getting twisted around trying to figure out how to describe to the computer which joints to add to which branch.
It’s not enough to know their levels. I also need to somehow incorporate the branches they belong to. But I can’t get it working properly.

-ScottA


#4

You just need to keep an array of pointers to the last element in each level. Then any element you encounter will be appended as next sibling in the hierarchy behind that last element on the level. You just need to set the conditions right, and of course the file source must never do funny stuff like skipping a level.

Like this:
Initialize your array with nulls.
You start with hips, will become the first element in the array because the array is null everywhere [null, null, null, null, null, …] and the hips are on level 0.
Array is now [hips, null, null, null, …]
Then the spine, is on the next level, where the array is still null, so the spine will be appended as first child of the previous array element, and the spine will become the second array element.
Array is now [hips, spine, null, null, …]
Then the chest, is on the next level again, same procedure. chest becomes a child of spine.
Array is now [hips, spine, chest, null, null, …]
And so on down to the RightThumb2, this is boring.
Array is now [hips, spine, chest, rightcollar, rightshoulder, righthand, rightthumb1, rightthumb2, null, null, …]
Now things become interesting as the next element has an index that was used before: rightindex1. This is on level 6. We already have an element on level 6 in the array, which is rightthumb1. Thus, rightindex1 becomes the next sibling of rightthumb1 (as child of righthand), will become the level 6 pointer in the array, and the array after level 6 is nulled.
Array is now [hips, spine, chest, rightcollar, rightshoulder, righthand, rightindex1, null, null, …]
Next element is rightindex2, which goes to level 7 which is now nulled, so it becomes the first child of rightindex1 again.
The game continues until all elements of the file are added. When the OM tree is built, you can throw away the array.
The array needs to be big enough to hold a pointer for all levels of the prospective hierarchy, but that is not difficult.


#5

Thanks for trying to the help Robert. But I’m not having troubles parsing the data into arrays.
The problem I’m having is trying to re-create those joints in C4D from the array elements.
Once I have the entire rig listed in an array in the correct order. I can’t just simply use doc.InsertObject(curArrItem, prevArrItem) on them. Because in some cases where there are multiple branches per level that puts them under the wrong parent.
I need to know the code to determine if a joint being inserted is the child of an existing branch. And where that parent is. Or if it’s the start of a brand new branch. And where that new branch is.
It’s not as simple as it sounds. It’s easy to get all twisted around trying to set up the rules to solve this.
I keep getting close…but not quite there.

Another approach I’ve tried is to read the joints from the text file. And create them in C4D as a flat tree (no parent/child branches) in the same order they are in the text file. Then iterate over them and set up the parent/child relationships.
Doing it this way. The joint names have empty spaces in front of them. Which I can count , and use to determine what level each joint should be moved to.
I can successfully create the flat tree with all the joints in the proper order. But once again. When I go to try to set up the parent/child relationships. I have the same problem where multiple joints have the same level (in this case. The same number of empty spaces in the names). And I can’t figure out how to determine which branch to put them in.
Then I start pulling my hair out again.

-ScottA


#6

[quote=]Thanks for trying to the help Robert. But I’m not having troubles parsing the data into arrays.
The problem I’m having is trying to re-create those joints in C4D from the array elements.
[/quote]
You misunderstand what I’m using the array for.

Please read the algorithm again TO THE END when I get to the fingers (the hip/spine/chest part is trivial), and you will see how the array serves as a marker for the last element in a level. This array is not the stuff you read from the file, but a dynamic structure that is used for delimiting the floating “border” of the generated OM tree.


#7

Sorry about that Robert.
I have a problem with sentence based coding explanations. If it’s not written in code form I have a very hard time understanding what the person is saying most of the time.
If the answer is there. I’m not seeing it.

-ScottA


#8


maxlevels = 25
edgeoftree = new Array [0..maxlevels] # this is the mysterious array
 
def clear_edge (start):
    # just for nulling the array
    for i in [start..maxlevels]:
        edgeoftree[i] = None

def insert_joint(rootobj, currentjoint):
    # naturally, you need to check the parameters for None here,
    # also the level in the currentjoint for > maxlevels
    currentjoint_obj = make_obj_from_joint(currentjoint) # create your C4D BaseObject from your own data type
    if currentjoint.level = 0:
        currentjoint_obj.InsertUnderLast(rootobj) # joint is inserted into OM tree on the top level
        edgeoftree[0] = currentjoint_obj # remember this object as last inserted for the level 0
    else:
        currentjoint_obj.InsertUnderLast(edgeoftree[currentjoint.level - 1]) # joint is inserted under the last created parent
        edgeoftree[currentjoint.level] = currentjoint_obj # remember this object for adding future children
        clear_edge(currentjoint.level + 1)
        # clearing the rest of the array is actually needed to perform validity test that I'm skipping here,
        # so it looks unnecessary, but you will appreciate it when you handle the error cases

def load_joints(obj):
    # parameter is the root of the constructed joint tree, for example a null in the hierarchy
    clear_edge(0)
    while there are joints in the file:
        currentjoint = read_from_file() # currentjoint is your own data type containing all the data from the file, including the level and the name
        insert_joint(obj,currentjoint)


No guarantees, as this is just pseudocode and obviously not tested in any way.


#9

For me there is two of doing it.
Implemting your own BaseList2D with serialization (I mean at least, each object get an Unique ID, each Object know parent uuid and his next / pred uuid), cool things about that it allow you to manage the file async or in parallel.

The other way is by register the order you foud an object. For Exemple to get the 4th object you have done. RootObj.GetNext().GetDown().GetNext() so you know the actual structure, since the order is the same and if in the the writting of the file you do it from top to bottom, you are sure if you insert from top to bottom to have the exact same thing. Cool things about this is way more easy to implement, since you don’t have to rewrite your kind of BaseList2D class.

Hope it’s help ! :slight_smile:


#10

Thanks Robert.
That pseudo code was just enough information for me to figure it out.
Have a nice Thanksgiving.

Here is a working Python version of it in case anyone else needs it:

import c4d

#Load and parse your text data into a list from a text file however you want
#Here I just hard code it manually
textData = [('Hips', 0),
             ('RightLeg',1),
               ('RightShin',2),
                 ('RightFoot',3),
               ('LeftLeg',1),
                 ('LeftShin',2),
                   ('LeftFoot',3),
              ('Spine',1),
                ('Chest',2),
                  ('RightCollar',3),
                    ('RightShldr',4),
                      ('RightForeArm',5),
                        ('RightHand',6),
                          ('RightThumb1',7),
                            ('RightThumb2',8), 
                          ('RightIndex1',7), 
                            ('RightIndex2',8),
                          ('RightMid1',7), 
                            ('RightMid2',8),
                          ('RightRing1',7), 
                            ('RightRing2',8),  
                          ('RightPinky1',7), 
                            ('RightPinky2',8),   
                            
                  ('LeftCollar',3),
                    ('LeftShldr',4),
                      ('LeftForeArm',5),
                        ('LeftHand',6),
                          ('LeftThumb1',7),
                            ('LeftThumb2',8), 
                          ('LeftIndex1',7), 
                            ('LeftIndex2',8),
                          ('LeftMid1',7), 
                            ('LeftMid2',8),
                          ('LeftRing1',7), 
                            ('LeftRing2',8),  
                          ('LeftPinky1',7), 
                            ('LeftPinky2',8)                                                                                
            ]

#This list is used to store the parent joints
#So we can go back and find the right parent joint later on as needed
parents = []

#This is a list of the current joints
joints = []

def main():

    #First create all of the joints from the names listed in the textData list
    #Then store them in a list
    #Then create a null element in the parents list for each joint
    for i in xrange(len(textData)):
        jnt = c4d.BaseObject(1019362)
        jnt.SetName(textData[i][0])
        joints.append(jnt)
        parents.append(None)

    #Add the first joint (hips) to the OM
    #Then set this joint as the first parent element in the parents list
    root = joints[0]
    doc.InsertObject(root)
    parents[0] = root
    
    """
        Now that we have the hips as the root of the hierarchy
        Lets create the children in the OM
    """        
    for i in xrange(len(textData)-1):            
        currentjoint = joints[i+1] #NOTE: i+1 skips processing the root joint
        level = textData[i+1][1]   #NOTE: i+1 skips processing the root joint
        
        currentjoint.InsertUnderLast(parents[level - 1])
        parents[level] = currentjoint
        parents[level + 1] = None

    c4d.EventAdd()

if __name__=='__main__':
    main()

-ScottA


#11

From artist perspective a question : in which cases do you get a text file with joints? Fbx multi app pipeline ? Custom rig builder?


#12

In my case it’s for 3 reasons.
1- To know how to do it in case I need it
2- For use in my own plugins
3 -The exporters/importers in my version of C4D (R13) have some limitations and bugs in them. So sometimes I want to do things myself with my own code

-ScottA


#13

I see, thanks!


#14

Why are you storing an int for the level rather than an array of joint and parent object? That way branching and such would be a non-issue, no?


#15

Hi Bret,
I’m using integers for the levels because it’s more application agnostic that way.
There will be times when I do this task inside of Qt, or Maya, or some other app. Not just in C4D.
There’s probably lots of other ways to skin this cat.
For example. I’ll bet it can also be done using recursion rather using arrays.

-ScottA


#16

I mean, you could still store the name of the parent then do a search for each parent


#17

Oh yeah. I’ll probably do it that way if I use it in a project. Mainly just to reduce the memory being used.
The main reason I stored joint objects in the arrays instead of their names is because I found that it was a bit quicker and less confusing to write the code that way.
I was doing a lot of experimenting, and it turned out to be less confusing and simpler to store joint objects and insert them into C4D rather than using their names.
I don’t think storing joints are large enough to cause a problem. But yeah I’ll most likely not do that when I actually use it in a plugin.

-ScottA


#18

Hi Scott,

dealing with the levels, you don’t have to store all the joints in a list.
You can go ‘up in hierarchy’ from the last inserted joint to the object one level above the level (i.e. the parent) of the new joint has to be.

Here’s the code for the ‘main’ function:


def main():
    # Create the root joint
    activeJoint = c4d.BaseObject(c4d.Ojoint)
    activeJoint.SetName(textData[0][0])
    activeLevel = textData[0][1]
    
    # Insert the root joint in the document und remove the first
    # element from the list.
    doc.InsertObject(activeJoint)   
    textData.pop(0)
    
    # Go through the elements of the (reduced) list
    for name, level in textData:
        # Go to the joint one level above the level of the new joint,
        # i.e. go to the parent
        while activeLevel >= level:
            activeJoint = activeJoint.GetUp()
            activeLevel -= 1

        # Create the new joint and insert it under the parent (activeJoint)
        newJoint = c4d.BaseObject(c4d.Ojoint)
        newJoint.SetName(name)        
        newJoint.InsertUnderLast(activeJoint)
        
        # Update informations about the active joint
        activeJoint = newJoint
        activeLevel = level
    
    c4d.EventAdd()

Peter


#19

Thanks Scott

take look at Kraken, old stuff of one fork before remove official repo - https://github.com/hoorayfor3d/Kraken


#20

Thanks for posting your code Peter. I like that version better than the one I posted. Less memory usage.
If anyone else has a different method. Please feel free share it. Especially one that uses recursion. We don’t have one of those here yet.

@spirel.
Thanks for the link… I’ll check it out.

-ScottA