#!BPY
 
"""
Name: 'Poser bones-import (.cr2)...'
Blender: 240
Group: 'Import'
Tooltip: 'Load just the skeleton from a Poser CR2 file'
"""

__author__ = "Frederick Lee"
__url__ = ["blender", "elysiun", "http://www.linux.ucla.edu/~phaethon/blender/cr2bone_import/cr2bone_import.html"]
__version__ = "0.11"

__bpydoc__ = """\
This script imports Poser "actors" as Blender armatures.

Usage:
Run this script from "File->Import" menu and then load the desired CR2 file.

Notes: ignores pretty much everything else in the file.
Also, depends on another module, 'cr2_parser.py'.
"""

# --------------------------------------------------------------------------
# cr2bone_import.py, import Poser bones as Blender armature.
# --------------------------------------------------------------------------
#
# ***** 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
#import cr2_parser
#---------------
#!/usr/bin/env python

#   cr2_parser, Poser file pseudo-parser; python module.
#   Copyright (C) 2006  Frederick Lee <phaethon@linux.ucla.edu>
#
#   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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA


PACKAGE="cr2_parser"
VERSION="0.01"


#Dumb parser version
#Relies on the tendancy of cr2 files to put "{" and "}" on lines of their own.
#Relies on newline appearing to be significant.

#Collects entire contents of cr2 into "blocks", which are just classes for now.

#Primary target: create armature skeleton

"""
Information from http://home.carolina.rr.com/kattman/Tutorials.htm
"""




debug=0

def READING (msg):
  if debug:
    print "READING", msg

def ENDREAD (msg):
  if debug:
    print "ENDREAD", msg




class cr2:
#  class version:
#  class figure:
#    class material:
#    class inkyChain:
#  class actor:
#    class channels:  #aka dials.
#      class keys:
  class version:
    def __init__ (self):  #version
      self.number = None
    def load (self, fileobj):
      s = fileobj.readline().strip()
      w = s.split()
      depth = 0
      while s==s and (depth > 0 or s != "}"):
        if len(w) < 1:
          w = ("",)
        if s == "{":
          depth += 1
        elif w[0] == "number":  #The only valid key known.
          self.number = w[1]
        s = fileobj.readline().strip()
        w = s.split()
        if s == "}":
          depth -= 1
      return

  class figure:
    class material:
      def __init__ (self):  #material
        pass
      def load (self, fileobj):
        #Expect { first.
        READING("material")
        s = fileobj.readline().strip()
        w = s.split()
        depth = 0
        while s==s and (depth > 0 or s != "}"):
          if len(w) < 1:
            w = ("",)
          if s == "{":
            depth += 1
          #TODO:
          """
        KdColor <float> <float> <float> <float>
        KaColor <float> <float> <float> <float>
        KsColor <float> <float> <float> <float>
        TextureColor <float> <float> <float> <float>
        NsExponent <int>
        tMin <float>
        tMax <float>
        tExpo <float>
        bumpStrength <float>
        ksIgnoreTexture <int>
        reflectThruLights <int>
        reflectThruKd <int>
        textureMap NO_MAP
        bumpMap NO_MAP
        reflectionMap NO_MAP
        transparencyMap NO_MAP
        ReflectionColor <float> <float> <float> <float>
        reflectionStrength <float>
"""
          s = fileobj.readline().strip()
          w = s.split()
          if s == "}":
            depth -= 1
        ENDREAD("material")
        return
      pass

    class inkyChain:
      def __init__ (self):  #inkyChain
        pass
      def load (self, fileobj):
        #Expect { first.
        s = fileobj.readline().strip()
        w = s.split()
        depth = 0
        while s==s and (depth > 0 or s != "}"):
          if len(w) < 1:
            w = ("",)
          if s == "{":
            depth += 1
          #TODO:
          """
        {
        on
        name <string>
        addLink <string:bone_name>
        addLink <string:bone_name>
        goal <string:bone_name>
        linkWeight <int> <float>
        linkWeight <int> <float>
        }
"""
          s = fileobj.readline().strip()
          w = s.split()
          if s == "}":
            depth -= 1
        return
      pass

    def __init__ (self):  #figure
      self.name = ""
      self.root = None
      self.parenting = {}  #parenting[child] = parent
      self.materials = {}
      self.inkyChains = {}
      self.defaultPick = None
      self.displayOn = 1
      self.welding = {}  #welding[child] = parent
      self.allowBending = 1
      self.figureType = 0
      self.canonType = 0
      self.conforming = 1
      self.displayMode = None
      self.locked = 0

    def load (self, fileobj):
      #Expect { first.
      READING("figure")
      s = fileobj.readline().strip()
      w = s.split()
      depth = 0
      while s==s and (depth > 0 or s != "}"):
        if len(w) < 1:
          w = ("",)
        if s == "{":
          depth += 1
        elif w[0] == "name":
          w = s.split(' ',1)
          self.name = w[1]
        elif w[0] == "addChild":
          child = w[1]
          s = fileobj.readline().strip()
          par = s
          self.parenting[child] = par
        elif w[0] == "weld":
          child = w[1]
          s = fileobj.readline().strip()
          par = s
          self.welding[child] = par
        elif w[0] == "inkyChain":
          ic = self.inkyChain()
          ic.name = w[1]
          ic.load(fileobj)
          self.inkyChains[ic.name] = ic
        elif (w[0] == "material"):
          m = self.material()
          m.name = w[1]
          m.load(fileobj)
          self.inkyChains[m.name] = m
        elif w[0] in ["root", "defaultPick", "displayMode"]:
          #Misc. string crap.
          self.__dict__[w[0]] = w[1]
        elif w[0] in ["displayOn", "allowBending", "figureType", "canonType", "conforming", "locked"]:
          #Misc integer crap.
          self.__dict__[w[0]] = int(w[1])
        s = fileobj.readline().strip()
        w = s.split()
        if s == "}":
          depth -= 1
      ENDREAD("figure")
      return self.name

  class actor:
    class channelset:  #aka dials.
      class dial:
        class key:
          def __init__ (self):  #key
            self.k = (0, 0)
            self.static = 0

          def load (self, fileobj):
            #Expect { first.
            READING("keys")
            s = fileobj.readline().strip()
            w = s.split()
            depth = 0
            while s==s and (depth > 0 or s != "}"):
              if len(w) < 1:
                w = ("",)
              if s == "{":
                depth += 1
              elif w[0] == "k":
                self.k = (float(w[1]), float(w[2]))
              elif w[0] == "static":
                self.static = int(w[1])
              s = fileobj.readline().strip()
              w = s.split()
              if s == "}":
                depth -= 1
            ENDREAD("keys")
            return

        class deltaset:
          def __init__ (self, size):  #deltaset
            self.d = [None] * size
            pass

          def load (self, fileobj):
            #Expect { first.
            READING("deltas")
            s = fileobj.readline().strip()
            w = s.split()
            depth = 0
            while s==s and (depth > 0 or s != "}"):
              if len(w) < 1:
                w = ("",)
              if s == "{":
                depth += 1
              elif w[0] == 'd':
                n = int(w[1])
                (x, y, z) = (w[2], w[3], w[4])
                self.d[n] = (x,y,z)
              s = fileobj.readline().strip()
              w = s.split()
              if s == "}":
                depth -= 1
            ENDREAD("deltas")
            return

        def __init__ (self):  #dial
          self.name = ""
          self.initvalue = 0
          self.hidden = 0
          self.forceLimits = 0
          self.min = 0
          self.max = 0
          self.trackingScale = 1
          self.keys = []
          self.deltaAddDelta = 1.0  #Some kind of scaling factor?
          self.indexes = 0  #Number of vertices that get changed in this delta
          self.numbDeltas = 0  #Total number of vertices available.
          self.angles = ()
          self.otherActor = None
          self.matrixActor = None
          self.center = ()
          self.flipped = 0
          self.sphereMatsRaw = []
          self.doBulge = 0
          self.posBulgeLeft = 0
          self.posBulgeRight = 0
          self.negBulgeLeft = 0
          self.negBulgeRight = 0
          self.jointMult = 0
          self.calcWeights = 0

        def load (self, fileobj):
          #Expect { first.
          READING("dial")
          s = fileobj.readline().strip()
          w = s.split()
          depth = 0
          while s==s and (depth > 0 or s != "}"):
            if len(w) < 1:
              w = ("",)
            if s == "{":
              depth += 1
            elif w[0] == "name":
              w = s.split(' ', 1)
              self.name = w[1]
            elif w[0] == "keys":
              k = self.key()
              k.load(fileobj)
              self.keys.append(k)
            elif w[0] == "deltaAddDelta":
              self.deltaAddDelta = float(w[1])
            elif w[0] == "indexes":
              self.indexes = int(w[1])
            elif w[0] == "numbDeltas":
              self.numbDeltas = int(w[1])
            elif w[0] == "deltas":
              deltaforce = self.deltaset(self.numbDeltas)
              deltaforce.load(fileobj)
              self.deltas = deltaforce
            elif w[0] == "sphereMatsRow":
              for i in xrange(0, 8):
                t = fileobj.readline().strip()
                v = map(float, t.split())
                self.sphereMatsRow.append(v)
            elif w[0] in ("flipped", "calcWeights"):
              #Misc. flag crap.
              self.__dict__[w[0]] = 1
            elif w[0] in ("otherActor", "MatrixActor"):
              #Misc. string crap.
              self.__dict__[w[0]] = w[1]
            elif w[0] in ("hidden", "forceLimits", "interpStyleLocked", "doBulge", "jointMult"):
              #Misc. integer crap.
              self.__dict__[w[0]] = int(w[1])
            elif w[0] in ("initValue", "min", "max", "trackingScale", "staticValue", "posBulgeLeft", "posBulgeRight", "negBulgeLeft", "negBulgeRight"):
              #Misc. float crap.
              self.__dict__[w[0]] = float(w[1])
            s = fileobj.readline().strip()
            w = s.split()
            if s == "}":
              depth -= 1
          ENDREAD("dial")
          return

      def __init__ (self):  #channelset
        self.dials = {}
        self.deltas = None
        pass

      def load (self, fileobj):
        #Expect { first.
        READING("channels")
        s = fileobj.readline().strip()
        w = s.split()
        depth = 0
        while s==s and (depth > 0 or s != "}"):
          if len(w) < 1:
            w = ("",)
          if s == "{":
            depth += 1
          elif len(w) > 1:  #Only "<type> <name>" statements allowed.
            dial = self.dial()
            dial.name = w[1]
            dial.load(fileobj)
            self.dials[dial.name] = dial
          s = fileobj.readline().strip()
          w = s.split()
          if s == "}":
            depth -= 1
        ENDREAD("channels")
        return

    def __init__ (self):  #actor
      self.name = ""
      self.bend = 1
      self.dynamicsLock = 0
      self.hidden = 0
      self.addToMenu = 0
      self.castsShadow = 1
      self.includeDepthCue = 1
      self.parent = None
      self.channels = self.channelset()
      self.displayOrigin = 0
      self.displayMode = None
      self.customMaterial = 0
      self.locked = 0
      pass

    def load (self, fileobj):
      #Expect { first.
      READING("actor %s" % self.name)
      s = fileobj.readline().strip()
      w = s.split()
      depth = 0
      while s==s and (depth > 0 or s != "}"):
        if len(w) < 1:
          w = ("",)
        if s == "{":
          depth += 1
        elif w[0] == "channels":
          self.channels.load(fileobj)
        elif w[0] == "on":
          pass
        elif w[0] == "off":
          pass
        elif w[0] == "parent":
          self.parent = w[1]
        elif w[0] == "displayMode":
          self.displayMode = w[1]
        elif w[0] in ("bend", "dynamicsLock", "hidden", "addToMenu", "castsShadow", "includeInDepthCue", "displayOrigin", "customMaterial", "locked"):
          #Misc. integer crap.
          self.__dict__[w[0]] = int(w[1])
        elif w[0] in ("endPoint", "origin", "orientation"):
          #Misc. coordinate crap.
          (x, y, z) = (float(w[1]), float(w[2]), float(w[3]))
          self.__dict__[w[0]] = (x, y, z)
        s = fileobj.readline().strip()
        w = s.split()
        if s == "}":
          depth -= 1
      ENDREAD("actor %s" % self.name)
      return self.name

  def __init__ (self): #cr2
    self.name = ""
    self.ver = self.version()
    self.figures = {}
    self.actors = {}
    self.figureResFile = None

  def load (self, fileobj):
    READING("cr2")
    s = fileobj.readline().strip()
    w = s.split()
    depth = 0
    while s==s and (depth > 0 or s != "}"):
#      print "s='%s'" % s
      if len(w) < 1:
         w = ("",)
      if s == "{":
        depth += 1
      elif w[0] == "version":
        self.ver.load(fileobj)
      elif w[0] == "figure":
        figure = self.figure()
        name = figure.load(fileobj)
        self.figures[name] = figure
      elif w[0] == "actor":
        actor = self.actor()
        actor.name = w[1]
        name = actor.load(fileobj)
        self.actors[name] = actor
      elif w[0] == "figureResFile":
        self.figureResFile = w[1]
      elif w[0] == "setGeomHandlerOffset":
        (x, y, z) = (w[1], w[2], w[3])
        (x, y, z) = (float(x), float(y), float(z))
        self.setGeomHandlerOffset = (x, y, z)
      else:
#        print "Unknown keyword", w[0], "at", fileobj.tell()
#        print s
        pass
      s = fileobj.readline().strip()
      w = s.split()
      if s == "}":
        depth -= 1
#      print "depth=%d" % depth, "s='%s'" % s
    ENDREAD("cr2")
    return self.name

  def test (self):
    f = open("test.cr2", "rt")
    self.load(file2(f))
    f.close()
    print self.__dict__

  def trace_import (self):
    print self.figures
    figure = self.figures[self.figures.keys()[0]]
    print "Expected number of bones:", len(self.actors.keys())
    keylist = self.actors.keys()
    for i in xrange(0, len(keylist)):
      bonename = keylist[i]
      actor = self.actors[bonename]
      print "Bone %d: '%s'" % (i, bonename)
      print " base is at", actor.origin, "extends to", actor.endPoint
      if figure.welding.has_key(bonename):
        print " parent to '%s'" % (figure.welding[bonename],)
    pass


class file2:
  def __init__ (self, origfile):
    self.origfile = origfile
    self.linecount = 0

  def readline (self):
    self.linecount += 1
    return self.origfile.readline()
  def tell (self):
    return self.linecount


'''
if __name__ == "__main__":
  x = cr2()
  x.test()
  x.trace_import()
'''
#-------------------

PACKAGE="cr2_import"
VERSION="0.11"


#testfname = "/home/fredslee/blends/python/test.cr2"

#x = cr2()
#f = open(testfname, "rt")
#x.load(f)
#f.close()


def crumb (breadcrumb):
  """printf is your friend."""
  if 0:
    print "%s" % breadcrumb


class minibone:
  def __init__ (self):
    self.name = None
    self.head = (0, 0, 0)
    self.tail = (0, 0, 1)
    self.orientation = (1, 0, 0)
    self.parentname = None


class blender_cr2:
  def __init__ (self):
    self.cr2 = cr2()
    self.conseq = []  #Construction sequence.
    self.miniskel = {}  #Temporary holding of (unconnected) bones.
    self.skelname = None
    pass

  def makedepend (self, depmap, childname, parentname):
    """Arrange construction dependency in self.conseq.  Parents must be constructed before their corresponding children <=> a bone's parent must be constructed before itself."""
    if parentname:
      #Request dependency check.
      if parentname in self.conseq:
        #Parent already slated for construction, add child.
        if not childname in self.conseq:
          self.conseq.append(childname)
      else:
        #Parent goes first.
        if depmap.has_key(parentname):
          #Parent has its own dependency.
          a = parentname
          b = depmap[a]
          self.makedepend(depmap, a, b)
        else:
          #Parent has no other other dependency; go.
          if not parentname in self.conseq:
            self.conseq.append(parentname)
        if not childname in self.conseq:
          self.conseq.append(childname)
    else:
      #Depends on nothing.
      if not childname in self.conseq:  #Avoid dupe.
        self.conseq.append(childname)

  def masticate (self):
    """Translate poser data into blender-friendly ones (using minibones).  I was going to name this 'digest', but that already means hashing, so I decided to go with 'chew'."""
    figure = self.cr2.figures[self.cr2.figures.keys()[0]]
    self.conseq.append(figure.root)  #Root Bone.
    self.skelname = "ARM.%s" % figure.root
    crumb("Expected number of bones: %d" % len(self.cr2.actors.keys()))
    keylist = self.cr2.actors.keys()
    for i in xrange(0, len(keylist)):
      mbone = minibone()
      mbone.name = keylist[i]
      actor = self.cr2.actors[mbone.name]
      crumb("Bone %d: '%s'" % (i, mbone.name))
      mbone.head = actor.origin
      mbone.tail = actor.endPoint
      crumb(" base is at %s extends to %s" % (mbone.head, mbone.tail))
      depmap = figure.welding
      if depmap.has_key(mbone.name):
        mbone.parentname = depmap[mbone.name]
        crumb(" parent to '%s'" % mbone.parentname)
      elif mbone.name != figure.root:
        crumb("Forcing parent of '%s' to '%s'" % (mbone.name, figure.root))
        mbone.parentname = figure.root
      #actor.orientation apparently has to do with bone rotation/roll.
      mbone.orientation = actor.orientation
      self.makedepend(depmap, mbone.name, mbone.parentname)
      self.miniskel[mbone.name] = mbone
    #Make sure all bones are queued up to be constructed.
    for k in keylist:
      self.makedepend(depmap, k, None)

    crumb("Construction sequence: %s" % self.conseq)
    pass

  def makebones (self):
    """Construct the actual Blender armature based on array self.conseq and dict self.miniskel"""
    scene = Blender.Scene.getCurrent()
#Create empties to make sure origins are correct.
#    for b in self.conseq:
#      mbone = self.miniskel[b]
#      ob = Blender.Object.New ('Empty') 
#      ob.setSize(0.1, 0.1, 0.1)
#      ob.setLocation(mbone.head[0], mbone.head[1], mbone.head[2])
#      tname = "E." + mbone.name
#      ob.setName(tname)
#      scene.link(ob)
#      print b, "as '%s'" % tname, "at", ob.getLocation()
#    a = Blender.Armature.Armature("TestArm")

    if not self.skelname:
      self.skelname = "ARM.Test"
    a = Blender.Armature.Armature(self.skelname)
#    print "a is", a
    o = Blender.Object.New("Armature")
    o.link(a)
    o.setName(self.skelname)
    scene.link(o)
    print "%s: creating armature object '%s'" % (PACKAGE, o.name)

    a.makeEditable()
#    b = self.conseq[0]
#    if 1:
    makelist = []
    print "%s: creating %d bones" % (PACKAGE, len(self.conseq))
    for b in self.conseq:
      crumb("Constructing %s" % b)
      makelist.append(b)
      mbone = self.miniskel[b]
      eb = Blender.Armature.Editbone()
      #orientation affects eb.roll somehow...
#      eb.roll = 10
      v = mbone.head
      eb.head = Blender.Mathutils.Vector(v[0], v[1], v[2])
      v = mbone.tail
      eb.tail = Blender.Mathutils.Vector(v[0], v[1], v[2])
#      bname = "E." + mbone.name
      if mbone.parentname in a.bones.keys():
        crumb("parenting %s <- %s" % (mbone.name, mbone.parentname))
        eb.parent = a.bones[mbone.parentname]
      else:
        crumb("Parent for %s not found." % mbone.name)
      bname = mbone.name
      a.bones[bname] = eb
    a.update()
    makelist.sort()
    crumb(makelist)  #Check for duped bones.
    print "%s: Done." % (PACKAGE,)
    pass


  def loadcr2 (self, fileobj):
    """Load CR2 via fileobject.  User loadcr2_name to load by filename."""
    self.cr2.load(fileobj)
    self.masticate()  #Chew it up into something Blender-useful.
    self.makebones()

  def loadcr2_name (self, fname):
    """Load CR2 by filename"""
    print "%s: reading '%s'" % (PACKAGE, fname)
    f = open(fname, "rt")
    self.loadcr2(f)
    f.close()
    
  

#imp = blender_cr2()
#imp.loadcr2_name(testfname)

def load_cr2bones (filename):
  imp = blender_cr2()
  imp.loadcr2_name(filename)


Blender.Window.FileSelector(load_cr2bones, 'Import Poser bones')






