Navigating CATRig Hierarchies using MAXScript

Accessing Bones in the CATRig Skeleton

Accessing bones by name in a skeleton tends to cause errors. For example, if an artist renames a bone, your scripts might stop working.

3ds Max CAT is designed to let you navigate hierarchies from a script without necessarily knowing the names of the bones in the rig. This means that you can write scripts that will work on any CATrig, regardless of the naming conventions used.

The CATRig is structured like a tree.

Each bone has child bones that you can navigate to and get their children.

There are special controllers in CAT that hold parts of the tree together, but do not show up in the hierarchy you see in 3ds Max. For example, there is a controller called "Limb" whose job is to maintain the limb (arm or leg). Each bone in the limb is generic and contains only information related to that specific bone. If you want information about the limb, you can access the limb controller.

The basic tree structure looks like this:

From a hub controller you can get to a limb controller and from a limb controller you can get to the collarbone controller.

From a hub controller you can get to a tail controller and from a tail controller you can get to any of the tailbone controllers.

From a hub controller you can get to a spine controller and from a spine controller you can get to any of the spine bone controllers, or to the hub controller at the tip of the spine.

Hubs

Hubs->Limbs

Hubs->Limbs->Collarbone

Hubs->Limbs->Limb Bones

Hubs->Limbs->Limb Bones->Bone Segments

Hubs->Limbs->Palm/Ankle

Hubs->Limbs->Palm/Ankle->Digits

Hubs->Limbs->Palm/Ankle->Digits->Digit Bones

Hubs->Tails

Hubs->Tails->Tail Bones

Hubs->Spines

Hubs->Spines->Spine Bones

Hubs->Spines->Tip Hub

Using Bone Addresses

The 3ds Max CAT scripting interface includes methods that let you access bones using bone "addresses". Each bone in a CATRig contains a unique address.

EXAMPLE

   --Select a bone, for example left elbow:
   theAddress =$[3].address
   --the result will be something like:
   "SceneRootNode.Hub.Spine[0].Hub.Limb[0].LimbBone[1].BoneSeg[0]"

Once you have an address, it is easy to get the same bone on a different rig.

There is a method on the CATParent for retrieving a bone from the skeleton using an address.

This method is called GetBoneUsingAddress:

EXAMPLE

   --Selectthe CATParent of a rig and call:
   $.GetBoneByAddresstheAddress
   --the result will be something like:
   $Object:MaramaLForeArm1 @ [18.374180,2.811419,91.353607]

Getting all the Bones that make up the CATRig

The CATParent provides a property called CATRigNodes that contains a list of all the bones in a CATRig.

EXAMPLE

   --Select the CATParent of a CATRig andexecute:
   $.CATRigNodes
   #($Object:MaramaPelvis @ [0.643538,12.460782,56.982174], $IKTarget:MaramaLPlatform @ [-9.479717,45.484272,2.016279], $Object:MaramaLThigh @ [-5.399519,13.610688,52.234413], $Object:MaramaLCalf @ [-5.073243,42.623035,42.755692], $Object:MaramaLAnkle @ [-9.923340,42.132290,12.445667], $Object:MaramaLToe @ [-8.354513,46.355228,5.667924], $IKTarget:MaramaRPlatform @ [22.299629,-26.312172,2.125263], $Object:MaramaRThigh @ [7.615567,12.286880,53.555431], $Object:MaramaRCalf @ [18.307613,1.576054,27.048307], $Object:MaramaRAnkle @ [15.838839,-24.331221,10.763937], $Object:MaramaRToe @ [21.849598,-24.039907,5.285047], $Object:MaramaSpine1 @ [0.084267,13.617851,61.769054], $Object:MaramaSpine2 @ [0.155154,17.742903,70.302628], $Object:MaramaSpine3 @ [0.838520,20.302643,79.403381], $Object:MaramaRibcage @ [1.721320,22.587023,88.560089], $IKTarget:MaramaLIKTarget @ [-14.076582,23.387424,73.722427], $Object:MaramaLCollarbone @ [-0.265896,27.761074,90.442078], $Object:MaramaLUpperArm @ [-7.214790,19.994431,91.494965], $Object:MaramaLForeArm1 @ [-22.375963,8.502111,91.433647], $Object:MaramaLForeArm2 @ [-18.546179,13.426756,83.416336], ...)

All the bones in the rig have been collected and put into an array for you. This makes it unnecessary to learn how to navigate CATRig hierarchies.

Getting All the Layer Controllers in a CATRig

The CATParent also provides a property called CATRigLayerCtrls that contains an array of all the layer controllers in a CATRig.

EXAMPLE

   -- Select the CATParent of a CATRig andexecute:
   $.CATRigLayerCtrls
   #(Controller:LayerWeights, Controller:LayerMatrix3, Controller:LayerFloat, Controller:LayerWeights, Controller:LayerFloat, Controller:LayerFloat, Controller:LayerFloat, Controller:LayerMatrix3, Controller:LayerMatrix3, Controller:LayerWeights, Controller:LayerMatrix3, Controller:LayerFloat, Controller:LayerMatrix3, Controller:LayerMatrix3, Controller:LayerWeights, Controller:LayerFloat, Controller:LayerFloat, Controller:LayerFloat, Controller:LayerMatrix3, Controller:LayerMatrix3, ...)

The Pose Mixer utility uses this function to find all the controllers that will be keyframed.

Each bone in a CATRig is controlled by a controller.

This controller exposes the CATNodeControl interface.

This means you can access information about that bone.

EXAMPLE

If a hub bone is selected and you want to know how many limbs are attached to the hub, use the following line of script.

   $[3].address
   "SceneRootNode.Hub.Spine[0].Hub.Limb[0].LimbBone[0].BoneSeg[2]"

EXAMPLE

To get the layer controller for a CATBone, use the following line:

   $[3].layertrans.controller
   Controller:LayerMatrix3

Note that some bones do not have a layertrans controller such as, the bones in the procedural spine.

Hubs are the "joiners" of the 3ds Max CAT hierarchy.

Hubs can have limbs (legs and arms), tails, spines, and extra bones attached to them.

For any Hub, you can see how many limbs are attached and then you can access the limb and get the bones from that limb.

EXAMPLE

If a hub bone is selected and you want to know how many limbs are attached to the hub, use the following line of script.

   -- Select the CATParent of a CATRig andexecute:
   $[3].limbs.count
   2 

Limbs are made up of the following types of controllers: Limb, Collarbone, Bone, BoneSeg, Palm, IKTarget, and Upvector.

The Limb controller holds all the information about the whole limb, such as its name, and also keep a pointer to each bone in the limb, collarbone, ankle, and the IKTarget.

From one limb bone you can access everything in a CATRig.

EXAMPLES

   --To get the limb controller from a limb bone use the following:
   $[3].Limb
   Controller:CATLimbData2

   --If you have a Limb bone selected and you want to know how many
   --bones in the Limb use the following:
   $[3].Limb.bones.count
   4

   --To get the second bone in the Limb use the following:
   $[3].Limb.bones[2]
   Controller:CATBoneData

   --If a limb bone is selected and you want to select all the bones
   --in the limb, you can use the following:
   select (for o in $[3].Limb.bonescollect o.node)
   OK 

EXAMPLE

If a limb bone is selected and you want to get the name of the first bone in the limb, you can use the following:

   $[3].limb.bones[1].node.name
   "MarineLUpperarm1"

   --Here is a quick overview of bone hierarchy access:
   $--> accesses the selected bone
   $Object:MarineLUpperarm3 @ [32.745049,27.963869,14.721746]
   $[3]--> accesses the selected bone's transform controller
   SubAnim:Transform
   $[3].limb--> accesses the selected bone's tail controller
   Controller:CATLimbData2
   $[3].limb.bones[1]--> accesses the 1st bone in the limb controller
   Controller:CATBoneData
   $[3].limb.bones[1].node--> accesses the node of the 1st bone.
   $Object:MarineLUpperarm1 @ [30.869087,27.893547,14.979661]
   $[3].limb.bones[1].node.name--> accesses the name of the node of the 1st bone in the Limb.
   "MarineLUpperarm1" 

Tails are made up of two types of controllers: TailData and TailTrans.

TailData holds all the information about the whole tail such as, its name and it also keep a pointer to each bone in the tail.

From one tail bone you can access everything in a CATRig.

EXAMPLE

   --To get the TailData controller from a tail bone use the following:
   $[3].tail

   --If you have a Tail bone selected and you want to know how many bones
   --arein the tail, use the following:
   $[3].tail.bones.count

   --To get the second bone in the tail, use the following:
   $[3].tail.bones[2]

   --If you have one tail bone selected and you want to select all
   --the bones in the tail, you can use the following:
   select (for o in $[3].Limb.bones collect o.node)
   OK

EXAMPLE

If you have a tail bone selected and you want to get the name of the first bone in the tail, you can use the following:

   $[3].tail.bones[1].node.name

   --Here is a detailed overview of the hierarchy access:
   $--> accesses the selected bone
   $[3]--> accesses the selected bone's transform controller
   $[3].tail--> accesses the selected bone's tail controller
   $[3].tail.bones[1]--> accesses the 1st bone in the tail controller
   $[3].tail.bones[1].node--> accesses the node of the 1st bone.
   $[3].tail.bones[1].node.name--> accesses the name of the node of the 1st bone in the tail.