The other Bit of the MEL BVH Exporter


//Matrix rotation stuff.

   string $roto=`xform -q -roo $psNode`;    //Get orientation.
   //xyz zxy zyx
   xform -p true -roo "xyz" $psNode;        //Force to orientation.
   if ($giBaseRotation==1 && $giIndentLvl==0) {     //Transform using world space.
      $p=`xform -ws -q -ro $psNode`;     //Get rotation.
   } else {
      $p=`xform -os -q -ro $psNode`;     //Get rotation.
   xform -p true -roo $roto $psNode;        //Return to current orient.

   float $x=deg_to_rad($p[0]);    //Convert to radians.
   float $y=deg_to_rad($p[1]);    
   float $z=deg_to_rad($p[2]);

   if ($x <= 0.0 && $x > -0.000000000001) $x=0.0;
   if ($y <= 0.0 && $y > -0.000000000001) $y=0.0;
   if ($z <= 0.0 && $z > -0.000000000001) $z=0.0;

   float $A=cos($x);
   float $B=sin($x);
   float $C=cos($y);
   float $D=sin($y);
   float $E=cos($z);
   float $F=sin($z);

   $rx= rad_to_deg(asin($B * $C));
   $ry= rad_to_deg(atan2($D, $A * $C));
   $rz= rad_to_deg(atan2(-1 * $B * $D * $E + $A * $F, $B * $D * $F + $A  * $E));

if ($psNode == “rFoot”) {
print("Node is rFoot. X, Y, and Z are “+$rx+”, “+$ry+”, “+$rz+”

   //Do a little cleanup of our numbers for easier parsing.

   if ($giIndentLvl==0) {
      //Xpos Ypos Zpos Xrot Zrot Yrot
      $lsTmp=$tx+" "+$ty+" "+ $tz + " "+$rx+" "+$rz+" "+$ry;
   } else {

      if ($lsChildren[0] != "") {    //End effector.
         //Zrotation Xrotation Yrotation
         $lsTmp=" "+$rz+" "+$rx+" "+$ry;
   fprint $ghFile $lsTmp;


//FindRootJoint climbs from the current node up the node tree to the trunk.
//Returns the name of the root bone node. This routine can start at any
//object which is a child of the root node and will stop at the toplevel

global proc string FindRootJoint ( string $psNode ) {
string $lsOutNode;

$lsOutNode = $psNode;        //Initialize while loop.

int $iJoint=0;            //Looking for parent that is a joint.
if (`objectType -isType joint $lsOutNode`) { 
string $lsParent[] = `listRelatives -p $lsOutNode`;

//Loop while type is joint or looking for a joint.
while ($lsParent[0] != "" && (`objectType -isType joint $lsParent[0]` || $iJoint==0)) {
   if (`objectType -isType joint $lsParent[0]`) {
      $iJoint++;            //Increment bone counter.
      $lsOutNode = $lsParent[0];    //Identify current toplevel bone.
   $lsParent = `listRelatives -p $lsParent[0]`;
return $lsOutNode;


//RecurseNodeTree looks at the children of this level object and does ‘stuff’
//with it. (stuff is a callback function to be executed via eval.)
//This routine is called recursively to navigate the object tree.
global proc RecurseNodeTree ( string $psNode, string $psStuff ) {
string $lsNodes[];
string $lsAttrib;
global int $giIndentLvl; //Level of indention.

//Evaluate callback function for this level.
string $lsCmd= $psStuff + " 1 \"" + $psNode + "\"";
eval $lsCmd;

$giIndentLvl ++;        //Going down a level, change indent for text.
string $lsChildren[] = `listRelatives -c $psNode`;

for ($lsChild in $lsChildren) {
   if (`objectType -isType joint $lsChild`) {    //Only recurse joints.
      RecurseNodeTree ($lsChild, $psStuff);

$giIndentLvl --;        //Going up a level, change indent for text.

string $lsCmd= $psStuff + " 2 \"" + $psNode + "\"";
eval $lsCmd;


//User toggled selected time range. Enable/disable accordingly and set vals.
global proc TimeRange (int $piTimeRange) {
global int $giRangeSelect; //Which joint level to use.
global string $ghRangeStart, $ghRangeEnd;

$giRangeSelect = $piTimeRange;
if ($ghRangeStart != "") {
   intField -edit -enable $piTimeRange $ghRangeStart;
   intField -edit -enable $piTimeRange $ghRangeEnd;


//User toggled sample rate… Enable/disable accordingly and set vals.
global proc SampleRate (int $piSampleSelect) {
global int $giSampleSelect; //Which joint level to use.
global string $ghSampleRate;

$giSampleSelect = $piSampleSelect;
if ($ghSampleRate != "") {
   intField -edit -enable $piSampleSelect $ghSampleRate;


//This routine is activated when the OK button is clicked.
global proc int OnOkay () {
global string $ghFilename, $ghRangeStart, $ghRangeEnd;
global int $ghFile;
global int $giFrameNo; //Frame number being processed.
global int $giJointSelect; //Use toplevel joint as root.
global int $giRangeSelect; //Timerange to use. 0=all. 1=selected.
global int $giRange[2]; //If not using all, use this range.
global int $giSampleRate; //Rate data is sampled at.
global string $ghSampleRate;
global int $giSampleSelect;
string $result;

$lbAuto=`autoKeyframe -q -st`;    //Get auto keyframing mode.
autoKeyframe -st false;            //Turn off auto keyframing.

string $lsNodes[] = `selectedNodes`;        //Get selected nodes.
string $lsNode = $lsNodes[0];        //Get first node selected.

if ($giJointSelect ==0) {        //Use the toplevel joint node.

if ($lsNode == "") {                //No selected nodes.

confirmDialog -title “Information” -message “No nodes selected. Please select root node before running this script.” -button “OK”;
return 0;

//Get filename from the filename object.
string $gcFilename = `textField -q -tx $ghFilename`;

if (`filetest -s $gcFilename`) {
   string $cMsg = "Overwrite " + $gcFilename + "?";

$result=confirmDialog -title "Confirm" -message $cMsg -button "Yes" -button "No" -defaultButton "Yes" -cancelButton "No" -dismissString "No";

   if ( $result == "No" ) return 0;

currentTime -20;    //Set the current frame number.

print ("File:  " + $gcFilename + "

print ("Generating BHV Header data.

$ghFile=`fopen $gcFilename "w"`;        //Open to write.
fprint $ghFile "HIERARCHY\r";        //Write first line of file.

RecurseNodeTree ($lsNode, "WriteBVHHeader");    //Recurse tree.

//****************FETCH DATA FROM SCREEN CONTROLS*******************
if ($giRangeSelect == 0) {        //Using full keyed time range.
   $giRange[1]=`findKeyframe -hierarchy below -which last $lsNode`;
} else {                //Using user-defined time range.
   $giRange[0]=`intField -q -v $ghRangeStart`;
   $giRange[1]=`intField -q -v $ghRangeEnd`;

if ($giSampleSelect == 0) {        //Full resolution sampling.
   $giSampleRate = 1;
} else {
   $giSampleRate=`intField -q -v $ghSampleRate`;

//****************START DEFINTION OF MOTION*******************
int $NumKeys=($giRange[1]-$giRange[0]+1)/$giSampleRate;
string $lsTmp = "Frames:	" + $NumKeys +  "\r";
fprint $ghFile "MOTION\r";        
fprint $ghFile $lsTmp;

float $lfSampleRate = 1.0/30.0;        //Float is for FPS.

$lfSampleRate = NoScience($lfSampleRate * (float)$giSampleRate);    //FPS.
$lsTmp = "Frame Time: " + (string)$lfSampleRate + "\r";

fprint $ghFile $lsTmp;

//Always write at least one frame.
if ($giRange[1] < $giRange[0]) $giRange[1]=$giRange[0];

//Loop through frames.  For each frame, recurse tree and write to file.
for ( $giFrameNo=$giRange[0];$giFrameNo<=$giRange[1];$giFrameNo+=$giSampleRate) {
   currentTime $giFrameNo;    //Set the current frame number.
   print ("Writing frame " + ($giFrameNo + 1 - $giRange[0]) + 
          " of " + $NumKeys + "

RecurseNodeTree ($lsNode, “WriteBVHFrame”);
fprint $ghFile “\r”;

fclose $ghFile;                //Close the file.

autoKeyframe -st $lbAuto;            //Restore keyframing mode.

return 1;


//ExitBVH writes the user’s preferences to a file and destroys the window.
global proc ExitBVH() {
global string $ghWindow;
global int $giPickRoot;

if ($giPickRoot==1) {        //If pick mode is set, unset.

WritePrefFile();            //Write BVH export preferences.
deleteUI -window $ghWindow;


//SetDir is called when the Pick directory button is pressed. It takes the
//filename string and parses off the filename, stores that to a temp name,
//and runs the directory command with the current path.
global proc SetDir() {
global string $ghFilename, $gcFilename, $gcShortFilename;

$gcFilename = `textField -query -text $ghFilename`;
string $lcDirName = dirname($gcFilename);

int $liDirLen =`size $lcDirName`+1;    //Move past last dir character.
int $liNameLen=`size $gcFilename`;

string $tmp=`substring $gcFilename $liDirLen $liDirLen`;
if ($tmp=="\\"||$tmp=="/") {

$gcShortFilename=`substring $gcFilename $liDirLen $liNameLen`;
fileBrowser "GetDir" "Path" "" 4;


//Gets directory from pick window, inserts it into filename.
global proc GetDir(string $select_dir,string $dir)
global string $gcFilename, $gcShortFilename, $ghFilename;
textField -edit -text $gcFilename $ghFilename;

//OnHelp generates a scrollable text window which describes the BVHexporter
global proc OnHelp() {
global string $ghHelpWin;

if (!`window -exists -query $ghHelpWin`) { //Open window if it doesn't exist.
   $ghHelpWin = `window -width 300 -height 200 
                    -title "Export to BVH Help" -iconName "BVH Export Help"`;

string $txt="FIXES:

The 1.2 version of BVHExporter fixes a known bug with autokeying where it would key all of your animation.


Root joint selection: The radio buttons under Root Joint let you pick whether to work from the current joint or to search for the base of the hierarchy. Selecting ‘Top’ should work, even if you have the skin selected. The Pick button switches selection mode to Hierarcy. Clicking it again or dismissing the window will restore your previous selection mode.

Time Range Selection: Keyed Range will automatically select all frames from 1 to N where keys are set on any part of the joint hiearchy. Manual allows you to specify a time range.

Sample Rate: All Frames will create a key for every frame in the timeline in the BVH file. Every Nth frame reduces the sample rate and changes the framespeed in the BVH file.

Base Adjustment: Both Translation and Rotation for the root of the hierarchy may be recorded with either object-local offsets or world ";
$txt=$txt+"offsets. If the hierarchy is parented to another object and the object is moved, in World mode, the translation and/or rotation could be recorded for the hierarchy root. In object mode, the hierarchy would stay in place.

File Selection: Now has a button to launch a file browser for path selection. (See NOTES)

Preferences Auto Save: ExportBVH now automatically saves your configuration for ease of use.

Apply: BVHExport may now be run without dismissing the window on completion.

Help: Displays this window.


If the path button does not work, you will need to locate the script ‘filebrowse.mel’ on your system and change the sourc FILEBROWSEPATH line accordingly.

This software is FREE for distribution and modification. Questions, comments, complaints, feature requests may be sent to Samantha Patterson,";
columnLayout -adjustableColumn true;
scrollField -wordWrap true -editable false -height 600 -nl 1000 -text $txt;
//button -label “Dismiss” -command (“deleteUI -window $ghHelpWin;”);
showWindow $ghHelpWin;

if (!window -exists -query $ghWindow) { //Open window if it doesn’t exist.
ReadPrefFile(); //Read user preferences from last run.

$ghWindow = `window -width 380 -height 260 
                    -title "Export to BVH 1.2" -iconName "BVH Export"`;

columnLayout hTop;                //Overall window by column
   rowLayout -nc 3 -parent hTop hRow1;    //Row for Root and Range frames.
      frameLayout -borderStyle "etchedIn" -label "Root Joint" 
                  -height 80 -width 100 -parent hRow1 hJointBox;
         columnLayout -parent hJointBox;
            string $hJBC=`radioCollection`;
            string $hJB1=`radioButton -label "Top"     -onCommand ("$giJointSelect=0")`;
            string $hJB2=`radioButton -label "Current" -onCommand ("$giJointSelect=1")`;
            if ($giJointSelect == 0) {
               radioCollection -edit -select $hJB1 $hJBC;
            } else {
               radioCollection -edit -select $hJB2 $hJBC;
            $ghPickRoot=`button -label "Pick" -command ("PickRoot()")`;

      frameLayout -borderStyle "etchedIn" -label "Time Range" 
                  -height 80 -width 100 -parent hRow1 hTimeRange;
         columnLayout -parent hTimeRange hTimeRangeCol;
            string $hTRC=`radioCollection`;
            string $hTR1=`radioButton -label "Keyed Range" -onCommand ("TimeRange(0)")`;
            string $hTR2=`radioButton -label "Manual"      -onCommand ("TimeRange(1)")`;
            if ( $giRangeSelect == 0) {
               radioCollection -edit -select $hTR1 $hTRC;
            } else {
               radioCollection -edit -select $hTR2 $hTRC;
            rowLayout -nc 3 -cw3 35 30 35 -width 100 
                      -parent hTimeRangeCol hTimeRangeRow;
               $ghRangeStart= `intField -value $giRange[0] -enable $giRangeSelect -width 30`;
               text -label " to ";
               $ghRangeEnd  = `intField -value $giRange[1] -enable $giRangeSelect -width 30`;

      frameLayout -borderStyle "etchedIn" -label "Sample Rate" 
                  -height 80 -width 180 -parent hRow1 hSampleRate;
         columnLayout -parent hSampleRate;
            string $hSRC=`radioCollection`;
            string $hSR1=`radioButton -label "All Frames" -onCommand ("SampleRate(0)")`;
            string $hSR2=`radioButton -label "Every Nth Frame" -onc  ("SampleRate(1)")`;
            if ( $giSampleSelect == 0) {
               radioCollection -edit -select $hSR1 $hSRC;
            } else {
               radioCollection -edit -select $hSR2 $hSRC;
            $ghSampleRate = `intField -enable $giSampleSelect -min 1
                                      -value $giSampleRate`;

   rowLayout -nc 2 -parent hTop hRow2;        //Row for translations.
      frameLayout -borderStyle "etchedIn" -label "Base Adjustment" 
                  -width 380 -parent hRow2 hBaseAdj;
         columnLayout -parent hBaseAdj hBaseAdjC;
            rowLayout -parent hBaseAdjC; 
               $ghTranslate = `radioButtonGrp -numberOfRadioButtons 2 
                                              -label "Translation:  "
                                              -labelArray2 "World" "Object"
                                              -select $giBaseTranslation`;
            rowLayout -parent hBaseAdjC; 
               $ghRotate    = `radioButtonGrp -numberOfRadioButtons 2 
                                              -label "Rotation:  "
                                              -labelArray2 "World" "Object"
                                              -select $giBaseRotation`;

   frameLayout -borderStyle "etchedIn" -width 380 -labelVisible false 
               -parent hTop hFileDia;
      rowLayout -nc 3 -cw3 60 270 50 -parent hFileDia;
         text -label "Filename" -width 50;
         $ghFilename = `textField -text $gcFilename -width 265`;
         button -label " Path" -width 50 -align right 
                -command ("SetDir();") "Path";

   frameLayout -borderStyle "etchedIn" -labelVisible false 
               -parent hTop -width 380;
      rowLayout -nc 4;
         button -label "Okay" -command ("if (OnOkay()) ExitBVH();") "Okay";
         button -label "Apply" -command ("OnOkay()") "Apply";
         button -label "Help" -command ("OnHelp()") "Help";
         button -label "Dismiss" -command ("ExitBVH();") "Dismiss";
   setParent ..;

showWindow $ghWindow;


This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.