#!BPY

""" Registration info for Blender menus:
Name: 'Blender Pose Handler'
Blender: 248
Group: 'Animation'
Tooltip: 'Specialized saving and loading of Blender Library-style poses'
"""
__author__ = ["Basil_Fawlty/Cage_drei (Andrew Cruse)"]
__url__ = ("http://wiki.blender.org/index.php/Extensions:Py/Scripts/Manual/Animation/PoseLib16","http://www.kuroyumes-developmentzone.com/~Cage/PoseLib16.zip")
__version__ = "1.0 (Sept 22,2006)"
__email__ = "Cagedrei@aol.com"
__bpydoc__ = """\

This script provides several tools for pose and armature handling.  It uses Blender Library type poses
and provides animation support using batch import and export of those poses.  It also provides various
tools for pose tweaking and development and can import and export Poser poses.

This script processes bone quaternion rotations four different ways to produce four varieties of poses.

1) Blender Library type.  This method seems to embed bone roll data in the pose, making it difficult
to use the pose with armatures other than the source armature.

Save:
quat without processing
Load:
quat without processing

Note: No conversion processing is required for default poses.

2) Restmatrix type (exported in bvh data, also from PoserPython).  Essentially lacks embedded bone roll
data in the pose, so the pose can be loaded onto armatures with different bone roll setups.

Save:
quat = (rest_matrix_invert * quat_as_matrix * rest_matrix).toQuat()
Load:
quat = (rest_matrix * quat_as_matrix * rest_matrix_invert).toQuat()

Note: Load restmatrix type poses using the 'Restmatrix pose ('No Roll')' option.

3) Posematrix type.  This type uses the poseMatrix to export IK and constraints.  This type also embeds
bone roll data.

Save:
quat = (posematrix * parent_posematrix).toQuat()
Load:
quat = (quat_as_matrix * (rest_matrix * parent_rest_matrix_invert).invert()).toQuat()

Note: Load posematrix type poses using the 'Posematrix pose ('IK')' option.

4) Combination posematrix and restmatrix type.  Combined correctly, types 2) and 3) can render a pose which
both contains IK and constraint data, but lacks embedded bone roll.  This is not simply a matter of encoding
the data both ways on export and import.  That works - in the order 3)-2) for export and 2)-3) for import - but
the bone roll information remains embedded.  To export the benefits of both, they are processed as they would be
if type 3) were saved, then loaded, then saved again as type 2).

Save:
quat = ((posematrix * parent_posematrix) * (rest_matrix * parent_restmatrix_invert).invert()).toQuat()
This is a combination of the save and load for type three.  Then it is passed to the save process for type two.
quat = (rest_matrix_invert * quat_as_matrix * rest_matrix).toQuat()
Load:
quat = (rest_matrix * quat_as_matrix * rest_matrix_invert).toQuat()

Note: Load combination type poses using the 'Restmatrix pose ('No Roll')' option.

That's kind of a shorthand version of the handling in this script.  Type 2 poses are based on the work of Campbell Barton.
Type three poses are based on scripts by der_ton and others.

- Save fxn, load fxn, and pose formatting are adapted from Blender Library<br>
- PoseMatrix pose handling adapted from der_ton's md5 I/O scripts<br>
- "No roll"/restmatrix pose handling adapted from Campbell Barton's bvh_import.py for 2.42<br>
- Radians conversion borrowed from xsi export, by "Elira"<br>
- GUI colorbox from discombobulator.py<br>

This script is designed to work with my poser2blender.py for PoserPython

Some elements of this script are still in development:<br>
- Bone layer handling will be added once it is added to the BPy API.<br>
- align_chains and boneroll_correction both need tweaking.<br>
- Poses exported with save_poser are occasionally incorrect.  Blender's quaternion to euler conversions don't
seem to return the correct results for this purpose, and no wholly correct conversion method has yet been found.
"""
# -------------------------------------------------------------------------- 
# Blender Pose Handler V1.0 
# -------------------------------------------------------------------------- 
# ***** BEGIN GPL LICENSE BLOCK ***** 
# 
# This program is free software; you can redistribute it and/or 
# modify it under the terms of the GNU General Public License 
# as published by the Free Software Foundation; either version 2 
# of the License, or (at your option) any later version. 
# 
# This program is distributed in the hope that it will be useful, 
# but WITHOUT ANY WARRANTY; without even the implied warranty of 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the 
# GNU General Public License for more details. 
# 
# You should have received a copy of the GNU General Public License 
# along with this program; if not, write to the Free Software Foundation, 
# Inc., 59 Temple Place - Suite 330, Boston, MA	02111-1307, USA. 
# 
# ***** END GPL LICENCE BLOCK ***** 
# -------------------------------------------------------------------------- 

import Blender
from Blender import *
from Blender.Mathutils import *
from Blender.Registry import *
from Blender.Draw import *
from Blender.BGL import *
import math, os, re

##-------Save Functions----------------------------------------------------
"""
object = Blender.Scene.GetCurrent().getActiveObject()
 if not object or object.getType() != 'Mesh':
"""
def goSave (save_name):  
  global EmpPose, PoserSave
  #print save_name
  if save_name != "":    
    poseText = ""    
    if EmpPose == 1: poseText = Export_Empties() #Export a pose from bvh import empties               
    elif EmpPose == 0:
      if Object.GetSelected() != []:      
        ob = Object.GetSelected()[0]    
        if ob.getType() == "Armature":       
          pose = ob.getPose()
          if PoserSave == 0: #save a Blender Library type pose
            for bone in pose.bones.values():                              
              savePart = save_part(bone) #check screening with partial groups and bones list                     
              if savePart == 1:
                quat = bone.quat
                quat = save_convert(ob, pose, bone, quat, 0) #posematrix and restmatrix type conversions              
                poseText += "%s," %(bone.name)                                                            
                if No_Trans == 1: poseText += "0.0,0.0,0.0," #Don't save translations
                elif No_Trans == 0: poseText += "%f,%f,%f," %(bone.loc[0], bone.loc[1], bone.loc[2]) #Save trans            
                poseText += "%f,%f,%f,%f," %(quat[0], quat[1], quat[2], quat[3]) 
                if No_Scale == 1: poseText += "1.0,1.0,1.0\n" #Don't save scaling
                elif No_Scale == 0: poseText += "%f,%f,%f\n" %(bone.size[0], bone.size[1], bone.size[2]) #Save scaling
                
          elif PoserSave == 1: poseText = save_poser(ob, pose)#save a Poser pose
                                
    if poseText != "": #Don't proceed if there's nothing to write
      if PoserSave == 0:
        if os.path.splitext(save_name)[1] == "": save_name += ".blend"      
        if os.path.splitext(save_name)[1] != ".blend" and os.path.splitext(save_name) != ".bPose": 
          save_name = sys.makename(save_name, ext = ".blend", strip = 0)
      elif PoserSave == 1:
        compress = PupMenu("Use file compression? %t|Yes %x1|No %x2")
        if compress == 2: save_name = os.path.splitext(save_name)[0] + ".pz2"
        else: save_name = os.path.splitext(save_name)[0] + ".p2z"
      warning = 0
      if sys.exists(save_name) == 1:
        warning = PupMenu("Overwrite file %s? %%t|Yes %%x1|No %%x2" %(sys.basename(save_name))) #Don't overwrite        
      if sys.exists(save_name) != 1 or warning != 2:
        if compress == 2:
          text = open(save_name,'w')          
        elif compress == 1: #save compressed Poser pose
          import gzip
          text = gzip.GzipFile(save_name,'wb')
        text.write(poseText)
        text.close()
      Blender.Redraw()
#------check for save part screening (partial groups and bones list)--------------
def save_part(bone):
  global partVar, sideVar
  savePart = 0
  if bonesSave == 1 and bone.name in boxBones: savePart = 1 #Save only bones listbox selections
  if bonesSave == 2 and bone.name not in boxBones: savePart = 1 #Exclude bones listbox selections from save
  if partVar == "" and sideVar == "" and bonesSave == 0: savePart = 1 #Partial pose saving
  if partVar != "":
    if sideVar != "": #Symmetrical save
      partVar2 = partVar.split("@%#%@")
      for group in range(0,len(partVar2)):                            
        if bone.name in groupsDict:
          if groupsDict[bone.name][:-1] == partVar2[group]:
            savePart = 0
            if (sideVar == "right" or sideVar == "both") and (pref == 1 and bone.name[:lenR] == right) or\
               (suff == 1 and bone.name[-lenR:] == right):
              savePart = 1                    
            if (sideVar == "left" or sideVar == "both") and (pref == 1 and bone.name[:lenL] == left) or\
               (suff == 1 and bone.name[-lenL:] == left):
              savePart = 1 
    partVar2 = partVar.split("@%#%@") #This allows multiple group handling            
    for group in range(0,len(partVar2)):              
      if bone.name in groupsDict:
        if groupsDict[bone.name][:-1] == partVar2[group]: savePart = 1  
  return savePart
#--------run pose type conversions (restmatrix/posematrix)--------------------------------
def save_convert(ob, pose, bone, quat):
  global IKPose, NoRollPose              
  if IKPose == 1: quat = IK_save(bone, ob, pose) #Save pose from bone.poseMatrix()            
  if IKPose == 1 and NoRollPose == 1: #This enables posematrix poses to have benefits of                               
    bone1 = pose.bones[bone.name]#restmatrix poses (bone roll becomes irrelevant)
    quat = IK_load(bone1, quat, ob, pose)            
  if NoRollPose == 1: quat = roll_correction(bone, quat) #Save pose relative to armature rest matrix
  return quat
#-------General save options---------------------------     
def export_options(): 
  global EmpPose, IKPose, NoRollPose, OptPose, saveBatch, LibSave, LibPath, LibTog, LibVar, partVar, sideVar,\
         NoRollTog, IKTog
           
  expo = PupMenu("Save pose options: %t|Save pose from .bvh empties - Load with 'No Roll' option. %x1\
                  |Save pose with IK/constraints (poseMatrix) - Load with 'IK' option. %x2\
                  |Save pose without embedded bone roll (restmatrix) - Load with 'No Roll' %x3\
                  |Save combination IK and 'no roll' - Load with 'No Roll' option %x4\
                  |Save animation as batch of pose files %x5\
                  |Save poses to current load list folder (load menu browser) %x6\
                  |Deactivate all options %x7|Cancel (Keep current options)%x8")
  if expo == 1: #save pose from empties
    EmpPose = 1 - EmpPose
    IKPose = 0
    NoRollPose = 0
  elif expo == 2: #save posematrix
    IKPose = 1 - IKPose
    NoRollPose = 0
    EmpPose = 0
  elif expo == 3: #save restmatrix
    NoRollPose = 1 - NoRollPose
    IKPose = 0
    EmpPose = 0
  elif expo == 4: #save combination posematrix and restmatrix 
    NoRollPose = IKPose = 1 - NoRollPose    
    EmpPose = 0
  elif expo == 5: #batch export for animation
    saveBatch = 1 - saveBatch   
  elif expo == 6: #save to menu browse folder
    if LibPath != "":
      LibSave = 1 - LibSave
    if LibPath == "":
      if LibTog == 0: sub = PupMenu("List load browsing not enabled %t|Enable list browsing %x1|Cancel %x2")
      if LibTog == 1: sub = PupMenu("List load folder not specified %t|Select list load folder %x1|Cancel %x2")
      if sub == 1:
        LibVar = LibTog = LibSave = OptPose = 1
        Window.FileSelector(list_browser, 'Select pose folder:')
              
    if LibSave == EmpPose == IKPose == NoRollPose == saveBatch == 0: OptPose = 0         
    
  elif expo == 7: EmpPose = IKPose = NoRollPose = saveBatch = OptPose = 0 #deactivate all  
    
  if EmpPose == IKPose == NoRollPose == saveBatch == LibSave == 0: #cancel
    OptPose = 0
    
  NoRollTog = NoRollPose ##set the toggle display
  IKTog = IKPose
  
#-----save posematrix (IK) conversion----------------------------
#Based (primarily) on der_ton's blender2md5export.py
def IK_save(bone, armature, pose): #Saves a poseMatrix pose - enabling saving of IK and constraints
  
  MATRIX_IDENTITY_4x4 = Matrix([1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1])
  AData = armature.getData()
  rest_bone = AData.bones[bone.name]
  pose_bone_mat = Matrix(bone.poseMatrix)
  if rest_bone.parent:
    parentposemat = Matrix(pose.bones[rest_bone.parent.name].poseMatrix)
  else: parentposemat = MATRIX_IDENTITY_4x4
  parentposemat.invert()  
  finalMatrix = pose_bone_mat * parentposemat
  
  return finalMatrix.toQuat()
#-------save restmatrix (no roll) conversion-------------------------------
#Based on Campbell Barton's bvh_import.py for Blender 2.42
def roll_correction(bone, quat): #This is the inverse of the Poser Convert function, adapted for save
  
  #rx,ry,rz = quat.toEuler().unique() #Saves a pose which will load on another armature, without bone roll problems.
  bone_rotation_matrix = quat.toMatrix()#go directly to matrix, skipping euler conversion....
  arm = Object.GetSelected()[0]  
  AData = arm.getData()
  bone1 = AData.bones[bone.name]   
  bone_rest_matrix = bone1.matrix['ARMATURESPACE'].rotationPart() 
  bone_rest_matrix_inv= Matrix(bone_rest_matrix) 
  bone_rest_matrix_inv.invert()        
  #bone_rotation_matrix= Euler(rx,ry,rz).toMatrix()      
  finalMatrix = bone_rest_matrix_inv * bone_rotation_matrix * bone_rest_matrix #Switch the order of multiplication
  
  return finalMatrix.toQuat()
#--------export empties---------------------------------
def Export_Empties(): #Export a pose from .bvh import empties     
  poseText = ""  
  if EmpPose == 1:
    objs = Blender.Scene.GetCurrent().getChildren()
    for ob in objs:
      if ob.getType() == "Empty":          
        if ob.name[0] == "_": ob.name = ob.name[1:len(ob.name)] 
        if ob.name[-4:] != "_end" and ob.name[0] != "_" and ob.name[-4:] != ".001": #omit junk empties
          poseText += ob.name + ","                            
          poseText += "0.0,0.0,0.0,"  #translation is ignored     
          oQuat = Euler(r2d(ob.rot[0]),r2d(ob.rot[1]),r2d(ob.rot[2])).unique().toQuat()#radians to degrees                             
          poseText += "%f,%f,%f,%f," %(oQuat[0], oQuat[1], oQuat[2], oQuat[3])
          poseText += "1.0,1.0,1.0\n" #Ignore scaling, too.          
  return poseText   
# ---------------------------------------------------------------------------
# r2d - radians to degrees (from .xsi export, by "Elira")
# ---------------------------------------------------------------------------
def r2d(r):
  return round(r*180.0/math.pi,4) #Approx. 57.295828 before rounding

#----save partial groups menu-------------------------------------
def partSave(): #Combination toggle/popup for partial save specification. 
  global partVar, sideVar, TrimPose, maxVal, minVal, bonesSave, boxBones
  groupcount = [] #Dict for list of parts visible to user
  groupsides = [] #Dict for internal version of parts (includes symmetry flag)
  puplist = "" #The menu list
  partvar = ""
  sideVar = ""

  if Object.GetSelected() != []:      
    ob = Object.GetSelected()[0]    
    if ob.getType() == "Armature":
      
      if groupsDict == {}: groups() #Find body part groups for partial save
      if flipDict == {}: flip()
      
      for group in groupsDict.values(): #Fill the menu list from the groupsDict
        if groupsides.count(group) < 1:
          groupsides.append(group)
          groupcount.append(group[:-1]) #Make the cleaned-up version visible
      groupcount.sort()
      groupsides.sort()
      noGroups = "No groups found.  Try changing selection parameters."
      if groupcount == []: groupcount.append(noGroups)
      for i in range(0, len(groupcount)):        
        puplist += "%s %%x%i|" %(groupcount[i], i + 1)
        if i == len(groupcount)-1:          
          puplist += "Alter automatic group selection parameters %%x%i|" %(i + 2)          
          puplist += "Save only bones listbox selections in pose %%x%i|" %(i + 3)          
          puplist += "Exclude bones listbox selections from saved pose %%x%i|" %(i + 4)          
          puplist += "Deactivate all groups %%x%i|" %(i + 5)          
          puplist += "Cancel (Keep current options) %%x%i" %(i + 6)
      
      partList = PupMenu("Select partial pose group or bones list screening: %t|" + puplist)
      for part in range(1, len(groupcount) + 1):
        if partList == part and groupcount[0] != noGroups:        
          partVar += groupcount[part - 1]
          partVar += "@%#%@" #Pick an unlikely sequence of symbols to split multiple entries for multiple group selections
          if groupsides[part - 1][-1:] == "!": #Check notation to see if it's centered                          
            sideVar = symmetrical_save() #Symmetry handling
            if sideVar == "": partVar = "" #Cancelled during symmetry selection               
            #break
          
      if partList == len(groupcount) + 1: #Change automatic group selection parameters
        maxVal = PupIntInput("Max group size:", totalBones/2, 1, totalBones-1)
        if maxVal == 0 or maxVal == None: maxVal = 1#Avoid division by zero errors
        maxVal = int(totalBones/maxVal) #Convert target size to divisor value
        if maxVal == 0 or maxVal == None: maxVal = 1
        minVal = PupIntInput("Min group size:", 3, 0, totalBones-1)      
        if minVal == None: minVal = 2
        partVar = sideVar = ""
        groups()                  
        if partVar == "" and sideVar == "": TrimPose = 0

      elif partList == len(groupcount) + 2 or partList == len(groupcount) + 3:
        if partVar != "":
          ask = PupMenu("Options %t|Combine listbox selection with partial save group(s) %x1\
                        |Deactivate partial save group(s) %x2|Cancel %x3")
          if ask == 2:
            partVar = ""
            sideVar = ""
          if ask == 3: return 
      
        if partList == len(groupcount) + 2: #bones list save inclusive
          if boxBones == []: error = PupMenu("No current bone listbox selection. %t|Please make a bone listbox selection.")
          else:
            if bonesSave == 1: bonesSave = 0
            elif bonesSave == 0 or bonesSave == 2: bonesSave = 1 #1 is inclusive
        elif partList == len(groupcount) + 3: #bones list save exclusive
          if boxBones == []: error = PupMenu("No current bone listbox selection. %t|Please make a bone listbox selection.")
          else:
            if bonesSave == 2: bonesSave = 0
            elif bonesSave == 0 or bonesSave == 1: bonesSave = 2 #2 is exclusive
          
      elif partList == len(groupcount) + 4 or\
         (partList == 1 and groupcount[0] == noGroups): #Deactivate all
        TrimPose = 0
        partVar = ""
        sideVar = ""
        bonesSave = 0          
      
      if partVar == sideVar == "" and bonesSave == 0: TrimPose = 0 #Cancel
      
  else:
    TrimPose = 0 #No armature selection
    error = PupMenu("Error.%t|Please select an Armature")
     
def symmetrical_save(): #Select the side if a group is symmetrized
  sideList = PupMenu("Select side(s) to save in pose: %t|Right %x1|Left %x2|Both %x3|Cancel %x4")
  if sideList == 1: sideVar = "right"      
  elif sideList == 2: sideVar = "left"
  elif sideList == 3: sideVar = "both"
  else: sideVar = ""
    
  return sideVar
##-------Load Functions (and "real time" application)-------------------------------------------------
def load_pose(file_name):
  global bodyscale, PoserConvert, flipbone, realTime, right, left, lenR, lenL
  ext = os.path.splitext(file_name)[1]
  if ext == ".pz2" or ext == ".p2z" or ext == ".hd2" or ext == ".hdz": #Poser pose loading
    load_poser_pose(file_name)
    return  
  if file_name != "" or realTime == 1:    
    if Object.GetSelected() != []:        
      ob = Object.GetSelected()[0]      
      if ob.getType() == "Armature":        
        if groupsDict == {}: groups() #partial groups and root part definition 
        pose = ob.getPose()           
        if realTime == 0:             #realTime is a toggle variable which enables us to multitask this loop. 
          txt = open(file_name,"r")   #With realTime == 0, we load a pose from a textfile.  
          lines = txt.readlines()     #With realTime == 1, we apply selected changes (from gui buttons) to    
        if realTime == 1:             #the current armature pose, without loading a file.
          lines = pose.bones.values()
        for line in lines:
          if realTime == 0:
            data = line.rstrip("\n").split(",")
            bone = pose.bones[data[0]]        
          if realTime == 1: #collect the bone's data in the form passed by a saved pose            
            bone = line
            loc = bone.loc
            quat = bone.quat
            size = bone.size             
            data = (bone.name, loc[0], loc[1], loc[2], quat[0], quat[1], quat[2], quat[3], size[0], size[1], size[2])         
            
          if bone:             
            if flipDict == {}: flip() #Opposite side bones            
            flipbone = ""
            
            screening = 0 #Flag variable to exclude a bone from handling in this loop            
            if ExcludePart == 1: screening = Part_Load(bone, 0) #Exclude selected partial save group when loading
            if IncludePart == 1: screening = Part_Load(bone, 1) #Use only selected partial save group when loading
            if bonesLoad == 1 and bone.name not in boxBones: screening = 1 #Load only bones listbox selections
            if bonesLoad == 2 and bone.name in boxBones: screening = 1 #Exclude bones listbox selections when loading
                    
            if (ScreenPose == 1 or DupPose == 1) and screening == 0:
              screening = Screen_Pose(bone.name, pose)  #Screen out right or left side when loading             
              
            #------HipPose and bodyscale are still formatted for cr2-derived armatures
            #------The HipPose functions should still find the root in an armature, however.
            #root and rootchild are inherited from the groups() fxn.
             
            if bone.name == "BODY"  and root.name == bone.name and bodyscale == 0:#On/off for BODY (root)
              screening = 1                                                              #Poser-specific
              #print bone.name, bone.loc, root.name, screening, rootchild
            noRot = 0
            noTrans = 0            
            
            if HipPose == 1: #On/off for root OR for hip (child of root) if root is BODY (as w/ Poser cr2)             
              if (bone.name == "hip" and rootchild != None and bone.name == rootchild.name) or \
                 (bone.name == root.name and bone.name != "BODY"):
                #print "root: ", root, "rootchild: ", rootchild, HipPose, hipR, hipT 
                if hipR == 1: noRot = 1
                if hipT == 1: noTrans = 1 #Need to clean this up.  Five variables?
            #---------------------------------------              
            if screening == 0:
              if noTrans == 1: loc = bone.loc #loc = Vector(0,0,0)
              if noTrans == 0: #We start applying the values (from Blender Library).                                  
                loc = Vector(data[1],data[2],data[3])                
              if noRot == 1: quat = bone.quat #quat = Quaternion(1,0,0,0)
              if noRot == 0:  
                quat = Quaternion(data[4],data[5],data[6],data[7])
                
                if bodyscale == 1 and addscale == 1 and bone.name == root.name:
                  quat = MixPose(quat, bone, 0, 0) #Additive rotation for BODY - cr2 armature-specific                  
              #------------------pose type conversions
              if PoserConvert == 1:                
                quat = Poser_Conversion(bone, quat, noRot) #Convert Poser export/"empty" pose/restmatrix "no roll" pose 
                
              if PoseMat == 1:
                quat = IK_load(bone, quat, ob, pose) #Convert poseMatrix "IK" pose
               #------------------------------combinatory modifiers                       
              if noRot == 0:
                if partPose != 1.0:                  
                  quat = Partial_Pose(quat, 0, 0) #Apply pose partially, from slider value
                  loc = Partial_Pose(0, loc, 1) #translation
                  
                if MixPose == 1:
                  quat = Mix_Pose(quat, bone, 0, 0) #Mix pose with current armature rotation
                  loc = Mix_Pose(0, bone, loc, 1) #translation
                 
                bone.loc = loc
                bone.quat = quat                
              size = Vector(data[8],data[9],data[10]) #Finish applying base values (Blender Library)
              bone.size = size
              #--------Symmetry stuff----------------------------------------              
              if DupPose == 1: #Duplicate sides (Symmetry Right/Left button)
                if bone.name in flipDict:
                  flipbone = flipDict[bone.name]                                   
                  flipbone = pose.bones[flipbone]  
                  loc = Flip_Pose2(0, loc, 2) 
                  flipbone.loc = loc                  
                  quat = Flip_Pose2(quat, 0, 1)                  
                  flipbone.quat = quat
                else: #Zero the torso chain or other un-mirrored parts
                  #This checks the flipDict.  Would it be better to check for centered parts, as w/ groups?
                  if torsoZero == 0:                    
                    bone.loc = Flip_Zero(0, loc, 2)
                    bone.quat = Flip_Zero(quat, 0, 1)
             
              if MirrorPose == 1 or MirrorBone == 1: #Mirror pose (Flip Pose button)
                if bone.name not in flipDict or MirrorBone == 1: #1 - parts not mirrored 
                  quat = Flip_Pose2(bone.quat, 0, 1)
                  loc = Flip_Pose2(0, bone.loc, 2)
                  bone.quat = quat
                  bone.loc = loc
                  
              #-------End symmetry stuff.  End main loop-----------------------------------------
                    
          else: #If body part in pose doesn't match any bones, just overlook it
            pass
        
        #-----------Last part of flip (outside loop)------------------------------------------------
        #Symmetrical parts are mirrored in a separate loop.  All data for both sides needs to
        #be available from the outset, which isn't true when reading a pose - so the function
        #is run outside the main loop.
        if MirrorPose == 1 and MirrorBone == 0: #These are mutually exclusive, so this is redundant         
          for bone in pose.bones.values():            
            if bone.name in flipDict: #2 - mirrored parts
              screening = 0 #repeat the partial groups screening from the main loop....           
              if ExcludePart == 1: screening = Part_Load(bone, 0)
              if IncludePart == 1: screening = Part_Load(bone, 1)
              if bonesLoad == 1 and bone.name not in boxBones: screening = 1 
              if bonesLoad == 2 and bone.name in boxBones: screening = 1
              if screening == 0:
                flipbone = pose.bones[flipDict[bone.name]]
                quat = Flip_Pose2(flipbone.quat, 0, 1)
                loc = Flip_Pose2(0, flipbone.loc, 2)
                flipquat = Flip_Pose2(bone.quat, 0, 1)
                fliploc = Flip_Pose2(0, bone.loc, 2)
                bone.quat = quat
                bone.loc = loc
                flipbone.quat = flipquat
                flipbone.loc = fliploc
                del flipDict[bone.name]
                del flipDict[flipbone.name]            
        #-----------------------------------------------------------------------------------------------
                
        if askKey != 2:                    
          if askKey == 0: #Ipo curve handling replaces Blender Library keyframing, which was slow.
            c = PupMenu("Insert pose keys for this frame?%t|Yes%x1|No%x2")  
          if askKey == 1: c = 1
          if c == 1:            
            for bone in pose.bones.values():
              add_curves(ob, pose, bone.name, bone.quat, bone.loc, bone.size, Blender.Get('curframe'))            
        
        if realTime == 0: txt.close()         
          
        if realTime == 1: realTime = 1 - realTime
        
        if askKey != 2: #openBatch sets askKey to 2.  This doesn't need to run w/ batch load (not sure why...?).
          pose.update()
          ob.makeDisplayList()
         
        if openBatch == 1: #Ipo curve keyframing          
          for bone in pose.bones.values():
            add_curves(ob, pose, bone.name, bone.quat, bone.loc, bone.size, Blender.Get('curframe'))     
        
      elif ob.getType() != "Armature":
        error = PupMenu("Error.%t|Please select an Armature")
      Blender.Redraw()      
      flipBone = ""
      flipDict.clear()

#---------------------------------------------------------    
def Mix_Pose(quat, bone, loc, switch):
  #Load pose additive to current pose
  if switch == 0: #rotation
    x1,y1,z1 = quat.toEuler().unique()
    xa,ya,za = bone.quat.toEuler().unique()
    qEu = Euler(x1+xa,y1+ya,z1+za).unique()  
    return qEu.toQuat()
  if switch == 1: #translation
    x1,y1,z1 = loc
    xa,ya,za = bone.loc
    loc = Vector(x1+xa,y1+ya,z1+za)
    return loc
#---------------------------------------------------------
def Partial_Pose(quat, loc, switch): 
  #Apply pose by slider percentage
  if switch == 0: #rotation
    xp,yp,zp = quat.toEuler().unique()  
    quat = Euler(xp * partPose,yp * partPose,zp * partPose).unique() 
    return quat.toQuat()
  if switch == 1: #translation
    xp,yp,zp = loc
    loc = Vector(xp * partPose,yp * partPose,zp * partPose)
    return loc

#-----"Screen" sides (exclusion)--------------------------------- 
#Screen one side, based on symmetry notation, with check to be sure no bone is screened
#which lacks a symmetrical counterpart.    
def Screen_Pose(bone_name, pose):
  screening = 0
  if bone_name in flipDict.keys():
    flipbone = flipDict[bone_name]
    if screenL == 1:
      if (pref == 1 and flipbone[:lenL] == left) or (suff == 1 and flipbone[-lenL:] == left) or\
        (pref == 1 and flipbone[:len(left_alt)] == left_alt):
        screening = 1
    elif screenR == 1:
      if (pref == 1 and flipbone[:lenR] == right) or (suff == 1 and flipbone[-lenR:] == right) or\
        (pref == 1 and flipbone[:len(right_alt)] == right_alt):
        screening = 1
      
  return screening

#-----Flip pose (mirror)--------------------------------------
#This handles both the "Flip" and "Symmetry" buttons 
#With DupPose - not MirrorPose - this inherits its symmetry screening from a ScreenPose call

def Flip_Pose2(quat, loc, switch): 
  if switch == 1:
    quat = Quaternion(quat[0], quat[1], -quat[2], -quat[3]) #Use quaternion flipping
    #xf,yf,zf = quat.toEuler().unique() #paste_posebuf in poseobject.c simply flips euler signs....
    #quat = Euler(xf,-yf,-zf).unique().toQuat()
    return quat
  if switch == 2:
    loc = Vector(-loc[0], loc[1], loc[2])
    return loc
#----zeroes un-mirrored bones------------------------
def Flip_Zero(quat, loc, switch):
  if switch == 1:
    quat = Quaternion(quat[0],quat[1],0.0,0.0) #Quaternion version 
    #quat = Quaternion(1,0,0,0) #Zero all rotational axes    
    #xq,yq,zq = quat.toEuler().unique() #Euler version
    #quat = Euler(xq,0,0).toQuat() #Just zero y and z     
    return quat
  if switch == 2:   
    loc = Vector(0,loc[1],loc[2]) #Just zero the x
    #loc = Vector(0,0,0)  #Zero all translation
    return loc
#-----Poser conversion (restmatrix/no roll) import--------------------------------    
def Poser_Conversion(bone, quat, noRot):  
  #Based on Campbell Barton's bvh_import.py (for 2.42)  
  if noRot == 0:
    #rx,ry,rz = quat.toEuler().unique() #Work with eulers mainly to reduce changes to Cambo source   
    bone_rotation_matrix = quat.toMatrix() #go straight to matrix
    arm = Object.GetSelected()[0]  
    AData = arm.getData()
    bone1 = AData.bones[bone.name]   
    bone_rest_matrix = bone1.matrix['ARMATURESPACE'].rotationPart() 
    bone_rest_matrix_inv= Matrix(bone_rest_matrix) 
    bone_rest_matrix_inv.invert()        
    #bone_rotation_matrix= Euler(rx,ry,rz).toMatrix()    
    finalMatrix = bone_rest_matrix * bone_rotation_matrix * bone_rest_matrix_inv
    
    return finalMatrix.toQuat()
#----PoseMatrix/constraints pose import----------------------------------------
# Based (primarily) on der_ton's blender2md5import.py
def IK_load(bone, quat, armature, pose): 
  MATRIX_IDENTITY_4x4 = Matrix([1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1])
  AData = armature.getData()
  bone1 = AData.bones[bone.name]  
  pose_bone_mat = quat.toMatrix().resize4x4()
  bone_rest_matrix = bone1.matrix['ARMATURESPACE']
  if bone1.parent:
    parent_restMat = bone1.parent.matrix['ARMATURESPACE']
  else: parent_restMat = MATRIX_IDENTITY_4x4
  parent_restMat_inv = Matrix(parent_restMat)#make a copy so the actual matrix isn't inverted inadvertantly
  parent_restMat_inv.invert()
  localMat = pose_bone_mat * (bone_rest_matrix * parent_restMat_inv).invert()
  
  return localMat.toQuat()
#-------Partial pose group loading------------------------------------------------
def Part_Load(bone, switch): #Same basic code is seen in the goSave function
  #This is used by load_pose and by bonebox_menu
  global partVar, sideVar
  
  screening = 0
  if partVar == "" and sideVar == "": return screening #This is redundant
  if partVar != "":
    if sideVar != "": #With symmetry selection
      partVar2 = partVar.split("@%#%@")
      for group in range(0,len(partVar2)): 
        if bone.name in groupsDict:
          if groupsDict[bone.name][:-1] == partVar2[group]:
            screening = 0
            if (sideVar == "right" or sideVar == "both") and (pref == 1 and bone.name[:lenR] == right) or\
               (suff == 1 and bone.name[-lenR:] == right):
              screening = 1
            if (sideVar == "left" or sideVar == "both") and (pref == 1 and bone.name[:lenL] == left) or\
               (suff == 1 and bone.name[-lenL:] == left):
              screening = 1
    partVar2 = partVar.split("@%#%@")
    for group in range(0,len(partVar2)):
      if bone.name in groupsDict:
        if groupsDict[bone.name][:-1] == partVar2[group]: screening = 1
  
  if switch == 0: return screening #Exclude
  if switch == 1: return 1 - screening #Include
  
#--------Loading options menu------------------------------------------------
def Load_Menu():
  global bodyscale, addscale, HipPose, hipR, hipT, LibVar, openBatch, LibTog, \
         TrimPose, LibPath, loadlist, poseList, askKey, torsoZero, poseLimit
  menu = PupMenu("Apply pose options: %t|Toggle menu browser and file browser %x1|Exclude root rotation %x2\
                  |Exclude root translation %x3|Exclude both root rotation and translation %x4\
                  |Don't zero un-mirrored parts with 'Symmetry' button %x5\
                  |Poser CR2 'BODY' handling %x6|Import animation as batch of pose files %x7\
                  |Configure keyframing %x8|Configure menu browsing %x9|Deactivate all options %x10\
                  |Cancel (Keep current options) %x11")
  if menu == 1: #toggle menu browser    
    LibVar = 1 - LibVar #1 = menu, 0 = file selector     
    if ListTabTog == 0:
      LibPath = "" #Only clear the path if the listbox browser is closed
      loadlist = "Pose list browser %t|Browse for a new folder."
      poseList = []    
  elif menu == 2: #exclude root rot
    HipPose = hipR = 1 - hipR   
    hipT = 0
  elif menu == 3: #exclude root trans
    HipPose = hipT = 1 - hipT   
    hipR = 0
  elif menu == 4: #exclude both rot and trans for root
    HipPose = hipR = hipT = 1
  elif menu == 5: torsoZero = 1 - torsoZero #Zero torso chain w/ mirror   
  elif menu == 6: #Poser CR2-derived armature special handling
    submenu = PupMenu("CR2 'BODY' options: %t|Use rotation %x1|Use additive rotation %x2| Cancel %x3")
    if submenu == 1:
      bodyscale = 1 - bodyscale
      addscale = 0
    elif submenu == 2:      
      bodyscale = addscale = 1 - addscale     
    elif submenu == 3:
      bodyscale = addscale = LibTog = 0      
  elif menu == 7: #Animation load
    openBatch = 1 - openBatch    
  elif menu == 8: #configure keyframing
    submenu = PupMenu("Keyframing options: %t|Ask every time %x1|Add keyframe without asking %x2\
                      |Don't add keyframes %x3|Remove all keyframes %x4|Cancel %x5")
    if submenu == 1: askKey = 0
    elif submenu == 2: askKey = 1
    elif submenu == 3: askKey = 2
    elif submenu == 4: remove_keys()
  elif menu == 9: #Configure menu browser columns
    poseLimit = PupIntInput("Max list items:", 54, 1, 100)
    
  elif menu == 10: #Deactivate    
    HipPose = hipR = hipT = bodyscale = addscale = openBatch\
              = LibTog = askKey = LibVar = 0    
  #if menu == 11 or menu == 0 or menu == -1 or menu == 11: #Cancel
  if LibVar == HipPose == hipR == hipT == bodyscale == addscale \
     == openBatch == askKey == torsoZero == 0:  
    LibTog = 0
  
#-----Load menu for partial group handling--------------------------------
def GroupLoad_Menu():
  global IncludePart, ExcludePart, TrimPose, bonesLoad, boxBones, GroupTog
  menu = PupMenu("Apply screening options: %t|Load only selected partial save group(s) %x1\
                  |Exclude partial save group(s) when loading %x2|Load only bone listbox selections %x3\
                  |Exclude bone listbox selections when loading %x4|Deactivate all groups %x5\
                  |Cancel (Keep current options) %x6")
  if menu == 1: #partial inclusive
    if partVar == sideVar == "":
      error = PupMenu("Please select a partial save group.")
      partSave()
      if partVar == sideVar == "": GroupTog = 0
    if partVar != "" or sideVar != "":
      IncludePart = 1
      ExcludePart = 0
      TrimPose = 1
  elif menu == 2: #partial exclusive
    if partVar == sideVar == "":
      error = PupMenu("Please select a partial save group.")
      partSave()
      if partVar == sideVar == "": GroupTog = 0
    if partVar != "" or sideVar != "":
      IncludePart = 0
      ExcludePart = 1
      TrimPose = 1
  elif menu == 3: #bones list inclusive
    if boxBones == []: error = PupMenu("No current bone listbox selection. %t|Please make a bone listbox selection.")          
    else:
      if bonesLoad == 1: bonesLoad = 0
      elif bonesLoad == 0 or bonesLoad == 2: bonesLoad = 1 #1 is inclusive
  elif menu == 4: #bones list exclusive
    if boxBones == []: error = PupMenu("No current bone listbox selection. %t|Please make a bone listbox selection.")
    else:
      if bonesLoad == 2: bonesLoad = 0
      elif bonesLoad == 0 or bonesLoad == 1: bonesLoad = 2 #2 is exclusive
      
  elif menu == 5: #Deactivate    
    IncludePart = ExcludePart = bonesLoad = GroupTog = 0    
  
  if IncludePart == ExcludePart == bonesLoad == 0: #Cancel 
    GroupTog = 0
##-----Zero pose fxn-------------------------------------------------------------	
#Set pose to zero values (rest position values)
def Zero_Pose():  
  if Object.GetSelected() != []:
    ob = Object.GetSelected()[0]  
    if ob and ob.getType() == "Armature":
      pose = ob.getPose()
      for bone in pose.bones.values():
        bone.loc = Vector(0.00,0.00,0.00)
        bone.quat = Quaternion(1.00,0.00,0.00,0.00)
        bone.size = Vector(1.00,1.00,1.00)
      if askKey != 2:                    
        if askKey == 0:
          c = PupMenu("Insert pose keys for this frame?%t|Yes%x1|No%x2")  
        if askKey == 1: c = 1
        if c == 1:                
          for bone in pose.bones.values():
            add_curves(ob, pose, bone.name, bone.quat, bone.loc, bone.size, Blender.Get('curframe'))	
      pose.update() 
      ob.makeDisplayList() 
    Blender.Redraw()
##-----Symmetry notation handling----------------------------------------------------------
#Change right and left values to alter default suffix of prefix
#Swap suff and pref values to change default from prefix to suffix
def Symmetry_Notation():
  global right, left
  right = PupStrInput("Enter right (-x):", right, 25)
  left = PupStrInput("Enter left (+x):", left, 25)
  if right == None: right = "r"
  if left == None: left = "l"
  right = right.replace(".", "") #Strip the dots (".") because len comparisons don't include them
  left = left.replace(".", "")
  lenR = len(right)
  lenL = len(left)
  
#-----Define the opposite side--------------------------------------
def flip():
  global right, left, suff, pref, flipbone
  if Object.GetSelected() != []:
    ob = Object.GetSelected()[0]  
    AData = ob.getData()
    if ob.getType() == "Armature":
      AData = ob.getData()
      flipnames = AData.bones.keys()  
      for name in flipnames:
        flipbone = "None"
        if suff == 1:            
          if name[-lenR:] == right: flipbone = name[:-lenR] + left
          elif name[-lenL:] == left: flipbone = name[:-lenL] + right
        if pref == 1:
          if name[:lenR] == right: flipbone = left + name[lenR:]
          elif name[:lenL] == left: flipbone = right + name[lenL:]
          elif name[:len(right_alt)] == right_alt: flipbone = left_alt + name[len(right_alt):] #custom extras
          elif name[:len(left_alt)] == left_alt: flipbone = right_alt + name[len(left_alt):]
            
        if flipbone in flipnames: #Don't enter the name if the bone doesn't exist...
          flipDict[name] = flipbone #(e.g. - "lowerfoot" might become "rowerfoot" and create a bad entry)   
    
#------Automatically locate partial save groups----------------
def groups(): #Fill groups for use with partial pose saves.
  global root, rootchild, maxVal, minVal, totalBones
  if flipDict == {}: flip()
  if Object.GetSelected() != []:
    ob = Object.GetSelected()[0]    
    if ob.getType() == "Armature":
      AData = ob.getData()    
      groupnames = AData.bones.values()  
      groupsDict.clear()
      for group1 in groupnames:
        if not group1.hasParent(): #We assume the root is the bone which has children, but no parent....
          if group1.hasChildren(): root = group1
          totalBones = len(group1.getAllChildren())        
          if len(root.children) == 1: rootchild = root.children[0]      
          break
      for group1 in groupnames: 
        if group1.hasChildren():      
          if len(group1.children) > 1:
            for i in group1.getAllChildren():            
              if len(group1.getAllChildren()) < totalBones/maxVal and len(group1.getAllChildren()) >= minVal:
                #Screening parameters are configurable, in the partSave function.
                if round(AData.bones[group1.name].head['ARMATURESPACE'][0],4) == 0.0000: add = "="
                elif round(AData.bones[group1.name].head['ARMATURESPACE'][0],4) != 0.0000: add = "!"
                #Determine whether the ultimate parent of group is centered on x.
                #(Note: using the flipDict for this proved inadequate....)               
                if pref == 1 and group1.name[:lenR] == right and group1.name in flipDict:
                    group_name = group1.name[lenR:] + "_parts" + add
                elif suff == 1 and group1.name[-lenR:] == right and group1.name in flipDict:
                    group_name = group1.name[:-lenR] + "_parts" + add
                elif pref == 1 and group1.name[:lenL] == left and group1.name in flipDict:
                    group_name = group1.name[lenL:] + "_parts" + add
                elif suff == 1 and group1.name[-lenL:] == left and group1.name in flipDict:              
                    group_name = group1.name[:-lenL] + "_parts" + add
                else: group_name = str(group1.name) + "_parts" + add
                group_name = group_name.lower()
                groupsDict[i.name] = group_name
                   
##----------Armature and Bone roll functions---------------------------------------------------
#makeEditable() reportedly has a bug.  Each implementation seems to require that the function be called
#in a different location, relative to a loop using the Armature.bones dict.  Placing it within the loop
#seems to cause a memory error in which Blender system variable names (or memory locations?) are substituted
#for dict keys or values.
#-------Menu for bone functions-----------------------------------------
def Bone_Menu():
 
  menu = PupMenu("Armature settings options: %t|Save armature settings to .txt %x1|Load armature settings from .txt %x2\
                  |Zero bone roll %x3|Restore bone roll (in-session only) %x4|Correct bone roll angles for all bones %x5\
                  |Correct bone roll for bones list bones %x6|Create armature from .txt or .cr2/.crz %x7|Cancel %x8")
  
  if menu == 1: Window.FileSelector(boneSave, 'Save as .txt file.')
  elif menu == 2: Window.FileSelector(boneLoad, 'Browse for .txt file.')      
  elif menu == 3: Bone_Roll_Z()
  elif menu == 4: Bone_Roll_R()
  elif menu == 5: boneroll_correct(0)
  elif menu == 6: boneroll_correct(1)
  elif menu == 7: Window.FileSelector(create_armature, 'Browse for .txt file.')
  
#--------Save armature settings to .txt file-----------------------------
def boneSave(save_name):
  #Saves roll, head, tail, parent, IK target bone, IK chainlen
  if save_name != "":    
    if Object.GetSelected() != []:
      ob = Object.GetSelected()[0]
      if ob.getType() == "Armature":
        temptext = ""
        AData = ob.getData()
        pose = ob.getPose()
        option = PupMenu("Bones to save? %t|Save all bones in armature %x1|Save only bones list selections %x2")
        if option == 1: templist = AData.bones.values()
        elif option == 2: templist = list(AData.bones[bone] for bone in boxBones)
        if AData:
          for bone1 in templist:
            head = bone1.head['ARMATURESPACE']
            tail = bone1.tail['ARMATURESPACE']
            parent = bone1.parent
            if parent == None: parent = "None"
            else:
              parent = parent.name
            IK = "None"
            chainlen = 0
            bone = pose.bones[bone1.name]
            for const in bone.constraints:             
             if const.type == Constraint.Type.IKSOLVER:
               IK = const[Constraint.Settings.BONE]
               chainlen = const[Constraint.Settings.CHAINLEN]
            temptext += "%s,%f,"%(bone1.name, bone1.roll['ARMATURESPACE'])
            temptext += "%f,%f,%f," %(head[0], head[1], head[2])
            temptext += "%f,%f,%f," %(tail[0], tail[1], tail[2])
            temptext += "%s,%s,%i\n" %(parent, IK, chainlen)        
          save_name = os.path.splitext(save_name)[0] + ".txt"
          if sys.exists(save_name) == 1:
            warning = PupMenu("Overwrite file %s? %%t|Yes %%x1|No %%x2" %(sys.basename(save_name))) #Don't overwrite        
          if sys.exists(save_name) != 1 or warning != 2:
            rollsave = open(save_name,"w")
            rollsave.write(temptext)
            rollsave.close()
        
#--------Load bone roll settings from .txt file---------------------------
def boneLoad(file_name):
  #Loads roll, head, tail, parent, IK target bone, IK chainlen; optionally adds missing bones 
  warning = PupMenu("Warning: This will alter your armature. %t|Load bone roll settings %x1\
                    |Load head and tail settings %x2|Load parenting %x3|Load IK %x4|Load IK with parenting %x5\
                    |Load all %x6|Cancel %x7")
  if warning != 7:
    if file_name != "":
      temptext = ""
      addlist = []
      if Object.GetSelected() != []:
        ob = Object.GetSelected()[0]
        if ob.getType() == "Armature":
          AData = ob.getData()
          pose = ob.getPose()
          if AData:            
            option = PupMenu("Bones to load: %t|Load all bones in armature %x1|Load only bones list selections %x2")
            if option == 1: templist = AData.bones.keys()
            elif option == 2: templist = boxBones
            AData.makeEditable()
            temptext = open(file_name,"r")
            lines = temptext.readlines()
            for line in lines:
              data = line.rstrip("\n").split(",")
              if data[0] not in AData.bones.keys(): #optionally add missing bones (all or none)
                addbone = PupMenu("Bone not found: %s %%t|Add missing bone(s) to armature %%x1\
                                  |Don't add missing bone(s) %%x2" %(data[0]))
                if addbone == 1: #Add all missing bones in a subloop, to avoid parenting problems
                  scale = PupFloatInput("Scaling:", 1.0, 0.001, 1000.0,10,2)
                  if scale == None: scale = 1.0 #pup inputs can return None                  
                  for line in lines:
                    data2 = line.rstrip("\n").split(",")
                    if data2[0] not in AData.bones.keys():                      
                      head = (float(data2[2])*scale, float(data2[3])*scale, float(data2[4])*scale)                      
                      head = Vector(head)
                      tail = (float(data2[5])*scale, float(data2[6])*scale, float(data2[7])*scale)
                      tail = Vector(tail)                      
                      create_bone(AData, data2[0], head, tail, float(data2[1]))
                      addlist.append(data2[0])#pass parenting info for new bones to parenting step
                      templist.append(data2[0])
                      AData.update()#these update the armature data in case two armatures are being combined
                      AData.makeEditable()
                      pose = ob.getPose()
              if data[0] in templist: ##set the requested values for bones
                bone1 = AData.bones[data[0]]
                if bone1.name == data[0]:            
                  if (warning == 1 or warning == 6) and data[0] not in addlist: #roll
                    bone1.roll = float(data[1])
                  if (warning == 2 or warning == 6) and data[0] not in addlist: #head and tail                  
                    bone1.head = Vector(data[2], data[3], data[4])
                    bone1.tail = Vector(data[5], data[6], data[7])
                  if warning == 3 or warning == 5 or warning == 6 or data[0] in addlist: #parenting                    
                    if data[8] in AData.bones.keys():
                      bone1.parent = AData.bones[data[8]]
                  if warning == 4 or warning == 5 or warning == 6: #IK                   
                    if data[9] in AData.bones.keys():                      
                      bone = pose.bones[bone1.name]
                      bone.constraints.append(Constraint.Type.IKSOLVER)
                      const = bone.constraints[len(bone.constraints)-1] #last entry is our appended IKSOLVER
                      const[Constraint.Settings.TARGET] = ob #target object is the selected armature
                      const[Constraint.Settings.BONE] = data[9] #active bones list selection is target bone
                      const[Constraint.Settings.CHAINLEN] = int(data[10])                  
              else: print "Bone not loaded:",data[0]
            temptext.close()
            AData.update()
            pose.update()
            ob.makeDisplayList()      
          Blender.Redraw()
          print "Done loading armature settings."
#---------Create a bone----------------------------------------------------
def create_bone(armature, name, head, tail, roll):
  bone = Armature.Editbone()      
  bone.roll = roll
  bone.head = head
  bone.tail = tail  
  armature.bones[name] = bone  
  print "Added bone %s to armature." %(name)
#---------Create an armature from saved .txt settings------------------------------------------
def create_armature(file_name):  
  if os.path.splitext(file_name)[1] == ".txt": switch = 1
  elif os.path.splitext(file_name)[1] == ".cr2" or os.path.splitext(file_name)[1] == ".crz": switch = 2
  scale = PupFloatInput("Scaling:", 1.0, 0.001, 1000.0,10,2)
  if scale == None: scale = 1.0 #pup inputs can return None 
  armname = PupStrInput("Armature name:", "ARM.NEWARM", 25) #get a name for the new armature
  if armname == None: armname = "ARM.NEWARM"
  objs = Blender.Scene.GetCurrent().getChildren()
  objs = list(obj.name for obj in objs)  
  if armname in objs: addname += "_" + str(len(objs)).rjust(4,"0")
  ob = Object.New('Armature')
  AData = Armature.Armature(armname)
  ob.link(AData)  
  AData.makeEditable()
  if switch == 1: #custom armature backup
    temptext = open(file_name,"r")
    lines = temptext.readlines()
  elif switch == 2: #Poser cr2 import    
    lines = parse_poser_file(file_name)       
  for line in lines: #add the bones    
    data = line.rstrip("\n").split(",")    
    bone = Armature.Editbone()      
    bone.roll = float(data[1])
    bone.head = Vector(float(data[2])*scale, float(data[3])*scale, float(data[4])*scale)
    bone.tail = Vector(float(data[5])*scale, float(data[6])*scale, float(data[7])*scale)     
    AData.bones[data[0]] = bone  
  for line in lines: #assign parenting after all bones are present
    data = line.rstrip("\n").split(",")
    if data[0] in AData.bones.keys():
      bone = AData.bones[data[0]]
      if data[8] in AData.bones.keys():
        bone.parent = AData.bones[data[8]]
  AData.update()
  scn= Blender.Scene.GetCurrent()               
  scn.link(ob)
  scn.update()
  pose = ob.getPose()
  try:
    for line in lines: #A third loop for IK (posebone-level) addition, after armature is updated and scene-linked
      data = line.rstrip("\n").split(",")
      if data[9] in pose.bones.keys():      
        bone1 = pose.bones[data[0]]
        bone1.constraints.append(Constraint.Type.IKSOLVER)
        const = bone1.constraints[len(bone1.constraints)-1] #last entry is our appended IKSOLVER
        const[Constraint.Settings.TARGET] = ob #target object is the new armature
        const[Constraint.Settings.BONE] = data[9] #target bone
        const[Constraint.Settings.CHAINLEN] = int(data[10])
        const.influence = 1.0
  except: pass #IK scripting not available before 242
  pose.update()
  ob.select(1)  
  if switch == 1: temptext.close()  
  ob.makeDisplayList() #to refresh the bones display
          
#---------Restore bone roll settings (in-session only) -----------------------------------------
def Bone_Roll_R(): #Restore boneroll from original values in oldRolls dict
  if Object.GetSelected() != []:
    ob = Object.GetSelected()[0]
    if ob.getType() == "Armature":
      AData = ob.getData()
      if AData:	
        AData.makeEditable()
        bones = AData.bones.values()                                      
        for bone1 in bones:
          if oldRolls.has_key(bone1.name):
            bRoll2 = oldRolls[bone1.name]      
            bone1.roll = bRoll2
        AData.update()
      
#--------------Zero bone roll settings----------------------------------------------------------
def Bone_Roll_Z(): #Zero bone roll angles; save to oldRolls dict w/ option to save .txt backup 
  if Object.GetSelected() != []:
    c = PupMenu("Save old bone roll settings to .txt file? %t|Yes %x1|No %x2|Cancel %x3")		
    if c == 1:
      Window.FileSelector(boneSave, 'File to save as?')
    if c != 3:
      ob = Object.GetSelected()[0]
      if ob.getType() == "Armature":
        AData = ob.getData()
        if AData:
          if oldRolls == {}:
            for bone1 in AData.bones.values(): #Don't replace backup values if they've already been set.       
              oldRolls[bone1.name] = int(bone1.roll['ARMATURESPACE'])
          AData.makeEditable()
          bones = AData.bones.values()    
          for bone1 in bones:      
            bone1.roll = 0.0      
        AData.update()

#-------"Correct" the roll angles to conform with Blender standards.------------------------------------
#Roll angles need to be zeroed before running this.
#This is probably a ridiculous kludge approach.  Some z-pointing bones aren't quite correct....
#points bone z-axis in "correct" direction.  Up for horizontal bones, forward for vertical.      
def boneroll_correct(switch): 
  if Object.GetSelected() != []: #if there is a current selection in 3d view
        ob = Object.GetSelected()[0]
        if ob and ob.getType() == "Armature": #if there is an armature selection
          c = PupMenu("Save old bone roll settings to .txt file? %t|Yes %x1|No %x2|Cancel %x3")		
          if c == 1:
            Window.FileSelector(boneSave, 'File to save as?')
          if c != 3:
            ob = Object.GetSelected()[0]
            AData = ob.getData()
            if AData:
              if oldRolls == {}:
                for bone1 in AData.bones.values(): #Don't replace backup values if they've already been set.       
                  oldRolls[bone1.name] = int(bone1.roll['ARMATURESPACE'])
                          
              pose = ob.getPose()
              rollDict = {}
              y_dir = Vector(0,1,0) #Directions x,y,z as vectors
              z_dir = Vector(0,0,1)
              x_dir = Vector(1,0,0)
              if switch == 0: templist = pose.bones.keys()
              elif switch == 1: templist = boxBones              
              #if AData.drawAxes == False: AData.drawAxes = True
              for bone in templist:                
                #y_dir2 = y_dir * pose.bones[bone].poseMatrix.rotationPart() # poseMatrix is worldspace                
                #z_dir2 = z_dir * pose.bones[bone].poseMatrix.rotationPart()
                #x_dir2 = x_dir * pose.bones[bone].poseMatrix.rotationPart()
                y_dir2 = y_dir * AData.bones[bone].matrix['ARMATURESPACE'].rotationPart()           
                z_dir2 = z_dir * AData.bones[bone].matrix['ARMATURESPACE'].rotationPart()
                #x_dir2 = x_dir * AData.bones[bone].matrix['ARMATURESPACE'].rotationPart()
                
                dx,dy,dz = y_dir2
                dirtup = (abs(dx),abs(dy),abs(dz))            
                dirtup = list(dirtup)
                dirmax = max(dirtup) #Find the dominant axis                                           
                axis = z_dir2 #The bone axis to be "pointed"
                
                #Define "direx" as the direction in which to point "axis"
                #Proof that this is a hack: I don't really know why the round(dirtup) stuff helps, but it seems to....
                if dirtup.index(dirmax) == 0: #x-pointing                
                  if round(dirtup[2],1) > 0.3: #if there is z-pointing angling
                    direx = x_dir                    
                  else:
                    direx = y_dir  #point up               
                elif dirtup.index(dirmax) == 1: #y-pointing 
                  if round(dirtup[2],1) > 0.3: #if there is z-pointing angling
                    print bone
                    if dy >= 0:
                      direx = y_dir-z_dir                      
                    elif dy < 0:
                      if dz >= 0: direx = y_dir+z_dir
                      else:
                        direx = y_dir-z_dir
                        direx = -direx
                  else:
                    direx = z_dir  #point forward                  
                elif dirtup.index(dirmax) == 2: #z-pointing                  
                  if round(dirtup[1],1) > 0.3: #if there is x-pointing angling                    
                    if dy >= 0:
                      direx = y_dir-z_dir                      
                    elif dy < 0:
                      direx = y_dir+z_dir    
                  else:
                    direx = y_dir #point up                   
                  
                roll_ang = AngleBetweenVecs(direx, axis) #roll value is angle between direx and axis                
                if dx > 0: roll_ang = -roll_ang #if bone points in +x direction, flip the roll value
                
                rollDict[bone] = roll_ang #can't query values after makeEditable, so pass between loops using dict
                
              AData.makeEditable()
              for bone in templist:
                roll_ang = rollDict[bone]
                AData.bones[bone].roll = roll_ang                
              AData.update()
              
##----------------Animation-----------------------------------------
#------------Keyframe handling--------------------------------------    
def anim_menu():
  menu = PupMenu("Frames options: %t|Select all keyframes %x1|De-select all %x2|Invert keyframe selection %x3\
                  |Delete all keyframes %x4|Delete selected keyframes for all bones %x5\
                  |Delete selected keyframes for bones list selections %x6\
                  |Delete selected keyframes for active bones list bone %x7|Change frame count %x8|Cancel %x9")
  
  if menu == 1: select_keys()
  elif menu == 2: deselect_keys()
  elif menu == 3: invert_keys()
  elif menu == 4: remove_keys()
  elif menu == 5: remove_selected(2)
  elif menu == 6: remove_selected(0)
  elif menu == 7: remove_selected(1)  
  elif menu == 8: change_frames()

def change_frames(): #This changes the length of the frames list display.  Python apparently can't set the endframe.
  global animlen
  animlen = PupIntInput("Frame count:", animlen, 1, animlen+1000)
  
def remove_keys(): #Delete all keyframes.  
  global boxFrames
  if Object.GetSelected() != []:
    ob = Object.GetSelected()[0]
    if ob and ob.getType() == "Armature":
      if ob.getAction():
        pose = ob.getPose()     
        act = ob.getAction() #get the current action
        all = act.getAllChannelIpos() #all channel curves for action
        if len(all) > 0: #if there are keyframes
          for bone in pose.bones.keys(): #loop through all bones
            if bone in all: #if bone is keyframed
              act.removeChannel(bone) #remove all channels for bone
          boxFrames = [] #clear the display list selections
        else: oops = PupMenu("No keyframes to delete!")      
      ob.makeDisplayList() #to refresh the bones display
           
 
def select_keys():#select current keyframes 
  global boxFrames  
  try:
    boxFrames = []
    if Object.GetSelected() != []:
      ob = Object.GetSelected()[0]
      if ob.getType() == "Armature":
        if ob.getAction():
          act = ob.getAction()
          c = act.getFrameNumbers() #Not available before 242        
          boxFrames = list(str(k) for k in c)
          if c == []: oops = PupMenu("No keyframes to select!")
  except: error = PupMenu("This feature is only available in versions later than 2.41")
  
def deselect_keys(): #deselect everything in anim listbox
  global boxFrames
  boxFrames = []

def invert_keys(): #invert keyframe selection in bones listbox
  global boxFrames  
  try:
    if Object.GetSelected() != []:
      ob = Object.GetSelected()[0]
      if ob.getType() == "Armature":
        if ob.getAction():
          act = ob.getAction()
          a = set(int(i) for i in boxFrames)
          b = set(act.getFrameNumbers())
          c = list(b-a)
          boxFrames = list(str(k) for k in c)
  except: error = PupMenu("This feature is only available in versions later than 2.41")
  
def remove_selected(switch):#Delete selected keyframes
  global boxFrames  
  try:
    if Object.GetSelected() != []: #if there is a current selection in 3d view
      ob = Object.GetSelected()[0]
      if ob.getType() == "Armature" and ob.getAction(): #if there is an armature selection
        startTime = sys.time()#keep track of speed
        templist = []
        if switch == 0 and boxBones != []: templist = boxBones #use only bones list selections
        if switch == 1 and lastBone != "": #use only active bones list selection
          templist = []
          templist.append(lastBone)
        if switch == 2: #use all bones in armature
          pose = ob.getPose()
          templist = pose.bones.keys()
                 
        act = ob.getAction() #get the action
        if len(act.getAllChannelIpos()) > 0: #if there are keyframes          
          
          a = set(templist)
          b = set(act.getAllChannelIpos())
          templist = list(a & b) #the list of bones which have Ipo curves
          a = set(int(i) for i in boxFrames)
          b = set(act.getFrameNumbers())
          c = sorted(list(b - a)) #the list of keyframes which aren't selected
          d = list(a & b) #the list of keyframes which are selected (for tracking)
          
          if templist != []:
            if c == []: #if all keyframed frames are selected
              for bone in templist:
                act.removeChannel(bone)#delete all channels for selected bones
            
            if c!= []: #not all keyframed frames selected         
              framesDict = {}          
              for indx, frame in enumerate(c):                
                data = ""
                Blender.Set('curframe', frame)              
                for bone in templist: #for selected bones
                  bone = pose.bones[bone] #collect the animation data
                  lx,ly,lz = bone.loc
                  qw,qx,qy,qz = bone.quat
                  sx,sy,sz = bone.size                
                  data += "%s,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f\n" %(bone.name,lx,ly,lz,qw,qx,qy,qz,sx,sy,sz)                
                  if indx == len(c) - 1: #if this is the last frame in the sequence
                    act.removeChannel(bone.name) #remove all the bone's ipo channels
                framesDict[frame] = data #add the data to the dict
              Blender.Set('curframe', c[0]) #restore first frame not to be deleted
                
              for frame, data in framesDict.iteritems(): #use the dict to add frames back into animation              
                lines = data.split("\n")
                for line1 in lines:
                  line = line1.split(",")                
                  if line != ['']: #don't try to run last line, which is blank
                    bone = pose.bones[line[0]]
                    bone.loc = Vector(line[1], line[2], line[3]) #rebuild the data
                    bone.quat = Quaternion(line[4], line[5], line[6], line[7])
                    bone.size = Vector(line[8], line[9], line[10])                  
                    add_curves(ob, pose, bone.name, bone.quat, bone.loc, bone.size, frame) #restore the undeleted frames                

              framesDict.clear()
              
            a = set(boxFrames)
            b = set(str(k) for k in act.getFrameNumbers())
            boxFrames = list(a & b) #refresh boxFrames as the set of keyframes which weren't deleted
            
            ob.makeDisplayList() #to refresh the bones display
            finTime = sys.time() - startTime
            print "Done deleting %i keyframes from %i bones in %f seconds" %(len(d), len(templist), finTime)
          
        else: oops = PupMenu("No keyframes to delete!")  
  except: error = PupMenu("This feature is only available in versions later than 2.41")

def add_curves(ob, pose, bone, quat, loc, size, time):
  #This is kind of a compressed and simplified version of Cambo's Ipo keyframing from bvh_import.py for 242.
  #This is used by open_batch and remove_selected...
  #Also by load_pose, Zero_Pose, numerical_entry, and load_poser_pose for keyframe insertion  
  if not ob.getAction():
    act = Armature.NLA.NewAction("Action")
    act.setActive(ob)
  else: act = ob.getAction()
  
  act_ipo = act.getAllChannelIpos()  
  
  if bone in act_ipo:    
    ipo = act_ipo[bone] 
    
    ipo[Ipo.PO_LOCX].append((time, loc[0]))    
    ipo[Ipo.PO_LOCY].append((time, loc[1]))    
    ipo[Ipo.PO_LOCZ].append((time, loc[2]))    
    ipo[Ipo.PO_SCALEX].append((time, size[0]))    
    ipo[Ipo.PO_SCALEY].append((time, size[1]))    
    ipo[Ipo.PO_SCALEZ].append((time, size[2]))    
    ipo[Ipo.PO_QUATW].append((time, quat[0]))   
    ipo[Ipo.PO_QUATX].append((time, quat[1]))   
    ipo[Ipo.PO_QUATY].append((time, quat[2]))    
    ipo[Ipo.PO_QUATZ].append((time, quat[3]))
  
  elif not bone in act_ipo: # a keyframe needs to be added to create the bone ipo if it doesn't already exist
    
    bone1 = pose.bones[bone]    
    bone1.insertKey(ob,time,Object.Pose.LOC)
    bone1.insertKey(ob,time,Object.Pose.ROT)
    bone1.insertKey(ob,time,Object.Pose.SIZE)    
  
#-----------Animation load and save (batch handling)------------------------------------------------
def frames_sort(): 
  global boxFrames
  if batchFrames == 1: #sort boxFrames numerically     
    templist = sorted(int(i) for i in boxFrames)
    boxFrames = list(str(i) for i in templist)
    
#-------------------------------------------------------------------------
def open_batch(filename): #load a batch of poses, option to specify destination keys using frames listbox
#This cannot change the endframe of an animation, but it will increase the range of the frames listbox
  global askKey, batchFrames, animlen
  
  if len(boxFrames) < 1: batchFrames = 0
  startTime = sys.time()
  curframe = Blender.Get('curframe')
  
  if batchLoad == 0: #default if not using pose listbox - load numbered batch, as saved 
    frames = PupIntInput("animation length:", 1, Blender.Get('staframe'), Blender.Get('endframe'))
    
    if frames != None:
      if batchFrames == 0: frame = curframe#Blender.Get('curframe') #load starts from current frame
      elif batchFrames == 1: #frames listbox screening
        frames_sort() #sort the selected list frames numerically
        frame = int(boxFrames[0])
        Blender.Set('curframe', frame)
      path = sys.dirname(filename)
      filename = sys.basename(filename)
      file_ext = os.path.splitext(filename)[1]
      filebase = os.path.splitext(filename)[0][:-4]  
      temp = askKey
      askKey = 2
      
      for filenum in range(1, frames+1):        
        fileload = sys.join(path,filebase + str(filenum).rjust(4,"0") + file_ext)
        if sys.exists(fileload) == 1:
          load_pose(fileload)      
          if batchFrames == 0:
            Blender.Set('curframe', frame + filenum)
            curframe = frame + filenum
          elif batchFrames == 1: #frames listbox screening
            if filenum < len(boxFrames):
              Blender.Set('curframe', int(boxFrames[filenum]))
              curframe = int(boxFrames[filenum])
            else:
              Blender.Set('curframe', int(boxFrames[len(boxFrames) - 1]) + (filenum - len(boxFrames))+1)
              curframe = int(boxFrames[len(boxFrames) - 1]) + (filenum - len(boxFrames))+1          
          if curframe > animlen: animlen += curframe - animlen
          
      askKey = temp #Reset keyframe screening
      Blender.Set('curframe', frame)#Go back to start frame
  #--------------------------------------------------------------------------------------
  elif batchLoad == 1: #loading via pose listbox - load custom listbox selections
    if len(boxPoses) > 0:
      frames = len(boxPoses)     
      if batchFrames == 0: frame = curframe#Blender.Get('curframe') #load starts from current frame
      elif batchFrames == 1: #frames listbox screening
        frames_sort() #sort the selected list frames numerically
        frame = int(boxFrames[0])
        Blender.Set('curframe', frame)
      temp = askKey
      askKey = 2
      
      for filenum in range(1, frames+1):
        fileload = sys.join(LibPath, boxPoses[filenum - 1])
        if sys.exists(fileload) == 1:
          load_pose(fileload)          
          if batchFrames == 0:
            Blender.Set('curframe', frame + filenum)
            curframe = frame + filenum
          elif batchFrames == 1: #frames listbox screening
            if filenum < len(boxFrames):
              Blender.Set('curframe', int(boxFrames[filenum]))
              curframe = int(boxFrames[filenum])
            else:
              Blender.Set('curframe', int(boxFrames[len(boxFrames) - 1]) + (filenum - len(boxFrames))+1)
              curframe = int(boxFrames[len(boxFrames) - 1]) + (filenum - len(boxFrames))+1         
          if curframe > animlen: animlen += curframe - animlen
      askKey = temp #Reset keyframe screening
      Blender.Set('curframe', frame)#Go back to start frame
      
  finTime = sys.time() - startTime
  if frames != None: print "Done loading %i poses in %f seconds" %(frames, finTime) #PupIntInput can return None.
#--------------------------------------------------------------------------------------------  
def save_batch(filename): #Save animation as a batch of numbered poses, optional selection via frames list
  if PoserSave == 0:    
    if batchFrames == 0:
      frames = PupIntInput("animation length:", 1, Blender.Get('staframe'), Blender.Get('endframe'))
      frame = Blender.Get('curframe') #default save starts from current frame
    elif batchFrames == 1:
      if len(boxFrames) > 0:
        frames_sort()
        frames = len(boxFrames)
        frame = int(boxFrames[0])
        Blender.Set('curframe', frame)
  path = sys.dirname(filename)
  filename = sys.basename(filename)  
  filebase = os.path.splitext(filename)[0]
  if PoserSave == 0: #Poser pose export is all written to one file; don't run batch
    for filenum in range(1, frames+1):
      filesave = sys.join(path,filebase + "_" + str(filenum).rjust(4,"0")) ##file extension will be appended by goSave    
      goSave(filesave)
      #print filesave, filenum, boxFrames[filenum-1]
      if batchFrames == 0:
        Blender.Set('curframe', frame+filenum)        
      elif batchFrames == 1:      
        if filenum < len(boxFrames):
          Blender.Set('curframe', int(boxFrames[filenum]))          
    Blender.Set('curframe', frame)#Go back to start frame
  elif PoserSave == 1:
    filesave = sys.join(path,filebase)
    goSave(filesave)
##--------------IK handling--------------------------------------------
def IK_menu():
  menu = PupMenu("IK options: %t|Select all bones with IK %x1|De-select all %x2|Set IK influence for all IK chains %x3\
                  |Set IK influence for list selections %x4|Set IK influence for active selection %x5|Cancel %x6")
  
  if menu == 1: select_IK()
  elif menu == 2: deselect_IK()
  elif menu == 3: IK_toggle(2)
  elif menu == 4: IK_toggle(0)
  elif menu == 5: IK_toggle(1)
  
    
def IK_toggle(switch):  
  try:
    if Object.GetSelected() != []: #if there is a current selection in 3d view
      ob = Object.GetSelected()[0]
      if ob and ob.getType() == "Armature": #if there is an armature selection
        pose = ob.getPose()
        togval = PupFloatInput("value:", 1.0, 0.0, 1.0,10,2) #get the setting
        if togval == None: togval = 1.0
        templist = []
        if switch == 0 and boxBones != []: templist = boxBones #use only bones list selections
        if switch == 1 and lastBone != "": #use only active bones list selection
          templist = []
          templist.append(lastBone)
        if switch == 2: #use all bones in armature        
          templist = pose.bones.keys()
        for bonename in templist:
          bone = pose.bones[bonename]  
          for const in bone.constraints:             
           if const.type == Constraint.Type.IKSOLVER:
             const.influence = togval      
        ob.makeDisplayList() #to refresh the bones display  
  except: error = PupMenu("This feature is only available in versions later than 2.41")
      
def deselect_IK(): #deselect everything in anim listbox
  global boxBones
  boxBones = []

def select_IK():
  global boxBones, lastBone  
  try:
    if Object.GetSelected() != []: #if there is a current selection in 3d view
      ob = Object.GetSelected()[0]
      if ob and ob.getType() == "Armature": #if there is an armature selection
        pose = ob.getPose()
        for bone in pose.bones.values():
          for const in bone.constraints:
            if const.type == Constraint.Type.IKSOLVER:
              boxBones.append(bone.name)              
        if len(boxBones) > 0: #Set active selection as first displayed item in list
          boxBones.sort()
          boxBones.reverse()
          lastBone = boxBones[len(boxBones)-1] 
  except: error = PupMenu("This feature is only available in versions later than 2.41")

#-------Bones list toolset for IK custom construction-------------------------
#some tools to assist in building or removing IK solver constraints

def tools_menu():
  menu = PupMenu("Bones toolset options: %t|De-select all %x1|Re-parent selected bones %x2\
                  |Change IK target bone for selections %x3|Add IK to selection(s) %x4\
                  |Remove IK from all selections %x5|Add a bone to current armature %x6\
                  |Add IK goal bone to armature %x7|Add IK bend target bone to armature %x8\
                  |Delete all selected bones %x9| %x18|Scale selected bones %x10\
                  |Translate selected bones %x11|Swap head and tail of selected bones %x12\
                  |Flip selected bones along an axis %x13|Connect selected bones with active selection %x14\
                  |Connect selected bones with their parents %x15|Straighten selected bones %x16\
                  |Straighten and align chains %x17|Cancel %x18")  
  
  if menu == 1: deselect_IK()
  elif menu == 2: reparent()
  elif menu == 3: retarget()
  elif menu == 4: add_IK()
  elif menu == 5: remove_IK()
  elif menu == 6: add_bone(0)
  elif menu == 7: add_bone(1)
  elif menu == 8: add_bone(2)
  elif menu == 9: delete_bones()
  elif menu == 10: scale_bones()
  elif menu == 11: move_bones()
  elif menu == 12: swap_bones()
  elif menu == 13: flip_bone_axis()
  elif menu == 14: connect_bones(0)
  elif menu == 15: connect_bones(1)
  elif menu == 16: align_bones()
  elif menu == 17: align_chains()
            
def reparent(): #make active (last) bones list selection new parent of all previous selections
  global root, boxBones, lastBone, boxList
  if Object.GetSelected() != []: #if there is a current selection in 3d view
    ob = Object.GetSelected()[0]
    if ob and ob.getType() == "Armature": #if there is an armature selection
      if lastBone != "":
        if len(boxBones) > 1:
          AData = ob.getData()
          
          templist = boxBones #keep bone from being parented to one of its offspring
          boxBones = []              
          for bone in templist:
            if bone != lastBone and AData.bones[bone].hasChildren():                  
              offspring = list(child.name for child in AData.bones[bone].getAllChildren())
              if not lastBone in offspring: #check the family tree                  
                  print "Reparented %s to %s." %(bone, lastBone)
                  boxBones.append(bone)
              else: print "Cannot reparent %s to %s: %s is a descendant of %s." %(bone, lastBone, lastBone, bone)
            else: boxBones.append(bone)#bones without children and active bone are restored to list
          
          AData.makeEditable()
          boneparent = AData.bones[lastBone]
          for selected in range(0,len(boxBones)-1):                
            bone = AData.bones[boxBones[selected]]                
            bone.parent = boneparent
          AData.update()
          ob.makeDisplayList() #to refresh the bones display
          if boneSort == 1: boxList = sort_hierarchy(boxList) #re-sort for hierarchical display
           
def retarget(): #change target bone to active list bone for all inactive selections  
  try:
    if Object.GetSelected() != []: #if there is a current selection in 3d view
      ob = Object.GetSelected()[0]
      if ob and ob.getType() == "Armature": #if there is an armature selection
        if lastBone != "":
          if len(boxBones) > 1:
            pose = ob.getPose()
            for selected in range(0,len(boxBones)-1):
              bone = pose.bones[boxBones[selected]]
              for const in bone.constraints:
                if const.type == Constraint.Type.IKSOLVER:
                  const[Constraint.Settings.BONE] = lastBone ##set the target bone                     
            pose.update()
            ob.makeDisplayList() #to refresh the bones display  
  except: error = PupMenu("This feature is only available in versions later than 2.41")

#This can crash Blender if no pose bone has been selected
def remove_IK(): #remove IK solver constraints from bones list selections
  global boxBones  
  try:
    if Object.GetSelected() != [] and boxBones != []:
      ob = Object.GetSelected()[0]
      pose = ob.getPose()
      if ob.getType() == "Armature":
        try: pose.bones[boxBones[0]].sel = True #try to select a bone (only available in 242 CVS)
        except: print "Could not auto-select a pose bone.  Method not available in this build of Blender."        
        AData = ob.getData() #Verify that a bone has been selected
        selected = 0            
        for selbone in AData.bones.values():                                          
          if Armature.BONE_SELECTED in selbone.options:                               
            selected = 1                              
            break          
        if selected == 1: #crash-prevention: only proceed if there is a user-selected bone                      
          for bone in boxBones:              
            bone = pose.bones[bone]
            for const in bone.constraints:                
              if const.type == Constraint.Type.IKSOLVER:
                bone.constraints.remove(const)                    
          boxBones = [] #entries have been deleted, so clear the list.
          pose.update()
          ob.makeDisplayList() #to refresh the bones display          
        else: error = PupMenu("A bone must be selected in the 3D view before IK can be removed.") 
  except: error = PupMenu("This feature is only available in versions later than 2.41")

def add_IK(): #add IK solver constraints to bones list selections  
  try: #This has gotten kind of messy... needs some reorganization....
    if Object.GetSelected() != []: #if there is a current selection in 3d view
      ob = Object.GetSelected()[0]
      if ob and ob.getType() == "Armature": #if there is an armature selection                    
        if len(boxBones) > 0:
          pose = ob.getPose()
          AData = ob.getData()
          chaintype = 1
          chain_len = 1
          if len(boxBones) > 1: #Chain will be added in order selected w/ chaintype = 2 
            chaintype = PupMenu("Options: %t|Add IK to last inactive list bone only %x1\
                                |Add IK as chain to all inactive selections %x2|Cancel %x3")
            if chaintype == 1:
              chain_len = PupIntInput("Chainlen:", 1, 1, len(pose.bones))
              option = 2
          if chaintype == 1 or chaintype == 2:
            if chaintype == 2:
              inkyname = PupStrInput("Name for new bone:", "inkychain", 25) #get a name
              if inkyname == None: inkyname = "inkychain"
              if inkyname in AData.bones.keys(): inkyname += "_" + str(len(AData.bones.keys())).rjust(4,"0")
              option = PupMenu("Include active selection in chain? %t|Yes %x1|No %x2")
              root = sort_hierarchy(pose.bones.keys())[0]
              rootlen = AData.bones[root].length              
              AData.makeEditable()
            
            for i in range(len(boxBones)-1,-1,-1):
              bone = boxBones[i]             
              bone1 = pose.bones[bone]
              const = None
              if (option == 2 and boxBones.index(bone) < len(boxBones)-1) or\
                 (option == 1 and boxBones.index(bone) < len(boxBones)):
                bone1.constraints.append(Constraint.Type.IKSOLVER)
                const = bone1.constraints[len(bone1.constraints)-1] #last entry is our appended IKSOLVER
                const[Constraint.Settings.TARGET] = ob #target object is the selected armature
                const[Constraint.Settings.CHAINLEN] = i+1#boxBones.index(bone) + 1 #add chainlen value
                const.influence = 1.0
              if chaintype == 1: #single bone
                if const != None:
                  const[Constraint.Settings.BONE] = lastBone #active bones list selection is target bone
                  const[Constraint.Settings.CHAINLEN] = chain_len #user-specified chainlen value                
              elif chaintype == 2:                
                bone1 = AData.bones[bone]
                h = ob.getData().bones[bone].head['ARMATURESPACE']
                t = ob.getData().bones[bone].tail['ARMATURESPACE']                
                dirextup = (abs(t[0]-h[0]),abs(t[1]-h[1]),abs(t[2]-h[2]))
                direx = max(dirextup)#find direction in which bone y axis points
                
                if boxBones.index(bone) == len(boxBones)-1: #create goal bone and parent Poser goal actor to it
                  if option == 2:
                    _t = ob.getData().bones[boxBones[len(boxBones)-2]].tail['ARMATURESPACE']
                    _h = ob.getData().bones[boxBones[len(boxBones)-2]].head['ARMATURESPACE']
                  else:
                    _t = t
                    _h = h
                  dirextup2 = (abs(_t[0]-_h[0]),abs(_t[1]-_h[1]),abs(_t[2]-_h[2]))
                  direx2 = max(dirextup2)#find direction in which bone y axis points                  
                  if direx2 == dirextup2[0]: length = max(abs(abs(_t[0])-abs(_h[0]))/2,rootlen/3)                        
                  elif direx2 == dirextup2[1]: length = max(abs(abs(_t[1])-abs(_h[1]))/2,rootlen/3)
                  elif direx2 == dirextup2[2]: length = max(abs(abs(_t[2])-abs(_h[2]))/2,rootlen/3)                  
                  _taillen = _t[1]-length
                  if _t[2] < -0.003: _taillen = -(_taillen/option)#needs work
                  _tail = Vector(_t[0],_taillen,_t[2])
                  if direx == dirextup[1]: _tail = Vector(_t[0],_t[1],_taillen)
                  create_bone(AData, inkyname, Vector(_t[0],_t[1],_t[2]), _tail, 0.0)                  
                  AData.bones[inkyname].parent = AData.bones[root]
                  if option == 2: bone1.parent = AData.bones[inkyname]
                  
                if (option == 2 and boxBones.index(bone) == len(boxBones)-2) or\
                   (option == 1 and boxBones.index(bone) == len(boxBones)-1): #point last link in Blender chain at goal bone
                  const[Constraint.Settings.BONE] = inkyname                                        
                  
                  
                if (option == 2 and boxBones.index(bone) < len(boxBones)-2) or\
                   (option == 1 and boxBones.index(bone) < len(boxBones)-1): #create bend target bones                 
                  if direx == dirextup[0]:#bone points on x
                    bend = max(abs(abs(t[2])-(abs(t[0])-abs(h[0]))/2),rootlen/3)
                    bend = -bend                      
                    bend2 = t[1]
                  elif direx == dirextup[1]:#bone points on y                    
                    if t[2] < -0.003: bend = -max(abs(abs(t[2])-(abs(t[1])-abs(h[1]))/2),rootlen/3)
                    else: bend = max(abs(abs(t[2])+(abs(t[1])-abs(h[1]))/2),rootlen/3)                    
                    bend2 = t[1]
                  elif direx == dirextup[2]:#bone points on z
                    bend2 = max(abs(abs(t[1])+(abs(t[2])-abs(h[2]))/2),rootlen/3)
                    bend = t[2]                                                         
                  create_bone(AData, bone+"_bend", Vector(t[0],bend2,bend), Vector(t[0],t[1],bend*2), 0.0)                  
                  AData.bones[bone+"_bend"].parent = AData.bones[root]
                  const[Constraint.Settings.BONE] = bone + "_bend"              
                
          if chaintype == 2: AData.update()
          pose.update()
          ob.makeDisplayList() #to refresh the bones display  
  except: error = PupMenu("This feature is only available in versions later than 2.41")

def add_bone(switch):
  global root
  if Object.GetSelected() != []: #if there is a current selection in 3d view
    ob = Object.GetSelected()[0]
    if ob and ob.getType() == "Armature": #if there is an armature selection
      if lastBone != "":
        if switch == 2: #are more options needed here?  Up-down?  Right-left?
          side = PupMenu("Place bone in front of or behind active list bone? %t\
                          |Front (joint bends forward) %x1|Behind (joint bends backward) %x2")              
          offset = PupFloatInput("Offset:", 0.0, 0.0, 100.0,10,2)
          if side == -1: side = 1
          if offset == None: offset = 0.0
        AData = ob.getData()
        pose = ob.getPose()
        addname = PupStrInput("Name for new bone:", "new_bone", 25) #get a name for the new bone
        if addname == None: addname = "new_bone"
        if addname in AData.bones.keys(): addname += "_" + str(len(AData.bones.keys())).rjust(4,"0")                        
        for bone in AData.bones.values(): #find the root bone
          if not bone.hasParent():
            if bone.hasChildren(): root = bone
            break
        #most of the following specifies positioning relative to active bones list selection  
        y_dir = Vector(0,1,0) #Cambo method to find direction in which bone axis points
        y_dir = y_dir * pose.bones[lastBone].poseMatrix.rotationPart() # poseMatrix is worldspace
        #y_dir = y_dir * AData.bones[bone].matrix['ARMATURESPACE'].rotationPart()                    
        dx,dy,dz = y_dir
        dirtup = (abs(dx),abs(dy),abs(dz))            
        dirtup = list(dirtup)
        dirmax = max(dirtup) #Find the dominant axis           
        bone1 = AData.bones[lastBone]
        tail_x = bone1.tail['ARMATURESPACE'][0]
        tail_y = bone1.tail['ARMATURESPACE'][1]
        tail_z = bone1.tail['ARMATURESPACE'][2]
        _len = max(bone1.length/2,root.length/5) #This is arbitrary - set length relative to selection or root      
        if switch < 2:  _head = Vector(tail_x,tail_y,tail_z)        
        elif switch == 2:
          if side == 2: offset = -offset
          if dirtup.index(dirmax) < 2: tail_z += root.length * offset
          elif dirtup.index(dirmax) == 2: tail_y += root.length * offset
          if side == 1:
            if dirtup.index(dirmax) < 2: _head = Vector(tail_x,tail_y,tail_z + (_len/2))
            elif dirtup.index(dirmax) == 2:  _head = Vector(tail_x,tail_y + (_len/2),tail_z)
            _len = -_len
          elif side == 2:
            if dirtup.index(dirmax) < 2: _head = Vector(tail_x,tail_y,tail_z - (_len/2))
            elif dirtup.index(dirmax) == 2:  _head = Vector(tail_x,tail_y - (_len/2),tail_z)            
        if dirtup.index(dirmax) == 0: _tail = Vector(tail_x,tail_y,tail_z-_len)
        elif dirtup.index(dirmax) == 1: _tail = Vector(tail_x,tail_y,tail_z-_len)
        elif dirtup.index(dirmax) == 2: _tail = Vector(tail_x,tail_y-_len,tail_z)
        #now add the bone (adapted from epydocs example)
        AData.makeEditable()
        bone = Armature.Editbone()
        if switch == 0: bone.parent = AData.bones[lastBone] #add mere bone 
        elif switch > 0: bone.parent = AData.bones[root.name] #add IK handling bones
        bone.roll = 0.0
        bone.head = _head
        bone.tail = _tail
        AData.bones[addname] = bone
        AData.update()
        ob.makeDisplayList() #to refresh the bones display            

def delete_bones(): #delete selected bones and re-parent their children
  global boxBones, lastBone
  if Object.GetSelected() != []: #if there is a current selection in 3d view
    ob = Object.GetSelected()[0]
    if ob and ob.getType() == "Armature": #if there is an armature selection
      if len(boxBones) > 0:
        warning = PupMenu("This will delete all selected bones.  Proceed? %t|Yes %x1|No %x2")
        if warning == 1:
          remove_selected(0) #Clear any Ipo curves associated with the bones before deleting 
          AData2 = ob.getData()
          parents = {} #stores a list of all parents of bone 
          children = {} #stores a list containing immediate children of bone
          for bone in boxBones: #gather parent and child data before calling makeEditable
            if AData2.bones[bone].hasParent():
              parentlist = []
              bone1 = bone
              while AData2.bones[bone1].parent != None: #Fill the list with the parenting hierarchy
                parentlist.append(AData2.bones[bone1].parent.name)
                bone1 = AData2.bones[bone1].parent.name
              parents[bone] = parentlist
            else: parents[bone] = None            
            if AData2.bones[bone].hasChildren():
              childlist = list(child.name for child in AData2.bones[bone].children)
              children[bone] = childlist
            else: children[bone] = "None"
          AData = AData2 #It's silly, but sometimes makeEditable seems to need this....
          AData.makeEditable()              
          for bone in boxBones:
            if children[bone] != "None":
              for child in children[bone]:                    
                if not child in boxBones: #Don't try to reparent what will be deleted
                  if parents[bone] != None:
                    for bone1 in parents[bone]: #Move up the hierarchical chain, find first ancestor not to be deleted                   
                      if not bone1 in boxBones:                                           
                        AData.bones[child].parent = AData.bones[bone1]
                        break
                  else: AData.bones[child].clearParent()#Set parent to None if root has been deleted
            del AData.bones[bone]
          AData.update()
          ob.makeDisplayList() #to refresh the bones display
          lastBone = "" #clear listbox info (can cause problems with number tab)
          boxBones = []

def scale_bones(): #Re-scale selected bones by specified value
  if Object.GetSelected() != []: #if there is a current selection in 3d view
    ob = Object.GetSelected()[0]
    if ob.getType() == "Armature": #if there is an armature selection
      if boxBones != []:
        scale = PupFloatInput("Scaling:", 1.0, 0.001, 1000.0,10,2)
        if scale == None: scale = 1.0 #pup inputs can return None
        AData = ob.getData()
        AData.makeEditable()
        for bone in boxBones:
          bone = AData.bones[bone]        
          _head = bone.head
          _tail = bone.tail        
          bone.head = Vector(_head[0]*scale, _head[1]*scale, _head[2]*scale)
          bone.tail = Vector(_tail[0]*scale, _tail[1]*scale, _tail[2]*scale)
        AData.update()
        ob.makeDisplayList() #to refresh the bones display
      else: error = PupMenu("No bones list selection.")

def move_bones(): #translates bones list selections as a group
  if Object.GetSelected() != []: #if there is a current selection in 3d view
    ob = Object.GetSelected()[0]
    if ob.getType() == "Armature": #if there is an armature selection
      if boxBones != []:
        xval = Create(0.0)
        yval = Create(0.0)
        zval = Create(0.0)
        block = []
        block.append(("X translation:",xval,-100.000,100.000,"Warning: this will alter your armature!"))
        block.append(("Y translation:",yval,-100.000,100.000,"Warning: this will alter your armature!"))
        block.append(("Z translation:",zval,-100.000,100.000,"Warning: this will alter your armature!"))
        moveVal = PupBlock("Translate selected bones", block)      
        if moveVal == 1:
          AData = ob.getData()
          AData.makeEditable()
          for bone in boxBones:
            bone = AData.bones[bone]        
            _head = bone.head #editbones are armaturespace
            _tail = bone.tail        
            bone.head = Vector(_head[0]+xval.val, _head[1]+yval.val, _head[2]+zval.val)
            bone.tail = Vector(_tail[0]+xval.val, _tail[1]+yval.val, _tail[2]+zval.val)
          AData.update()
          ob.makeDisplayList() #to refresh the bones display
      else: error = PupMenu("No bones list selection.")

def swap_bones(): #flips bones list selections over themselves (swaps head and tail)
  if Object.GetSelected() != []: #if there is a current selection in 3d view
    ob = Object.GetSelected()[0]
    if ob.getType() == "Armature": #if there is an armature selection
      if boxBones != []:
        AData = ob.getData()
        AData.makeEditable()
        for bone in boxBones:
          bone = AData.bones[bone]        
          hx = bone.head[0]#using same assignment method as move_bones leads to zero-sized deletion errors
          hy = bone.head[1]
          hz = bone.head[2]
          tx = bone.tail[0]
          ty = bone.tail[1]
          tz = bone.tail[2]         
          bone.head = Vector(tx,ty,tz)
          bone.tail = Vector(hx,hy,hz)          
        AData.update()
        ob.makeDisplayList() #to refresh the bones display
      else: error = PupMenu("No bones list selection.")

def flip_bone_axis(): #flips bones list selections over selected axes, as a group
  if Object.GetSelected() != []: #if there is a current selection in 3d view
    ob = Object.GetSelected()[0]
    if ob.getType() == "Armature": #if there is an armature selection
      if boxBones != []:
        xval = Create(0)
        yval = Create(0)
        zval = Create(0)
        block = []
        block.append(("Flip X axis",xval,"Warning: this will alter your armature!"))
        block.append(("Flip Y axis",yval,"Warning: this will alter your armature!"))
        block.append(("Flip Z axis",zval,"Warning: this will alter your armature!"))
        moveVal = PupBlock("Axes to flip:", block)      
        if moveVal == 1:
          AData = ob.getData()
          AData.makeEditable()
          for bone in boxBones:
            bone = AData.bones[bone]        
            hx = bone.head[0]
            hy = bone.head[1]
            hz = bone.head[2]
            tx = bone.tail[0]
            ty = bone.tail[1]
            tz = bone.tail[2]
            if xval.val == 1:
              hx = -hx
              tx = -tx
            if yval.val == 1:
              hy = -hy
              ty = -ty
            if zval.val == 1:
              hz = -hz
              tz = -tz            
            bone.head = Vector(hx,hy,hz)
            bone.tail = Vector(tx,ty,tz)            
          AData.update()
          ob.makeDisplayList() #to refresh the bones display
      else: error = PupMenu("No bones list selection.")

def connect_bones(switch): #connect bone head with parent tail or active bones list selection tail
  if Object.GetSelected() != []: 
    ob = Object.GetSelected()[0]
    if ob.getType() == "Armature":       
      if len(boxBones) > 1:
        AData = ob.getData()
        if switch == 0:
          _tail = AData.bones[lastBone].tail['ARMATURESPACE']
        elif switch == 1:
          parents = []
          for bone in boxBones:
            if AData.bones[bone].hasParent():
              par_tail = AData.bones[bone].parent.tail['ARMATURESPACE']
              parents.append(par_tail)
            else: parents.append(AData.bones[bone].head['ARMATURESPACE'])
        AData.makeEditable()
        for count, bone in enumerate(boxBones):
          if bone != lastBone:
            if switch == 0: AData.bones[bone].head = _tail
            elif switch == 1:
              AData.bones[bone].head = parents[count]
        AData.update()
        ob.makeDisplayList()

def align_bones(): #align bones list selections with the axis along which they predominantly point
  if Object.GetSelected() != []: #if there is a current selection in 3d view
    ob = Object.GetSelected()[0]
    if ob.getType() == "Armature": #if there is an armature selection      
      if len(boxBones) != 0:
        AData = ob.getData()        
        AData.makeEditable()
        for bone in boxBones:
          y_dir = Vector(0,1,0) #Cambo method to find direction in which bone axis points
          bone = AData.bones[bone]        
          hx = bone.head[0]
          hy = bone.head[1]
          hz = bone.head[2]
          tx = bone.tail[0]
          ty = bone.tail[1]
          tz = bone.tail[2]
          y_dir = y_dir * bone.matrix.rotationPart()
          xd,yd,zd = y_dir
          y_dir = [abs(xd),abs(yd),abs(zd)]
          dirmax = max(y_dir) #this may get funny with 45 degree angles....
          if y_dir.index(dirmax) == 0: bone.tail = Vector(tx,hy,hz)
          elif y_dir.index(dirmax) == 1: bone.tail = Vector(hx,ty,hz)
          elif y_dir.index(dirmax) == 2: bone.tail = Vector(hx,hy,tz)
        AData.update()
        ob.makeDisplayList()

def align_chains(): #align bones with parents, straightening chains
  global boxBones, boxList
  if Object.GetSelected() != []: 
    ob = Object.GetSelected()[0]
    if ob.getType() == "Armature":      
      if len(boxBones) != 0:
        hval = Create(0)
        tval = Create(0)        
        block = []
        block.append(("head to parent tail",hval,"Warning: this will alter your armature!"))
        block.append(("tail to parent tail",tval,"Warning: this will alter your armature!"))        
        moveVal = PupBlock("Align bones:", block)      
        if moveVal == 1:
          if flipDict == {}: flip()          
          AData = ob.getData()
          parents = []
          boxBones = sort_hierarchy(boxBones)        
          for bone in boxBones:
            if AData.bones[bone].hasChildren(): numchild = len(AData.bones[bone].children)
            else: numchild = 0
            if AData.bones[bone].hasParent():            
              par_tail = (AData.bones[bone].parent.name,AData.bones[bone].parent.tail['ARMATURESPACE'],
                          numchild)            
            else:
              par_tail = ("None",AData.bones[bone].head['ARMATURESPACE'],numchild)            
            parents.append(par_tail)
          AData.makeEditable()                
          for count, bone in enumerate(boxBones):           
            y_dir = Vector(0,1,0) 
            bone = AData.bones[bone]
            _tail = parents[count][1]         
            hx = bone.head[0]
            hy = bone.head[1]
            hz = bone.head[2]
            tpx = _tail[0]
            tpy = _tail[1]
            tpz = _tail[2]          
            y_dir = y_dir * bone.matrix.rotationPart()
            xd,yd,zd = y_dir
            y_dir = [abs(xd),abs(yd),abs(zd)]
            dirmax = max(y_dir)
            if y_dir.index(dirmax) == 0:              
              if parents[count][2] == 1:
                if hval.val == 1: bone.head = Vector(hx,tpy,tpz)
                if tval.val == 1: bone.tail = Vector(bone.tail[0],tpy,tpz)            
            elif y_dir.index(dirmax) == 1:
              if (parents[count][0] in flipDict and bone.name in flipDict) or\
                 (parents[count][0] not in flipDict and bone.name not in flipDict):                
                if hval.val == 1: bone.head = Vector(tpx,hy,tpz)
                if tval.val == 1: bone.tail = Vector(tpx,bone.tail[1],tpz)
            elif y_dir.index(dirmax) == 2:              
              if parents[count][2] == 1:
                if hval.val == 1: bone.head = Vector(tpx,tpy,hz)
                if tval.val == 1: bone.tail = Vector(tpx,tpy,bone.tail[2])          
          AData.update()
          ob.makeDisplayList()
          boxList = sort_hierarchy(boxList)
#------Other bone functions------------------------------------------------------------------
def sort_hierarchy(bonelist): #produces a hierarchically sorted bone list
  global listTree
  if len(bonelist) != 0: #if the submitted list isn't blank
    if Object.GetSelected() != []: #if there is a current selection in 3d view
      ob = Object.GetSelected()[0]
      if ob.getType() == "Armature": #if there is an armature selection    
        AData = ob.getData()
        templist = []
        levellist = []
        listTree = []
        level = 0
        for bone in AData.bones.keys():  #a separate loop to force these to be first in list
          if not AData.bones[bone].hasParent():
            templist.append(bone)
            levellist.append(level)
        templist.sort()        
        for bone in templist:          
          if AData.bones[bone].hasChildren():
            children = sorted(child.name for child in AData.bones[bone].children)
            children.reverse()
            for child in children:
              level = levellist[templist.index(AData.bones[child].parent.name)]+1
              if not child in templist:
                if not AData.bones[child].parent.name in templist:
                  templist.append(child)
                  levellist.append(level)
                else:
                  templist.insert(templist.index(AData.bones[child].parent.name)+1, child)
                  levellist.insert(templist.index(AData.bones[child].parent.name)+1, level)
        templist2 = []
        for bone in templist: #templist contains all bones.  Now screen against submitted list          
          if bone in bonelist:
            templist2.append(bone)
            if AData.bones[bone].parent:
              level = levellist[templist.index(bone)]
              #listTree.append((AData.bones[bone].parent.name,level))            
              listTree.append(level)
            #else: listTree.append(("None",0))
            else: listTree.append(0)
        #print zip(templist2,listTree)
        return templist2
  else: return [] #Need to return a blank list if one was submitted as an argument
  
def bone_search():
  global boxBones, lastBone
  name = PupStrInput("Name of bone:", "", 50) #get a name for the new bone
  if name != None and name != "":
    if Object.GetSelected() != []: #if there is a current selection in 3d view
      ob = Object.GetSelected()[0]
      if ob.getType() == "Armature": #if there is an armature selection
        try:
          pose = ob.getPose()          
          if name[-1:] == "*": prog = re.compile(name[:-1],re.I) #wildcards          
          else: prog = re.compile(name)#case sensitive          
          for bone in pose.bones.keys():
            result = prog.search(bone)
            if name == bone or result != None:
              pose.bones[bone].sel = TRUE
              if bone not in boxBones: boxBones.append(bone)
              lastBone = bone
          pose.update()          
        except:
          pass #pose bone .sel is not available in current (Sept 06) formal release of 242, only CVS
              
def bone_grab():
  if Object.GetSelected() != []: #if there is a current selection in 3d view
    ob = Object.GetSelected()[0]
    if ob.getType() == "Armature": #if there is an armature selection     
      try:
        pose = ob.getPose()
        if len(boxBones) != 0:          
          for bone in boxBones:
            pose.bones[bone].sel = 1 - pose.bones[bone].sel #toggle selection         
        elif len(boxBones) == 0: #clear selections if box is empty
          for bone in pose.bones.values():
            bone.sel = FALSE
        pose.update()
        ob.makeDisplayList()
        Blender.Redraw()
      except:          
        pass #pose bone .sel is not available in current (Sept 06) formal release of 242, only CVS      
##----------------Poser-specific functions----------------------------------
#-------Poser pose (.pz2 or .p2z) export functions-------------------------------------------------
def save_poser(ob, pose):
  global NoRollPose, PoserConvert, curFrame  
  poseText = ""
  temp = NoRollPose   
  NoRollPose = 1 #Poser poses are restMatrix type  
  frames = [] #the list of frames to export
  runAnim = 0 #armature can have an action with no keyframes, so we need a control variable
  curframe = Blender.Get('curframe')#local - used to advance frames for posematrix use 
  curFrame = Blender.Get('curframe')#global - used to reset start frame at end of run
  #thighLen = ""
  if ob.getAction():
    act = ob.getAction()
    if len(act.getAllChannelIpos()) > 0: #if there are keyframes
      runAnim = 1
  if saveBatch == 1 and runAnim == 1: #animation
    if batchFrames == 0: #not using frames list screening
      framelen = PupIntInput("animation length:", 1, Blender.Get('staframe'), Blender.Get('endframe'))
      if framelen == None: framelen = 1
      frames = list(i+curframe for i in range(framelen))      
    elif batchFrames == 1: #with frames list screening
      if len(boxFrames) > 0:
        frames_sort()
        frames = list(int(i) for i in boxFrames)        
  elif saveBatch == 0 or runAnim == 0: #single frame; only current
    frames.append(curframe)
  
  poseText += "{\nversion\n\t{\n\tnumber\n\t}\n\n" #version header
  if saveBatch == 1 and runAnim == 1:
    poseText += "clearFigureKeys %i\n\n" %(frames[len(frames)-1])#clears the keyframes in Poser  
  bonelist = pose.bones.keys()
  #bonelist.sort() #alphabetical sort
  bonelist = sort_hierarchy(bonelist) #get the hierarchical setup
  AData = ob.getData()  
  for bone in AData.bones.keys(): #Poser pose format omits root part - not necessary
    if not AData.bones[bone].hasParent():
      if AData.bones[bone].hasChildren():
        bonelist.remove(bone)
    #if AData.bones[bone].name == "rThigh": #only mess with thighLength if this is a Poser-derived armature
    #  thighLen = "thighLength %f\n\n" %(AData.bones[bone].length)
    #  poseText += thighLen
  for bone in bonelist:
    if JPdict == {} or len(JPdict.keys()) != len(AData.bones.keys()):
      axis_direction(AData, 0) #get generic rotation orders for bones without defined joint orders
    order = JPdict[bone] #define joint rotation order     
    rxlist = [] #we only need to worry about six parameters
    rylist = [] #these are lists for those parameters
    rzlist = []
    txlist = []
    tylist = []
    tzlist = []
    curvelist = [] #stores the Ipo curves
    savePart = save_part(bone) #screening check                     
    if savePart == 1:      
      if saveBatch == 1 and runAnim == 1: #act is defined when runAnim is set, above        
        if bone in act.getAllChannelIpos(): #if the bone is keyframed
          ipo = act.getChannelIpo(bone) #gather together all the key data for the bone 
          icu_locx = ipo[Ipo.PO_LOCX] #1
          icu_locy = ipo[Ipo.PO_LOCY] #2
          icu_locz = ipo[Ipo.PO_LOCZ] #3
          icu_quatw = ipo[Ipo.PO_QUATW] #25
          icu_quatx = ipo[Ipo.PO_QUATX] #26
          icu_quaty = ipo[Ipo.PO_QUATY] #27
          icu_quatz = ipo[Ipo.PO_QUATZ] #28
          curvelist = [icu_locx, icu_locy, icu_locz, icu_quatw, icu_quatx, icu_quaty, icu_quatz]
          
      for framenum in frames: #loop through the frames to reorganize all the frame data
        lx = 0
        ly = 0
        lz = 0
        qw = 0
        qx = 0
        qy = 0
        qz = 0
        if saveBatch == 1 and runAnim == 1: #get the data for each frame if we're handling multiple frames
          for count, icu in enumerate(curvelist):
            if icu != None and len(icu.bezierPoints) > 0:#We might get errors is some parameters lack curves....
              if count == 0: lx = icu[framenum]
              elif count == 1: ly = icu[framenum]
              elif count == 2: lz = icu[framenum]
              elif count == 3: qw = icu[framenum]
              elif count == 4: qx = icu[framenum]
              elif count == 5: qy = icu[framenum]
              elif count == 6: qz = icu[framenum]
          quat = Quaternion(qw,qx,qy,qz) ##compile the quaternion rotations         
        elif saveBatch == 0 or runAnim == 0:
          quat = pose.bones[bone].quat #if we're only doing current frame, get the current pose values 
          lx,ly,lz = pose.bones[bone].loc
        if IKPose == 1: #posematrix needs to get data from current frame - this is very bloody slow!!!!
          Blender.Set('curframe',framenum)        
        quat = save_convert(ob, pose, pose.bones[bone], quat) #Do the restmatrix/posematrix conversions
        #qx,qy,qz = quat.toEuler()#.unique() #convert the quaternion to euler for Poser (unique doesn't help)        
        qx,qy,qz = quat_to_euler2(quat)#alternate conversion returns better results - but still not quite right....
        rxlist.append(qx) #append the gathered animation data to the proper list for each parameter
        rylist.append(qy)
        rzlist.append(qz)
        txlist.append(lx)
        tylist.append(lz)#flip y and z... not really sure if this helps anything, or not....
        tzlist.append(ly) 
      params1 = [] #a list to store Poser parameter names 
      params2 = [] #a list to store the corresponding list of animation data (created above)
      for axis in order: #order is a list containing the joint rotation order
        if axis == 0: #fill the parameters lists - this is where we specify joint rotation order
          params1.append("rotateX xrot")
          params2.append(rxlist)
        elif axis == 1:
          params1.append("rotateY yrot")
          params2.append(rylist)
        elif axis == 2:
          params1.append("rotateZ zrot")
          params2.append(rzlist)
      params1.append("translateX xtran")
      params1.append("translateY ytran")
      params1.append("translateZ ztran")
      params2.append(txlist)
      params2.append(tylist)
      params2.append(tzlist)
      
      poseText += "actor %s:1\n\t{\n\tchannels\n\t\t{\n" %(bone) #actor line    
      for channel, param in zip(params1,params2): #correlate the two parameters lists
        poseText += "\t\t%s\n\t\t\t{\n\t\t\tkeys\n\t\t\t\t{\n" %(channel) #channel line and keys
        for value in param: #identify parameters with all zero keys - only export the first key for these
          if round(value,6) <> 0.000000:
            allZero = 0
            break
          else: allZero = 1
        for key, value in enumerate(param):
          if allZero == 0:
            if saveBatch == 1 and runAnim == 1: frame = frames[key]-1#multi-frame saves to frame index 
            elif saveBatch == 0 or runAnim == 0: frame = key #single frame should save to zero
          elif allZero == 1 and key == 0: frame = key 
          poseText += "\t\t\t\tk  %i  %f\n" %(frame, value) #add each keyframe, value pair - double spaces between!
          #if allZero == 0:
          #  poseText += "\t\t\t\tsl  1\n\t\t\t\tspl\n\t\t\t\tsm\n" #spline interpolation listings
          #elif allZero == 1: break
          if allZero == 1: break
        poseText += "\t\t\t\t}\n" #close all the brackets
        poseText += "\t\t\t}\n"
    poseText += "\t\t}\n\t}\n\n" #(That's a lotta brackets....)  
  poseText += "figure\n\t{\n\t}\n" #figure section at end of pose
  poseText += "\nversion\n\t{\n\tnumber\n\t}\n\n" #IK section repeats header,clearfigure, and figure blocks
  if saveBatch == 1 and runAnim == 1: poseText += "clearFigureKeys %i\n\n" %(frames[len(frames)-1])
  #if thighLen != "": poseText += thighLen
  poseText += "figure\n\t{\n\t}\n}" 
  NoRollPose = temp #reset restMatrix save value
  if IKPose == 1: Blender.Set('curframe',curFrame)#restore the start frame if posematrix has had us walk the frames
  return poseText

JPdict = {}
def axis_direction(AData, switch): #find generic joint rotation orders for Poser export
  #Generic orders are based on a sampling of applied rotation orders in Poser figures.  When building a Poser figure,
  #'bend' apparently s/b the axis least likely to receive 90 degrees rotation - but that standard rarely seems to have
  #been followed with existing figs.  Here, bend is the common bend axis in most figures I examined.
  for bone in AData.bones.keys():
    if switch == 1 or bone not in JPdict:
      bonemat = AData.bones[bone].matrix['ARMATURESPACE'].rotationPart() 
      direx = ["X","Y","Z"]
      y_dir = [bonemat[1][0],bonemat[1][1],bonemat[1][2]]
      y_dir2 = list(abs(i) for i in y_dir)
      y_dir3 = max(y_dir2) #direction in which bone y axis points  
      direx[0] = y_dir2.index(y_dir3)
      if direx[0] == 0: #x-pointing bone      
        direx[1] = 1 #assume most humanoid rigs will have y-bend for x-twist bones
      elif direx[0] == 1: #y-pointing
        direx[1] = 0 #assume most humanoid rigs will have x-bend for y-twist bones
      elif direx[0] == 2: #z-pointing
        direx[1] = 1 #assume most humanoid rigs will have y-bend for z-twist bones
      for i in range(3): #fill direx[2] with whichever axis is left unaccounted for
        if not i in direx:
          direx[2] = i
          break
      JPdict[bone] = direx

#------quaternion conversion---------------------------------------------------------
def quat_to_euler2(q1):
  #adapted from Maths - Conversion Quaternion to Euler - Martin Baker
  #http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/index.htm
  sqw = q1.w * q1.w
  sqx = q1.x * q1.x
  sqy = q1.y * q1.y
  sqz = q1.z * q1.z
  unit = sqx + sqy + sqz + sqw # if normalised is one, otherwise is correction factor
  test = (q1.x * q1.y) + (q1.z * q1.w)
  #test = (q1.x*q1.z) - (q1.y*q1.w)
  
  if test > 0.499*unit:# and test < 0.501*unit:  # singularity at north pole
    #print "north", test, unit, 0.498*unit, 0.501*unit 
    bank = 2 * math.atan2(q1.x,q1.w)
    attitude = math.pi/2
    heading = 0    
  elif test < -0.499*unit:# and test > -0.501*unit:  # singularity at south pole
    #print "south", test, unit, -0.498*unit, -0.501*unit 
    bank = -2 * math.atan2(q1.x,q1.w)
    attitude = -math.pi/2
    heading = 0    
  else:     
    heading = math.atan2(2 * q1.y * q1.w-2 * q1.x * q1.z , sqx - sqy - sqz + sqw)
    attitude = math.asin(2 * test/unit)
    bank = math.atan2(2 * q1.x * q1.w-2 * q1.y * q1.z , -sqx + sqy - sqz + sqw)
  return r2d(bank),r2d(heading),r2d(attitude)
  #return r2d(heading),r2d(attitude),r2d(bank)

#-----joint order functions----------------------------------------------  
def change_JP_order():  
  if len(boxBones) > 0:    
    if Object.GetSelected() != []:
      ob = Object.GetSelected()[0]
      if ob.getType() == "Armature":
        if JPdict == {}:
          axis_direction(ob.getData(), 1)
        if JPdict != {}:  
          menu = PupMenu("New joint rotation order for bones list selections: %t|XYZ %x1|XZY %x2|YXZ %x3\
                          |YZX %x4|ZXY %x5|ZYX %x6|Cancel %x7")
          if menu != 7:
            if menu == 1: order = [0,1,2]
            elif menu == 2: order = [0,2,1]
            elif menu == 3: order = [1,0,2]
            elif menu == 4: order = [1,2,0]
            elif menu == 5: order = [2,0,1]
            elif menu == 6: order = [2,1,0]
            for bone in boxBones:
              JPdict[bone] = order
      else: error = PupMenu("You must make an armature selection to use this function.") 
  else: error = PupMenu("You must make one or more bones listbox selections to use this function.")
  
def export_JP_order(save_name): #
  if save_name != "":     
    if Object.GetSelected() != []:
      ob = Object.GetSelected()[0]
      if ob.getType() == "Armature":
        temptext = ""
        pose = ob.getPose()
        if JPdict == {}: axis_direction(ob.getData(), 1)
        for bone in pose.bones.keys():
          rotOrder = ""
          order = JPdict[bone]
          for i in order:
            if i == 0: rotOrder += "X"
            elif i == 1: rotOrder += "Y"
            elif i == 2: rotOrder += "Z"
          temptext += "%s,%s,%s,%s\n" %(bone,rotOrder[0],rotOrder[1],rotOrder[2])
        save_name = os.path.splitext(save_name)[0] + ".txt"
        if sys.exists(save_name) == 1:
          warning = PupMenu("Overwrite file %s? %%t|Yes %%x1|No %%x2" %(sys.basename(save_name))) #Don't overwrite        
        if sys.exists(save_name) != 1 or warning != 2:
          jpsave = open(save_name,"w")
          jpsave.write(temptext)
          jpsave.close()

def save_JP_order(save_name): #saves a rotation order text file straight from JP dict
  if save_name != "":
    if JPdict != {}:
      temptext = ""
      for key, value in JPdict.iteritems():
        rotOrder = ""
        for i in value:
          if i == 0: rotOrder += "X"
          elif i == 1: rotOrder += "Y"
          elif i == 2: rotOrder += "Z"
        temptext += "%s,%s,%s,%s\n" %(key,rotOrder[0],rotOrder[1],rotOrder[2])
        save_name = os.path.splitext(save_name)[0] + ".txt"
      if sys.exists(save_name) == 1:
        warning = PupMenu("Overwrite file %s? %%t|Yes %%x1|No %%x2" %(sys.basename(save_name))) #Don't overwrite        
      if sys.exists(save_name) != 1 or warning != 2:
        jpsave = open(save_name,"w")
        jpsave.write(temptext)
        jpsave.close()
          
def import_JP_order(file_name): #imports a rotation order text file saved by this script  
  if file_name != "":
    if Object.GetSelected() != []:
      ob = Object.GetSelected()[0]
      if ob.getType() == "Armature":
        pose = ob.getPose()
        temptext = open(file_name,"r")
        lines = temptext.readlines()
        for line in lines:
          data = line.rstrip("\n").split(",")
          if data[0] in pose.bones.keys(): #Keep dict from being badly updated; would create errors later
            rotOrder = []
            for i in range(1,4):
              if data[i] == "X": rotOrder.append(0) 
              elif data[i] == "Y": rotOrder.append(1) 
              elif data[i] == "Z": rotOrder.append(2)
            JPdict[data[0]] = rotOrder
        temptext.close()
            
def parse_JP_order(file_name): #parses rotation orders out of a Poser .pz2 or .cr2 file  
  if file_name != "":
    ext = os.path.splitext(file_name)[1]
    if ext == ".cr2" or ext == ".pz2" or ext == ".crz" or ext == ".p2z":
      if Object.GetSelected() != []:
          ob = Object.GetSelected()[0]
          if ob.getType() == "Armature":
            pose = ob.getPose()            
            if ext[3] == "2":
              temptext = open(file_name,"r")              
            if ext[3] == "z":
              try :
                import gzip 
                temptext=gzip.GzipFile(file_name)                               
              except:                
                error = Blender.Draw.PupMenu("Could not read .pzz file.  Try decompressing and reading as .pz2")
                return   
            lines = temptext.readlines()
            name = ""
            rotOrder = []
            if len(lines) == 1: lines = lines[0].replace("\r","\n").split("\n")#if it's all been read as one line            
            for line in lines:
              line = line.replace("\n","").replace("\t","").replace("\r","").replace("  "," ")
              if line.find("actor") != -1:
                name = line.split(":")[0].replace("actor ","")   
              if name != "":               
                if line.find("xrot") != -1: rotOrder.append(0)
                if line.find("yrot") != -1: rotOrder.append(1)
                if line.find("zrot") != -1: rotOrder.append(2)                
              if name != "" and len(rotOrder) == 3:
                if name in pose.bones.keys():  
                  JPdict[name] = rotOrder
                name = ""
                rotOrder = []            
            temptext.flush()
            temptext.close()
            if JPdict != {}:
              menu = PupMenu("Save rotation data as text file? %t|Yes %x1|No %x2")
              if menu == 1: Window.FileSelector(save_JP_order, 'Browse for save location.') 
#---------Poser file reading--------------------------------------------------------
#This is called by create_armature(); this merely collects data - create armature makes the rig
def parse_poser_file(file_name): #Build an armature from a .cr2 file, optionally including IK and rotation order save  
  if file_name != "":
    ext = os.path.splitext(file_name)[1]
    if ext == ".cr2" or ext == ".crz":      
      IKask = PupMenu("Add IK to armature? %t|Yes %x1|No %x2|Yes, but omit 'Collar' parts %x3")
      startTime = sys.time()#keep track of speed
      if ext[3] == "2":
        temptext = open(file_name,"r")              
      if ext[3] == "z":
        try :
          import gzip 
          temptext=gzip.GzipFile(file_name)                               
        except:                
          error = Blender.Draw.PupMenu("Could not read .pzz file.  Try decompressing and reading as .pz2")
          return   
      lines = temptext.readlines()      
      flag = 0
      data = []
      rotOrder = []
      IKlist = []
      temp = ["name","h0","h1","h2","t0","t1","t2","None"]
      if len(lines) == 1: lines = lines[0].replace("\r","\n").split("\n")#Macs save \r and .crz reading fails to split at \r     
      for indx,line in enumerate(lines):
        line = line.replace("\n","").replace("\t","").replace("\r","").replace("  "," ")
        if line.find("actor") != -1 and lines[indx+2].find("name") != -1: #get the bone name
          name = line.split(":")[0].replace("actor ","").replace("\t","").replace("\n","").replace("\r","")         
          temp[0] = name                   
        if line.find("parent") != -1: #get the parent name
          if lines[indx+2].find("nonInkyParent") != -1:#if cr2 was saved with IK on
            read = lines[indx+2].split(":")[0].replace("\n","").replace("\t","").replace("\r","").split(" ")
          else: read = line.split(":")[0].split(" ")          
          if read[1] != "UNIVERSE": temp[7] = read[1]
          else: root = name      
        if len(rotOrder) < 3: #get the joint rotation order for the actor
          if line.find("xrot") != -1: rotOrder.append(0)
          if line.find("yrot") != -1: rotOrder.append(1)
          if line.find("zrot") != -1: rotOrder.append(2)
        if line.find("endPoint") != -1: #get the endPoint data (tail)          
          read = line.split(" ")
          for i in range(1,4): temp[i+3] = read[i]                   
        if line.find("origin") != -1: #get the origin data (head)         
          read = line.split(" ")
          for i in range(1,4): temp[i] = read[i]          
          flag = 1
        #build the IK structure-------------------------------------------------------------
        if IKask == 1 or IKask == 3:
          if line.find("inkyChain") != -1:         
            inkyname = line.replace("inkyChain  ","").strip()
            IKlist = []
            num = indx + 4            
            while lines[num].find("linkWeight") == -1: #fill a list with the IK chain data
              if not (IKask == 3 and lines[num].find('Collar') != -1): #omit collars, which don't handle well w/ Blender IK
                IKlist.append(lines[num].split(" ")[1].replace("\t","").replace("\n","").replace("\r","").split(":")[0])
              num += 1              
            for chainlen, bone in enumerate(IKlist):
              for items in data:
                item = items.split(",")
                if item[0] == bone:                            
                  item[10] = str(chainlen+1) #add chainlen value
                  if chainlen < len(IKlist)-2: #create bend target bones
                    dirextup = (abs(float(item[5])-float(item[2])),abs(float(item[6])-float(item[3])),
                                abs(float(item[7])-float(item[4])))
                    direx = max(dirextup)#find direction in which bone y axis points
                    if direx == dirextup[0]:#bone points on x
                      bend = max(abs(abs(float(item[7]))-(abs(float(item[5]))-abs(float(item[2])))/2),rootlen/3)
                      bend = -bend                      
                      bend2 = float(item[6])
                    elif direx == dirextup[1]:#bone points on y
                      bend = max(abs(abs(float(item[7]))+(abs(float(item[6]))-abs(float(item[3])))/2),rootlen/3)
                      bend2 = float(item[6])
                    elif direx == dirextup[2]:#bone points on z
                      bend2 = max(abs(abs(float(item[6]))+(abs(float(item[7]))-abs(float(item[4])))/2),rootlen/3)
                      bend = float(item[7])
                    dataline = "%s,0.0,%s,%f,%f,%s,%s,%f,%s,None,0\n" %(bone+"_bend",item[5],bend2,bend,item[5],
                                                                        item[6],bend*2,root)                                       
                    data.append(dataline)
                    item[9] = bone + "_bend"                    
                  elif chainlen == len(IKlist)-2: #point last link in Blender chain at goal bone
                    item[9] = inkyname                                        
                  elif chainlen == len(IKlist)-1: #create goal bone and parent Poser goal actor to it
                    item[8] = inkyname                    
                    _tail = data[data.index(items)-1].split(",")
                    if direx == dirextup[0]: length = max(abs(abs(float(_tail[5]))-abs(float(_tail[2])))/2,rootlen/3)                        
                    elif direx == dirextup[1]: length = max(abs(abs(float(_tail[6]))-abs(float(_tail[3])))/2,rootlen/3)
                    elif direx == dirextup[2]: length = max(abs(abs(float(_tail[7]))-abs(float(_tail[4])))/2,rootlen/3)                      
                    _taillen = float(_tail[6])-length
                    dataline = "%s,0.0,%s,%s,%s,%s,%f,%s,%s,None,0\n" %(inkyname,_tail[5],_tail[6],_tail[7],_tail[5],
                                                                        _taillen,_tail[7],root)
                    data.append(dataline)                                  
                  dataline = ""
                  for param in item:
                    dataline += str(param)+","
                  dataline += "\n" #string will be split by commas, so \n after the comma is no problem
                  data[data.index(items)] = dataline
                  #end IK----------------------------------------------------
        if flag == 1:          
          if name == root: #line up the root (BODY) with its child (hip - Poser will have only one rootchild)
            rootlen = max(abs(float(temp[5])-float(temp[3]))/3,0.2)#0.2 is a value which is scaled for Poser sizes                      
          if temp[7] == root:          
            dataline = "%s,0.0,%s,%s,%s,%s,%s,%f,None,None,0\n" %(root,temp[1],temp[2],temp[3],temp[1],
                                                               temp[2],-rootlen)
            data[0] = dataline                       
          dataline = "%s,0.0,%s,%s,%s,%s,%s,%s,%s,None,0\n" %(temp[0],temp[1],temp[2],temp[3],temp[4],
                                                               temp[5],temp[6],temp[7])
          data.append(dataline)          
          JPdict[name] = rotOrder #enter rotation orders in our Poser export dict
          flag = 0          
          name = ""
          rotOrder = []      
      temptext.flush()
      temptext.close()
      finTime = sys.time() - startTime
      print "Done reading armature from %s in %f seconds" %(sys.basename(file_name),finTime)
      option = PupMenu("Save joint rotation orders for Poser pose export? %t|Yes %x1|No %x2")
      if option == 1: Window.FileSelector(save_JP_order, 'Browse for save location.')
      return data #passes armature data back to create_armature()

def load_poser_pose(file_name):
  global PoserConvert, PoseMat, realTime, askKey, flipbone
  if file_name != "":
    ext = os.path.splitext(file_name)[1]
    if ext == ".pz2" or ext == ".p2z" or ext == ".hd2" or ext == ".hdz": #support pose and hand files     
      if Object.GetSelected() != []:
          ob = Object.GetSelected()[0]
          if ob.getType() == "Armature":            
            startTime = sys.time()#keep track of speed
            hand = 0
            if ext == ".hd2" or ext == ".hdz":
              hand = PupMenu("Load for which hand? %t|Right %x1|Left %x2")
            tempval = PoserConvert
            PoserConvert = 1
            pose = ob.getPose()               
            if ext[3] == "2":
              temptext = open(file_name,"r")              
            if ext[3] == "z":
              try :
                import gzip 
                temptext=gzip.GzipFile(file_name)                               
              except:                
                error = Blender.Draw.PupMenu("Could not read .p2z file.  Try decompressing and reading as .pz2")
                return               
            lines = temptext.readlines()  
            name = ""            
            flag = 0            
            multi = 1
            numActors = 0
            if len(lines) == 1:
                lines = lines[0].replace("\r","\n").split("\n")#Macs save \r and .p2z reading fails to split at \r            
            for indx,line in enumerate(lines):              
              line = line.replace("\n","").replace("\t","").replace("\r","").replace("  "," ")
              if line.find("clearFigureKeys") != -1: multi = int(line.split(" ")[1])#number of frames, if animated
              if line.find("actor") != -1:
                name = line.split(":")[0].replace("actor ","") #bone name is Poser internal name minus figure number                
                xrot = []
                yrot = []
                zrot = []
                xtran = []
                ytran = []
                ztran = []                
              if name != "":               
                if line.find("xrot") != -1: xrot, flag = read_keys(lines, line, indx, xrot, flag)                  
                if line.find("yrot") != -1: yrot, flag = read_keys(lines, line, indx, yrot, flag)
                if line.find("zrot") != -1: zrot, flag = read_keys(lines, line, indx, zrot, flag)
                if line.find("xtran") != -1: xtran, flag = read_keys(lines, line, indx, xtran, flag)
                if line.find("ytran") != -1: ytran, flag = read_keys(lines, line, indx, ytran, flag)
                if line.find("ztran") != -1: ztran, flag = read_keys(lines, line, indx, ztran, flag)
              if name != "" and flag == 6:
                if name in pose.bones.keys():
                  numActors += 1
                  #pose screening (from load_pose())----------------                  
                  if flipDict == {}: flip()         
                  flipbone = ""                  
                  screening = 0
                  noRot = 0
                  noTrans = 0                  
                  if ExcludePart == 1: screening = Part_Load(pose.bones[name], 0)
                  if IncludePart == 1: screening = Part_Load(pose.bones[name], 1)
                  if bonesLoad == 1 and name not in boxBones: screening = 1
                  if bonesLoad == 2 and name in boxBones: screening = 1                          
                  if (ScreenPose == 1 or DupPose == 1) and screening == 0: screening = Screen_Pose(name, pose)               
                  #if (name == "BODY"  or numActors == 1) and bodyscale == 0:  screening = 1                             
                  if HipPose == 1:          
                    if name == "hip" or numActors == 1:                      
                      if hipR == 1: noRot = 1
                      if hipT == 1: noTrans = 1
                  #if numActors <= 2: print name, numActors, noRot, noTrans, HipPose, bodyscale, screening
                  if numActors > 1:  noTrans = 1 #exclude IK goal translation in poses
                  #end pose screening---------------------
                  if screening == 0:                    
                    size = Vector(1,1,1)
                    xr = 0.0
                    yr = 0.0
                    zr = 0.0
                    xt = 0.0
                    yt = 0.0
                    zt = 0.0
                    for i in range(0,multi): #loop through frames, sort out the keys
                      if noRot == 0:
                        if len(xrot) > i and xrot[i][0] == i: xr = xrot[i][1]
                        if len(yrot) > i and yrot[i][0] == i: yr = yrot[i][1]
                        if len(zrot) > i and zrot[i][0] == i: zr = zrot[i][1]
                      if noTrans == 0:
                        if len(xtran) > i and xtran[i][0] == i: xt = xtran[i][1]
                        if len(ytran) > i and ytran[i][0] == i: yt = ytran[i][1]
                        if len(ztran) > i and ztran[i][0] == i: zt = ztran[i][1]                    
                      quat = Euler(xr,yr,zr)
                      quat = quat.toQuat()
                      quat = Poser_Conversion(pose.bones[name], quat, 0) #convert Poser eulers to restmatrix type quat
                      
                      if hand != 0: #selected hand for .hd2 hand poses
                        if (hand == 1 and (pref == 1 and name[:lenR] == left) or (suff == 1 and name[-lenR:] == left)) or\
                            (hand == 2 and (pref == 1 and name[:lenR] == right) or (suff == 1 and name[-lenR:] == right)):
                          name = flipDict[name]
                          quat = Flip_Pose2(quat, 0, 1)                        
                          
                      if noTrans == 0: loc = Vector(xt,zt,yt) #change axis order - only for tran
                      else: loc = Vector(0,0,0)
                      #combinatory modifiers (from load_pose())---------------------
                      if noRot == 0:
                        if partPose != 1.0:                  
                          quat = Partial_Pose(quat, 0, 0)
                          loc = Partial_Pose(0, loc, 1)                        
                        if MixPose == 1:
                          quat = Mix_Pose(quat, pose.bones[name], 0, 0)
                          loc = Mix_Pose(0, pose.bones[name], loc, 1)
                      #end combo modifiers------------------------------------------   
                      if batchFrames == 0:
                        time = i + curFrame# - 1 #load relative to current frame                        
                      elif batchFrames == 1:
                        if i < len(boxFrames):                          
                          time = int(boxFrames[i])# - 1
                          if i == 0 and curFrame != time: Blender.Set('curframe', time)#for add_curves() in frame 1 
                        else:                        
                          time = int(boxFrames[len(boxFrames)-1]) + (i - (len(boxFrames)) + 1)                      
                      if i == 0:#multi == 0: #single frame or first keyframe                        
                        bone = pose.bones[name]
                        bone.loc = loc
                        bone.quat = quat
                        bone.size = size                      
                      if multi != 1:                        
                        add_curves(ob, pose, name, quat, loc, size, time) #multiple frames                     
                flag = 0
                name = ""
            if DupPose == 1 or MirrorPose == 1 or MirrorBone == 1: #pose symmetry modifiers                   
              tempval2 = PoseMat
              tempval3 = askKey
              askKey = 2
              PoseMat = 0
              PoserConvert = 0
              realTime = 1
              load_pose("")                    
              PoseMat = tempval2
              askKey = tempval3
            if multi == 1 and askKey != 2: #keyframe single pose                                 
              if askKey == 0:
                c = PupMenu("Insert pose keys for this frame?%t|Yes%x1|No%x2")  
              if askKey == 1: c = 1
              if c == 1:                
                for bone in pose.bones.values():
                  add_curves(ob, pose, bone.name, bone.quat, bone.loc, bone.size, Blender.Get('curframe'))
            PoserConvert = tempval
            pose.update()
            ob.makeDisplayList()
            Blender.Redraw()            
            temptext.flush()
            temptext.close()
            Blender.Set('curframe', curFrame)#reset current frame
            finTime = sys.time() - startTime
            #xrot = yrot = zrot = xtran = ytran = ztran = xr = yr = zr = xt = yt = zt = 0.0 
            print "Done loading %i frames for %i bones in %f seconds" %(multi, numActors, finTime)
            
def read_keys(lines, line, indx, paramlist, flag): #reads keyframe ('k') lines in a Poser pose
  num = indx + 4  
  while lines[num].find("}") == -1:
    if lines[num].find("k ") != -1:
      read = lines[num].replace("\n","").replace("\r","").replace("\t","").replace("  "," ").split(" ")    
      param = (int(read[1]), float(read[2]))
      paramlist.append(param)    
    num += 1
  flag += 1    
  return (paramlist, flag)

##---------------Numerical entry-----------------------------------------
#Results of rounding a float are not as controlled as one would like.  The decimal module might be a better
#tool for handling controlled rounding, but it is not part of the standard BPy package....
def entry_values():  
  #Sets the dial values for the numerical tab
  global _tran_x, _tran_y, _tran_z, _rot_x, _rot_y, _rot_z, _size_x, _size_y, _size_z, \
         _boneroll_num, _head_x, _head_y, _head_z, _tail_x, tail_y, _tail_z, lastBone, lastLen, \
         boxBones
  
  if lastBone != "":
    if Object.GetSelected() != []:
      ob = Object.GetSelected()[0]
      if ob and ob.getType() == "Armature":
        AData = ob.getData()
        pose = ob.getPose()
        bone1 = AData.bones[lastBone]
        lastLen = bone1.length #Bone length is used to configure loc dial sensitivity
        bone = pose.bones[lastBone]
        if uniqueRot == 1: rot = bone.quat.toEuler().unique() #option to use unique to avoid gimbal lock
        elif uniqueRot == 0: rot = bone.quat.toEuler()
        _tran_x.val = round(bone.loc[0],6) #control value inputs with rounding
        _tran_y.val = round(bone.loc[1],6)
        _tran_z.val = round(bone.loc[2],6)
        _rot_x.val = round(rot[0],6)
        _rot_y.val = round(rot[1],6)
        _rot_z.val = round(rot[2],6)
        _size_x.val = round(bone.size[0],6)
        _size_y.val = round(bone.size[1],6)
        _size_z.val = round(bone.size[2],6)
        _boneroll_num.val = round(bone1.roll['ARMATURESPACE'],6)
        _head_x.val = round(bone1.head['ARMATURESPACE'][0],6)
        _head_y.val = round(bone1.head['ARMATURESPACE'][1],6)
        _head_z.val = round(bone1.head['ARMATURESPACE'][2],6)
        _tail_x.val = round(bone1.tail['ARMATURESPACE'][0],6)
        _tail_y.val = round(bone1.tail['ARMATURESPACE'][1],6)
        _tail_z.val = round(bone1.tail['ARMATURESPACE'][2],6)
        if BonesTabTog == 1:
          boxBones = []
          boxBones.append(lastBone) #Restrict bones list selections to one when in numerical mode       
      
def numerical_entry():
  global _tran_x, _tran_y, _tran_z, _rot_x, _rot_y, _rot_z, _size_x, _size_y, _size_z#, myKeys, keyType, boxBones
  #Applies pose mode dial entries returned by the numerical tab
  if lastBone != "":
    if Object.GetSelected() != []:      
      ob = Object.GetSelected()[0]
      if ob and ob.getType() == "Armature":
        pose = ob.getPose()      
        bone = pose.bones[lastBone]      
        if uniqueRot == 1: rot = bone.quat.toEuler().unique()
        elif uniqueRot == 0: rot = bone.quat.toEuler()
        
        if round(_tran_x.val,6) != round(bone.loc[0],6): #locX
          loc = round(_tran_x.val,6), bone.loc[1], bone.loc[2]
          bone.loc = Vector(loc)
        elif round(_tran_y.val,6) != round(bone.loc[1],6): #locY
          loc = bone.loc[0], round(_tran_y.val,6), bone.loc[2]
          bone.loc = Vector(loc)
        elif round(_tran_z.val,6) != round(bone.loc[2],6): #locZ
          loc = bone.loc[0], bone.loc[1], round(_tran_z.val,6)
          bone.loc = Vector(loc)
        elif round(_rot_x.val,6) != round(rot[0],6): #rotX
          quat = round(_rot_x.val,6), rot[1], rot[2]
          if uniqueRot == 1: bone.quat = Euler(quat).unique().toQuat()
          if uniqueRot == 0: bone.quat = Euler(quat).toQuat()
        elif round(_rot_y.val,6) != round(rot[1],6): #rotY
          quat = rot[0], round(_rot_y.val,6), rot[2]
          if uniqueRot == 1: bone.quat = Euler(quat).unique().toQuat()
          if uniqueRot == 0: bone.quat = Euler(quat).toQuat()
        elif round(_rot_z.val,6) != round(rot[2],6): #rotZ
          quat = rot[0], rot[1], round(_rot_z.val,6)
          if uniqueRot == 1: bone.quat = Euler(quat).unique().toQuat()
          if uniqueRot == 0: bone.quat = Euler(quat).toQuat()
        elif round(_size_x.val,6) != round(bone.size[0],6): #scaleX
          size = round(_size_x.val,6), bone.size[1], bone.size[2]
          bone.size = Vector(size)
        elif round(_size_y.val,6) != round(bone.size[1],6): #scaleY
          size = bone.size[0], round(_size_y.val,6), bone.size[2]
          bone.size = Vector(size)
        elif round(_size_z.val,6) != round(bone.size[2],6): #scaleZ
          size = bone.size[0], bone.size[1], round(_size_z.val,6)
          bone.size = Vector(size)     
        
        if myKeys == 1: #Add keyframe
          bonelist = [] #Note: 2 and 3 will be the same if bones list is open in dual mode.
          if keyType == 1: bonelist = pose.bones.values()
          elif keyType == 2: bonelist = list(pose.bones[bone] for bone in boxBones) 
          elif keyType == 3: bonelist.append(pose.bones[lastBone])
          for bone in bonelist:
            add_curves(ob, pose, bone.name, bone.quat, bone.loc, bone.size, Blender.Get('curframe'))         
        
        pose.update()      
        ob.makeDisplayList()
        entry_values() #refresh the listings
      Blender.Redraw()
    
def numerical_edit():
  global _boneroll_num, _head_x, _head_y, _head_z, _tail_x, tail_y, _tail_z, lastBone
  #Applies edit mode dial entries from numerical tab
  if lastBone != "":
    if Object.GetSelected() != []:    
      ob = Object.GetSelected()[0]
      if ob and ob.getType() == "Armature":
        AData = ob.getData()
        bone1 = AData.bones[lastBone]
        roll1 = bone1.roll['ARMATURESPACE']
        head1 = bone1.head['ARMATURESPACE']
        tail1 = bone1.tail['ARMATURESPACE']      
        AData2 = AData #makeEditable seems to require this odd step sometimes....     
        AData2.makeEditable()
        bone1 = AData2.bones[lastBone] #makeEditable() has a real #$%@*!! problem....
        if round(_boneroll_num.val,6) != round(roll1,6): #roll
          bone1.roll = round(_boneroll_num.val,6)      
        elif round(_head_x.val,6) != round(head1[0],6): #head x
          head = round(_head_x.val,6), head1[1], head1[2]
          bone1.head = Vector(head)
        elif round(_head_y.val,6) != round(head1[1],6): #head y
          head = head1[0], round(_head_y.val,6), head1[2]
          bone1.head = Vector(head)
        elif round(_head_z.val,6) != round(head1[2],6): #head z
          head = head1[0], head1[1], round(_head_z.val,6)
          bone1.head = Vector(head)        
        elif round(_tail_x.val,6) != round(tail1[0],6): #tail x
          tail = round(_tail_x.val,6), tail1[1], tail1[2]
          bone1.tail = Vector(tail)
        elif round(_tail_y.val,6) != round(tail1[1],6): #tail y
          tail = tail1[0], round(_tail_y.val,6), tail1[2]
          bone1.tail = Vector(tail)
        elif round(_tail_z.val,6) != round(tail1[2],6): #tail z
          tail = tail1[0], tail1[1], round(_tail_z.val,6)
          bone1.tail = Vector(tail)
        
        AData.update()
        ob.makeDisplayList()
        entry_values() #refresh the listings
      Blender.Redraw()

def zero_entries(): #Zero the buttons
  global _tran_x, _tran_y, _tran_z, _rot_x, _rot_y, _rot_z, _size_x, _size_y, _size_z, \
         _boneroll_num, _head_x, _head_y, _head_z, _tail_x, tail_y, _tail_z
  
  _tran_x.val = 0.0000 
  _tran_y.val = 0.0000
  _tran_z.val = 0.0000
  _rot_x.val = 0.0000
  _rot_y.val = 0.0000
  _rot_z.val = 0.0000
  _size_x.val = 0.0000
  _size_y.val = 0.0000
  _size_z.val = 0.0000
  _head_x.val = 0.0000
  _head_y.val = 0.0000
  _head_z.val = 0.0000
  _tail_x.val = 0.0000
  _tail_y.val = 0.0000
  _tail_z.val = 0.0000
  _boneroll_num.val = 0.0
      
##---------------File browsing (Menu and listbox)------------------------------------------------
#----Fills the poseList for menu and listbox browsing, fills loadlist for menu-------  
def list_browser(path):  
  global LibPath, loadlist, poseList, poseStart, posePages, poseLimit, foldTog, boxList  
  poseList = []
  puplist = ""
  if LibPath == "": # or ListTabTog == 1:
    poseStart = 0
    posePages = 0
  if os.path.exists(sys.dirname(path)): path = sys.dirname(path)
  else: path = sys.expandpath('//') 
  LibPath = path   
  
  for posefile in os.listdir(LibPath):
    ext = os.path.splitext(posefile)[1]
    if ext == ".blend" or ext == ".bPose" or\
       ext == ".pz2" or ext == ".p2z" or ext == ".hd2" or ext == ".hdz":
      poseList.append(sys.basename(posefile))
         
  if poseList == []:
    if ListTabTog == 0: 
      nothing = PupMenu("No files found %t|Please browse to select a different folder.")    
      LibPath = ""
    if ListTabTog == 1:      
      poseList.append("No poses found.")
      poseList.append("Select a new folder.")
  if ListTabTog == 1: boxList = poseList #Fill the listbox to pass to gui
            
  if poseList != []:    
    poseList.sort()        
    poseTotal = len(poseList) #Total number of poses in folder
    poseSplit = int(math.ceil(poseTotal/poseLimit)) #Number of split listings   
    path = os.path.split(path)[1]
    if poseTotal > poseLimit: path += "(%i)" %(posePages + 1)    
    puplist += "Poses in: %s %%t|Browse for a new folder %%x1|" %(path)
    for i in range(poseStart, min(poseStart + poseLimit, poseTotal)):      
      puplist += "%s %%x%i|" %(sys.basename(poseList[i]), i + 2)
      if i == poseStart + poseLimit - 1 or i == poseTotal -1:      
        if poseTotal > poseLimit:
          if poseStart/poseLimit < poseSplit:            
            puplist += "Forward (%i more poses) %%x%i" %(poseTotal-(poseStart + poseLimit),poseTotal + 10)
            if poseStart == 0:              
              puplist += "|List part %i of %i %%x%i" %(posePages + 1, poseSplit + 1, poseTotal + 11)
            if poseStart > 0: puplist += "|Back (%i previous poses) %%x%i" %(poseStart, poseTotal + 11)
          if posePages == poseSplit:            
            puplist += "List part %i of %i %%x%i" %(posePages + 1, poseSplit + 1, poseTotal + 10)           
            puplist += "|Back (%i previous poses) %%x%i" %(poseStart, poseTotal + 11)
                
    loadlist = puplist
    
#-----Menu browser file loading--------------------
def list_select(selected):  
  global poseStart, LibPath, loadlist, posePages, poseLimit, boxList
  poseTotal = len(poseList)  
  poseSplit = int(math.ceil(poseTotal/poseLimit))  
  pageTotal = min((posePages + 1) * poseLimit, poseTotal)   
  if selected == 1: #One is always 'browse for new folder'
    loadlist = "Pose list browser %t|Browse for a new folder %x1" 
    LibPath = ""    
    Window.FileSelector(list_browser, 'Locate pose folder:')
    
  if selected > poseTotal + 5: #Set arbitrarily high      
    if selected == poseTotal + 10 and posePages != poseSplit: #Total plus 10 is always forward
      poseStart += poseLimit        
      posePages += 1        
    if selected == poseTotal + 11 and poseStart != 0: #Total plus 11 is always back
      poseStart -= poseLimit      
      posePages -= 1     
    LibPath = sys.join(LibPath, sys.basename(Blender.Get('filename')))
    list_browser(LibPath) 
  if selected > 1 and selected <= pageTotal + 1 and selected < poseTotal + 2:
    filepath = sys.join(LibPath, poseList[selected - 2])      
    #load_pose(filepath)
    if sys.exists(filepath) == 1:
      ext = os.path.splitext(filepath)[1]
      if ext == ".pz2" or ext == ".p2z" or ext == "hd2" or ext == ".hdz": load_poser_pose(filepath)
      else:
        if openBatch == 0: load_pose(filepath) #single pose
        elif openBatch == 1: open_batch(filepath) #Animated batch support
      print "Menu browse file:", filepath
    else: error = PupMenu("Error %t|Selected path is not valid.")
  #print selected  
#-----Folder browsing with listbox-------------------------
def folder_browse(path):
  global LibPath, poseList  
  poseList = []
  if path == "": path = sys.expandpath('//') #Start in current .blend folder    
  if path.endswith(sys.sep) and path != os.path.splitdrive(path)[0] + sys.sep: path = path[:-1] #Strip trailing sep symbol
  LibPath = path
  
  for folder in os.listdir(LibPath):      
    if sys.exists(sys.join(LibPath, folder)) == 2:
      poseList.append(sys.basename(folder))     
    
  if poseList != []: poseList.sort()
  poseList.insert(0,"Up a level")  
  #print "folder", LibPath, poseList
#---------search for poses in folder (pose tab search button)----------------------
def pose_search():
  global boxPoses, batchLoad, openBatch, LibTog, saveBatch, OptPose
  name = PupStrInput("Name of pose:", "", 50) #get a name for the new bone
  if name != None and name != "":
    if LibPath != "" and poseList != []: #if a folder is selected
      boxPoses = []
      if name[-1:] == "*": prog = re.compile(name[:-1],re.I) #wildcards          
      else: prog = re.compile(name)#case sensitive          
      for pose in poseList:
        result = prog.search(pose)
        if name == pose or result != None:
          boxPoses.append(pose)
      if len(boxPoses) > 1:
        batchLoad = 1
        openBatch = 1 #batch loading toggle
        LibTog = 1 #load options menu toggle
        saveBatch = 1 #batch save toggle
        OptPose = 1 #save options menu toggle
  
##---------Listbox handling-------------------------------------------------
#-----------------Draws contents of listbox-----------------------  
def listbox(box_l, box_r, box_b, box_t, text_h, line_top, line_bot, multi, text_offset, armname):  
  global boxList, boxPoses, boxBones, in_box, lastBone, boxFrames, curFrame
  
  if ListTabTog == 1: box_select = boxPoses #Multi-tasking with multiple lists 
  elif BonesTabTog == 1: box_select = boxBones
  elif AnimTabTog == 1: box_select = boxFrames
    
  glColor3f(0.0,0.0,0.0) #Text color = black
  
  for line in range(line_top,min(line_bot,len(boxList))):      
    text_l = (box_t-text_h-(line*text_h))+line_top*text_h #Line position
    glRasterPos2i(box_l+8, text_l)#Draw position
    #---Draw listbox contents--------        
    if mouse_y > text_l and mouse_y < text_l+text_h and in_box == 1: #Select current line      
      if box_select.count(boxList[line]) == 0:
        if multi == 0 and len(box_select) == 0:
          box_select.append(boxList[line])
        elif multi == 1:
          box_select.append(boxList[line])
          lastBone = boxList[line] #Assign active user selection (always most recent) in multi-select mode
        elif multi == 2: #multiple select w/out active assignment
          box_select.append(boxList[line])
          
      elif box_select.count(boxList[line]) != 0: #Remove w/ click during mutliple use
        if multi != 0:
          box_select.remove(boxList[line])
          if multi == 1:
            if len(box_select) >= 1: lastBone = box_select[len(box_select)-1] #Assign last item in list as active selection
            if len(box_select) == 0: lastBone = "" #Clear active selection if list is empty
          
      if multi != 0: in_box = 0 #This needs to be reset to prevent looping and display errors w/ wheelmouse
      
    elif mouse_y > text_l and mouse_y < text_l+text_h and in_box == 2: #right click
      if AnimTabTog == 1:
        Blender.Set("curframe",int(boxList[line])) #Make 3d window current frame equal clicked frame number
        curFrame = int(boxList[line])
        if NumTabTog == 1: entry_values() #refresh the numbers tab listings
        in_box = 0
      
    if box_select.count(boxList[line]) != 0: #selection boxes     
      glColor3f(1.00, 1.00, 0.00)#yellow      
      glRecti(box_l + 1, text_l-text_h/5, box_r - 1, text_l + 4*text_h/5)#draw selection box      
      if multi == 1: #Special handling for active selection with multiple (also a draw event in box_click)
        if boxList[line] == lastBone:
          glColor3f(1.00, 0.60, 0.60)#red for active selection
          glRecti(box_l + 1, text_l-text_h/5, box_r - 1, text_l + 4*text_h/5)#draw selection box        
       
    glColor3f(0.0,0.0,0.0)#restore text color
    lineText = boxList[line]    
    if BonesTabTog == 1 and boneSort == 1: #indentation with tree view
      space = "." #character used for display spacing 
      level = listTree[line]#[1] #number of spaces (depth of bone in hierarchy tree)
      #lineText = str(level) + " " + (space*level) + lineText
      lineText = "%s%s" %((space*level),lineText)
    if GetStringWidth(lineText) > (box_r-box_l)-10: #trim lines which are too long        
      for i in range(len(lineText),0,-1):
        if GetStringWidth(lineText[:i]) < (box_r-box_l)-10:
          Text(lineText[:i])
          break
    else: Text(lineText) #Draw unaltered line    
    #----Line numbers and headers------------
    if numList == 1:
      glRasterPos2i(box_l-15, text_l)#Draw line numbers
      Text(str(line+1),"tiny")#Start from one
      
  glColor3f(0.0,0.0,0.0) #Draw headers
  glRasterPos2i(box_l, box_t+(text_offset/2))
  if ListTabTog == 1:
    if foldTog == 0: #Pose browse mode      
      Text("Poses in: %s (%i)" %(os.path.split(LibPath)[1],len(boxList)), "small")      
    if foldTog == 1: #Folder browse mode
      path = LibPath      
      if path != os.path.splitdrive(path)[0] + sys.sep: path = os.path.split(path)[1]      
      Text("Folders in: %s (%i)" %(path, len(boxList)), "small")
    boxPoses = box_select
  elif BonesTabTog == 1:
    if armname != "": #If there's an armature selection      
      Text("bones in %s: %i" %(armname,len(boxList)), "small")
      boxBones = box_select
  elif AnimTabTog == 1:
    boxFrames = box_select  

#-----------------Mouse click interaction with listbox-------------------
def box_click(double, right):  
  global box_l, box_r, box_t, box_b, mouse_y, in_box, boxPoses, box_select 
  in_box = 0
  #Find out if we're in the box-------------------------
  for win in range(0,len(Window.GetScreenInfo())): #Find out if we're inside the box
    win1 = Window.GetScreenInfo()[win] #Blender screen arrangement       
    if win1['type'] == 14: #script window type is 14        
      l = win1['vertices'][0] + box_l
      r = win1['vertices'][0] + box_r
      t = win1['vertices'][1] + (box_b-8)
      b = win1['vertices'][1] + (box_t+3)
      #wx = win1['vertices'][0]
      wy = win1['vertices'][1]
  
  if Window.GetMouseCoords()[0] > l and Window.GetMouseCoords()[0] < r and \
     Window.GetMouseCoords()[1] > t and Window.GetMouseCoords()[1] < b:    
    mouse_y = Window.GetMouseCoords()[1]-wy #We only need mouse y value to find the clicked line
    
    if multi == 0:  box_select = [] #Limit list of selected items to 1     
    if right == 0: in_box = 1 #Allows listbox fxn to respond
    elif right == 1: in_box = 2 #right-click handling
    
    #print "INSIDE BOX!", Window.GetMouseCoords(), Window.GetMouseCoords()[0]-wx, Window.GetMouseCoords()[1]-wy
    
    #Process information returned by the box-------------------------------
    if multi == 0: #single mode
      if double == 0:
        boxPoses = []        
      if double == 1:
        box_run() #Run the activation function if double click
        boxPoses = box_select
        box_select = []    
    Draw()#Display results of interaction with box
    if multi == 1:
      if NumTabTog == 1: entry_values() #refresh numerical entry values       
      Draw() #Refresh to display active selection line correctly (multiple mode)
              
#---------Runs the click activation for listbox when multiple selections are disabled--------      
def box_run():  
  global boxPoses, LibPath, in_box, box_select, boxList, _partial_slider, partPose   
  if in_box == 1 and len(boxPoses) == 1:  
    #-----------Poses  
    if foldTog == 0:
      partPose = _partial_slider.val #pass the partial pose value to load_pose      
      filepath = sys.join(LibPath, poseList[poseList.index(boxPoses[0])])#With single selection, index 0 is always selected
      if sys.exists(filepath) == 1:
        if os.path.splitext(filepath)[1] == ".pz2" or os.path.splitext(filepath)[1] == ".p2z": load_poser_pose(filepath)
        else:
          if openBatch == 0: load_pose(filepath) #single pose
          elif openBatch == 1: open_batch(filepath) #Animated batch support
        print "listbox file:", filepath
      else: error = PupMenu("Error %t|Selected path is not valid.")
      boxPoses = []
      if NumTabTog == 1: entry_values() #refresh numerical entry values
    #----------Folders    
    if foldTog == 1 and boxPoses[0] in boxList:      
      if LibPath.endswith(sys.sep): LibPath = LibPath[:-1]
      if boxList.index(boxPoses[0]) == 0: #if selection is "Up a level"        
        LibPath = LibPath.rsplit(sys.sep,1)[0]         
        if LibPath == os.path.splitdrive(LibPath)[0]: LibPath += sys.sep  #Special handling for root (Windows-specific?)      
      if boxList.index(boxPoses[0]) > 0: LibPath = sys.join(LibPath, boxPoses[0])      
      if os.path.isdir(LibPath):
        folder_browse(LibPath)
      else: print "Clicked selection is not a directory", LibPath
      boxList = poseList #fill the display list    
      in_box = 0 #re-set to prevent looping
      Draw() #Folder browser changes list and display needs to be refreshed

#----------------Selection options for the bone listbox----------------
def bonebox_menu():  
  global boxBones, boxList, TrimPose, lastBone, boneSort  
  menu = PupMenu("Bone listbox selection options: %t|Select All %x1|Deselect All %x2|Invert current selection %x6\
                  |Select using partial save group %x3|Remove using partial save group %x4\
                  |Change Partial group selection %x5|Add immediate children of active selection %x7\
                  |Add all children of active selection %x8|Remove immediate children of active selection %x9\
                  |Remove all children of active selection %x10|Toggle sorting: alphabtically/hierarchically %x11\
                  |Cancel %x11")
  
  if menu == 1: #Add all
    if Object.GetSelected() != []:
      ob = Object.GetSelected()[0]  
      if ob and ob.getType() == "Armature":
        pose = ob.getPose()        
        for bone in pose.bones.keys():
          boxBones.append(bone)
        if boneSort == 0: boxBones.sort()          
        elif boneSort == 1: boxBones = sort_hierarchy(boxBones)
        boxBones.reverse() #To make new active selection the first sorted entry in new list
        if len(boxBones) > 0: lastBone = boxBones[len(boxBones)-1] #Active is last item in list
          
  elif menu == 2: #Remove all
    boxBones = []
    lastBone = "" #Clear the active user selection
    
  elif menu == 3 or menu == 4: #Add or remove using partial save group
    if partVar == "" and sideVar == "":
      partSave() #Show the save groups selection pupmenu
    if partVar != "": #Proceed if there is a selected group
      if Object.GetSelected() != []:
        ob = Object.GetSelected()[0]  
        if ob and ob.getType() == "Armature":
          pose = ob.getPose()
          for bone in pose.bones.values():
            screening = Part_Load(bone, 0)
            if menu == 3: #add
              if screening == 1: boxBones.append(bone.name)                            
            elif menu == 4: #remove
              if screening == 1:
                if bone.name in boxBones:                  
                  boxBones.remove(bone.name)
                  if bone.name == lastBone and lastBone in boxBones:
                    boxBones.remove(lastBone) #This needs to be specified or lastBone is retained
                    lastBone = "" #Clear the active user selection if it is removed
                  if lastBone == "" and len(boxBones) > 0:
                    if boneSort == 0: boxBones.sort()          
                    elif boneSort == 1: boxBones = sort_hierarchy(boxBones)
                    boxBones.reverse()
                    lastBone = boxBones[len(boxBones)-1]            
          if menu == 3: #sort the added listings             
            if boneSort == 0: boxBones.sort()          
            elif boneSort == 1: boxBones = sort_hierarchy(boxBones)              
            boxBones.reverse()
            if lastBone in boxBones:
              boxBones.remove(lastBone)
              boxBones.append(lastBone) #Place active selection at end of list           
            else:
              if len(boxBones) > 0: lastBone = boxBones[len(boxBones)-1] #Active is last item in list
            
                 
  elif menu == 5: #Change partial save group selections   
    partSave()
    if partVar != "": TrimPose = 1 #Set partial group save toggle
    
  elif menu == 6: #Invert selection
    boxList = boxBones
    boxBones = []
    if Object.GetSelected() != []:
      ob = Object.GetSelected()[0]  
      if ob and ob.getType() == "Armature":
        pose = ob.getPose()        
        for bone in pose.bones.keys():
          if bone not in boxList: boxBones.append(bone)
        if boneSort == 0: boxBones.sort()          
        elif boneSort == 1: boxBones = sort_hierarchy(boxBones)
        #print boxBones
        boxBones.reverse() #To make new active selection the first sorted entry in new list
        if len(boxBones) > 0: lastBone = boxBones[len(boxBones)-1] #Active is last item in list
        
  elif menu > 6 and menu < 11: #work with children
    if Object.GetSelected() != []:
      ob = Object.GetSelected()[0]    
      if ob.getType() == "Armature" and len(boxBones) > 0: #There must be a list selection
        AData = ob.getData()                
        bone1 = AData.bones[lastBone] #Last bone selected
        if bone1.hasChildren():
          if menu % 2 != 0: offspring = bone1.children #7, 9 = odd
          elif menu % 2 == 0: offspring = bone1.getAllChildren()#8, 10 = even          
          if menu < 9: #7 & 8 are additive
            for child in offspring: boxBones.append(child.name)
            if boneSort == 0: boxBones.sort()          
            elif boneSort == 1: boxBones = sort_hierarchy(boxBones)
            boxBones.reverse()
            if lastBone in boxBones:
              boxBones.remove(bone1.name) #update to keep last user selection last in list
              boxBones.append(bone1.name)
          elif menu > 8: #9, 10 are subtractive
            for child in offspring:
              if child.name in boxBones:
                boxBones.remove(child.name)
                if child.name == lastBone: lastBone = "" #Clear the active user selection if it is removed
            if boneSort == 0: boxBones.sort()          
            elif boneSort == 1: boxBones = sort_hierarchy(boxBones)
            boxBones.reverse()

  elif menu == 11: boneSort = 1 - boneSort
  
  if boneSort == 0: boxList.sort() #boxList needs to be sorted after sort/reverse for lastBone display (above).
  elif boneSort == 1: boxList = sort_hierarchy(boxList)  
  #boxList = boxBones
  
##---------------------GUI adapted from Blender Cal3D script----------------------
#----------------------Some additions adapted from discombobulator.py------------------

#pose application----------------------------------------------------
realTime = 0 #Toggle variable for applying pose changes to current without loading a file
#load pose type conversion------------------------------------------ 
PoserConvert = 0 #Poser conversion toggle
PoseMat = 0 #Toggle for import conversion of a pose saved using the PoseMatrix option for IK
#save pose type conversion-------------------------------------------
NoRollTog = 0 #Toggle button variable for restmatrix pose save
IKTog = 0 #Toggle button variable for posematrix save
PoserSave = 0 #Toggle button variable for Poser pose export
#pose modifiers------------------------------------------------------
MirrorPose = 0 #Toggle for option to flip sides of a pose R/L
ScreenPose = 0 #Screen pose toggle
screenR = 1 #Load only side R with screening
screenL = 0 #Load only side L with screening
DupPose = 0 #Symmetrical pose toggle variable
dupR = 1 #Symmetrical pose R to L
dupL = 0 #Symmetrical pose L to R
torsoZero = 0 #Option to zero centered parts with symmetrize button.  0 = zero them, 1 = don't.
partPose = 1.0 #Partial load pose slider value
MixPose = 0 #Combine new pose with current toggle variable
MirrorBone = 0 #Toggle for option to flip a bone's own values
#root handling-------------------------------------------------------
bodyscale = 0 #BODY screening toggle
HipPose = 0 #hip screening toggle
hipR = 0 #Screen hip rotation
hipT = 0 #Screen hip translation
addscale = 0 #Additive rotation loading for BODY - will probably remove this.
#partial pose loading------------------------------------------------
IncludePart = 0 #Toggle var to load only partial save group(s) in pose
ExcludePart = 0 #Toggle var to exclude partial save groups when loading a pose
#animation-----------------------------------------------------------
openBatch = 0 #For batch file import to animate from a series of static poses
saveBatch = 0 #For batch file save for animation support
askKey = 0 #Variable for keyframe options. 0 = ask, 1 = yes, 2 = no
batchLoad = 0 #Batch toggle for poses listbox.  Allows multiple selections for loading of non-sequential frames
batchFrames = 0 #Batch toggle for frames listbox.  Screen load/save using selected frame numbers
animlen = Blender.Get('endframe') - (Blender.Get('staframe')-1) #length of the current animation
curFrame = Blender.Get("curframe") #Blender.Get calls seem to cause memory jumps, so minimize the calls w/ a global
#save pose types------------------------------------------------------
EmpPose = 0 #Save pose from empties toggle
IKPose = 0 #Toggle to save a poseMatrix pose, including IK/constraints.
NoRollPose = 0 #Toggle to save a "PoserConvert" compatible pose, which excludes embedded bone roll data
#symmetry notation-----------------------------------------------------
suff = 0 #Symmetry notation using prefix
pref = 1 #Symmetry notation using suffix
right = "r" #"R" = right(-x) prefix/suffix
left = "l" #"L" = left(+x) prefix/suffix
flipbone = "" #The bone which flips
lenR = len(right) #length of right prefix/suffix
lenL = len(left) #length of left prefix/suffix
right_alt = "capeR" #Specify for non-standard part handling
left_alt = "capeL" #alt types currently only work with prefixes
flipDict = {} #Dict to store symmetrical bone pairs
#save modifiers----------------------------------------------------------
No_Trans = 0 #Toggle variable to exclude translation w/ pose save
No_Scale = 0 #Toggle variable to exclude scaling with pose save
#save and load option menus-----------------------------------------------
LibTog = 0 #Variable for pose load options toggle
OptPose = 0 #Toggle for export options on or off (toggle display)
#partial group handling---------------------------------------------------
TrimPose = 0 #Partial pose menu toggle for save
GroupTog = 0 #Partial pose menu toggle for load
partVar = "" #Stores partial group selection
sideVar = "" #Stores selected side with symmetrized groups 
root = None #root part
rootchild = None #Single child of root if root has only one child (for special handling as w/ cr2 BODY and hip).
groupsDict = {} #Store the groups
maxVal = 2 #Value by which total number of bones will be divided when determining group selection maximum size
minVal = 3 #Minimum number of children and subchildren a bone can have for group selection minimum size
totalBones = 1 #Total count of bones in armature (minus the root itself)
#Bone roll----------------------------------------------------------------
oldRolls = {} #Dictionary to cache old values for in-session restoration
#menu and listbox file browser handling-------------------------------------
LibVar = 0 #Toggle var to select or deselect Pupmenu file loading.  1 is menu browsing, 0 is file selector browsing.
LibPath = "" #Path to pose folder
loadlist = "Pose list browser %t|Browse for a new folder %x1" #Variable to store menu for list browsing
LibSave = 0 #Option to save to selected list loading folder
poseList = [] #List of poses in target folder
poseStart = 0 #Starting number for each displayed sub-list
posePages = 0 #Number of sub-lists into which list of poses is split
poseLimit = 54 #Max number of poses per displayed list (user-configurable in load menu)
#page tabs-----------------------------------------------------------------
LoadTabTog = 1 #Load tab
SaveTabTog = 0 #Save tab
MiscTabTog = 0 #Miscellaneous options tab
ListTabTog = 0 #Listbox tab
BonesTabTog = 0 #Bone list tab
NumTabTog = 0 #Numerical entry tab
AnimTabTog = 0 #Animation frames listbox tab
dualList = 0 #Dual display toggle for page tabs
#listbox------------------------------------------------------------------
line_top = 0 #listbox top line
line_bot = 0 # listbox bottom line
box_h = 0 #Listbox height (calculated later; global to pass limits to mouse wheel event)
box_select = [] #Active list of selected listbox items
boxList = [] #Active listbox multitasking list.  Stores poseList, bones list, or folders list
boxPoses = [] #Stored listbox pose selections to pass on or restore
boxBones = [] #Stored listbox bone selections to pass on or restore
boxFrames = [] #Stored listbox frames selection for animation i/o and keyframe use
mouse_y = 0 #pass the mouse y coords between click event and listbox gui
in_box = 0 #Screening for inside/outside of box. 0 = out, 1 = in/left click, 2 = in/right click
multi = 0 #multiple selection support flag; 0 = single select, 1 = multiple w/ active, 2 = multiple w/out active 
numList = 1 #Toggle for listbox line numbers
wheelslider = 1 #Toggle listbox scrolling between slider and wheelmouse modes.  Slider = 0, wheelmouse = 1
listScroll = 1 - wheelslider #Start value for pose/file list slider
listScrollB = 1 - wheelslider #Start value for bones list slider
listScrollA = 1 - wheelslider #Start value for animation list slider
foldTog = 0 #Folder browser toggle in pose listbox mode
#Bone listbox selection handling------------------------------------------
bonesLoad = 0 #Toggle option for load/apply screening: 1 = include, 2 = exclude
bonesSave = 0 #Toggle option for save screening: 1 = include, 2 = exclude
lastBone = "" #Last user-selected bone name (the active bones list selection)
boneSort = 1 #Type of sorting for bones list. 0 = alphabetical, 1 = hierarchical
listTree = [] #List for treeview
#Numerical entry--------------------------------------------------------
myKeys = 0 #Keyframe toggle button variable for number tab.
keyType = 0 #Keyframe handling for myKeys toggle. 0 = no keyframes, 1 = keyframe all bones, 2 = keyframe bones list active
lastLen = 0 #Length of lastBone (to configure translation sensitivity)
uniqueRot = 0 #Toggle to use euler.unique() with rot dials (may return very unusual results....)
#General preferences----------------------------------------------------
setprefs = 0 #flag to set preferences at next exit


#gui buttons-------------------------------------------------------------
_partial_slider = Create(1.0) #Slider handling from der_ton's .md5 exporter.
_load_menu = Create(1) #So we can pass the load menu .val parameter
_scroll_slider = Create(0.0) #Listbox slider
_dual_width = Create(240) #dual width number button - to alter button_width and display width
_tran_x = Create(0.0000) #These are numerical entry tab dials
_tran_y = Create(0.0000)
_tran_z = Create(0.0000)
_rot_x = Create(0.0000)
_rot_y = Create(0.0000)
_rot_z = Create(0.0000)
_size_x = Create(0.0000)
_size_y = Create(0.0000)
_size_z = Create(0.0000)
_head_x = Create(0.0000)
_head_y = Create(0.0000)
_head_z = Create(0.0000)
_tail_x = Create(0.0000)
_tail_y = Create(0.0000)
_tail_z = Create(0.0000)
_boneroll_num = Create(0.0)

#These are all buttons which don't use .val as their linked values
_quit_button = Create(0)
_loadtab_toggle = Create(0)
_savetab_toggle = Create(0)
_misctab_toggle = Create(0)
_numtab_toggle = Create(0)
_listtab_toggle = Create(0)
_bonetab_toggle = Create(0)
_animtab_toggle = Create(0)
_savelist_button = Create(0)
_folder_toggle = Create(0)
_folder_button = Create(0)
_menu_button = Create(0)
_load_button = Create(0)
_load_menu = Create(0)
_apply_button = Create(0)
_zero_button = Create(0)
_lib_toggle = Create(0)
_group_toggle = Create(0)
_poserL_toggle = Create(0)
_posematL_toggle = Create(0)
_flip_toggle = Create(0)
_flipbone_toggle = Create(0)
_screen_toggle = Create(0)
_screen_R = Create(0)
_screen_L = Create(0)
_dup_toggle = Create(0)
_dup_R = Create(0)
_dup_L = Create(0)
_mix_toggle = Create(0)
_save_button = Create(0)
_part_toggle = Create(0)
_noTrans_toggle = Create(0)
_noScale_toggle = Create(0)
_opt_toggle = Create(0)
_poserS_toggle = Create(0)
_posematS_toggle = Create(0)
_suffBone_button = Create(0)
_prefix_toggle = Create(0)
_suffix_toggle = Create(0)
_wheel_toggle = Create(0)
_number_toggle = Create(0)
_dual_toggle = Create(0)
_menuA_button = Create(0)
_batchload_toggle = Create(0)
_batchload_button = Create(0)
_batchframes_toggle = Create(0)
_IK_button = Create(0)
_tools_button = Create(0)
_reg_button = Create(0)
_poserSave_toggle = Create(0)
_boneSearch_button = Create(0)
_boneGrab_button = Create(0)
_poseSearch_button = Create(0)

#Button event constants---------------------------
EVENT_NOEVENT = 0
EVENT_QUIT = 1
EVENT_SAVE = 2
EVENT_LOAD = 3
EVENT_CONVERT = 4
EVENT_FLIP = 5
EVENT_SCREEN = 6
EVENT_DUP = 7
EVENT_SCR_R = 8
EVENT_SCR_L = 9
EVENT_DUP_R = 10
EVENT_DUP_L = 11  
EVENT_PART = 12  
EVENT_MIX = 13
EVENT_EMP = 14  
EVENT_ZERO = 15
EVENT_SUFFPREF = 16
EVENT_PREF = 17
EVENT_SUFF = 18
EVENT_APPLY = 19
EVENT_NOTRANS = 20
EVENT_NOSCALE = 21
EVENT_POSEMAT = 22
EVENT_BONE = 23
EVENT_LIB = 24
EVENT_TAB_L = 25
EVENT_TAB_S = 26
EVENT_TAB_M = 27
EVENT_TAB_T = 28
EVENT_TAB_B = 29
EVENT_LISTSCROLL = 30
EVENT_WHEEL = 31
EVENT_NUMB = 32
EVENT_FOLDER = 33
EVENT_BONEMENU = 34
EVENT_GROUP = 35
EVENT_NOROLL = 36
EVENT_IK = 37
EVENT_DUAL = 38
EVENT_TAB_N = 39
EVENT_ENTRY = 40
EVENT_KEYF = 41
EVENT_EDIT = 42
EVENT_UNIQUE = 43
EVENT_LISTLOAD = 44
EVENT_FLIPBONE = 45
EVENT_TAB_A = 46
EVENT_ANIMMENU = 47
EVENT_BATCHTOG = 48
EVENT_BATCHLOAD = 49
EVENT_BATCHFRAMES = 50
EVENT_IKTOG = 51
EVENT_TOOLS = 52
EVENT_REG = 53
EVENT_POSERSAVE = 54
EVENT_BONESEARCH = 55
EVENT_BONEGRAB = 56
EVENT_POSESEARCH = 57
  
def colorbox(x,y,xright,bottom): #from discombobulator.py
  glColor3f(0.75, 0.75, 0.75)  
  glRecti(x + 1, y + 1, xright - 1, bottom - 1)
  

def gui():
  
  global _partial_slider, _load_menu, _scroll_slider, _dual_width, _tran_x, _tran_y, \
         _tran_z, _rot_x, _rot_y, _rot_z, _size_x, _size_y, _size_z, _head_x, _head_y, \
         _head_z, _tail_x, _tail_y, _tail_z, _boneroll_num, _quit_button, _loadtab_toggle, \
         _savetab_toggle, _misctab_toggle, _numtab_toggle, _listtab_toggle, _bonetab_toggle, \
         _savelist_button, _folder_toggle, _folder_button, _menu_button, _load_button, \
         _load_menu, _apply_button, _zero_button, _lib_toggle, _group_toggle, _poserL_toggle, \
         _posematL_toggle, _flip_toggle, _flipbone_toggle, _screen_toggle, _screen_R, _screen_L, \
         _dup_toggle, _dup_R, _dup_L, _mix_toggle, _save_button, _part_toggle, _noTrans_toggle, \
         _noScale_toggle, _opt_toggle, _poserS_toggle, _posematS_toggle, _suffBone_button, \
         _prefix_toggle, _suffix_toggle, _wheel_toggle, _number_toggle, _dual_toggle, \
         _animtab_toggle, _menuA_button, _batchload_toggle, _batchload_button, _batchframes_toggle, \
         _IK_button, _tools_button, _reg_button, _poserSave_toggle, _boneSearch_button, _boneGrab_button,\
         _poseSearch_button
  global line_top, line_bot,  box_l, box_r, box_t, box_b, box_h, mouse_y, multi, \
         box_select, boxList, listScroll, numList, text_h, boxPoses, boxBones, wheelslider, \
         listScrollB, foldTog, lastBone, button_width, myKeys, lastLen, uniqueRot, \
         boxFrames, listScrollA, animlen, boneSort, curFrame
  
  button_left = 20
  #button_width = 240 #(default)
  button_width = _dual_width.val
  button_height = 15
  text_offset = 5
  button_space = button_height + button_height/4
  
  #Various functions-------------------------------------------------------
  colorbox(button_left - 8, button_height/4, button_width + 28, 2*button_space+text_offset)
  glColor3f(0.0,0.0,0.0)
  glRasterPos2i(button_left + (button_width/2 - GetStringWidth("Blender Pose Handler V.1","small")/2),
                button_space/2-text_offset/2)
  Text("Blender Pose Handler V.1", "small")
  _quit_button = Button("Quit", EVENT_QUIT, button_left, button_space, button_width, button_height, "Exit from script")
  
  #Tab toggles------------------------------------------------------------
  colorbox(button_left - 8, 23*button_space-text_offset, button_width + 28, 25*button_space+text_offset)  
  _loadtab_toggle = Toggle("Apply", EVENT_TAB_L, button_left, 24*button_space, button_width/4, button_height,
                           LoadTabTog, "Select to view pose options for loading or real-time application.")
  _savetab_toggle = Toggle("Save", EVENT_TAB_S, button_left+button_width/4, 24*button_space, button_width/4,
                           button_height, SaveTabTog, "Select to view pose options for saving.")
  _misctab_toggle = Toggle("Options", EVENT_TAB_M, button_left+(2*button_width/4), 24*button_space, button_width/4,
                           button_height, MiscTabTog,
                           "Select to view options for bone roll, symmetry notation handling, and IK.")
  _numtab_toggle = Toggle("Number", EVENT_TAB_N, button_left+(3*button_width/4), 24*button_space, button_width/4,
                          button_height, NumTabTog, "Numerical entry posing for bones list selection.")
  _listtab_toggle = Toggle("Poses", EVENT_TAB_T, button_left, 23*button_space, button_width/3, button_height,
                           ListTabTog, "Use listbox file browser.")
  _bonetab_toggle = Toggle("Bones", EVENT_TAB_B, button_left+(button_width/3), 23*button_space, button_width/3,
                           button_height, BonesTabTog, "Work with list of bones in the selected armature.")
  _animtab_toggle = Toggle("Frames", EVENT_TAB_A, button_left+(2*button_width/3), 23*button_space, button_width/3,
                          button_height, AnimTabTog, "Work with list of frames in current animation.")
  
  #----Listboxes----------------------------------------------------------    
  if ListTabTog == 1 or BonesTabTog == 1 or AnimTabTog == 1:
    
    #These are the GUI draw elements of the empty listbox.  Listbox contents are in the listbox() fxn.
    #Mouse interaction is in box_click().
    #Change these to alter drawn position of listbox, slider, and listbox contents
    num_offset = 0
    if numList == 1: num_offset = 15 #GUI re-spacing w/ line numbers on/off
    dual_offset = 0
    if dualList == 1: dual_offset = button_left+button_width+8 #Dual tab display spacing
    text_h = 12 #text height (10 plus spacing)
    box_l = button_left+num_offset+dual_offset #box left
    box_r = 4*(button_width/5)+num_offset+dual_offset #box right
    box_b = 5 * button_height #box bottom
    box_t = 26 * button_height #box top   
    box_h = ((box_t+3)-(box_b-8))/text_h #box height  
    multi = 0 #multiple selection toggle
    armname = "" #Name of selected armature  
    
    colorbox(box_l-num_offset-8, 3*button_space-10, dual_offset+button_width+28, box_t+button_height)#background    
    but_w = button_width/4 #re-spacing w/ list number toggle
    but_l = (3*but_w)+dual_offset      
    if numList == 1:
      but_w = button_width/5
      but_l = (4*but_w)+dual_offset
      
    if ListTabTog == 1:#-----file---------------------------------------------
      multi = 0
      if batchLoad == 1: multi = 2 #multiple selection for animation import
      armname = ""
      #For the pose listbox, the boxList is filled in:
      #--EVENT_FOLDER for _folder_toggle (on and off)
      #--list_browser() for _folder_button (and for list refresh with _save_button)
      #--box_run() for folder click.
      #--get_from_ini() for startup if ListTabTog == 1 and preferred settings are used
      
      _savelist_button = Button("Save", EVENT_SAVE, button_left+but_l, 8*button_height, but_w, button_height, 
                              "Save pose or batch of poses to current folder")      
      _folder_toggle = Toggle("Folder", EVENT_FOLDER, button_left+but_l, 10*button_height, but_w, button_height, foldTog, 
                              "Use listbox to locate a new folder.")
      _folder_button = Button("Browse", EVENT_LISTLOAD, button_left+but_l, 12*button_height, but_w, button_height, 
                              "Locate a new folder using the file browser")
      _batchload_toggle = Toggle("Batch", EVENT_BATCHTOG, button_left+but_l, 18*button_height, but_w, button_height,
                                 batchLoad, "Toggle multiple file selections, batch load, and batch save for animation.")
      _poseSearch_button = Button("Search", EVENT_POSESEARCH, button_left+but_l, 14*button_height, but_w, button_height, 
                              "Search by name for pose files in current folder.")
      if batchLoad == 1:
        _batchload_button = Button("load", EVENT_BATCHLOAD, button_left+but_l, 16*button_height, but_w, button_height, 
                                "Load multiple selections as animated batch.")     
      else: _batchload_button = Create(0)
      if wheelslider == 0:
        _scroll_slider = Slider("list top: ", EVENT_LISTSCROLL, box_l, (3*button_space)-8, box_r-box_l, button_height,
                                listScroll, 1, max((len(boxList)-box_h)+1,0), 0, "Scroll the listbox with the slider bar.")
    elif BonesTabTog == 1:#---bones-------------------------------------------------
      multi = 1
      #For the bones listbox, this is the only time the boxlist is filled
      if armname == "": #Get armature data only if boxList is empty        
        if Object.GetSelected() != []:
          ob = Object.GetSelected()[0]  
          if ob.getType() == "Armature":
            armname = ob.name
            pose = ob.getPose()
            if len(boxList) != len(pose.bones.keys()) or (len(boxList) == 0 and armname != ""): #refill the list              
              boxList = pose.bones.keys()
              if boneSort == 0: boxList.sort()
              elif boneSort == 1: boxList = sort_hierarchy(boxList)
          else:
            boxList = []
            armname = ""
      if NumTabTog == 0:      
        _menu_button = Button("Select", EVENT_BONEMENU, button_left+but_l, 8*button_height, but_w, button_height, 
                                "Selection options for bones listbox")
        _IK_button = Button("IK", EVENT_IKTOG, button_left+but_l, 10*button_height, but_w, button_height, 
                                "IK handling options for bones listbox")
        _tools_button = Button("Tools", EVENT_TOOLS, button_left+but_l, 12*button_height, but_w, button_height, 
                                "Edit mode tools to aid IK construction - warning: these will alter your armature.")
        _boneSearch_button = Button("Search", EVENT_BONESEARCH, button_left+but_l, 14*button_height, but_w, button_height, 
                                "Search by name for bones in bones list and 3d view.")
        _boneGrab_button = Button("Grab", EVENT_BONEGRAB, button_left+but_l, 16*button_height, but_w, button_height, 
                                "Grab selected bones list bones in the 3D view.")
      if wheelslider == 0: 
        _scroll_slider = Slider("list top: ", EVENT_LISTSCROLL, box_l, (3*button_space)-8, box_r-box_l, button_height,
                                listScrollB, 1, max((len(boxList)-box_h)+1,0), 0, "Scroll the listbox with the slider bar.")
    elif AnimTabTog == 1: #----frames--------------------------------------------------------
      multi = 2
      armname = ""             
      frame = curFrame#Blender.Get("curframe")      
      if frame > animlen: animlen += frame - animlen #extend the displayed list if user goes beyond the limit
      #This is the only time boxList is filled for the frames list
      if len(boxList) != animlen: #Fill the frames list       
        boxList = []        
        for f in range(Blender.Get('staframe'), animlen+1):
          boxList.append(str(f))

      _menuA_button = Button("Select", EVENT_ANIMMENU, button_left+but_l, 8*button_height, but_w, button_height, 
                                "Selection options for frames listbox")
      _batchframes_toggle = Toggle("Batch", EVENT_BATCHFRAMES, button_left+but_l, 18*button_height, but_w, button_height,
                                 batchFrames, "Screen batch load/save using selected frames.")
      if wheelslider == 0: 
        _scroll_slider = Slider("list top: ", EVENT_LISTSCROLL, box_l, (3*button_space)-8, box_r-box_l, button_height,
                                listScrollA, 1, max((len(boxList)-box_h)+1,0), 0, "Scroll the listbox with the slider bar.")
      glColor3f(0.0,0.0,0.0) 
      glRasterPos2i(box_l-10, box_t+(text_offset/2))      
      Text("Frame count: %i  Current frame: %i" %(animlen, frame), "small")
    #---------wheelmouse vs. slider---------------------------------------------------------    
    if wheelslider == 1: #Label if slider is not in use
      glColor3f(0.0,0.0,0.0)       
      glRasterPos2i(box_l, 3*button_space-text_offset)
      Text("Use mouse wheel to scroll list")
      
    elif wheelslider == 0:        
      if ListTabTog == 1:
        line_top = int(listScroll)-1 #Set top line for box display (slider value minus one to convert to list indices)
      if BonesTabTog == 1:
        line_top = int(listScrollB)-1
      if AnimTabTog == 1:
        line_top = int(listScrollA)-1
    #--------default drawn elements---------------------------------------------  
    glColor3f(1.0,1.0,1.0) #listbox color = white
    box = glRecti(box_l, box_b-text_h, box_r, box_t) #Draw the textbox  
    line_bot = min(line_top + box_h, len(boxList)) #Define the bottom
    
    if ListTabTog == 1 and LibPath == "": boxList = [] #If folder browse w/ file browser was cancelled.
    if boxList != []:      
      listbox(box_l, box_r, box_b, box_t, text_h, line_top, line_bot, multi, text_offset, armname)
    elif boxList == []:
      glColor3f(0.0,0.0,0.0) 
      glRasterPos2i(box_l, box_t+(text_offset/2))      
      if ListTabTog == 1:
        Text("No folder selected.")#Default header for pose list tab
      if BonesTabTog == 1:
        if armname == "": Text("No armature selected.")#Default header for bones list tab
      #Frames list will always be filled... unless a zero length animation is possible?        
  #------Pages-------------------------------------------------------
  if LoadTabTog == 1:#Load tab
    #Load functions------------------------------------------------------
    colorbox(button_left - 8, 19*button_space - 8, button_width + 28, 22*button_space)#options
    colorbox(button_left - 8, 12*button_space - 8, button_width + 28, 18*button_space)#modifier
    colorbox(button_left - 8, 8*button_space - 8, button_width + 28, 11*button_space)#conversion
    colorbox(button_left - 8, 3*button_space - 8, button_width + 28, 7*button_space)#loading
    glColor3f(0.0,0.0,0.0)
    glRasterPos2i(button_left, 21 * button_space + text_offset)
    Text("Pose application options:")
    glRasterPos2i(button_left, 17 * button_space + text_offset)
    Text("Pose modifier settings:")
    glRasterPos2i(button_left, 6 * button_space + text_offset)
    Text("Loading and application:")
    glRasterPos2i(button_left, 10 * button_space + text_offset)
    Text("Pose type conversion:")
    glRasterPos2i(button_left, 16 * button_space + text_offset)
    Text("pose load settings:")
    
    if LibVar == 0:     
      _load_button = Button("Load pose from file", EVENT_LOAD, button_left, 3*button_space, button_width, button_height, 
                          "Browse for Pose to Load")    
    elif LibVar == 1: #use if "if" rather than "elif" causes a python24dll error crash bug in 242 (w/ dual ListTabTog)
      glColor3f(0.0,0.0,0.0)
      glRasterPos2i(button_left, 4 * button_space + 3)
      Text("Menu browser:")
      _load_menu = Menu(loadlist, EVENT_LOAD, button_left+(button_width/3), 3*button_space, 2*button_width/3,
                        button_height, _load_menu.val, "Browse for Pose to Load")      
    _apply_button = Button("Apply modifiers to current pose", EVENT_APPLY, button_left, 4*button_space, button_width,
                           button_height, "Apply pose modifier or type conversion settings to current pose.")
    _zero_button = Button("Zero current pose", EVENT_ZERO, button_left, 5*button_space, button_width, button_height, 
                            "Restore all rotation, scaling, and translation settings to rest pose values")  
    _lib_toggle = Toggle("General loading options", EVENT_LIB, button_left, 20*button_space, button_width,
                         button_height, LibTog, "Set options for loading a pose.")
    _group_toggle = Toggle("Partial group loading options", EVENT_GROUP, button_left, 19*button_space, button_width,
                           button_height, GroupTog, "Set screening options to exclude some bones when loading a pose.")
    #Pose types
    _poserL_toggle = Toggle("Restmatrix pose ('No Roll')", EVENT_CONVERT, button_left, 9*button_space, button_width,
                           button_height, PoserConvert, "Convert a restmatrix/no roll pose or a combo type pose.")        
    _posematL_toggle = Toggle("Posematrix pose ('IK')", EVENT_POSEMAT, button_left, 8*button_space, button_width,
                             button_height, PoseMat, "Convert a pose saved with IK/constraints (poseMatrix) option.")  
    #Symmetry handling
    _flip_toggle = Toggle("Flip Pose", EVENT_FLIP, button_left, 12*button_space, button_width/2, button_height,
                          MirrorPose, "Mirror the pose (reverse left and right).")
    _flipbone_toggle = Toggle("Flip Bones", EVENT_FLIPBONE, button_left+(button_width/2), 12*button_space, button_width/2,
                              button_height, MirrorBone, "Flip each bone over itself.")
    _screen_toggle = Toggle("Screen Right/Left", EVENT_SCREEN, button_left, 13*button_space, (button_width/10)*8,
                            button_height, ScreenPose, "Apply only one side of pose.")
    _screen_R = Toggle("R", EVENT_SCR_R, button_left+(button_width/10)*8, 13*button_space, button_width/10,
                       button_height, screenR, "Use only right side of pose.")
    _screen_L = Toggle("L", EVENT_SCR_L, button_left+(button_width/10)*9, 13*button_space, button_width/10,
                       button_height, screenL, "Use only left side of pose.")
    _dup_toggle = Toggle("Symmetry Right/Left", EVENT_DUP, button_left, 14*button_space, (button_width/10)*8,
                         button_height, DupPose, "Load one side of pose and mirror it to second side.")
    _dup_R = Toggle("R", EVENT_DUP_R, button_left+(button_width/10)*8, 14*button_space, button_width/10,
                    button_height, dupR, "Copy right side to left side.")
    _dup_L = Toggle("L", EVENT_DUP_L, button_left+(button_width/10)*9, 14*button_space, button_width/10,
                    button_height, dupL, "Copy left side to right side.") 
    #Combinatory options
    if dualList == 0: sliderlabel = "Pose intensity:"
    elif dualList == 1: sliderlabel = "Intensity:"
    _partial_slider = Slider(sliderlabel, EVENT_NOEVENT, button_left, 15*button_space, button_width, button_height,
                             _partial_slider.val, 0.01, 2.0, 0, "Adjust the amount of effect for the selected pose.")
    _mix_toggle = Toggle("Combine with current pose", EVENT_MIX, button_left, 16*button_space, button_width,
                         button_height, MixPose, "Add loaded pose data to current pose (mix).")  
  
  if SaveTabTog == 1:#Save tab
    #Save functions-------------------------------------------------------
    colorbox(button_left - 8, 19*button_space - 8, button_width + 28, 22*button_space)#options
    colorbox(button_left - 8, 16*button_space - 8, button_width + 28, 18*button_space)#modifier
    colorbox(button_left - 8, 11*button_space - 8, button_width + 28, 15*button_space)#conversion
    colorbox(button_left - 8, 8*button_space - 8, button_width + 28, 10*button_space)#save
    glColor3f(0.0,0.0,0.0)
    glRasterPos2i(button_left, 21 * button_space + text_offset)
    Text("Pose save options:")
    glRasterPos2i(button_left, 17 * button_space + text_offset)
    Text("Save modifier settings:")
    glRasterPos2i(button_left, 14 * button_space + text_offset)
    Text("Save type conversion:")
    glRasterPos2i(button_left, 9 * button_space + text_offset)
    Text("Save:") 
    _save_button = Button("Save Pose", EVENT_SAVE, button_left, 8*button_space, button_width, button_height, 
                            "Browse for Pose Save Location")
    _part_toggle = Toggle("Partial group save options", EVENT_PART, button_left, 19*button_space, button_width,
                          button_height, TrimPose, "Set screening options to exclude some bones from saved pose.")  
    _noTrans_toggle = Toggle("No translation", EVENT_NOTRANS, button_left, 16*button_space, button_width/2,
                             button_height, No_Trans, "Select to exclude translation data from saved pose.")
    _noScale_toggle = Toggle("No scaling", EVENT_NOSCALE, button_left+(button_width/2), 16*button_space, button_width/2,
                             button_height, No_Scale, "Select to exclude scaling data from saved pose.")
    _opt_toggle = Toggle("General save options", EVENT_EMP, button_left, 20*button_space, button_width, button_height,
                         OptPose, "Set options for saving poses.")
    #Save types
    _poserS_toggle = Toggle("Restmatrix pose ('No Roll')", EVENT_NOROLL, button_left, 13*button_space, button_width,
                           button_height, NoRollTog, "Save a pose without 'embedded' bone roll.")        
    _posematS_toggle = Toggle("Posematrix pose ('IK')", EVENT_IK, button_left, 12*button_space, button_width,
                             button_height, IKTog, "Save IK and constraints data in the pose.")
    _poserSave_toggle = Toggle("Export Poser pose (.pz2)", EVENT_POSERSAVE, button_left, 11*button_space, button_width,
                             button_height, PoserSave, "Save a Poser pose, instead of a Blender pose.")
    
  if MiscTabTog == 1:#Options tab
    #Symmetry notation functions------------------------------------------
    colorbox(button_left - 8, 19*button_space - 8, button_width + 28, 22*button_space)
    glColor3f(0.0,0.0,0.0)
    glRasterPos2i(button_left, 21 * button_space + text_offset)
    Text("Symmetry Notation Handling:")
    _suffBone_button = Button("Enter symmetry prefix or suffix", EVENT_SUFFPREF, button_left, 19*button_space,
                              (button_width), button_height, "Press to enter right/left prefix or suffix")
    _prefix_toggle = Toggle("Use prefix", EVENT_PREF, button_left, 20*button_space, button_width/2, button_height,
                            pref, "select if right/left handling uses a prefix")
    _suffix_toggle = Toggle("Use suffix", EVENT_SUFF, button_left+(button_width/2), 20*button_space, button_width/2,
                            button_height, suff, "select if right/left handling uses a suffix")
    #Listbox settings options-----------------------------------------
    colorbox(button_left - 8, 15*button_space - 8, button_width + 28, 18*button_space)
    glColor3f(0.0,0.0,0.0)
    glRasterPos2i(button_left, 17 * button_space + text_offset)
    Text("Listbox display options:")
    _wheel_toggle = Toggle("Wheel mouse", EVENT_WHEEL, button_left, 16*button_space, button_width/2, button_height,
                           wheelslider, "Select to use wheel mouse to scroll listboxes")
    _number_toggle = Toggle("Line numbers", EVENT_NUMB, button_left+(button_width/2), 16*button_space, button_width/2,
                            button_height, numList, "Select to use line numbers with listboxes")
    _dual_toggle = Toggle("Dual display", EVENT_DUAL, button_left, 15*button_space, button_width/2, button_height,
                          dualList, "Select to use two tab pages side-by-side")
    _dual_width = Number("Width", EVENT_NOEVENT, button_left+(button_width/2), 15*button_space, button_width/2,
                         button_height, _dual_width.val, 100, 240, "Change width of displayed page (default value is 240).")
    #Armature backup functions--------------------------------------------------
    colorbox(button_left - 8, 12*button_space - 8, button_width + 28, 14*button_space)
    glColor3f(0.0,0.0,0.0)
    glRasterPos2i(button_left, 13 * button_space + text_offset)
    Text("Armature options:")
    _bone_button = Button("Armature text backup options", EVENT_BONE, button_left, 12*button_space, button_width,
                          button_height, "Save or load armature settings from a textfile backup.")
    #Numerical entry options----------------------------------------------
    colorbox(button_left - 8, 9*button_space - 8, button_width + 28, 11*button_space)
    glColor3f(0.0,0.0,0.0)
    glRasterPos2i(button_left, 10 * button_space + text_offset)
    Text("Numerical entry options:")
    _unique_toggle = Toggle("Use Euler Unique", EVENT_UNIQUE, button_left, 9*button_space, button_width, button_height,
                           uniqueRot, "Select to use euler.unique() to avoid gimbal lock with rotation dials.")
    #Registry backup options-----------------------------------------------
    colorbox(button_left - 8, 6*button_space - 8, button_width + 28, 8*button_space)
    glColor3f(0.0,0.0,0.0)
    glRasterPos2i(button_left, 7 * button_space + text_offset)
    Text("General settings options:")
    _reg_button = Button("Gui preference options", EVENT_REG, button_left, 6*button_space, button_width, button_height, 
                         "Set or deactivate general preferences.")

  if NumTabTog == 1: #Numerical entry tab
    #Pose mode entry----------------------------------------------------
    #Number buttons handle differently in 241 and 242.  This is optimized for 242 handling
    maxval = 1000.0000
    minval = -1000.0000    
    colorbox(button_left - 8, 3*button_space - 8, button_width + 28, 22*button_space+text_offset)    
    glColor3f(0.67,0.67,0.67)
    glRecti(button_left - 8, 21*button_space, button_width + 28, 23*button_space-text_offset)
    glRecti(button_left - 8, 15*button_space, button_width + 28, 18*button_space)
    glRecti(button_left - 8, 10*button_space-(button_height/4), button_width + 28, 12*button_space-(button_height/4))
    glRecti(button_left - 8, 4*button_space-(button_height/4), button_width + 28, 7*button_space-(button_height/4))    
    glColor3f(0.0,0.0,0.0)
    glRasterPos2i(button_left, 22 * button_space+text_offset)
    if lastBone == "": Text("No active bones list selection","small") 
    if lastBone != "": Text("Active bones list bone: %s" %(lastBone),"small")
    glRasterPos2i(button_left, 21 * button_space+text_offset)
    Text("Pose mode:")
    glRasterPos2i(button_left+(button_width/3)+8, 20 * button_space+text_offset)
    Text("locX: %f" %(_tran_x.val))
    glRasterPos2i(button_left+(button_width/3)+8, 19 * button_space+text_offset)
    Text("LocY: %f" %(_tran_y.val))
    glRasterPos2i(button_left+(button_width/3)+8, 18 * button_space+text_offset)
    Text("LocZ: %f" %(_tran_z.val))
    glRasterPos2i(button_left+(button_width/3)+8, 17 * button_space+text_offset)
    Text("RotX: %f" %(_rot_x.val))
    glRasterPos2i(button_left+(button_width/3)+8, 16 * button_space+text_offset)
    Text("RotY: %f" %(_rot_y.val))
    glRasterPos2i(button_left+(button_width/3)+8, 15 * button_space+text_offset)
    Text("RotZ: %f" %(_rot_z.val))
    glRasterPos2i(button_left+(button_width/3)+8, 14 * button_space+text_offset)
    Text("ScaleX: %f" %(_size_x.val))
    glRasterPos2i(button_left+(button_width/3)+8, 13 * button_space+text_offset)
    Text("ScaleY: %f" %(_size_y.val))
    glRasterPos2i(button_left+(button_width/3)+8, 12 * button_space+text_offset)
    Text("ScaleZ: %f" %(_size_z.val))
    _tran_x = Number("", EVENT_ENTRY, button_left, 20*button_space, button_width/3, button_height, _tran_x.val,
                     minval*(lastLen/2), maxval*(lastLen/2), "Set x translation of active bone list selection")
    _tran_y = Number("", EVENT_ENTRY, button_left, 19*button_space, button_width/3, button_height, _tran_y.val,
                     minval*(lastLen/2), maxval*(lastLen/2), "Set y translation of active bone list selection")
    _tran_z = Number("", EVENT_ENTRY, button_left, 18*button_space, button_width/3, button_height, _tran_z.val,
                     minval*(lastLen/2), maxval*(lastLen/2), "Set z translation of active bone list selection")
    _rot_x = Number("", EVENT_ENTRY, button_left, 17*button_space, button_width/3, button_height, _rot_x.val,
                    minval*10, maxval*10, "Set x rotation of active bone list selection")
    _rot_y = Number("", EVENT_ENTRY, button_left, 16*button_space, button_width/3, button_height, _rot_y.val,
                    minval*10, maxval*10, "Set y rotation of active bone list selection")
    _rot_z = Number("", EVENT_ENTRY, button_left, 15*button_space, button_width/3, button_height, _rot_z.val,
                    minval*10, maxval*10, "Set z rotation of active bone list selection")
    _size_x = Number("", EVENT_ENTRY, button_left, 14*button_space, button_width/3, button_height, _size_x.val,
                     minval, maxval, "Set x scaling for active bone list selection")
    _size_y = Number("", EVENT_ENTRY, button_left, 13*button_space, button_width/3, button_height, _size_y.val,
                     minval, maxval, "Set y scaling for active bone list selection")
    _size_z = Number("", EVENT_ENTRY, button_left, 12*button_space, button_width/3, button_height, _size_z.val,
                     minval, maxval, "Set z scaling for active bone list selection")
    _keyframe_toggle = Toggle("Add keyframes", EVENT_KEYF, button_left+(button_width/2), 21*button_space+text_offset,
                              button_width/2, button_height, myKeys, "Toggle on to add keyframing for each entry")    
    #Edit mode entry-----------------------------------------
    glRasterPos2i(button_left, 3 * button_space)
    Text("Caution: these will alter your armature.", "small")
    glRasterPos2i(button_left, 11 * button_space + text_offset)
    Text("Edit mode:")
    glRasterPos2i(button_left+(button_width/3)+8, 11 * button_space + text_offset)
    Text("Length: %f" %(lastLen))
    glRasterPos2i(button_left+(button_width/3)+8, 10 * button_space+text_offset)
    Text("Roll: %f" %(_boneroll_num.val))
    glRasterPos2i(button_left+(button_width/3)+8, 9 * button_space+text_offset)
    Text("RootX: %f" %(_head_x.val))
    glRasterPos2i(button_left+(button_width/3)+8, 8 * button_space+text_offset)
    Text("RootY: %f" %(_head_y.val))
    glRasterPos2i(button_left+(button_width/3)+8, 7 * button_space+text_offset)
    Text("RootZ: %f" %(_head_z.val))
    glRasterPos2i(button_left+(button_width/3)+8, 6 * button_space+text_offset)
    Text("TipX: %f" %(_tail_x.val))
    glRasterPos2i(button_left+(button_width/3)+8, 5 * button_space+text_offset)
    Text("TipY: %f" %(_tail_y.val))
    glRasterPos2i(button_left+(button_width/3)+8, 4 * button_space+text_offset)
    Text("TipZ: %f" %(_tail_z.val))    
    _boneroll_num = Number("", EVENT_EDIT, button_left, 10*button_space, button_width/3, button_height,
                           _boneroll_num.val,  -360, 360, "Set bone roll for active bone list selection")
    _head_x = Number("", EVENT_EDIT, button_left, 9*button_space, button_width/3, button_height, _head_x.val,
                     minval*(lastLen/2), maxval*(lastLen/2), "Set head x translation for active bone list selection")
    _head_y = Number("", EVENT_EDIT, button_left, 8*button_space, button_width/3, button_height, _head_y.val,
                     minval*(lastLen/2), maxval*(lastLen/2), "Set head y translation for active bone list selection")
    _head_z = Number("", EVENT_EDIT, button_left, 7*button_space, button_width/3, button_height, _head_z.val,
                     minval*(lastLen/2), maxval*(lastLen/2), "Set head z translation for active bone list selection")
    _tail_x = Number("", EVENT_EDIT, button_left, 6*button_space, button_width/3, button_height, _tail_x.val,
                     minval*(lastLen/2), maxval*(lastLen/2), "Set tail x translation for active bone list selection")
    _tail_y = Number("", EVENT_EDIT, button_left, 5*button_space, button_width/3, button_height, _tail_y.val,
                     minval*(lastLen/2), maxval*(lastLen/2), "Set tail y translation for active bone list selection")
    _tail_z = Number("", EVENT_EDIT, button_left, 4*button_space, button_width/3, button_height, _tail_z.val,
                     minval*(lastLen/2), maxval*(lastLen/2), "Set tail z translation for active bone list selection")
    
  #if NumTabTog == 0: zero_entries() #Clear the numerical tab buttons if the tab is not in use.  
    
clicktime = sys.time()
def event(evt, val):
  global line_top, box_h, clicktime, box_select, boxPoses, curFrame
  
  if AnimTabTog == 1:
    curFrame = Blender.Get('curframe')
    
  if not val:
    
    if AnimTabTog == 1:
      curFrame = Blender.Get('curframe')
      
    if (evt == ESCKEY):
      if setprefs == 1: update_ini()
      Exit()
      return
    
    elif evt == LEFTMOUSE: #For the listbox
      if ListTabTog == 1 or BonesTabTog == 1 or AnimTabTog == 1:        
        double = 0
        if round(sys.time()-clicktime,1) < 0.5: double = 1 #Alter to change double-click sensitivity                
        clicktime = sys.time()        
        box_click(double, 0)

    elif evt == RIGHTMOUSE: #Right-click changes current frame in animation tab
      if AnimTabTog == 1: box_click(0, 1)
  
  elif (evt == WHEELDOWNMOUSE or evt == DOWNARROWKEY) and len(boxList) > box_h: #Avoid list index errors
    if (ListTabTog == 1 or BonesTabTog == 1 or AnimTabTog == 1) and wheelslider == 1:            
      if line_top < (len(boxList)-box_h): line_top +=3
      if line_top > (len(boxList)-box_h): line_top = len(boxList)-box_h          
    Draw()

  elif (evt == WHEELUPMOUSE or evt == UPARROWKEY) and len(boxList) > box_h:
    if (ListTabTog == 1 or BonesTabTog == 1 or AnimTabTog == 1) and wheelslider == 1:      
      if line_top > 0: line_top -=3
      if line_top < 0: line_top = 0         
    Draw()    
  
def bevent(evt):  
  global _partial_slider, _load_menu, _scroll_slider, \
         bodyscale, PoserConvert, MirrorPose, ScreenPose, screenR, screenL, DupPose, dupR, dupL, \
         HipPose, hipR, hipT, addscale, partPose, MixPose, EmpPose, suff, pref, TrimPose, \
         openBatch, saveBatch, realTime, No_Trans, No_Scale, IKPose, NoRollPose, OptPose, PoseMat, \
         LibVar, LibPath, LibSave, LibTog, IncludePart, ExcludePart, askKey, LoadTabTog, \
         SaveTabTog, MiscTabTog, ListTabTog, BonesTabTog, line_top, multi, boxList, listScroll, numList, \
         boxPoses, wheelslider, listScrollB, foldTog, in_box, GroupTog, NoRollTog, IKTog, lastBone, \
         dualList, NumTabTog, myKeys, uniqueRot, MirrorBone, loadlist, AnimTabTog, boxFrames, listScrollA, \
         batchLoad, batchFrames, keyType, setprefs, PoserSave, curFrame
  
  if AnimTabTog == 1:
    curFrame = Blender.Get('curframe')
    
  if evt == EVENT_LOAD: #load button and load pulldown menu   
    partPose = _partial_slider.val    
    if LibVar == 0:
      if openBatch == 0: Window.FileSelector(load_pose, 'Pose to open?') #File browse loading
      elif openBatch == 1: Window.FileSelector(open_batch, 'Pose to open?')#Animated batch support
    elif LibVar == 1:      #Menu browse loading
      if LibPath == "":
        Window.FileSelector(list_browser, 'Select pose folder:')        
      elif LibPath != "":        
        list_select(_load_menu.val)

  elif evt == EVENT_LISTLOAD: #Listbox folder loading
    partPose = _partial_slider.val    
    if ListTabTog == 1: #Listbox use
      foldTog = 0
      line_top = 0 #Re-set top line of listbox to keep things in-bounds
      listScroll = 1 - wheelslider
      Window.FileSelector(list_browser, 'Select pose folder:')
      
  elif evt == EVENT_SAVE: #Save button for both save tab and listbox tab
    if ListTabTog == 1:
      temp = LibSave
      LibSave = 1 #Listbox use
    if LibSave == 0:
      if saveBatch == 0: Window.FileSelector(goSave, 'File to save as?') #File browse saving
      elif saveBatch == 1: Window.FileSelector(save_batch, 'File to save as?')#Animated batch support
    if LibSave == 1: #Menu and listbox browse saving      
      savename = Create("Pose")
      saveblock = [("Enter save name:", savename, 0, 30, "Enter name as which to save pose file.")]
      saveVal = PupBlock("Save to list browse folder", saveblock)
      if saveVal == 1 and LibPath != "":        
        savename = sys.join(LibPath,savename.val)        
        if saveBatch == 0: goSave(savename)
        elif saveBatch == 1: save_batch(savename)
        LibPath = sys.join(LibPath, sys.basename(Blender.Get('filename')))#pass a dummy filename to list_browser
        list_browser(LibPath) #Refesh the menu to display new addition     
    if ListTabTog == 1: LibSave = temp
    
  elif evt == EVENT_QUIT: #"Quit" button
    if setprefs == 1: update_ini()
    Exit()
    return

  elif evt == EVENT_CONVERT:  #load tab restmatrix conversion toggle  
    PoserConvert = 1 - PoserConvert
    if PoserConvert == 1:
      PoseMat = 0
    
  elif evt == EVENT_FLIP: #load tab "Flip Pose" toggle   
    MirrorPose = 1 - MirrorPose
    if MirrorPose == 1:
      MirrorBone = 0

  elif evt == EVENT_SCREEN: #load tab screen pose toggle
    ScreenPose = 1 - ScreenPose
    if DupPose == 1:
      DupPose = 0

  elif evt == EVENT_DUP:  #load tab symmetrize pose toggle  
    DupPose = 1 - DupPose
    if ScreenPose == 1:
      ScreenPose = 0
   
  elif evt == EVENT_SCR_R: #load tab screen right toggle
    screenL = screenR  
    screenR = 1 - screenR
    dupR = screenR
    dupL = screenL
    
  elif evt == EVENT_SCR_L: #load tab screen left toggle
    screenR = screenL
    screenL = 1 - screenL
    dupR = screenR
    dupL = screenL

  elif evt == EVENT_DUP_R: #load tab symmetrize right toggle
    dupL = dupR
    dupR = 1 - dupR
    screenR = dupR
    screenL = dupL

  elif evt == EVENT_DUP_L: #load tab symmetrize left toggle
    dupR = dupL
    dupL = 1 - dupL
    screenR = dupR
    screenL = dupL
  
  elif evt == EVENT_MIX: #load tab additive modifier toggle
    MixPose = 1 - MixPose

  elif evt == EVENT_EMP: #Toggle adjusted to allow multiple menu selections
    if OptPose == 0: #save tab general options toggle
      OptPose = 1 - OptPose
    if OptPose == 1: export_options()
    
  elif evt == EVENT_POSEMAT: #load tab posematrix conversion toggle
    PoseMat = 1 - PoseMat
    if PoseMat == 1:
      PoserConvert = 0

  elif evt == EVENT_ZERO: #load tab zero pose button
    Zero_Pose()

  elif evt == EVENT_SUFFPREF: #options tab symmetry button
    Symmetry_Notation()

  elif evt == EVENT_PREF: #options tab symmetry prefix toggle
    pref = 1 - pref
    suff = 1 - suff

  elif evt == EVENT_SUFF: #options tab symmetry suffix toggle
    suff = 1 - suff
    pref = 1 - pref

  elif evt == EVENT_PART: #Toggle adjusted to allow multiple menu selections
    if TrimPose == 0: #save tab partial groups toggle
      TrimPose = 1 - TrimPose
    if TrimPose == 1: partSave()
    if TrimPose == 0:
      partVar = 0
      sideVar = 0

  elif evt == EVENT_APPLY: #load tab realtime application button
    realTime = 1 - realTime
    partPose = _partial_slider.val
    load_pose('')

  elif evt == EVENT_NOTRANS: #save tab no translation modifier toggle
    No_Trans = 1 - No_Trans

  elif evt == EVENT_NOSCALE: #save tab no scaling modifier toggle
    No_Scale = 1 - No_Scale

  elif evt == EVENT_BONE: #options tab boneroll button
    Bone_Menu()

  elif evt == EVENT_LIB: #Toggle adjusted to allow multiple menu selections
    if LibTog == 0: #Load tab general options toggle
      LibTog = 1 - LibTog    
    if LibTog == 1: Load_Menu()    

  elif evt == EVENT_TAB_L: #Load tab
    if NumTabTog == 1: zero_entries() #Clear the numerical tab buttons if the tab is not in use.
    LoadTabTog = 1
    SaveTabTog = 0
    MiscTabTog = 0
    NumTabTog = 0     
    if dualList == 0:
      if wheelslider == 1:
        if ListTabTog == 1: listScroll = line_top #store the current listbox top line
        elif BonesTabTog == 1: listScrollB = line_top
        elif AnimTabTog == 1: listScrollA = line_top
      ListTabTog = 0
      BonesTabTog = 0
      AnimTabTog = 0
      boxList = [] #clear the display list      

  elif evt == EVENT_TAB_S: #Save tab
    if NumTabTog == 1: zero_entries() #Clear the numerical tab buttons if the tab is not in use.
    LoadTabTog = 0
    SaveTabTog = 1
    MiscTabTog = 0
    NumTabTog = 0    
    if dualList == 0:
      if wheelslider == 1:
        if ListTabTog == 1: listScroll = line_top 
        elif BonesTabTog == 1: listScrollB = line_top
        elif AnimTabTog == 1: listScrollA = line_top
      ListTabTog = 0
      BonesTabTog = 0
      AnimTabTog = 0
      boxList = []       

  elif evt == EVENT_TAB_M: #options (Miscelllaneous) Tab
    if NumTabTog == 1: zero_entries() #Clear the numerical tab buttons if the tab is not in use.
    LoadTabTog = 0
    SaveTabTog = 0
    MiscTabTog = 1
    NumTabTog = 0   
    if dualList == 0:
      if wheelslider == 1:
        if ListTabTog == 1: listScroll = line_top 
        elif BonesTabTog == 1: listScrollB = line_top
        elif AnimTabTog == 1: listScrollA = line_top
      ListTabTog = 0
      BonesTabTog = 0
      AnimTabTog = 0
      boxList = []      

  elif evt == EVENT_TAB_N: #Numerical entry tab   
    LoadTabTog = 0
    SaveTabTog = 0
    MiscTabTog = 0
    NumTabTog = 1    
    if dualList == 0:
      if wheelslider == 1:
        if ListTabTog == 1: listScroll = line_top 
        elif BonesTabTog == 1: listScrollB = line_top
        elif AnimTabTog == 1: listScrollA = line_top
      ListTabTog = 0
      BonesTabTog = 0
      AnimTabTog = 0
      boxList = []
    lastBone = ""
    if len(boxBones) > 0: lastBone = boxBones[len(boxBones)-1]
    entry_values() #load the dial values
    
  elif evt == EVENT_TAB_T: #lisT tab
    if wheelslider == 1:
      if BonesTabTog == 1: listScrollB = line_top
      elif AnimTabTog == 1: listScrollA = line_top
      line_top = listScroll
    if BonesTabTog == 1 or AnimTabTog == 1: in_box = 0
    boxList = poseList #Fill the pose list for display    
    ListTabTog = 1
    BonesTabTog = 0
    AnimTabTog = 0
    if dualList == 0:
      if NumTabTog == 1: zero_entries() #Clear the numerical tab buttons if the tab is not in use. 
      LoadTabTog = 0
      SaveTabTog = 0
      MiscTabTog = 0
      NumTabTog = 0      

  elif evt == EVENT_TAB_B: #Bones tab
    if wheelslider == 1:
      if ListTabTog == 1: listScroll = line_top
      elif AnimTabTog == 1: listScrollA = line_top
      line_top = listScrollB
    if ListTabTog == 1 or AnimTabTog == 1: in_box = 0 #Re-set to keep multi-task list from bleeding over
    boxList = [] #clear the display list (re-filled in gui listbox draw event)
    ListTabTog = 0
    BonesTabTog = 1
    AnimTabTog = 0
    if dualList == 0:
      if NumTabTog == 1: zero_entries() #Clear the numerical tab buttons if the tab is not in use. 
      LoadTabTog = 0
      SaveTabTog = 0
      MiscTabTog = 0
      NumTabTog = 0    
    if NumTabTog == 1: entry_values() #Refresh both numbers tab and the boxBones display list      

  elif evt == EVENT_TAB_A: #Animation frames tab
    if wheelslider == 1:
      if ListTabTog == 1: listScroll = line_top
      elif BonesTabTog == 1: line_top = listScrollB
      line_top = listScrollA
    if ListTabTog == 1 or BonesTabTog == 1: in_box = 0 #Re-set to keep multi-task list from bleeding over
    boxList = [] #clear the display list (re-filled in gui listbox draw event)
    ListTabTog = 0
    BonesTabTog = 0
    AnimTabTog = 1
    if dualList == 0:
      if NumTabTog == 1: zero_entries() #Clear the numerical tab buttons if the tab is not in use. 
      LoadTabTog = 0
      SaveTabTog = 0
      MiscTabTog = 0
      NumTabTog = 0
    curFrame = Blender.Get('curframe')
  
  elif evt == EVENT_LISTSCROLL: #listbox slider handling
    if ListTabTog == 1: #This handles three different listbox sliders
      listScroll = int(_scroll_slider.val)
      if listScroll == 0: listScroll += 1      
    elif BonesTabTog == 1:
      listScrollB = int(_scroll_slider.val)
      if listScrollB == 0: listScrollB += 1      
    elif AnimTabTog == 1:      
      listScrollA = int(_scroll_slider.val)
      if listScrollA == 0: listScrollA += 1
    in_box = 0 #Keep mouse event from mis-interpreting click release
    
  elif evt == EVENT_WHEEL: #options tab wheelmouse toggle
    wheelslider = 1 - wheelslider
    if wheelslider == 1: #Convert between displayed line number values and list index values      
      listScroll -= 1
      listScrollB -= 1
      listScrollA -= 1
    if wheelslider == 0:
      listScroll += 1
      listScrollB += 1
      listScrollA += 1

  elif evt == EVENT_NUMB: #options tab line numbers toggle
    numList = 1 - numList

  elif evt == EVENT_FOLDER: #listbox folder browse toggle
    foldTog = 1 - foldTog 
    box_select = [] #clear the display list
    if foldTog == 1:
      line_top = 0 #Re-set top line of listbox to keep things in-bounds
      listScroll = 1
      folder_browse(LibPath)
    if foldTog == 0:
      LibPath = sys.join(LibPath, sys.basename(Blender.Get('filename')))#list_browser needs a dummy filename
      list_browser(LibPath)
    batchLoad = 0 #turn off batch mode
    batchSave = 0
    boxList = poseList #fill the list

  elif evt == EVENT_BONEMENU: #Bones list options button
    bonebox_menu()

  elif evt == EVENT_GROUP: #Toggle adjusted to allow multiple menu selections
    if GroupTog == 0: #Load tab partial groups toggle
      GroupTog = 1 - GroupTog    
    if GroupTog == 1: GroupLoad_Menu()

  elif evt == EVENT_NOROLL: #Save tab restmatrix conversion button
    NoRollTog = 1 - NoRollTog
    NoRollPose = NoRollTog

  elif evt == EVENT_IK: #Save tab posematrix conversion button
    IKTog = 1 - IKTog
    IKPose = IKTog

  elif evt == EVENT_DUAL: #Dual display option toggle (with width change)
    dualList = 1 - dualList
    if dualList == 1:
      if _dual_width.val == 240: _dual_width.val = 180 #resets only if user hasn't altered defaults
      ListTabTog = 1 #Turn on the pose listbox
      boxList = poseList #Fill the pose list for display      
    if dualList == 0:
      if _dual_width.val == 180: _dual_width.val = 240
      if ListTabTog == 1: #Close lists
        if wheelslider == 1: listScroll = line_top
        ListTabTog = 0         
      elif BonesTabTog == 1:
        if wheelslider == 1: listScrollB = line_top
        BonesTabTog = 0
      elif AnimTabTog == 1:
        if wheelslider == 1: listScrollA = line_top
        AnimTabTog = 0
      in_box = 0 #prevent mouse-click errors with listbox re-positioning

  elif evt == EVENT_ENTRY: #numerical tab pose mode entries   
    numerical_entry()

  elif evt == EVENT_KEYF: #numerical tab keyframes toggle
    myKeys = 1 - myKeys
    if myKeys == 1:
      options = PupMenu("Select keyframing option: %t|Add keyframes for all bones %x1\
                        |Add Keyframes only for bones list selections %x2|Add keyframe only for active bones list bone %x3\
                        |Cancel %x5")
      if options == 1: keyType = 1
      elif options == 2: keyType = 2
      elif options == 3: keyType = 3      
      else:
        if keyType == 0: myKeys = 0 #cancel selected or -1 returned
    elif myKeys == 0: keyType = 0

  elif evt == EVENT_EDIT: #numerical tab edit mode entries
    numerical_edit()

  elif evt == EVENT_UNIQUE: #euler unique option in options tab
    uniqueRot = 1 - uniqueRot

  elif evt == EVENT_FLIPBONE: #load tab "Flip Bones" button
    MirrorBone = 1 - MirrorBone
    if MirrorBone == 1:
      MirrorPose = 0

  elif evt == EVENT_ANIMMENU: #Select button on frames tab
    anim_menu()

  elif evt == EVENT_BATCHLOAD: #Load button on poses tab (batch mode)    
    if len(boxPoses) > 0:
      partPose = _partial_slider.val
      open_batch(boxPoses[0])

  elif evt == EVENT_BATCHTOG: #Batch toggle on poses tab.
    if foldTog == 0:
      batchLoad = 1 - batchLoad
      if batchLoad == 1:
        openBatch = 1 #batch loading toggle
        LibTog = 1 #load options menu toggle
        saveBatch = 1 #batch save toggle
        OptPose = 1 #save options menu toggle
        
      elif batchLoad == 0: #reset load/save menu toggles, if appropriate
        openBatch = 0
        saveBatch = 0
        if LibVar == HipPose == hipR == hipT == bodyscale == addscale \
         == openBatch == askKey == torsoZero == 0:  
          LibTog = 0
        if EmpPose == IKPose == NoRollPose == saveBatch == LibSave == 0:
          OptPose = 0    
    boxPoses = []      

  elif evt == EVENT_BATCHFRAMES: #Batch toggle on frames tab
    batchFrames = 1 - batchFrames

  elif evt == EVENT_IKTOG: #IK button on frames tab
    IK_menu()

  elif evt == EVENT_TOOLS: #Tolls button on frames tab
    tools_menu()

  elif evt == EVENT_REG:
    regmenu = PupMenu("Options: %t|Set GUI preferences with next script exit %x1\
                      |Remove preferred settings %x2|Cancel %x3")
    if regmenu == 1:
      setprefs = 1 - setprefs
      if setprefs == 1: notice = PupMenu("Settings will be stored in registry when you exit the script.")      
    elif regmenu == 2:
      saveloc = Blender.Get('homedir')
      if sys.exists(sys.join(saveloc,"PoseHandlerSettings.txt")) == 1:
        os.remove(sys.join(saveloc,"PoseHandlerSettings.txt"))
        
  elif evt == EVENT_POSERSAVE:
    global JPdict
    PoserSave = 1 - PoserSave
    if PoserSave == 0: JPdict.clear()
    if PoserSave == 1:
      menu = PupMenu("Poser export requires joint rotation order data %t|Parse joint order from a cr2/crz or pz2/p2z %x1\
                    |Load joint order from a saved text file %x2|Save current loaded joint orders to a text file %x3\
                    |Change joint order for bones list selections %x4|Use automatic (generic) joint rotation order  %x5")
      if menu == 1:        
        JPdict = {}
        Window.FileSelector(parse_JP_order, 'Browse for .cr2 or .pz2.')
      elif menu == 2:
        JPdict = {}
        Window.FileSelector(import_JP_order, 'Browse for .txt file.')
      elif menu == 3:        
        Window.FileSelector(export_JP_order, 'Browse for save location.')
      elif menu == 4:
        change_JP_order()

  elif evt == EVENT_BONESEARCH:
    bone_search()

  elif evt == EVENT_BONEGRAB:
    bone_grab()

  elif evt == EVENT_POSESEARCH:
    if foldTog == 0: #disabled in folder browser mode
      pose_search()

  elif evt == EVENT_NOEVENT: #This prompts a gui re-draw
    pass
    
  else: return # no need to redraw if nothing changed
    
  Draw()

def update_ini():
  saveloc = Blender.Get('homedir')
  temptext = ""
  temptext += "LoadTabTog<:>%i\n" %(LoadTabTog)
  temptext += "SaveTabTog<:>%i\n" %(SaveTabTog)
  temptext += "MiscTabTog<:>%i\n" %(MiscTabTog)
  temptext += "ListTabTog<:>%i\n" %(ListTabTog)
  temptext += "BonesTabTog<:>%i\n" %(BonesTabTog)
  temptext += "NumTabTog<:>%i\n" %(NumTabTog)
  temptext += "AnimTabTog<:>%i\n" %(AnimTabTog)
  temptext += "numList<:>%i\n" %(numList)
  temptext += "wheelslider<:>%i\n" %(wheelslider)
  temptext += "dualList<:>%i\n" %(dualList)
  temptext += "uniqueRot<:>%i\n" %(uniqueRot)
  temptext += "loadlist<:>%s\n" %(loadlist)
  temptext += "_dual_width<:>%i\n" %(_dual_width.val)
  temptext += "bodyscale<:>%i\n" %(bodyscale)
  temptext += "addscale<:>%i\n" %(addscale)
  temptext += "HipPose<:>%i\n" %(HipPose)
  temptext += "hipR<:>%i\n" %(hipR)
  temptext += "hipT<:>%i\n" %(hipT)
  temptext += "LibVar<:>%i\n" %(LibVar)
  temptext += "LibTog<:>%i\n" %(LibTog)
  temptext += "LibPath<:>%s\n" %(LibPath)
  temptext += "poseList<:>%s\n" %(poseList)
  temptext += "torsoZero<:>%i\n" %(torsoZero)
  temptext += "poseStart<:>%i\n" %(poseStart)
  temptext += "posePages<:>%i\n" %(posePages)
  temptext += "poseLimit<:>%i\n" %(poseLimit)
  temptext += "suff<:>%i\n" %(suff)
  temptext += "pref<:>%i\n" %(pref)
  temptext += "right<:>%s\n" %(right)
  temptext += "left<:>%s\n" %(left)
  temptext += "LibPath<:>%s\n" %(LibPath)
  temptext += "right_alt<:>%s\n" %(right_alt)
  temptext += "left_alt<:>%s\n" %(left_alt)
  temptext += "minVal<:>%i\n" %(minVal)
  temptext += "maxVal<:>%i\n" %(maxVal)
  temptext += "boneSort<:>%i" %(boneSort)
  settings = open(sys.join(saveloc,"PoseHandlerSettings.txt"),"w")
  settings.write(temptext)
  settings.close()
  

def get_from_ini():
  global LoadTabTog, SaveTabTog, MiscTabTog, ListTabTog, BonesTabTog, numList, wheelslider, dualList,\
         NumTabTog, uniqueRot, loadlist, AnimTabTog, _dual_width, listScroll, listScrollA, listScrollB
  global bodyscale, addscale, HipPose, hipR, hipT, LibVar, openBatch, LibTog, \
         TrimPose, LibPath, poseList, torsoZero, boneSort
  global poseStart, posePages, poseLimit, suff, pref, right, left , lenR, lenL, right_alt, \
         left_alt, maxVal, minVal

  saveloc = Blender.Get('homedir')
  if sys.exists(sys.join(saveloc,"PoseHandlerSettings.txt")) == 1:
    settings = open(sys.join(saveloc,"PoseHandlerSettings.txt"),"r")
    lines = settings.readlines()  
    for line in lines: 
      data = line.rstrip("\n").split("<:>")
   
      if data[0] == "LoadTabTog": LoadTabTog = int(data[1])
      elif data[0] == "SaveTabTog": SaveTabTog = int(data[1])
      elif data[0] == "MiscTabTog": MiscTabTog = int(data[1])
      elif data[0] == "ListTabTog": ListTabTog = int(data[1])
      elif data[0] == "BonesTabTog": BonesTabTog = int(data[1])
      elif data[0] == "NumTabTog": NumTabTog = int(data[1])
      elif data[0] == "AnimTabTog": AnimTabTog = int(data[1])
      elif data[0] == "numList": numList = int(data[1])
      elif data[0] == "wheelslider": wheelslider = int(data[1])
      elif data[0] == "dualList": dualList = int(data[1])        
      elif data[0] == "uniqueRot": uniqueRot = int(data[1])
      elif data[0] == "loadlist": loadlist = data[1]     
      elif data[0] == "_dual_width": _dual_width.val = int(data[1])
      elif data[0] == "bodyscale": bodyscale = int(data[1])
      elif data[0] == "addscale": addscale = int(data[1])
      elif data[0] == "HipPose": HipPose = int(data[1])
      elif data[0] == "hipR": hipR = int(data[1])
      elif data[0] == "hipT": hipT = int(data[1])
      elif data[0] == "LibVar": LibVar = int(data[1])     
      elif data[0] == "LibTog": LibTog = int(data[1])     
      elif data[0] == "LibPath": LibPath = data[1]
      elif data[0] == "poseList": poseList = data[1]
      elif data[0] == "torsoZero": torsoZero = int(data[1])
      elif data[0] == "poseStart": poseStart = int(data[1])
      elif data[0] == "posePages": posePages = int(data[1])
      elif data[0] == "poseLimit": poseLimit = int(data[1])
      elif data[0] == "suff": suff = int(data[1])
      elif data[0] == "pref": pref = int(data[1])
      elif data[0] == "right": right = data[1]
      elif data[0] == "left": left = data[1]
      elif data[0] == "right_alt": right_alt = data[1]
      elif data[0] == "left_alt": left_alt = data[1]
      elif data[0] == "maxVal": maxVal = int(data[1])
      elif data[0] == "minVal": minVal = int(data[1])
      elif data[0] == "boneSort": boneSort = int(data[1])
      
    settings.close()
    
    lenR = len(right)
    lenL = len(left)
    listScroll = 1 - wheelslider 
    listScrollB = 1 - wheelslider 
    listScrollA = 1 - wheelslider    
    if sys.exists(LibPath) != 2:
      LibPath = ""
      libVar = 0
      loadlist = []
      poseStart = 0
      posePages = 0
    else:      
      LibPath = sys.join(LibPath, sys.basename(Blender.Get('filename')))#list_browser needs a dummy filename
      list_browser(LibPath)      
      if ListTabTog == 1: boxList = poseList #fill the list
      else: boxList = [] #force a refresh for anim and bones tab
    
get_from_ini()

Register(gui, event, bevent)

if Blender.Get('version') < 242: #notification for versions prior to 242
  notice = PupMenu("Some features will not function properly in versions before 2.42: %t\
                    |- Numerical entry dials on 'number' tab will respond incorrectly %x1\
                    |- Some keyframe options on 'frames' tab will not work %x2\
                    |- All IK-related functions on 'bones' tab will be disabled %x3\
                    |Some features will not work without a CVS build of 242: %x6\
                    |- The 'bones' tab 'grab' function will not work. %x4\
                    |- The 'bones' tab 'search' function will not select a bone in 3d view. %x5") 
