A pose-handling script

You use the try/except pair to catch/screen out errors.


try:
 selState=bone.sel
except:
 print 'bone.sel not available in this Blender version'

This is the error I get when I run the script and choose that option.

Traceback (most recent call last):
  File "C:\Blender\2.42a\.blender\scripts\PoseLib15g2c.py", line 3534, in bevent    bonebox_menu()
  File "C:\Blender\2.42a\.blender\scripts\PoseLib15g2c.py", line 2516, in bonebox_menu     elif boneSort == 1: boxList = sort_hierarchy(boxList)
  File "C:\Blender\2.42a\.blender\scripts\PoseLib15g2c.py", line 2009, in sort_hierarchy
    children = list(child.name for child in AData.bones[bone].getAllChildren())
TypeError: iteration over non-sequence


Aha! Why am I afraid of try-except? Hmm. Possibly because it was singled out in the scripting guidelines… I’ll set to that problem. Thank you. :slight_smile:

Regarding the sorting error, what can you tell me about your armture setup? How many bones are there? Are any of them complete orphans (no parent, no children)? Are there multiple bones without parents? Any unusual conditions, at all? A one or two bone testing armature?

I’ll see what I can work out, with the sort, too. Hmm.

Thanks.

I’m glad you brought that sorting error to my attention. I was using a gimmicky approach. I think this one may be better. Try this:

 
from Blender import *
def sort_hierarchy(bonelist): #produces a hierarchically sorted bone list of bones in armature
  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 = []        
        for bone in AData.bones.keys():  #get bones without parents first
          if not AData.bones[bone].hasParent():
            templist.append(bone)
        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:
              if not child in templist:
                if not AData.bones[child].parent.name in templist: templist.append(child)
                else: templist.insert(templist.index(AData.bones[child].parent.name)+1, child)
        #print templist, len(templist), len(AData.bones.keys())
        #return templist
        templist2 = []
        for bone in templist: #templist contains all bones.  Now screen against submitted list
          if bone in bonelist:
            templist2.append(bone)        
        return templist2
    
if Object.GetSelected() != []: 
  ob = Object.GetSelected()[0]
  if ob.getType() == "Armature": 
    pose = ob.getPose()
    BonesList = sort_hierarchy(pose.bones.keys()) #submit a list of bone names
    print BonesList, len(BonesList), len(pose.bones.keys())


And here’s sample code which uses Cambo’s new pose bone additions. Tested in the JMS 8-31-06 CVS compilation. (Is compilation the correct term in this context? Hmm. Anyway, he compiled it…) :slight_smile:

 
from Blender import *
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
      pose = ob.getPose()
      for bone in pose.bones.values():
        try:
          if bone.parent:
            print bone.name, bone.parent.name, bone.sel
          else:
            print bone.name, "None", bone.sel
        except:          
          print "pose bone .parent and .sel not available in this version."

Update.

  • Revised hierarchical sort method for bones list, as seen in the posted example code.

  • Added Mike_S’s requested search function as the ‘search’ button on the bones list tab. This will perform a case insensitive search of all bone names in the armature and select all bones which match the submitted string. Supports use of wildcards. (This function and the code, below, have been altered for the latest version.)

  • Added a ‘grab’ button to the bones tab. This will toggle 3d view selection for all pose bones selected in the bones list. If the list is blank, it will deselect all bones in the 3d view.

  • Added joint rotation order export to Poser pose files. Joint order is important for that format. Poser applies poses in a specific order as its way of combatting gimbal lock. There are still some occasional rotation errors in these poses, which do, unfortunately, look like a gimbal problem (either that, or yet another conversion type is needed). Unfortunately, euler.unique() only seems to worsen matters. :frowning: Just as a note, Poser exports use restMatrix type poses. Presumably the same would be true of .bvh exports…

The bone selection methods will only work in a CVS build of Blender, at this point. Tested with the 9-03-06 build from JMS’s site.

This is the basic code used for the search function. Thanks to Cambo for adding these new features! :slight_smile:

 
from Blender import *
import re
 
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
 
bone_search()

That’s it. Please let me know if there are problems or questions. Thanks. :slight_smile:

http://www.kuroyumes-developmentzone.com/~Cage/PoseLib15g2f.zip

I tested it on a full rig, appears to be working :slight_smile:

Now … since you love programming … :slight_smile: … instead of just dumping/appending it into a list you need to tag the parents/children somehow or store it in some other data structure so that an indented or other “tree” form can be printed out :slight_smile:

Mike

(When you’re done with that, I’ll think of something else for you to try :slight_smile:

Now … since you love programming … :slight_smile: … instead of just dumping/appending it into a list you need to tag the parents/children somehow or store it in some other data structure so that an indented or other “tree” form can be printed out :slight_smile:

It doesn’t do what you needed, then? :frowning: Bummer. Or is it just incorrect programming procedure, somehow? Are you saying it’s still gimmicky? I don’t get it. (:confused: Kind of slow…) It works for my purposes, anyway.

I don’t quite follow what you are requesting, apparently. I’m thinking of it in terms of my own project. You need a hierarchy tree?

(When you’re done with that, I’ll think of something else for you to try :slight_smile:

Err… um. Sure. :eek: :slight_smile:

The actual sort seems to be working great.

I’m trying to figure out a way to generate tree output like the outliner … heck clickable/expandable would be cool too :slight_smile:

Hey don’t bust your brain on my account :slight_smile: You should wait to hear from people that actually going to use your script (AndyD, BugMan … others ) :

And please DO focus on your own project, I’m happy just to find the ocassional bug.

I’ll just “borrow” your sort code and see what I can come up with :slight_smile:

Hmm maybe I’ll dig around for the outliner source code and see how easily it can be adapted to Python)

Pelvis
 Hip.R
  Thigh.R
    Shin.R
      Foot.R
 Hip.L
  Thigh.L
    Shin.L
     Foot.L

Mike

I’m trying to figure out a way to generate tree output like the outliner … heck clickable/expandable would be cool too :slight_smile:

You might try looking at Mariano’s (Useless Dreamer’s) Blender Library Outliner.

Feel free to ‘borrow’ all that you need from anything I’ve cobbled together. I’ve done my share of borrowing… Thats’ kind of what all this open source-y GPL business is about, sort of, maybe, right? People putting their heads together to improve things, rather than fighting over ownership. Maybe.

Hmm.

Thanks I’ll look at it, I think I downloaded it awhile ago.

Don’t worry, anything I borrow, is only for my own use, unless I come up with something really useful, which I’ll then post and give credit due … btw, you might want to add your name /screen or real (author) and your code version to the header of your scripts

Mike

I altered the sorting code to keep track of a second list, which stores the parent and depth as a tuple. I’ve tested using this to indent my hierarchical list view, and it works properly in that context. That will be in the next update, perhaps as a display option. The altered code is below… but I haven’t tacked on the pieces that will make it run on its own, as I did with the previous examples.

I’m not sure about making my lists collapsible, however. That would involve some complications… I’m not sure the code I have here would be appropriate for that. Maybe it would…

 
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))
            else: listTree.append(("None",0))
        print zip(templist2,listTree)
        return templist2
 

Seriously, borrow anything you want (the same to anyone else reading this, too). Cambo has a great attitude about such things. “It’s all good.” :slight_smile: I’m more interested in learning things and extending functionality than I am about retaining possession of anything I’ve written… which is not to say I should expect as much from others, I suppose. I should warn that I am still near the beginning of the learning process, so my approach is probably not the best one possible, much of the time. I bet there’s a way to do what the above code does with only one list and one loop… but I don’t know how. :slight_smile:

I plan to add header info before the script is ready for bugman2000.

Heh heh, I knew you couldn’t resist :smiley:

Have you ever looked into programmng a solver for Rubik’s Cube :smiley: :eek:

Mike

Heh heh, I knew you couldn’t resist :smiley:

Have you ever looked into programmng a solver for Rubik’s Cube :smiley: :eek:

I’ve got issues, that way. :slight_smile: I couldn’t resist this, either. I haven’t tested this, mind you, but try this for your Rubik’s:

 
##Sorts all faces of a Rubik's cube to make like colors face the same way.
 
from Blender import *
 
import puzzles, bafflement, frustration, hammer
 
from frustration import berserker_rage
 
colors = [red, green, blue, yellow, orange, white]
color = ""
confusion = [None]*3 #this is how you initialize a 2-d array (der_ton)
blinding_rage = 7 #arbitrary value... set to reflect your own thresholds
cube = puzzles.cube
cube = unsorted(cube)
sides = cube.sides
#stickerList = []
stickers = {}
 
for side in sides: #prep the face.color stickers
  color = side[1][1] #the center square
  stickerList = []
  for face in side:    
    if face.color != color:
      face.sticker = puzzles.peelOffTheSticker()
      stickerList.append(face.sticker)      
    else:
      pass
    if face.sticker.condition == "torn":
      face.color = frustration.startCursingLoudly("@#$%&!!") #curseValue '%&*@!*!' might also work here....
    stickers[color] = stickerList #all the stickers are removed....  
 
  if frustration.frustrationIndex >= blinding_rage: #I think this should work....
    break_it(cube)
    #return  #we don't need these....
 
for side in sides: #a second loop to stick them back on (there's probably a better way to do this....)
  color = side[1][1] #the center square
  stickerList = stickers[color]
  for face in side:
    if face.color != color or face.sticker == None:
      face.color = stickerList[color]
      if face.color == "@#$%&!!":
        try:
          puzzles.stickItOnAnyway()
        except:
          break_it(cube)
 
for side in sides: #a third loop to check (this started out simple, I swear....)
  color = side[1][1] #the center square
  for face in side:
    if face.color != color:
      break_it(cube)
      #return
    else:
      pass
  show_friends(cube) #if we've gotten this far, show it off.
 
def show_friends(cube):
  cube = puzzles.amazeEveryoneYetFeelASecretShame() #puzzles.amazePets() doesn't work....
  Draw.Exit()
 
def break_it(cube):    
  cube = hammer.smashItWithTheHammerUntilItsBrokenIntoTeensyBits()
  Draw.Exit()

:smiley: :wink: :rolleyes:

Actually I think the code is “cleaner” when you do :


#
# improved Cube Solver v.001
#
#  avoids the messyness of pulling off stickers and trying to re-apply them
#

def TakeCubeApart():
   for miniCubes in Cube:
      if first(miniCube):
        UseScrewdriverOrOtherToolToSeperateIfNeccessary()
      else:
           SeperateMiniCubesWithFingers()

def ReAssembleCube:
   for Minicubes in Cube:
       PlaceMiniCubeInCorrectPosition()


:smiley: :smiley: :smiley:

Mike

Actually I think the code is “cleaner” when you do

Well, sure… if you want to do it the easy way…

Your way may be more efficient, nicer looking, and less confusing… but, uh, mine uses more variables. Ahem. Actually, I was making fun of my own real coding style, which is a lot like that. Hmm.

A small update, with corrections to the bone search feature, Poser pose export, and a few lesser things. This also adds the indented hierarchical sort.

http://www.kuroyumes-developmentzone.com/~Cage/PoseLib15g3c.zip

Finally, another update. This one primarily focuses on more Poser integration, but it also adds a couple of other things.

  • Expanded and revised Poser pose exports. Several formatting issues were corrected and an alternate quaternion to euler conversion is now used. Apparently conversion to euler is a somewhat imprecise process. The alternate method (see code, below) works better than the native Blender toQuat() for this purpose, but it still isn’t quite right. I’ve been testing import back into Poser with three figures. One of them is usally about 95 to 99% correct with imports, but the other two only achieve perhaps 75 to 80% accuracy. (Which is odd, because the successful cr2 is derived from one of the less successful ones, so joint orders and origin/endpoint positions are identical…) I did a lot of testing, trying to isolate the cause of these import errors, and it really looks like it must be a result of the imprecision or irregularity in the euler conversions. If anyone has any quat to euler suggestions, please let me know. (Hey Cambo - these same issues will presumably be part of the BVH export process… :wink: ) Poser export also requires that rotation order be specified, so I have added some tools for handling that. An option to save to .p2z compressed format is also included.

  • Joint rotation orders for Poser export can be parsed from a .pz2/.p2z or a .cr2/.crz file. These can be saved to a text file, for easier access later. Rotation orders can also be automatically assigned, in which case generic orders will be applied, using some general assumptions based on a bone’s length/twist axis direction. Rotation orders can be specifically altered for bones list selections. These options are all accessed via the save tab Poser export toggle.

  • An armature, with or without IK, can be parsed from a .cr2 or .crz file. Automatic IK is of the variety used in the sample armature which nozzy posted.

  • Poses can be read from Poser .pz2/.p2z (pose) or .hd2/.hdz (hand) files. All of the pose modifiers should work with this process. When loading a hand pose, you have the option to select the target hand (as in Poser). Poser poses will now appear in the pose listbox. Please note that there is no batch support for this pose type, because pz2 is a multi-frame format. With both import and export support, Poser poses can now be used as a pose format for Blender. Unfortunately, Poser pose handling is both slower and more prone to (apparent) memory leaks than the Blender Library-style pose handling. When using Poser-type poses strictly within Blender, joint rotation order is not an issue.

I should clarify: batch import methods are not supported for Poser poses, because they are a multi-frame format. But. Batch export methods need to be used to specify that a multi-frame export is being made. Otherwise only the current frame will be exported. I guess it’s time for that .pdf guide that Mike joked about, huh? :slight_smile:

  • The bones list tool for IK addition has been expanded to allow addition of a chain of IK. This will create an IK chain similar to those which are created automatically with .cr2 armature import. To use this, select the bones to be affected in the bones list, selecting them in order from the beginning of the chain to the end. (e.g. Thigh, then shin, then foot.) Then select the “Add IK to selection(s)” option in the menu. Specify a name for the chain (to be the name of the added goal bone). This includes the option to add the last selection to the chain (useful in some cases) or not (as with the foot, which should be re-parented to the goal bone, rather than becoming part of the actual chain).

  • A search button has been added to the poses listbox tab. This functions much like the search button for the bones list. This is useful for quickly selecting an entire batch of numbered poses for import.

I have also added Blender Library type pose import to the Poser Python companion script. Unfortunately, this suffers from the same issues with euler rotations which beset Poser pose export from Blender - a pretty clear indication that the problem is, indeed, the euler conversion method.

Just in case anyone here wants to jump in and offer pointers for converting from quat to euler, here is the code (r2d is radians to degrees 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
 
  if (test > 0.499999*unit) and (test < 0.500001*unit):  # singularity at north pole
    heading = 2 * math.atan2(q1.x,q1.w)
    attitude = math.pi/2
    bank = 0    
  elif (test < -0.499999*unit) and (test > -0.500001*unit):  # singularity at south pole
    heading = -2 * math.atan2(q1.x,q1.w)
    attitude = -math.pi/2
    bank = 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)

That’s it, for now. I think I’m nearing the end with this, aside from de-bugging. One of my main concerns at this point is memory use. All of the functions which access Ipo curves in any way seem to contribute to a consistent increase in memory use, which concerns me. My earlier effort to inquire about such matters seemed to be quickly quashed (probably because it was framed in terms of a hypothetical Blender memory leak). I’m asking again, here, if anyone has any observations or ideas about this. If you test this script and note anything in particular about the effect it has on memory, please let me know. I’m still trying to gather observations before I even try to guess about this problem - it’s a little early, IMO, to start filing formal bug reports.

Please let me know if you have any problems, questions, or comments.

Thanks.

Blender script:
http://www.kuroyumes-developmentzone.com/~Cage/PoseLib15h7h.zip

Poser script:
http://www.kuroyumes-developmentzone.com/~Cage/poser2blender_v1e.py

Oiler. Oiler. Not You-ler. Oiler. :slight_smile:

Basil,

Expanded and revised Poser pose exports. Several formatting issues were corrected and an alternate quaternion to euler conversion is now used. Apparently conversion to euler is a somewhat imprecise process.

Eulers depend on rotation order thus applying eulerx, eulery, eulerz, is different from applying eulerz, eulery, eulerx.

If you are interested in direct import/export of poser animation I had found documentation on it before, althought the site it was on is currently down (can’t reach it via wayback machine right now either due to technical difficulties…). If you are interested I can try and dig it up again.

LetterRip

Hi, LetterRip. I am definitely interested in learning as much as I can handle on this topic. I have already tried to account for rotation order issues while constructing this. Unfortunately, regulating the rotation orders upon Blender export really doesn’t seem to solve the Poser import problems. (Note that import into Blender is fine. Import into Poser is a problem.) In fact, I’ve encountered a couple of cases in which Poser imports are better when I use the incorrect rotation order for the target figure. (?!?) Hmm. I keep reading about the “fuzzy” nature of quat to euler/matrix to euler conversion, however, and my week of testing slowly ruled out one thing after another until it looks like the conversion method may be the source of the inaccuracies I’m seeing. Then again, I’m no expert, and I’m also a math doofus. :slight_smile:

If you have any links or resources to share, I’d be thankful. I’ve done a lot of digging, including through the Blender source, but I haven’t yet found a better solution to the limitations in the current export method. :frowning:

Are you suggesting that the order of processing is important, during conversion? Reevan McKay’s BVH export script seems to suggest that, offering different conversion routines for each order. But I adapted the below from his code, and it was far less effective than what I posted above… I also tried going through axis-angle, two matrix methods, and two other direct quat to euler methods. The version I post above is the only one which returned results as good as (actually better than, for my needs) the native Blender processing.


def mat3ToRotOrder(m,order):
  #adapted from Reevan McKay's BVH export script
  PI=3.14159
  if order == [0,1,2]: a,b,c,d,e,f,g,h = (m[0][2],-m[1][2],m[2][2],-m[0][1],m[0][0],m[1][0],m[1][1],m[1][0])
  if order == [0,2,1]: a,b,c,d,e,f,g,h = (-m[0][1],m[2][1],m[1][1],m[0][2],m[0][0],-m[2][0],m[2][2],-m[2][0])
  if order == [1,0,2]: a,b,c,d,e,f,g,h = (-m[1][2],m[0][2],m[2][2],m[1][0],m[1][1],-m[0][1],m[0][0],-m[0][1])
  if order == [1,2,0]: a,b,c,d,e,f,g,h = (m[1][0],-m[2][0],m[0][0],-m[1][2],m[1][1],m[2][1],m[2][2],m[2][1])
  if order == [2,0,1]: a,b,c,d,e,f,g,h = (m[2][1],-m[0][1],m[1][1],-m[2][0],m[2][2],m[0][2],m[0][0],m[0][2])
  if order == [2,1,0]: a,b,c,d,e,f,g,h = (-m[2][0],m[1][0],m[0][0],m[2][1],m[2][2],m[0][1],m[0][2],-m[0][1])
  thetaY=math.asin (a)
  if (thetaY<PI/2):
    if (thetaY>-PI/2):
      thetaZ=math.atan2(b,c)
      thetaX=math.atan2(d,e)
    else:
      thetaZ=-math.atan2(f,g)
      thetaX=0.0
  else:
    thetaZ=math.atan2(h,g)
    thetaX=0.0
  return thetaX,thetaY,thetaZ

This is a fairly minor update, presenting only a few small bug fixes and revisions and the addition of information for the Python script and help menus. Barring any reported bugs or the development of a fix for the fawlty… faulty… :slight_smile: quat to euler handling for Poser export, this is looking like a final form. Thank you everyone for all the assistance and patience. :slight_smile:

http://www.kuroyumes-developmentzone.com/~Cage/PoseLib16.zip

Please let me know if you do find anything in this script which produces errors or is otherwise in need of correction or revision. (Or if you have any good euler conversion code… :slight_smile: .)

Hey Basil,

If you’re bored, a neat addition to your script would be the option to show only the bones in the bones list that were the result of a search. I may take a try at altering your script to include that, but maybe it’s somethig you might find useful?

Mike

That’s a good suggestion, Mike. I’ve been waiting for Cambo’s promised bone layer additions to the API, but there are probably other ways to go about what you suggest. If you want to try your hand at adding anything, feel free. My apologies in advance for the cluttered quality of my code. Still learning, you know.

I’ve been fighting a gradual battle with a new computer, trying to get it all set up, so I haven’t had much time for Blender or Python these past few weeks. I hope to get back to it soon.

And, unfortunately, I still have no idea how to proceed with the euler rotation quandaries…