Python: Spoonfed - (2-17) Joint Walking

After so much theory, it's time for some practical application (and then some).

There recently was the question on a forum how you can walk the joint tree of a skeleton without using the Object Manager. Now, I love the Object Manager dearly but with a huge skeleton, you may end up opening and closing branches a lot, or scrolling through the full skeleton to see all joints. So, I see the desire to have some keyboard shortcuts to walk the skeleton.

If you're just interested in the scripts and not in the programming, download the scripts and put them into your library/scripts folder. You will want to assign shortcuts to them, preferrably with the cursor keys; I am using the double-shortcuts J~Up, J~Down, J~Right, and J~Left. To keep track of the selection, you may also want to show the active object in the HUD of your viewport. Read on for details, or just select any joint and try the scripts, they are self-explanatory.


Let's get to work. Selecting the parent of a joint is easy enough, we did that already in the general "select parent" script. Here, we just add a test for the type of the original and parent object - if either is not a joint nor a null, the selection will not move farther up.

I am using GetActiveObjects here to retrieve the list of selected objects, and then check the size of the list and extract the single selected object with the index [0]. You could, of course, use the predefined variable op instead. Other than the parent selection script from the past, the script PS_SelectJoint_Parent only allows a single selection.

The null is allowed here instead of a joint so you can "split" the skeleton with null objects; sometimes that comes in handy. If you do not want to allow the null, you can just remove the test for Onull from all scripts - or add more allowed object types if your joint tree is differently built.

The rest of the script is self-explanatory. It's so short that I didn't even use functions.

The scripts for selecting the next and previous joints (or nulls) on the same hierarchy level are similar. Here, we do not stop when encountering an object that is not joint or null; we just skip over it. The script only exits without doing anything if there is no more next/previous joint at all - observe the while loop.

Note that the next/previous joints are located parallel to the original joint in the object tree. An example for that would be to move from RightShoulder to LeftShoulder if both have a common parent. The nomenclature I'm using here is the one of the object tree (well, "prev" and "pred" be darned). You may not use the same terms when it comes to joint hierarchies - if the "next" joint to an R_UpperArm is an R_LowerArm in your mind, you can rename the scripts of course, or choose your keyboard shortcuts appropriately.

The final and most complicated script is the one to go "down" in the object tree to the next dependent joint. The reason is, naturally, that this is not a unique relationship. From your R_Hand joint, you can branch off into R_Thumb_Root or R_MiddleFinger_Root or any other finger, for example. The script cannot know where you want to go - and while it would be possible to go just to the first child and then let the user go to the next or previous joint (I'll leave that as homework for you...), it's nicer to show a bit of "awareness" to the user and offer them a selection.

Therefore, I'm opening a menu in place of the mouse pointer, which shows all suitable children and asks the user just to click one. That is something we haven't done yet but for the current situation it is fairly simple to achieve.

First, we create a new, empty BaseContainer. This is a common data structure serving as container for various data; we'll get to that in time. Then we fill this BaseContainer by invoking InsData with pairs consisting of one numeric index value and one string for the text. As we want to list the child objects only, we don't need to care for internationalization of the texts; we just use the name.

The numeric indices must start at c4d.FIRST_POPUP_ID. We use this as offset for the index of the child in the children list, so it is not necessary to fumble with many different IDs later. That makes our menu-filling for loop a vey basic three-liner.

After filling the BaseContainer, we can directly call the menu by using ShowPopupDialog. We pass the container as parameter bc, and use the MOUSEPOS constant to tell the function to open the menu at the cursor position. (Note that MOUSEPOS is not an actual x or y value in screen coordinates but a special value to tell the function to calculate the actual position by itself! That's why we can use it on both x and y parameters.)

The return value of ShowPopupDialog is the ID that we passed in the BaseContainer, or something else if the user clicks elsewhere or uses ESC to break off. We check the range of valid values, and subtract FIRST_POPUP_ID as necessary to move the offset back into the index range of the children list.

And that's all the secrets there are.

Note: The menu itself has apparently no keyboard shortcuts except ESC; if the menu appears, you need to click on a selection. As the menu appears readily at the mouse pointer position, this is not a huge downside, but I wish I could use the up/down arrow keys to navigate here.

Also note: You will find that I clean the initial list of children from entries that are neither joint nor null. This is done with a for loop over children[:]. Why do I use a slice here, instead of simply employing the list itself?

The reason is that I am manipulating the list, removing items as I go. It is not a good idea to change a list that I iterate at the same time, as the "current object" of the iteration may get lost. So, the slice [:] is making a temporary copy of the list that serves as stable basis for the iteration, while I remove elements from the original.


These scripts are not linked to serial numbers, user IDs or machine IDs. You can freely install them on any Cinema 4D installation that you own, now and in the future (as long as there are no API changes that technically prevent running these scripts).

The code for these scripts is open so you can adapt them to your personal needs.

It is not allowed to pass on, repost, or publish these scripts in any way. It is especially not allowed to sell them. If you wish to recommend the scripts to someone else, by all means do so with a link to this Patreon.

If you have issues running the scripts, or ideas on how to improve it, feel free to leave a note.

By becoming a patron, you'll instantly unlock access to 114 exclusive posts
By becoming a patron, you'll instantly unlock access to 114 exclusive posts