# ##### 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# Project Name:        MakeHuman
# Product Home Page:   http://www.makehuman.org/
# Code Home Page:      http://code.google.com/p/makehuman/
# Authors:             Thomas Larsson
# Script copyright (C) MakeHuman Team 2001-2013
# Coding Standards: See http://www.makehuman.org/node/165

"""
Abstract
MHX (MakeHuman eXchange format) importer for Blender 2.6x.

This script should be distributed with Blender.
If not, place it in the .blender/scripts/addons dir
Activate the script in the "Addons" tab (user preferences).
Access from the File > Import menu.

Alternatively, run the script in the script editor (Alt-P), and access from the File > Import menu
"""

bl_info = {
    "name": "Import: MakeHuman (.mhx)",
    "author": "Thomas Larsson",
    "version": "1.16.12",
    "blender": (2, 68, 0),
    "location": "File > Import > MakeHuman (.mhx)",
    "description": "Import files in the MakeHuman eXchange format (.mhx)",
    "warning": "",
    "wiki_url": "http://www.makehuman.org/documentation",
    "category": "Import-Export",
}

MAJOR_VERSION = 1
MINOR_VERSION = 16
FROM_VERSION = 13
SUB_VERSION = 12

majorVersion = MAJOR_VERSION
minorVersion = MINOR_VERSION

#
#
#

import bpy
import os
import time
import math
import mathutils
from mathutils import Vector, Matrix, Quaternion
from bpy.props import *

MHX249 = False
Blender24 = False
Blender25 = True
theDir = "~/makehuman/exports"

#
#
#

theScale = 1.0
One = 1.0/theScale
useMesh = 1
verbosity = 2
warnedTextureDir = False
warnedVersion = False

true = True
false = False
Epsilon = 1e-6
nErrors = 0
theTempDatum = None
theMessage = ""
theMhxFile = ""

#
#    toggle flags
#

T_EnforceVersion = 0x01
T_Clothes = 0x02
T_HardParents = 0x0
T_CrashSafe = 0x0

T_Diamond = 0x10
T_Replace = 0x20
T_Shapekeys = 0x40
T_ShapeDrivers = 0x80

T_Face = T_Shapekeys
T_Shape = T_Shapekeys

T_Mesh = 0x100
T_Armature = 0x200
T_Proxy = 0x400
T_Cage = 0x800

T_Rigify = 0x1000
T_Opcns = 0x2000
T_Symm = 0x4000

DefaultToggle = ( T_EnforceVersion + T_Mesh + T_Armature +
    T_Shapekeys + T_ShapeDrivers + T_Proxy + T_Clothes + T_Rigify )

toggle = DefaultToggle
toggleSettings = toggle
loadedData = None

#
#   mhxEval(expr) - an attempt at a reasonably safe eval.
#   Note that expr never contains any whitespace due to the behavior
#   of the mhx tokenizer.
#

def mhxEval(expr, locls={}):
    globls = {
        '__builtins__' : {},
        'toggle' : toggle,
        'theScale' : theScale,
        'One' : One,
        'T_EnforceVersion' : T_EnforceVersion,
        'T_Clothes' : T_Clothes,
        'T_HardParents' : T_HardParents,
        'T_CrashSafe' : T_CrashSafe,
        'T_Diamond' : T_Diamond,
        'T_Replace' : T_Replace,
        'T_Shapekeys' : T_Shapekeys,
        'T_ShapeDrivers' : T_ShapeDrivers,
        'T_Face' : T_Face,
        'T_Shape' : T_Shape,
        'T_Mesh' : T_Mesh,
        'T_Armature' : T_Armature,
        'T_Proxy' : T_Proxy,
        'T_Cage' : T_Cage,
        'T_Rigify' : T_Rigify,
        'T_Opcns' : T_Opcns,
        'T_Symm' : T_Symm,
    }
    return eval(expr, globls, locls)

#
#    Dictionaries
#

def initLoadedData():
    global loadedData

    loadedData = {
    'NONE' : {},

    'Object' : {},
    'Mesh' : {},
    'Armature' : {},
    'Lamp' : {},
    'Camera' : {},
    'Lattice' : {},
    'Curve' : {},
    'Text' : {},

    'Material' : {},
    'Image' : {},
    'MaterialTextureSlot' : {},
    'Texture' : {},

    'Bone' : {},
    'BoneGroup' : {},
    'Rigify' : {},

    'Action' : {},
    'Group' : {},

    'MeshTextureFaceLayer' : {},
    'MeshColorLayer' : {},
    'VertexGroup' : {},
    'ShapeKey' : {},
    'ParticleSystem' : {},

    'ObjectConstraints' : {},
    'ObjectModifiers' : {},
    'MaterialSlot' : {},
    }
    return

def reinitGlobalData():
    global loadedData
    for key in [
        'MeshTextureFaceLayer', 'MeshColorLayer', 'VertexGroup', 'ShapeKey',
        'ParticleSystem', 'ObjectConstraints', 'ObjectModifiers', 'MaterialSlot']:
        loadedData[key] = {}
    return

Plural = {
    'Object' : 'objects',
    'Mesh' : 'meshes',
    'Lattice' : 'lattices',
    'Curve' : 'curves',
    'Text' : 'texts',
    'Group' : 'groups',
    'Empty' : 'empties',
    'Armature' : 'armatures',
    'Bone' : 'bones',
    'BoneGroup' : 'bone_groups',
    'Pose' : 'poses',
    'PoseBone' : 'pose_bones',
    'Material' : 'materials',
    'Texture' : 'textures',
    'Image' : 'images',
    'Camera' : 'cameras',
    'Lamp' : 'lamps',
    'World' : 'worlds',
}

#
#    readMhxFile(filePath):
#

def readMhxFile(filePath):
    global nErrors, theScale, theArmature, defaultScale, One
    global toggle, warnedVersion, theMessage, alpha7, theDir

    defaultScale = theScale
    One = 1.0/theScale
    theArmature = None
    alpha7 = False
    warnedVersion = False
    initLoadedData()
    theMessage = ""

    theDir = os.path.dirname(filePath)
    fileName = os.path.expanduser(filePath)
    _,ext = os.path.splitext(fileName)
    if ext.lower() != ".mhx":
        print("Error: Not a mhx file: %s" % fileName.encode('utf-8', 'strict'))
        return
    print( "Opening MHX file %s " % fileName.encode('utf-8', 'strict') )
    print("Toggle %x" % toggle)
    time1 = time.clock()

    # ignore = False  # UNUSED
    stack = []
    tokens = []
    key = "toplevel"
    level = 0
    nErrors = 0
    comment = 0
    nesting = 0

    file= open(fileName, "rU")
    print( "Tokenizing" )
    lineNo = 0
    for line in file:
        # print(line)
        lineSplit= line.split()
        lineNo += 1
        if len(lineSplit) == 0:
            pass
        elif lineSplit[0][0] == '#':
            if lineSplit[0] == '#if':
                if comment == nesting:
                    try:
                        res = mhxEval(lineSplit[1])
                    except:
                        res = False
                    if res:
                        comment += 1
                nesting += 1
            elif lineSplit[0] == '#else':
                if comment == nesting-1:
                    comment += 1
                elif comment == nesting:
                    comment -= 1
            elif lineSplit[0] == '#endif':
                if comment == nesting:
                    comment -= 1
                nesting -= 1
        elif comment < nesting:
            pass
        elif lineSplit[0] == 'end':
            try:
                sub = tokens
                tokens = stack.pop()
                if tokens:
                    tokens[-1][2] = sub
                level -= 1
            except:
                print( "Tokenizer error at or before line %d.\nThe mhx file has been corrupted.\nTry to export it again from MakeHuman." % lineNo )
                print( line )
                stack.pop()
        elif lineSplit[-1] == ';':
            if lineSplit[0] == '\\':
                key = lineSplit[1]
                tokens.append([key,lineSplit[2:-1],[]])
            else:
                key = lineSplit[0]
                tokens.append([key,lineSplit[1:-1],[]])
        else:
            key = lineSplit[0]
            tokens.append([key,lineSplit[1:],[]])
            stack.append(tokens)
            level += 1
            tokens = []
    file.close()

    if level != 0:
        MyError("Tokenizer error (%d).\nThe mhx file has been corrupted.\nTry to export it again from MakeHuman." % level)
    scn = clearScene()
    print( "Parsing" )
    parse(tokens)

    scn.objects.active = theArmature
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.select_all(action='DESELECT')
    theArmature.select = True
    bpy.ops.object.mode_set(mode='POSE')
    theArmature.MhAlpha8 = not alpha7
    #bpy.ops.wm.properties_edit(data_path="object", property="MhxRig", value=theArmature["MhxRig"])

    time2 = time.clock()
    msg = "File %s loaded in %g s" % (fileName, time2-time1)
    if nErrors:
        msg += " but there where %d errors. " % (nErrors)
    print(msg)
    return

#
#    getObject(name, var):
#

def getObject(name, var):
    try:
        return loadedData['Object'][name]
    except:
        raise MhxError("Bug: object %s not found" % ob)

#
#    checkMhxVersion(major, minor):
#

def checkMhxVersion(major, minor):
    global warnedVersion
    print("MHX", (major,minor), (MAJOR_VERSION, MINOR_VERSION), warnedVersion)
    if  major != MAJOR_VERSION or minor < FROM_VERSION:
        if warnedVersion:
            return
        else:
            msg = (
"Wrong MHX version\n" +
"Expected MHX %d.%02d but the loaded file " % (MAJOR_VERSION, MINOR_VERSION) +
"has version MHX %d.%02d\n" % (major, minor))
            if minor < FROM_VERSION:
                msg += (
"You can disable this error message by deselecting the \n" +
"Enforce version option when importing. Better:\n" +
"Export the MHX file again with an updated version of MakeHuman.\n" +
"The most up-to-date version of MakeHuman is the nightly build.\n")
            else:
                msg += (
"Download the most recent Blender build from www.graphicall.org. \n" +
"The most up-to-date version of the import script is distributed\n" +
"with Blender. It can also be downloaded from MakeHuman. \n" +
"It is located in the importers/mhx/blender25x \n" +
"folder and is called import_scene_mhx.py. \n")
        if (toggle & T_EnforceVersion or minor > MINOR_VERSION):
            MyError(msg)
        else:
            print(msg)
            warnedVersion = True
    return

#
#    parse(tokens):
#

ifResult = False

def printMHXVersionInfo(versionStr, performVersionCheck = False):
    versionInfo = dict()
    val = versionStr.split()

    majorVersion = int(val[0])
    minorVersion = int(val[1])

    for debugVal in val[2:]:
        debugVal = debugVal.replace("_"," ")
        dKey, dVal = debugVal.split(':')
        versionInfo[ dKey.strip() ] = dVal.strip()

    if 'MHXImporter' in versionInfo:
        print("MHX importer version: ", versionInfo["MHXImporter"])
    if performVersionCheck:
        checkMhxVersion(majorVersion, minorVersion)
    else:
        print("MHX: %s.%s" % (majorVersion, minorVersion))

    for (key, value) in versionInfo.items():
        if key == "MHXImporter":
            continue
        print("%s: %s" % (key, value))

def parse(tokens):
    global MHX249, ifResult, theScale, defaultScale, One
    global majorVersion, minorVersion
    versionInfoStr = ""

    for (key, val, sub) in tokens:
        data = None
        if key == 'MHX':
            importerVerStr = "MHXImporter:_%s" % (bl_info["version"])
            versionInfoStr = " ".join(val + [importerVerStr])

            printMHXVersionInfo(versionInfoStr, performVersionCheck = True)
        elif key == 'MHX249':
            MHX249 = mhxEval(val[0])
            print("Blender 2.49 compatibility mode is %s\n" % MHX249)
        elif MHX249:
            pass
        elif key == 'print':
            msg = concatList(val)
            print(msg)
        elif key == 'warn':
            msg = concatList(val)
            print(msg)
        elif key == 'error':
            msg = concatList(val)
            MyError(msg)
        elif key == 'NoScale':
            if mhxEval(val[0]):
                theScale = 1.0
            else:
                theScale = defaultScale
            One = 1.0/theScale
        elif key == "Object":
            parseObject(val, sub, versionInfoStr)
        elif key == "Mesh":
            reinitGlobalData()
            data = parseMesh(val, sub)
        elif key == "Armature":
            data = parseArmature(val, sub)
        elif key == "Pose":
            data = parsePose(val, sub)
        elif key == "Action":
            data = parseAction(val, sub)
        elif key == "Material":
            data = parseMaterial(val, sub)
        elif key == "Texture":
            data = parseTexture(val, sub)
        elif key == "Image":
            data = parseImage(val, sub)
        elif key == "Curve":
            data = parseCurve(val, sub)
        elif key == "TextCurve":
            data = parseTextCurve(val, sub)
        elif key == "Lattice":
            data = parseLattice(val, sub)
        elif key == "Group":
            data = parseGroup(val, sub)
        elif key == "Lamp":
            data = parseLamp(val, sub)
        elif key == "World":
            data = parseWorld(val, sub)
        elif key == "Scene":
            data = parseScene(val, sub)
        elif key == "DefineProperty":
            parseDefineProperty(val, sub)
        elif key == "Process":
            parseProcess(val, sub)
        elif key == "PostProcess":
            postProcess(val)
            hideLayers(val)
        elif key == "CorrectRig":
            correctRig(val)
        elif key == "Rigify":
            if toggle & T_Rigify:
                rigifyMhx(bpy.context)
        elif key == 'AnimationData':
            try:
                ob = loadedData['Object'][val[0]]
            except:
                ob = None
            if ob:
                bpy.context.scene.objects.active = ob
                parseAnimationData(ob, val, sub)
        elif key == 'MaterialAnimationData':
            try:
                ob = loadedData['Object'][val[0]]
            except:
                ob = None
            if ob:
                bpy.context.scene.objects.active = ob
                mat = ob.data.materials[int(val[2])]
                parseAnimationData(mat, val, sub)
        elif key == 'ShapeKeys':
            try:
                ob = loadedData['Object'][val[0]]
            except:
                MyError("ShapeKeys object %s does not exist" % val[0])
            if ob:
                bpy.context.scene.objects.active = ob
                parseShapeKeys(ob, ob.data, val, sub)
        else:
            data = parseDefaultType(key, val, sub)


#
#    parseDefaultType(typ, args, tokens):
#

def parseDefaultType(typ, args, tokens):
    name = args[0]
    data = None
    expr = "bpy.data.%s.new('%s')" % (Plural[typ], name)
    data = mhxEval(expr)

    bpyType = typ.capitalize()
    loadedData[bpyType][name] = data
    if data is None:
        return None

    for (key, val, sub) in tokens:
        defaultKey(key, val, sub, data)
    return data

#
#    concatList(elts)
#

def concatList(elts):
    string = ""
    for elt in elts:
        string += " %s" % elt
    return string

#
#    parseAction(args, tokens):
#    parseFCurve(fcu, args, tokens):
#    parseKeyFramePoint(pt, args, tokens):
#

def parseAction(args, tokens):
    name = args[0]
    if invalid(args[1]):
        return

    ob = bpy.context.object
    bpy.ops.object.mode_set(mode='POSE')
    if ob.animation_data:
        ob.animation_data.action = None
    created = {}
    for (key, val, sub) in tokens:
        if key == 'FCurve':
            prepareActionFCurve(ob, created, val, sub)

    act = ob.animation_data.action
    loadedData['Action'][name] = act
    if act is None:
        print("Ignoring action %s" % name)
        return act
    act.name = name
    print("Action", name, act, ob)

    for (key, val, sub) in tokens:
        if key == 'FCurve':
            fcu = parseActionFCurve(act, ob, val, sub)
        else:
            defaultKey(key, val, sub, act)
    ob.animation_data.action = None
    bpy.ops.object.mode_set(mode='OBJECT')
    return act

def prepareActionFCurve(ob, created, args, tokens):
    dataPath = args[0]
    index = args[1]
    (expr, channel) = channelFromDataPath(dataPath, index)
    try:
        if channel in created[expr]:
            return
        else:
            created[expr].append(channel)
    except:
        created[expr] = [channel]

    times = []
    for (key, val, sub) in tokens:
        if key == 'kp':
            times.append(int(val[0]))

    try:
        data = mhxEval(expr)
    except:
        print("Ignoring illegal expression: %s" % expr)
        return

    n = 0
    for t in times:
        #bpy.context.scene.current_frame = t
        bpy.ops.anim.change_frame(frame = t)
        try:
            data.keyframe_insert(channel)
            n += 1
        except:
            pass
            #print("failed", data, expr, channel)
    if n != len(times):
        print("Mismatch", n, len(times), expr, channel)
    return

def channelFromDataPath(dataPath, index):
    words = dataPath.split(']')
    if len(words) == 1:
        # location
        expr = "ob"
        channel = dataPath
    elif len(words) == 2:
        # pose.bones["tongue"].location
        expr = "ob.%s]" % (words[0])
        cwords = words[1].split('.')
        channel = cwords[1]
    elif len(words) == 3:
        # pose.bones["brow.R"]["mad"]
        expr = "ob.%s]" % (words[0])
        cwords = words[1].split('"')
        channel = cwords[1]
    return (expr, channel)

def parseActionFCurve(act, ob, args, tokens):
    dataPath = args[0]
    index = args[1]
    (expr, channel) = channelFromDataPath(dataPath, index)
    index = int(args[1])

    success = False
    for fcu in act.fcurves:
        (expr1, channel1) = channelFromDataPath(fcu.data_path, fcu.array_index)
        if expr1 == expr and channel1 == channel and fcu.array_index == index:
            success = True
            break
    if not success:
        return None

    n = 0
    for (key, val, sub) in tokens:
        if key == 'kp':
            try:
                pt = fcu.keyframe_points[n]
                pt.interpolation = 'LINEAR'
                pt = parseKeyFramePoint(pt, val, sub)
                n += 1
            except:
                pass
                #print(tokens)
                #MyError("kp", fcu, n, len(fcu.keyframe_points), val)
        else:
            defaultKey(key, val, sub, fcu)
    return fcu

def parseKeyFramePoint(pt, args, tokens):
    pt.co = (float(args[0]), float(args[1]))
    if len(args) > 2:
        pt.handle1 = (float(args[2]), float(args[3]))
        pt.handle2 = (float(args[3]), float(args[5]))
    return pt

#
#    parseAnimationData(rna, args, tokens):
#    parseDriver(drv, args, tokens):
#    parseDriverVariable(var, args, tokens):
#

def parseAnimationData(rna, args, tokens):
    if not mhxEval(args[1]):
        return
    if rna.animation_data is None:
        rna.animation_data_create()
    adata = rna.animation_data
    for (key, val, sub) in tokens:
        if key == 'FCurve':
            fcu = parseAnimDataFCurve(adata, rna, val, sub)
        else:
            defaultKey(key, val, sub, adata)
    return adata

def parseAnimDataFCurve(adata, rna, args, tokens):
    if invalid(args[2]):
        return
    dataPath = args[0]
    index = int(args[1])
    n = 1
    for (key, val, sub) in tokens:
        if key == 'Driver':
            fcu = parseDriver(adata, dataPath, index, rna, val, sub)
            fmod = fcu.modifiers[0]
            fcu.modifiers.remove(fmod)
        elif key == 'FModifier':
            parseFModifier(fcu, val, sub)
        elif key == 'kp':
            pt = fcu.keyframe_points.insert(n, 0)
            pt.interpolation = 'LINEAR'
            pt = parseKeyFramePoint(pt, val, sub)
            n += 1
        else:
            defaultKey(key, val, sub, fcu)
    return fcu

"""
        fcurve = con.driver_add("influence", 0)
        driver = fcurve.driver
        driver.type = 'AVERAGE'
"""
def parseDriver(adata, dataPath, index, rna, args, tokens):
    if dataPath[-1] == ']':
        words = dataPath.split(']')
        expr = "rna." + words[0] + ']'
        pwords = words[1].split('"')
        prop = pwords[1]
        bone = mhxEval(expr)
        return None
    else:
        words = dataPath.split('.')
        channel = words[-1]
        expr = "rna"
        for n in range(len(words)-1):
            expr += "." + words[n]
        expr += ".driver_add('%s', index)" % channel

    fcu = mhxEval(expr, locals())
    drv = fcu.driver
    drv.type = args[0]
    for (key, val, sub) in tokens:
        if key == 'DriverVariable':
            var = parseDriverVariable(drv, rna, val, sub)
        else:
            defaultKey(key, val, sub, drv)
    return fcu

def parseDriverVariable(drv, rna, args, tokens):
    var = drv.variables.new()
    var.name = args[0]
    var.type = args[1]
    nTarget = 0
    for (key, val, sub) in tokens:
        if key == 'Target':
            parseDriverTarget(var, nTarget, rna, val, sub)
            nTarget += 1
        else:
            defaultKey(key, val, sub, var)
    return var

def parseFModifier(fcu, args, tokens):
    fmod = fcu.modifiers.new(args[0])
    #fmod = fcu.modifiers[0]
    for (key, val, sub) in tokens:
        defaultKey(key, val, sub, fmod)
    return fmod

"""
        var = driver.variables.new()
        var.name = target_bone
        var.targets[0].id_type = 'OBJECT'
        var.targets[0].id = obj
        var.targets[0].rna_path = driver_path
"""
def parseDriverTarget(var, nTarget, rna, args, tokens):
    targ = var.targets[nTarget]
    name = args[0]
    #targ.id_type = args[1]
    dtype = args[1].capitalize()
    dtype = 'Object'
    targ.id = loadedData[dtype][name]
    for (key, val, sub) in tokens:
        if key == 'data_path':
            words = val[0].split('"')
            if len(words) > 1:
                targ.data_path = propNames(words[1])[1]
            else:
                targ.data_path = propNames(val)[1]
        else:
            defaultKey(key, val, sub, targ)
    return targ


#
#    parseMaterial(args, ext, tokens):
#    parseMTex(mat, args, tokens):
#    parseTexture(args, tokens):
#

def parseMaterial(args, tokens):
    name = args[0]
    mat = bpy.data.materials.new(name)
    if mat is None:
        return None
    loadedData['Material'][name] = mat
    for (key, val, sub) in tokens:
        if key == 'MTex':
            parseMTex(mat, val, sub)
        elif key == 'Ramp':
            parseRamp(mat, val, sub)
        elif key == 'RaytraceTransparency':
            parseDefault(mat.raytrace_transparency, sub, {}, [])
        elif key == 'Halo':
            parseDefault(mat.halo, sub, {}, [])
        elif key == 'SSS':
            parseDefault(mat.subsurface_scattering, sub, {}, [])
        elif key == 'Strand':
            parseDefault(mat.strand, sub, {}, [])
        elif key == 'NodeTree':
            mat.use_nodes = True
            parseNodeTree(mat.node_tree, val, sub)
        elif key == 'AnimationData':
            parseAnimationData(mat, val, sub)
        else:
            exclude = ['specular_intensity', 'tangent_shading']
            defaultKey(key, val, sub, mat)

    return mat

def parseMTex(mat, args, tokens):
    index = int(args[0])
    texname = args[1]
    texco = args[2]
    mapto = args[3]
    tex = loadedData['Texture'][texname]
    mtex = mat.texture_slots.add()
    mtex.texture_coords = texco
    mtex.texture = tex

    for (key, val, sub) in tokens:
        defaultKey(key, val, sub, mtex)

    return mtex

def parseTexture(args, tokens):
    if verbosity > 2:
        print( "Parsing texture %s" % args )
    name = args[0]
    tex = bpy.data.textures.new(name=name, type=args[1])
    loadedData['Texture'][name] = tex

    for (key, val, sub) in tokens:
        if key == 'Image':
            try:
                imgName = val[0]
                img = loadedData['Image'][imgName]
                tex.image = img
            except:
                msg = "Unable to load image '%s'" % val[0]
        elif key == 'Ramp':
            parseRamp(tex, val, sub)
        elif key == 'NodeTree':
            tex.use_nodes = True
            parseNodeTree(tex.node_tree, val, sub)
        else:
            defaultKey(key, val, sub, tex, ['use_nodes', 'use_textures', 'contrast', 'use_alpha'])

    return tex

def parseRamp(data, args, tokens):
    setattr(data, "use_%s" % args[0], True)
    ramp = getattr(data, args[0])
    elts = ramp.elements
    n = 0
    for (key, val, sub) in tokens:
        if key == 'Element':
            elts[n].color = mhxEval(val[0], locals())
            elts[n].position = mhxEval(val[1], locals())
            n += 1
        else:
            defaultKey(key, val, sub, tex, ['use_nodes', 'use_textures', 'contrast'])

def parseSSS(mat, args, tokens):
    sss = mat.subsurface_scattering
    for (key, val, sub) in tokens:
        defaultKey(key, val, sub, sss)

def parseStrand(mat, args, tokens):
    strand = mat.strand
    for (key, val, sub) in tokens:
        defaultKey(key, val, sub, strand)

#
#    parseNodeTree(tree, args, tokens):
#    parseNode(node, args, tokens):
#    parseSocket(socket, args, tokens):
#

def parseNodeTree(tree, args, tokens):
    return
    print("Tree", tree, args)
    print(list(tree.nodes))
    tree.name = args[0]
    for (key, val, sub) in tokens:
        if key == 'Node':
            parseNodes(tree.nodes, val, sub)
        else:
            defaultKey(key, val, sub, tree)

def parseNodes(nodes, args, tokens):
    print("Nodes", nodes, args)
    print(list(nodes))
    node.name = args[0]
    for (key, val, sub) in tokens:
        if key == 'Inputs':
            parseSocket(node.inputs, val, sub)
        elif key == 'Outputs':
            parseSocket(node.outputs, val, sub)
        else:
            defaultKey(key, val, sub, node)

def parseNode(node, args, tokens):
    print("Node", node, args)
    print(list(node.inputs), list(node.outputs))
    node.name = args[0]
    for (key, val, sub) in tokens:
        if key == 'Inputs':
            parseSocket(node.inputs, val, sub)
        elif key == 'Outputs':
            parseSocket(node.outputs, val, sub)
        else:
            defaultKey(key, val, sub, node)

def parseSocket(socket, args, tokens):
    print("Socket", socket, args)
    socket.name = args[0]
    for (key, val, sub) in tokens:
        if key == 'Node':
            parseNode(tree.nodes, val, sub)
        else:
            defaultKey(key, val, sub, tree)



#
#    loadImage(filepath):
#    parseImage(args, tokens):
#

def loadImage(relFilepath):
    filepath = os.path.normpath(os.path.join(theDir, relFilepath))
    print( "Loading %s" % filepath.encode('utf-8','strict'))
    if os.path.isfile(filepath):
        #print( "Found file %s." % filepath.encode('utf-8','strict') )
        try:
            img = bpy.data.images.load(filepath)
            return img
        except:
            print( "Cannot read image" )
            return None
    else:
        print( "No such file: %s" % filepath.encode('utf-8','strict') )
        return None


def parseImage(args, tokens):
    imgName = args[0]
    img = None
    for (key, val, sub) in tokens:
        if key == 'Filename':
            filename = val[0]
            for n in range(1,len(val)):
                filename += " " + val[n]
            img = loadImage(filename)
            if img is None:
                return None
            img.name = imgName
        else:
            defaultKey(key, val, sub, img, ['depth', 'dirty', 'has_data', 'size', 'type', 'use_premultiply'])
    print ("Image %s" % img )
    loadedData['Image'][imgName] = img
    return img

#
#    parseObject(args, tokens):
#    createObject(type, name, data, datName):
#    setObjectAndData(args, typ):
#

def parseObject(args, tokens, versionInfoStr=""):
    if verbosity > 2:
        print( "Parsing object %s" % args )
    name = args[0]
    typ = args[1]
    datName = args[2]

    if typ == 'EMPTY':
        ob = bpy.data.objects.new(name, None)
        loadedData['Object'][name] = ob
        linkObject(ob, None)
    else:
        try:
            data = loadedData[typ.capitalize()][datName]
        except:
            MyError("Failed to find data: %s %s %s" % (name, typ, datName))
            return

    try:
        ob = loadedData['Object'][name]
        bpy.context.scene.objects.active = ob
        #print("Found data", ob)
    except:
        ob = None

    if ob is None:
        ob = createObject(typ, name, data, datName)
        linkObject(ob, data)

    for (key, val, sub) in tokens:
        if key == 'Modifier':
            parseModifier(ob, val, sub)
        elif key == 'Constraint':
            parseConstraint(ob.constraints, None, val, sub)
        elif key == 'AnimationData':
            parseAnimationData(ob, val, sub)
        elif key == 'ParticleSystem':
            parseParticleSystem(ob, val, sub)
        elif key == 'FieldSettings':
            parseDefault(ob.field, sub, {}, [])
        else:
            defaultKey(key, val, sub, ob, ['type', 'data'])

    if versionInfoStr:
        print('============= updating version string %s' % versionInfoStr)
        ob.MhxVersionStr = versionInfoStr
    else:
        print('============= not updating version str')

    if bpy.context.object == ob:
        if ob.type == 'MESH':
            bpy.ops.object.shade_smooth()
    else:
        print("Context", ob, bpy.context.object, bpy.context.scene.objects.active)
    return

def createObject(typ, name, data, datName):
    # print( "Creating object %s %s %s" % (typ, name, data) )
    ob = bpy.data.objects.new(name, data)
    if data:
        loadedData[typ.capitalize()][datName] = data
    loadedData['Object'][name] = ob
    return ob

def linkObject(ob, data):
    #print("Data", data, ob.data)
    if data and ob.data is None:
        ob.data = data
    scn = bpy.context.scene
    scn.objects.link(ob)
    scn.objects.active = ob
    #print("Linked object", ob)
    #print("Scene", scn)
    #print("Active", scn.objects.active)
    #print("Context", bpy.context.object)
    return ob

def setObjectAndData(args, typ):
    datName = args[0]
    obName = args[1]
    #bpy.ops.object.add(type=typ)
    ob = bpy.context.object
    ob.name = obName
    ob.data.name = datName
    loadedData[typ][datName] = ob.data
    loadedData['Object'][obName] = ob
    return ob.data


#
#    parseModifier(ob, args, tokens):
#


def parseModifier(ob, args, tokens):
    name = args[0]
    typ = args[1]
    if typ == 'PARTICLE_SYSTEM':
        return None
    mod = ob.modifiers.new(name, typ)
    for (key, val, sub) in tokens:
        if key == 'HookAssignNth':
            if val[0] == 'CURVE':
                hookAssignNth(mod, int(val[1]), True, ob.data.splines[0].points)
            elif val[0] == 'LATTICE':
                hookAssignNth(mod, int(val[1]), False, ob.data.points)
            elif val[0] == 'MESH':
                hookAssignNth(mod, int(val[1]), True, ob.data.vertices)
            else:
                MyError("Unknown hook %s" % val)
        else:
            defaultKey(key, val, sub, mod)
    return mod

def hookAssignNth(mod, n, select, points):
    if select:
        for pt in points:
            pt.select = False
        points[n].select = True
        sel = []
        for pt in points:
            sel.append(pt.select)
        #print(mod, sel, n, points)

    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.object.hook_reset(modifier=mod.name)
    bpy.ops.object.hook_select(modifier=mod.name)
    bpy.ops.object.hook_assign(modifier=mod.name)
    bpy.ops.object.mode_set(mode='OBJECT')
    return

#
#    parseParticleSystem(ob, args, tokens):
#    parseParticles(particles, args, tokens):
#    parseParticle(par, args, tokens):
#

def parseParticleSystem(ob, args, tokens):
    print(ob, bpy.context.object)
    pss = ob.particle_systems
    print(pss, pss.values())
    name = args[0]
    typ = args[1]
    #psys = pss.new(name, typ)
    bpy.ops.object.particle_system_add()
    print(pss, pss.values())
    psys = pss[-1]
    psys.name = name
    psys.settings.type = typ
    loadedData['ParticleSystem'][name] = psys
    print("Psys", psys)

    for (key, val, sub) in tokens:
        if key == 'Particles':
            parseParticles(psys, val, sub)
        else:
            defaultKey(key, val, sub, psys)
    return psys

def parseParticles(psys, args, tokens):
    particles = psys.particles
    bpy.ops.particle.particle_edit_toggle()
    n = 0
    for (key, val, sub) in tokens:
        if key == 'Particle':
            parseParticle(particles[n], val, sub)
            n += 1
        else:
            for par in particles:
                defaultKey(key, val, sub, par)
    bpy.ops.particle.particle_edit_toggle()
    return particles

def parseParticle(par, args, tokens):
    n = 0
    for (key, val, sub) in tokens:
        if key == 'h':
            h = par.hair[n]
            h.location = mhxEval(val[0], locals())
            h.time = int(val[1])
            h.weight = float(val[2])
            n += 1
        elif key == 'location':
            par.location = mhxEval(val[0], locals())
    return

#
#    unpackList(list_of_tuples):
#

def unpackList(list_of_tuples):
    l = []
    for t in list_of_tuples:
        l.extend(t)
    return l


#

#    parseMesh (args, tokens):
#

def parseMesh (args, tokens):
    global BMeshAware
    if verbosity > 2:
        print( "Parsing mesh %s" % args )

    mename = args[0]
    obname = args[1]
    me = bpy.data.meshes.new(mename)
    ob = createObject('MESH', obname, me, mename)

    verts = []
    edges = []
    faces = []
    vertsTex = []
    texFaces = []

    for (key, val, sub) in tokens:
        if key == 'Verts':
            verts = parseVerts(sub)
        elif key == 'Edges':
            edges = parseEdges(sub)
        elif key == 'Faces':
            faces = parseFaces(sub)

    if faces:
        me.from_pydata(verts, [], faces)
    else:
        me.from_pydata(verts, edges, [])
    me.update()
    linkObject(ob, me)

    if faces:
        try:
            me.polygons
            BMeshAware = True
        except:
            BMeshAware = False

    mats = []
    nuvlayers = 0
    for (key, val, sub) in tokens:
        if key == 'Verts' or key == 'Edges' or key == 'Faces':
            pass
        elif key == 'MeshTextureFaceLayer':
            if BMeshAware:
                parseUvTextureBMesh(val, sub, me)
            else:
                parseUvTextureNoBMesh(val, sub, me)
        elif key == 'MeshColorLayer':
            parseVertColorLayer(val, sub, me)
        elif key == 'VertexGroup':
            parseVertexGroup(ob, me, val, sub)
        elif key == 'ShapeKeys':
            parseShapeKeys(ob, me, val, sub)
        elif key == 'Material':
            try:
                mat = loadedData['Material'][val[0]]
            except:
                mat = None
            if mat:
                me.materials.append(mat)
        else:
            defaultKey(key, val, sub, me)

    for (key, val, sub) in tokens:
        if key == 'Faces':
            if BMeshAware:
                parseFaces2BMesh(sub, me)
            else:
                parseFaces2NoBMesh(sub, me)
    return me

#
#    parseVerts(tokens):
#    parseEdges(tokens):
#    parseFaces(tokens):
#    parseFaces2(tokens, me):
#

def parseVerts(tokens):
    verts = []
    for (key, val, sub) in tokens:
        if key == 'v':
            verts.append( (theScale*float(val[0]), theScale*float(val[1]), theScale*float(val[2])) )
    return verts

def parseEdges(tokens):
    edges = []
    for (key, val, sub) in tokens:
        if key == 'e':
            edges.append((int(val[0]), int(val[1])))
    return edges

def parseFaces(tokens):
    faces = []
    for (key, val, sub) in tokens:
        if key == 'f':
            if len(val) == 3:
                face = [int(val[0]), int(val[1]), int(val[2])]
            elif len(val) == 4:
                face = [int(val[0]), int(val[1]), int(val[2]), int(val[3])]
            faces.append(face)
    return faces

def parseFaces2BMesh(tokens, me):
    n = 0
    for (key, val, sub) in tokens:
        if key == 'ft':
            f = me.polygons[n]
            f.material_index = int(val[0])
            f.use_smooth = int(val[1])
            n += 1
        elif key == 'ftn':
            mn = int(val[1])
            us = int(val[2])
            npts = int(val[0])
            for i in range(npts):
                f = me.polygons[n]
                f.material_index = mn
                f.use_smooth = us
                n += 1
        elif key == 'mn':
            fn = int(val[0])
            mn = int(val[1])
            f = me.polygons[fn]
            f.material_index = mn
        elif key == 'ftall':
            mat = int(val[0])
            smooth = int(val[1])
            for f in me.polygons:
                f.material_index = mat
                f.use_smooth = smooth
    return

def parseFaces2NoBMesh(tokens, me):
    n = 0
    for (key, val, sub) in tokens:
        if key == 'ft':
            f = me.faces[n]
            f.material_index = int(val[0])
            f.use_smooth = int(val[1])
            n += 1
        elif key == 'ftn':
            mn = int(val[1])
            us = int(val[2])
            npts = int(val[0])
            for i in range(npts):
                f = me.faces[n]
                f.material_index = mn
                f.use_smooth = us
                n += 1
        elif key == 'mn':
            fn = int(val[0])
            mn = int(val[1])
            f = me.faces[fn]
            f.material_index = mn
        elif key == 'ftall':
            mat = int(val[0])
            smooth = int(val[1])
            for f in me.faces:
                f.material_index = mat
                f.use_smooth = smooth
    return


#
#    parseUvTexture(args, tokens, me,):
#    parseUvTexData(args, tokens, uvdata):
#

def parseUvTextureBMesh(args, tokens, me):
    name = args[0]
    bpy.ops.mesh.uv_texture_add()
    uvtex = me.uv_textures[-1]
    uvtex.name = name
    uvloop = me.uv_layers[-1]
    loadedData['MeshTextureFaceLayer'][name] = uvloop
    for (key, val, sub) in tokens:
        if key == 'Data':
            parseUvTexDataBMesh(val, sub, uvloop.data)
        else:
            defaultKey(key, val, sub, uvtex)
    return

def parseUvTexDataBMesh(args, tokens, data):
    n = 0
    for (key, val, sub) in tokens:
        if key == 'vt':
            data[n].uv = (float(val[0]), float(val[1]))
            n += 1
            data[n].uv = (float(val[2]), float(val[3]))
            n += 1
            data[n].uv = (float(val[4]), float(val[5]))
            n += 1
            if len(val) > 6:
                data[n].uv = (float(val[6]), float(val[7]))
                n += 1
    return

def parseUvTextureNoBMesh(args, tokens, me):
    name = args[0]
    uvtex = me.uv_textures.new(name = name)
    loadedData['MeshTextureFaceLayer'][name] = uvtex
    for (key, val, sub) in tokens:
        if key == 'Data':
            parseUvTexDataNoBMesh(val, sub, uvtex.data)
        else:
            defaultKey(key, val, sub, uvtex)
    return

def parseUvTexDataNoBMesh(args, tokens, data):
    n = 0
    for (key, val, sub) in tokens:
        if key == 'vt':
            data[n].uv1 = (float(val[0]), float(val[1]))
            data[n].uv2 = (float(val[2]), float(val[3]))
            data[n].uv3 = (float(val[4]), float(val[5]))
            if len(val) > 6:
                data[n].uv4 = (float(val[6]), float(val[7]))
            n += 1
    return

#
#    parseVertColorLayer(args, tokens, me):
#    parseVertColorData(args, tokens, data):
#

def parseVertColorLayer(args, tokens, me):
    name = args[0]
    print("VertColorLayer", name)
    vcol = me.vertex_colors.new(name)
    loadedData['MeshColorLayer'][name] = vcol
    for (key, val, sub) in tokens:
        if key == 'Data':
            parseVertColorData(val, sub, vcol.data)
        else:
            defaultKey(key, val, sub, vcol)
    return

def parseVertColorData(args, tokens, data):
    n = 0
    for (key, val, sub) in tokens:
        if key == 'cv':
            data[n].color1 = mhxEval(val[0])
            data[n].color2 = mhxEval(val[1])
            data[n].color3 = mhxEval(val[2])
            data[n].color4 = mhxEval(val[3])
            n += 1
    return


#
#    parseVertexGroup(ob, me, args, tokens):
#

def parseVertexGroup(ob, me, args, tokens):
    global toggle
    if verbosity > 2:
        print( "Parsing vertgroup %s" % args )
    grpName = args[0]
    try:
        res = mhxEval(args[1])
    except:
        res = True
    if not res:
        return

    if (toggle & T_Armature) or (grpName in ['Eye_L', 'Eye_R', 'Gums', 'Head', 'Jaw', 'Left', 'Middle', 'Right', 'Scalp']):
        try:
            group = loadedData['VertexGroup'][grpName]
        except KeyError:
            group = ob.vertex_groups.new(grpName)
            loadedData['VertexGroup'][grpName] = group
        for (key, val, sub) in tokens:
            if key == 'wv':
                group.add( [int(val[0])], float(val[1]), 'REPLACE' )
    return


#
#    parseShapeKeys(ob, me, args, tokens):
#    parseShapeKey(ob, me, args, tokens):
#    addShapeKey(ob, name, vgroup, tokens):
#    doShape(name):
#

def doShape(name):
    if (toggle & T_Shapekeys) and (name == 'Basis'):
        return True
    else:
        return (toggle & T_Face)


def parseShapeKeys(ob, me, args, tokens):
    for (key, val, sub) in tokens:
        if key == 'ShapeKey':
            parseShapeKey(ob, me, val, sub)
        elif key == 'AnimationData':
            if me.shape_keys:
                parseAnimationData(me.shape_keys, val, sub)
        elif key == 'Expression':
            prop = "Mhe" + val[0].capitalize()
            parseUnits(prop, ob, sub)
        elif key == 'Viseme':
            name = val[0].upper()
            if name in ["REST", "ETC"]:
                name = name.capitalize()
            prop = "Mhv" + name
            parseUnits(prop, ob, sub)
    ob.active_shape_key_index = 0


def parseUnits(prop, ob, sub):
    string = ""
    for words in sub:
        unit = words[0].replace("-","_")
        value = words[1][0]
        string += "%s:%s;" % (unit, value)
    rig = ob.parent
    rig[prop] = string


def parseShapeKey(ob, me, args, tokens):
    if verbosity > 2:
        print( "Parsing ob %s shape %s" % (bpy.context.object, args[0] ))
    name = args[0]
    lr = args[1]
    if invalid(args[2]):
        return

    if lr == 'Sym': # or toggle & T_Symm:
        addShapeKey(ob, name, None, tokens)
    elif lr == 'LR':
        addShapeKey(ob, name+'_L', 'Left', tokens)
        addShapeKey(ob, name+'_R', 'Right', tokens)
    else:
        MyError("ShapeKey L/R %s" % lr)
    return


def addShapeKey(ob, name, vgroup, tokens):
    skey = ob.shape_key_add(name=name, from_mix=False)
    if name != 'Basis':
        skey.relative_key = loadedData['ShapeKey']['Basis']
    skey.name = name
    if vgroup:
        skey.vertex_group = vgroup
    loadedData['ShapeKey'][name] = skey

    for (key, val, sub) in tokens:
        if key == 'sv':
            index = int(val[0])
            pt = skey.data[index].co
            pt[0] += theScale*float(val[1])
            pt[1] += theScale*float(val[2])
            pt[2] += theScale*float(val[3])
        else:
            defaultKey(key, val, sub, skey)

    return


#
#    parseArmature (obName, args, tokens)
#

def parseArmature (args, tokens):
    global toggle, theArmature
    if verbosity > 2:
        print( "Parsing armature %s" % args )

    amtname = args[0]
    obname = args[1]
    mode = args[2]

    amt = bpy.data.armatures.new(amtname)
    ob = createObject('ARMATURE', obname, amt, amtname)
    linkObject(ob, amt)
    theArmature = ob

    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.mode_set(mode='EDIT')

    heads = {}
    tails = {}
    for (key, val, sub) in tokens:
        if key == 'Bone':
            bname = val[0]
            if not invalid(val[1]):
                bone = amt.edit_bones.new(bname)
                parseBone(bone, amt, sub, heads, tails)
                loadedData['Bone'][bname] = bone
        elif key == 'RecalcRoll':
            rolls = {}
            for bone in amt.edit_bones:
                bone.select = False
            blist = mhxEval(val[0])
            for name in blist:
                bone = amt.edit_bones[name]
                bone.select = True
            bpy.ops.armature.calculate_roll(type='Z')
            for bone in amt.edit_bones:
                rolls[bone.name] = bone.roll
            bpy.ops.object.mode_set(mode='OBJECT')
            for bone in amt.bones:
                bone['Roll'] = rolls[bone.name]
            bpy.ops.object.mode_set(mode='EDIT')
        else:
            defaultKey(key, val, sub, amt, ['MetaRig'])
    bpy.ops.object.mode_set(mode='OBJECT')

    return amt

#
#    parseBone(bone, amt, tokens, heads, tails):
#

def parseBone(bone, amt, tokens, heads, tails):
    for (key, val, sub) in tokens:
        if key == "head":
            bone.head = (theScale*float(val[0]), theScale*float(val[1]), theScale*float(val[2]))
        elif key == "tail":
            bone.tail = (theScale*float(val[0]), theScale*float(val[1]), theScale*float(val[2]))
        #elif key == 'restrict_select':
        #    pass
        elif key == 'hide' and val[0] == 'True':
            name = bone.name
        else:
            defaultKey(key, val, sub, bone)
    return bone

#
#    parsePose (args, tokens):
#

def parsePose (args, tokens):
    name = args[0]
    ob = loadedData['Object'][name]
    bpy.context.scene.objects.active = ob
    bpy.ops.object.mode_set(mode='POSE')
    pbones = ob.pose.bones
    nGrps = 0
    for (key, val, sub) in tokens:
        if key == 'Posebone':
            parsePoseBone(pbones, ob, val, sub)
        elif key == 'BoneGroup':
            parseBoneGroup(ob.pose, nGrps, val, sub)
            nGrps += 1
        elif key == 'SetProp':
            bone = val[0]
            prop = val[1]
            value = mhxEval(val[2])
            pb = pbones[bone]
            pb[prop] = value
        else:
            defaultKey(key, val, sub, ob.pose)
    bpy.ops.object.mode_set(mode='OBJECT')
    return ob


#
#    parsePoseBone(pbones, args, tokens):
#    parseArray(data, exts, args):
#

def parseBoneGroup(pose, nGrps, args, tokens):
    if verbosity > 2:
        print( "Parsing bonegroup %s" % args )
    name = args[0]
    bpy.ops.pose.group_add()
    bg = pose.bone_groups.active
    loadedData['BoneGroup'][name] = bg
    for (key, val, sub) in tokens:
        defaultKey(key, val, sub, bg)
    return

def parsePoseBone(pbones, ob, args, tokens):
    if invalid(args[1]):
        return
    name = args[0]
    pb = pbones[name]
    amt = ob.data
    amt.bones.active = pb.bone

    for (key, val, sub) in tokens:
        if key == 'Constraint':
            amt.bones.active = pb.bone
            cns = parseConstraint(pb.constraints, pb, val, sub)
        elif key == 'bpyops':
            amt.bones.active = pb.bone
            expr = "bpy.ops.%s" % val[0]
            raise MhxError("MHX bug: Must not exec %s" % expr)
        elif key == 'ik_dof':
            parseArray(pb, ["ik_dof_x", "ik_dof_y", "ik_dof_z"], val)
        elif key == 'ik_limit':
            parseArray(pb, ["ik_limit_x", "ik_limit_y", "ik_limit_z"], val)
        elif key == 'ik_max':
            parseArray(pb, ["ik_max_x", "ik_max_y", "ik_max_z"], val)
        elif key == 'ik_min':
            parseArray(pb, ["ik_min_x", "ik_min_y", "ik_min_z"], val)
        elif key == 'ik_stiffness':
            parseArray(pb, ["ik_stiffness_x", "ik_stiffness_y", "ik_stiffness_z"], val)
        elif key == 'hide':
            #bpy.ops.object.mode_set(mode='OBJECT')
            amt.bones[name].hide = mhxEval(val[0])
            #bpy.ops.object.mode_set(mode='POSE')

        else:
            defaultKey(key, val, sub, pb)
    return

def parseArray(data, exts, args):
    n = 1
    for ext in exts:
        setattr(data, ext, mhxEval(args[n]))
        n += 1
    return

#
#    parseConstraint(constraints, pb, args, tokens)
#

def parseConstraint(constraints, pb, args, tokens):
    if invalid(args[2]):
        return None
    if (toggle&T_Opcns and pb):
        print("Active")
        aob = bpy.context.object
        print("ob", aob)
        aamt = aob.data
        print("amt", aamt)
        apose = aob.pose
        print("pose", apose)
        abone = aamt.bones.active
        print("bone", abone)
        print('Num cns before', len(list(constraints)))
        bpy.ops.pose.constraint_add(type=args[1])
        cns = constraints.active
        print('and after', pb, cns, len(list(constraints)))
    else:
        cns = constraints.new(args[1])

    cns.name = args[0]
    for (key,val,sub) in tokens:
        if key == 'invert':
            parseArray(cns, ["invert_x", "invert_y", "invert_z"], val)
        elif key == 'use':
            parseArray(cns, ["use_x", "use_y", "use_z"], val)
        elif key == 'pos_lock':
            parseArray(cns, ["lock_location_x", "lock_location_y", "lock_location_z"], val)
        elif key == 'rot_lock':
            parseArray(cns, ["lock_rotation_x", "lock_rotation_y", "lock_rotation_z"], val)
        else:
            defaultKey(key, val, sub, cns, ["use_target"])


    #print("cns %s done" % cns.name)
    return cns

#


#    parseCurve (args, tokens):
#    parseSpline(cu, args, tokens):
#    parseBezier(spline, n, args, tokens):
#

def parseCurve (args, tokens):
    if verbosity > 2:
        print( "Parsing curve %s" % args )
    bpy.ops.object.add(type='CURVE')
    cu = setObjectAndData(args, 'Curve')

    for (key, val, sub) in tokens:
        if key == 'Spline':
            parseSpline(cu, val, sub)
        else:
            defaultKey(key, val, sub, cu)
    return

def parseTextCurve (args, tokens):
    if verbosity > 2:
        print( "Parsing text curve %s" % args )
    bpy.ops.object.text_add()
    txt = setObjectAndData(args, 'Text')

    for (key, val, sub) in tokens:
        if key == 'Spline':
            parseSpline(txt, val, sub)
        elif key == 'BodyFormat':
            parseCollection(txt.body_format, sub, [])
        elif key == 'EditFormat':
            parseDefault(txt.edit_format, sub, {}, [])
        elif key == 'Font':
            parseDefault(txt.font, sub, {}, [])
        elif key == 'TextBox':
            parseCollection(txt.body_format, sub, [])
        else:
            defaultKey(key, val, sub, txt)
    return


def parseSpline(cu, args, tokens):
    typ = args[0]
    spline = cu.splines.new(typ)
    nPointsU = int(args[1])
    nPointsV = int(args[2])
    #spline.point_count_u = nPointsU
    #spline.point_count_v = nPointsV
    if typ == 'BEZIER' or typ == 'BSPLINE':
        spline.bezier_points.add(nPointsU)
    else:
        spline.points.add(nPointsU)

    n = 0
    for (key, val, sub) in tokens:
        if key == 'bz':
            parseBezier(spline.bezier_points[n], val, sub)
            n += 1
        elif key == 'pt':
            parsePoint(spline.points[n], val, sub)
            n += 1
        else:
            defaultKey(key, val, sub, spline)
    return

def parseBezier(bez, args, tokens):
    bez.co = mhxEval(args[0])
    bez.co = theScale*bez.co
    bez.handle1 = mhxEval(args[1])
    bez.handle1_type = args[2]
    bez.handle2 = mhxEval(args[3])
    bez.handle2_type = args[4]
    return

def parsePoint(pt, args, tokens):
    pt.co = mhxEval(args[0])
    pt.co = theScale*pt.co
    print(" pt", pt.co)
    return

#
#    parseLattice (args, tokens):
#

def parseLattice (args, tokens):
    if verbosity > 2:
        print( "Parsing lattice %s" % args )
    bpy.ops.object.add(type='LATTICE')
    lat = setObjectAndData(args, 'Lattice')
    for (key, val, sub) in tokens:
        if key == 'Points':
            parseLatticePoints(val, sub, lat.points)
        else:
            defaultKey(key, val, sub, lat)
    return

def parseLatticePoints(args, tokens, points):
    n = 0
    for (key, val, sub) in tokens:
        if key == 'pt':
            v = points[n].co_deform
            v.x = theScale*float(val[0])
            v.y = theScale*float(val[1])
            v.z = theScale*float(val[2])
            n += 1
    return

#
#    parseLamp (args, tokens):
#    parseFalloffCurve(focu, args, tokens):
#

def parseLamp (args, tokens):
    if verbosity > 2:
        print( "Parsing lamp %s" % args )
    bpy.ops.object.add(type='LAMP')
    lamp = setObjectAndData(args, 'Lamp')
    for (key, val, sub) in tokens:
        if key == 'FalloffCurve':
            parseFalloffCurve(lamp.falloff_curve, val, sub)
        else:
            defaultKey(key, val, sub, lamp)
    return

def parseFalloffCurve(focu, args, tokens):
    return

#
#    parseGroup (args, tokens):
#    parseGroupObjects(args, tokens, grp):
#

def parseGroup (args, tokens):
    if verbosity > 2:
        print( "Parsing group %s" % args )

    grpName = args[0]
    grp = bpy.data.groups.new(grpName)
    loadedData['Group'][grpName] = grp
    for (key, val, sub) in tokens:
        if key == 'Objects':
            parseGroupObjects(val, sub, grp)
        else:
            defaultKey(key, val, sub, grp)
    return

def parseGroupObjects(args, tokens, grp):
    rig = None
    for (key, val, sub) in tokens:
        if key == 'ob':
            try:
                ob = loadedData['Object'][val[0]]
                grp.objects.link(ob)
            except:
                ob = None
            if ob:
                print(ob, ob.type, rig, ob.parent)
                if ob.type == 'ARMATURE':
                    rig = ob
                elif ob.type == 'EMPTY' and rig and not ob.parent:
                    ob.parent = rig
                    print("SSS")
    return

#
#    parseWorld (args, tokens):
#

def parseWorld (args, tokens):
    if verbosity > 2:
        print( "Parsing world %s" % args )
    world = bpy.context.scene.world
    for (key, val, sub) in tokens:
        if key == 'Lighting':
            parseDefault(world.lighting, sub, {}, [])
        elif key == 'Mist':
            parseDefault(world.mist, sub, {}, [])
        elif key == 'Stars':
            parseDefault(world.stars, sub, {}, [])
        else:
            defaultKey(key, val, sub, world)
    return

#
#    parseScene (args, tokens):
#    parseRenderSettings(render, args, tokens):
#    parseToolSettings(tool, args, tokens):
#

def parseScene (args, tokens):
    if verbosity > 2:
        print( "Parsing scene %s" % args )
    scn = bpy.context.scene
    for (key, val, sub) in tokens:
        if key == 'NodeTree':
            scn.use_nodes = True
            parseNodeTree(scn, val, sub)
        elif key == 'GameData':
            parseDefault(scn.game_data, sub, {}, [])
        elif key == 'KeyingSet':
            pass
            #parseDefault(scn.keying_sets, sub, {}, [])
        elif key == 'ObjectBase':
            pass
            #parseDefault(scn.bases, sub, {}, [])
        elif key == 'RenderSettings':
            parseRenderSettings(scn.render, sub, [])
        elif key == 'ToolSettings':
            subkeys = {'ImagePaint' : "image_paint",
                'Sculpt' : "sculpt",
                'VertexPaint' : "vertex_paint",
                'WeightPaint' : "weight_paint" }
            parseDefault(scn.tool_settings, sub, subkeys, [])
        elif key == 'UnitSettings':
            parseDefault(scn.unit_settings, sub, {}, [])
        else:
            defaultKey(key, val, sub, scn)
    return

def parseRenderSettings(render, args, tokens):
    if verbosity > 2:
        print( "Parsing RenderSettings %s" % args )
    for (key, val, sub) in tokens:
        if key == 'Layer':
            pass
            #parseDefault(scn.layers, sub, [])
        else:
            defaultKey(key, val, sub, render)
    return

#
#    parseDefineProperty(args, tokens):
#

def parseDefineProperty(args, tokens):
    prop = "%sProperty" % (args[1])
    c = '('
    for option in args[2:]:
        prop += "%s %s" % (c, option)
        c = ','
    prop += ')'
    setattr(bpy.types.Object, args[0], prop)
    return

#
#    correctRig(args):
#

def correctRig(args):
    human = args[0]
    print("CorrectRig %s" % human)
    try:
        ob = loadedData['Object'][human]
    except:
        return
    ob.MhxShapekeyDrivers = (toggle&T_Shapekeys != 0 and toggle&T_ShapeDrivers != 0)
    bpy.context.scene.objects.active = ob
    bpy.ops.object.mode_set(mode='POSE')
    amt = ob.data
    cnslist = []
    for pb in ob.pose.bones:
        pb.bone.select = False
        for cns in pb.constraints:
            if cns.type == 'CHILD_OF':
                cnslist.append((pb, cns, cns.influence))
                cns.influence = 0

    for (pb, cns, inf) in cnslist:
        amt.bones.active = pb.bone
        cns.influence = 1
        #print("Childof %s %s %s %.2f" % (amt.name, pb.name, cns.name, inf))
        bpy.ops.constraint.childof_clear_inverse(constraint=cns.name, owner='BONE')
        bpy.ops.constraint.childof_set_inverse(constraint=cns.name, owner='BONE')
        cns.influence = 0

    for (pb, cns, inf) in cnslist:
        cns.influence = inf
    return


#
#    postProcess(args)
#

def postProcess(args):
    human = args[0]
    print("Postprocess %s" % human)
    try:
        ob = loadedData['Object'][human]
    except:
        ob = None
    if toggle & T_Diamond == 0 and ob:
        deleteDiamonds(ob)
    return

#
#    deleteDiamonds(ob)
#    Delete joint diamonds in main mesh
#    Invisio = material # 1
#

def deleteDiamonds(ob):
    bpy.context.scene.objects.active = ob
    if not bpy.context.object:
        return
    print("Delete helper geometry in %s" % bpy.context.object)
    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.mesh.select_all(action='DESELECT')
    bpy.ops.object.mode_set(mode='OBJECT')
    me = ob.data
    invisioNum = -1
    for mn,mat in enumerate(me.materials):
        if "Invis" in mat.name:
            invisioNum = mn
            break
    if invisioNum < 0:
        print("WARNING: Nu Invisio material found. Cannot delete helper geometry")
    elif BMeshAware:
        for f in me.polygons:
            if f.material_index >= invisioNum:
                for vn in f.vertices:
                    me.vertices[vn].select = True
    else:
        for f in me.faces:
            if f.material_index >= invisioNum:
                for vn in f.vertices:
                    me.vertices[vn].select = True
    if BMeshAware and toggle&T_CrashSafe:
        theMessage = "\n  *** WARNING ***\nHelper deletion turned off due to Blender crash.\nHelpers can be deleted by deleting all selected vertices in Edit mode\n     **********\n"
        print(theMessage)
    else:
        bpy.ops.object.mode_set(mode='EDIT')
        print("Do delete")
        bpy.ops.mesh.delete(type='VERT')
        print("Verts deleted")
        bpy.ops.object.mode_set(mode='OBJECT')
        print("Back to object mode")
    return

#
#    defaultKey(ext, args, tokens, data, exclude):
#

theProperty = None

def propNames(string):
    global alpha7
    #string = string.encode('utf-8', 'strict')

    # Alpha 7 compatibility
    if string[0:2] == "&_":
        string = "Mhf"+string[2:]
        alpha7 = True
    elif string[0] == "&":
        string = "Mha"+string[1:]
        alpha7 = True
    elif string[0] == "*":
        string = "Mhs"+string[1:]
        alpha7 = True
    elif len(string) > 4 and string[0:4] == "Hide":
        string = "Mhh"+string[4:]
        alpha7 = True

    if string[0] == "_":
        return None,None
    elif (len(string) > 3 and
          string[0:3] in ["Mha", "Mhf", "Mhs", "Mhh", "Mhv", "Mhc"]):
        name = string.replace("-","_")
        return name, '["%s"]' % name
    else:
        return string, '["%s"]' % string


def defProp(args, var):
    proptype = args[0]
    name = propNames(args[1])[0]
    value = args[2]
    rest = 'description="%s"' % args[3].replace("_", " ")
    if len(args) > 4:
        rest += ", " + args[4]

    if name:
        var[name] = value


def defNewProp(name, proptype, rest):
    prop = "%sProperty(%s)" % (proptype, rest)
    setattr(bpy.types.Object, name, eval(prop)) # safe: only called from this file


def setProperty(args, var):
    global theProperty
    tip = ""
    name = propNames(args[0])[0]
    value = mhxEval(args[1])
    if name:
        var[name] = value
        if len(args) > 2:
            tip = 'description="%s"' % args[2].replace("_", " ")
        theProperty = (name, tip, value)


def setPropKeys(args):
    global theProperty
    if theProperty is None:
        return
    (name, tip, value) = theProperty
    if len(args) >= 2 and not isinstance(value, bool):
        if "BOOLEAN" in args[1]:
            value = bool(value)
        else:
            tip = tip + "," + args[1].replace(":", "=").replace('"', " ")
    #expr = "bpy.types.Object.%s = %sProperty(%s)" % (name, proptype, tip)
    if isinstance(value, bool):
        prop = BoolProperty(tip)
    elif isinstance(value, int):
        prop = IntProperty(tip)
    elif isinstance(value, float):
        prop = FloatProperty(tip)
    elif isinstance(value, string):
        prop = StringProperty(tip)
    setattr(bpy.types.Object, name, prop)
    theProperty = None


def defaultKey(ext, args, tokens, var, exclude=[]):
    if ext == 'Property':
        return setProperty(args, var)
    elif ext == 'PropKeys':
        return setPropKeys(args)
    elif ext == 'DefProp':
        return defProp(args, var)

    if ext == 'bpyops':
        expr = "bpy.ops.%s" % args[0]
        print(expr)
        raise MhxError("MHX bug: calling %s" % expr)

    if ext in exclude:
        return
    nvar = getattr(var, ext)

    if len(args) == 0:
        MyError("Key length 0: %s" % ext)

    rnaType = args[0]
    if rnaType == 'Refer':
        typ = args[1]
        name = args[2]
        setattr(var, ext, loadedData[typ][name])
        return

    elif rnaType == 'Struct' or rnaType == 'Define':
        raise MhxError("Struct/Define!")
        typ = args[1]
        name = args[2]
        try:
            data = getattr(var, ext)
        except:
            data = None
        # print("Old structrna", nvar, data)

        if data is None:
            try:
                creator = args[3]
            except:
                creator = None

            try:
                rna = mhxEval(var, locals())
                data = mhxEval(creator)
            except:
                data = None
            # print("New struct", nvar, typ, data)

        if rnaType == 'Define':
            loadedData[typ][name] = data

        if data:
            for (key, val, sub) in tokens:
                defaultKey(key, val, sub, data)
        return

    elif rnaType == 'PropertyRNA':
        raise MhxError("PropertyRNA!")
        #print("PropertyRNA ", ext, var)
        for (key, val, sub) in tokens:
            defaultKey(ext, val, sub, nvar, [])
        return

    elif rnaType == 'Array':
        for n in range(1, len(args)):
            nvar[n-1] = mhxEval(args[n], locals())
        if len(args) > 0:
            nvar[0] = mhxEval(args[1], locals())
        return

    elif rnaType == 'List':
        raise MhxError("List!")
        data = []
        for (key, val, sub) in tokens:
            elt = mhxEval(val[1], locals())
            data.append(elt)
        setattr(var, ext, data)
        return

    elif rnaType == 'Matrix':
        raise MhxError("Matrix!")
        i = 0
        n = len(tokens)
        for (key, val, sub) in tokens:
            if key == 'row':
                for j in range(n):
                    nvar[i][j] = float(val[j])
                i += 1
        return

    else:
        try:
            data = loadedData[rnaType][args[1]]
            raise MhxError("From loaded %s %s!" % (rnaType, args[1]))
        except KeyError:
            pass
        data = mhxEval(rnaType, locals())
        setattr(var, ext, data)


#
#
#

def pushOnTodoList(var, expr):
    print("Unrecognized expression", expr)
    return
    print(dir(mhxEval(var)))
    MyError(
        "Unrecognized expression %s.\n"  % expr +
        "This can mean that Blender's python API has changed\n" +
        "since the MHX file was exported. Try to export again\n" +
        "from an up-to-date MakeHuman nightly build.\n" +
        "Alternatively, your Blender version may be obsolete.\n" +
        "Download an up-to-date version from www.graphicall.org")


#
#    parseBoolArray(mask):
#

def parseBoolArray(mask):
    list = []
    for c in mask:
        if c == '0':
            list.append(False)
        else:
            list.append(True)
    return list

#    parseMatrix(args, tokens)
#

def parseMatrix(args, tokens):
    matrix = mathutils.Matrix()
    i = 0
    for (key, val, sub) in tokens:
        if key == 'row':
            matrix[i][0] = float(val[0])
            matrix[i][1] = float(val[1])
            matrix[i][2] = float(val[2])
            matrix[i][3] = float(val[3])
            i += 1
    return matrix

#
#    parseDefault(data, tokens, subkeys, exclude):
#

def parseDefault(data, tokens, subkeys, exclude):
    for (key, val, sub) in tokens:
        if key in subkeys.keys():
            for (key2, val2, sub2) in sub:
                ndata = getattr(data, subkeys[key])
                defaultKey(key2, val2, sub2, ndata)
        else:
            defaultKey(key, val, sub, data, exclude)

def parseCollection(data, tokens, exclude):
    return


#
#    Utilities
#

#
#    extractBpyType(data):
#
"""
def extractBpyType(data):
    typeSplit = str(type(data)).split("'")
    if typeSplit[0] != '<class ':
        return None
    classSplit = typeSplit[1].split(".")
    if classSplit[0] == 'bpy' and classSplit[1] == 'types':
        return classSplit[2]
    elif classSplit[0] == 'bpy_types':
        return classSplit[1]
    else:
        return None

#
#    Bool(string):
#

def Bool(string):
    if string == 'True':
        return True
    elif string == 'False':
        return False
    else:
        MyError("Bool %s?" % string)
"""
#
#    invalid(condition):
#

def invalid(condition):
    global rigLeg, rigArm, toggle
    try:
        res = mhxEval(condition)
        #print("%s = %s" % (condition, res))
        return not res
    except:
        #print("%s invalid!" % condition)
        return True



#
#    clearScene(context):
#

def clearScene():
    global toggle
    scn = bpy.context.scene
    for n in range(len(scn.layers)):
        scn.layers[n] = True
    return scn
    print("clearScene %s %s" % (toggle & T_Replace, scn))
    if not toggle & T_Replace:
        return scn

    for ob in scn.objects:
        if ob.type in ['MESH', 'ARMATURE', 'EMPTY', 'CURVE', 'LATTICE']:
            scn.objects.active = ob
            ob.name = "#" + ob.name
            try:
                bpy.ops.object.mode_set(mode='OBJECT')
            except:
                pass
            scn.objects.unlink(ob)
            del ob

    for grp in bpy.data.groups:
        grp.name = "#" + grp.name
    #print(scn.objects)
    return scn

#
#    hideLayers(args):
#    args = sceneLayers sceneHideLayers boneLayers boneHideLayers or nothing
#

def hideLayers(args):
    if len(args) > 1:
        sceneLayers = int(args[2], 16)
        sceneHideLayers = int(args[3], 16)
        boneLayers = int(args[4], 16)
        # boneHideLayers = int(args[5], 16)
        boneHideLayers = 0
    else:
        sceneLayers = 0x00ff
        sceneHideLayers = 0
        boneLayers = 0
        boneHideLayers = 0

    scn = bpy.context.scene
    mask = 1
    hidelayers = []
    for n in range(20):
        scn.layers[n] = True if sceneLayers & mask else False
        if sceneHideLayers & mask:
            hidelayers.append(n)
        mask = mask << 1

    for ob in scn.objects:
        for n in hidelayers:
            if ob.layers[n]:
                ob.hide = True
                ob.hide_render = True

    if boneLayers:
        human = args[1]
        try:
            ob = loadedData['Object'][human]
        except:
            return

        mask = 1
        hidelayers = []
        for n in range(32):
            ob.data.layers[n] = True if boneLayers & mask else False
            if boneHideLayers & mask:
                hidelayers.append(n)
            mask = mask << 1

        for b in ob.data.bones:
            for n in hidelayers:
                if b.layers[n]:
                    b.hide = True

    return


#
#    readDefaults():
#    writeDefaults():
#

ConfigFile = '~/mhx_import.cfg'


def readDefaults():
    global toggle, toggleSettings, theScale
    path = os.path.realpath(os.path.expanduser(ConfigFile))
    try:
        fp = open(path, 'rU')
        print('Storing defaults')
    except:
        print('Cannot open "%s" for reading' % path)
        return
    bver = ''
    for line in fp:
        words = line.split()
        if len(words) >= 3:
            try:
                toggle = int(words[0],16)
                theScale = float(words[1])
            except:
                print('Configuration file "%s" is corrupt' % path)
    fp.close()
    toggleSettings = toggle
    return

def writeDefaults():
    global toggleSettings, theScale
    path = os.path.realpath(os.path.expanduser(ConfigFile))
    try:
        fp = open(path, 'w')
        print('Storing defaults')
    except:
        print('Cannot open "%s" for writing' % path)
        return
    fp.write("%x %f Graphicall" % (toggleSettings, theScale))
    fp.close()
    return

###################################################################################
#
#   Postprocessing of rigify rig
#
#   rigifyMhx(context):
#
###################################################################################

class RigifyBone:
    def __init__(self, eb):
        self.name = eb.name
        self.realname = None
        self.realname1 = None
        self.realname2 = None
        self.fkname = None
        self.ikname = None

        self.head = eb.head.copy()
        self.tail = eb.tail.copy()
        self.roll = eb.roll
        self.deform = eb.use_deform
        self.parent = None
        self.child = None
        self.connect = False
        self.original = False
        self.extra = (eb.name in ["spine-1"])

    def __repr__(self):
        return ("<RigifyBone %s %s %s>" % (self.name, self.realname, self.realname1))


def rigifyMhx(context):
    global theArmature
    from collections import OrderedDict

    print("Modifying MHX rig to Rigify")
    scn = context.scene
    ob = context.object
    if ob.type == 'ARMATURE':
        rig = ob
    elif ob.type == 'MESH':
        rig = ob.parent
    else:
        rig = None
    if not(rig and rig.type == 'ARMATURE'):
        raise NameError("Rigify: %s is neither an armature nor has armature parent" % ob)
    rig.MhxRigify = True
    scn.objects.active = rig

    group = None
    for grp in bpy.data.groups:
        if rig.name in grp.objects:
            group = grp
            break
    print("Group: %s" % group)

    # Setup info about MHX bones
    bones = OrderedDict()
    bpy.ops.object.mode_set(mode='EDIT')
    for eb in rig.data.edit_bones:
        bone = bones[eb.name] = RigifyBone(eb)
        if eb.parent:
            bone.parent = eb.parent.name
            bones[bone.parent].child = eb.name
    bpy.ops.object.mode_set(mode='OBJECT')

    # Create metarig
    try:
        bpy.ops.object.armature_human_metarig_add()
    except AttributeError:
        raise MyError("The Rigify add-on is not enabled. It is found under rigging.")
    bpy.ops.object.location_clear()
    bpy.ops.object.rotation_clear()
    bpy.ops.object.scale_clear()
    bpy.ops.transform.resize(value=(100, 100, 100))
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)

    # Fit metarig to default MHX rig
    meta = context.object
    bpy.ops.object.mode_set(mode='EDIT')
    extra = []
    for bone in bones.values():
        try:
            eb = meta.data.edit_bones[bone.name]
        except KeyError:
            eb = None
        if eb:
            eb.head = bone.head
            eb.tail = bone.tail
            eb.roll = bone.roll
            bone.original = True
        elif bone.extra:
            extra.append(bone.name)
            bone.original = True
            eb = meta.data.edit_bones.new(bone.name)
            eb.use_connect = False
            eb.head = bones[bone.parent].tail
            eb.tail = bones[bone.child].head
            eb.roll = bone.roll
            parent = meta.data.edit_bones[bone.parent]
            child = meta.data.edit_bones[bone.child]
            child.parent = eb
            child.head = bones[bone.child].head
            parent.tail = bones[bone.parent].tail
            eb.parent = parent
            eb.use_connect = True

    # Add rigify properties to extra bones
    bpy.ops.object.mode_set(mode='OBJECT')
    for bname in extra:
        pb = meta.pose.bones[bname]
        pb["rigify_type"] = ""

    # Generate rigify rig
    bpy.ops.pose.rigify_generate()
    gen = context.object
    print("Generated", gen)
    scn.objects.unlink(meta)
    del meta

    for bone in bones.values():
        if bone.original:
            setBoneName(bone, gen)

    # Add extra bone to generated rig
    bpy.ops.object.mode_set(mode='EDIT')
    layers = 32*[False]
    layers[1] = True
    for bone in bones.values():
        if not bone.original:
            if bone.deform:
                bone.realname = "DEF-" + bone.name
            else:
                bone.realname = "MCH-" + bone.name
            eb = gen.data.edit_bones.new(bone.realname)
            eb.head = bone.head
            eb.tail = bone.tail
            eb.roll = bone.roll
            eb.use_deform = bone.deform
            if bone.parent:
                parent = bones[bone.parent]
                if parent.realname:
                    eb.parent = gen.data.edit_bones[parent.realname]
                elif parent.realname1:
                    eb.parent = gen.data.edit_bones[parent.realname1]
                else:
                    print(bone)
            eb.use_connect = (eb.parent != None and eb.parent.tail == eb.head)
            eb.layers = layers

    bpy.ops.object.mode_set(mode='OBJECT')
    for bone in bones.values():
        if not bone.original:
            pb = gen.pose.bones[bone.realname]
            db = rig.pose.bones[bone.name]
            pb.rotation_mode = db.rotation_mode
            for cns1 in db.constraints:
                cns2 = pb.constraints.new(cns1.type)
                fixConstraint(cns1, cns2, gen, bones)

    # Add MHX properties
    for key in rig.keys():
        gen[key] = rig[key]

    # Copy MHX bone drivers
    if rig.animation_data:
        for fcu1 in rig.animation_data.drivers:
            rna,channel = fcu1.data_path.rsplit(".", 1)
            pb = mhxEval("gen.%s" % rna)
            fcu2 = pb.driver_add(channel, fcu1.array_index)
            copyDriver(fcu1, fcu2, gen)

    # Copy MHX morph drivers and change armature modifier
    for ob in rig.children:
        if ob.type == 'MESH':
            ob.parent = gen

            if ob.data.animation_data:
                for fcu in ob.data.animation_data.drivers:
                    print(ob, fcu.data_path)
                    changeDriverTarget(fcu, gen)

            if ob.data.shape_keys and ob.data.shape_keys.animation_data:
                for fcu in ob.data.shape_keys.animation_data.drivers:
                    print(skey, fcu.data_path)
                    changeDriverTarget(fcu, gen)

            for mod in ob.modifiers:
                if mod.type == 'ARMATURE' and mod.object == rig:
                    mod.object = gen

    if group:
        group.objects.link(gen)

    # Parent widgets under empty
    empty = bpy.data.objects.new("Widgets", None)
    scn.objects.link(empty)
    empty.layers = 20*[False]
    empty.layers[19] = True
    empty.parent = gen
    for ob in scn.objects:
        if ob.type == 'MESH' and ob.name[0:4] == "WGT-" and not ob.parent:
            ob.parent = empty
            grp.objects.link(ob)

    #Clean up
    gen.show_x_ray = True
    gen.data.draw_type = 'STICK'
    gen.MhxRigify = False
    name = rig.name
    scn.objects.unlink(rig)
    del rig
    gen.name = name
    bpy.ops.object.mode_set(mode='POSE')
    theArmature = gen
    print("MHX rig %s successfully rigified" % name)



def setBoneName(bone, gen):
    fkname = bone.name.replace(".", ".fk.")
    try:
        gen.data.bones[fkname]
        bone.fkname = fkname
        bone.ikname = fkname.replace(".fk.", ".ik")
    except KeyError:
        pass

    defname = "DEF-" + bone.name
    try:
        gen.data.bones[defname]
        bone.realname = defname
        return
    except KeyError:
        pass

    defname1 = "DEF-" + bone.name + ".01"
    try:
        gen.data.bones[defname1]
        bone.realname1 = defname1
        bone.realname2 = defname1.replace(".01.", ".02.")
        return
    except KeyError:
        pass

    defname1 = "DEF-" + bone.name.replace(".", ".01.")
    try:
        gen.data.bones[defname1]
        bone.realname1 = defname1
        bone.realname2 = defname1.replace(".01.", ".02")
        return
    except KeyError:
        pass

    try:
        gen.data.edit_bones[bone.name]
        bone.realname = bone.name
    except KeyError:
        pass


def fixConstraint(cns1, cns2, gen, bones):
    for key in dir(cns1):
        if ((key[0] != "_") and
            (key not in ["bl_rna", "type", "rna_type", "is_valid", "error_location", "error_rotation"])):
            expr = ("cns2.%s = cns1.%s" % (key, key))
            setattr(cns2, key, getattr(cns1, key))

    cns2.target = gen

    if cns1.type == 'STRETCH_TO':
        bone = bones[cns1.subtarget]
        if bone.realname:
            cns2.subtarget = bone.realname
            cns2.head_tail = cns1.head_tail
        elif not bone.realname1:
            print(bone)
            halt
        elif cns1.head_tail < 0.5:
            cns2.subtarget = bone.realname1
            cns2.head_tail = 2*cns1.head_tail
        else:
            cns2.subtarget = bone.realname2
            cns2.head_tail = 2*cns1.head_tail-1

    elif cns1.type == 'TRANSFORM':
        bone = bones[cns1.subtarget]
        if bone.fkname:
            cns2.subtarget = bone.fkname
        elif bone.ikname:
            cns2.subtarget = bone.ikname
        else:
            cns2.subtarget = bone.realname


def copyDriver(fcu1, fcu2, id):
    drv1 = fcu1.driver
    drv2 = fcu2.driver

    for var1 in drv1.variables:
        var2 = drv2.variables.new()
        var2.name = var1.name
        var2.type = var1.type
        targ1 = var1.targets[0]
        targ2 = var2.targets[0]
        targ2.id = id
        targ2.data_path = targ1.data_path

    drv2.type = drv1.type
    drv2.expression = drv1.expression
    drv2.show_debug_info = drv1.show_debug_info


def changeDriverTarget(fcu, id):
    for var in fcu.driver.variables:
        targ = var.targets[0]
        targ.id = id


#
#   class OBJECT_OT_RigifyMhxButton(bpy.types.Operator):
#

class OBJECT_OT_RigifyMhxButton(bpy.types.Operator):
    bl_idname = "mhxrig.rigify_mhx"
    bl_label = "Rigify MHX rig"
    bl_options = {'UNDO'}

    def execute(self, context):
        rigifyMhx(context)
        return{'FINISHED'}

#
#   class RigifyMhxPanel(bpy.types.Panel):
#

class RigifyMhxPanel(bpy.types.Panel):
    bl_label = "Rigify MHX"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"

    @classmethod
    def poll(cls, context):
        return (context.object and context.object.MhxRigify)

    def draw(self, context):
        self.layout.operator("mhxrig.rigify_mhx")
        return

###################################################################################
#
#    Error popup
#
###################################################################################

DEBUG = False
from bpy.props import StringProperty, FloatProperty, EnumProperty, BoolProperty

class ErrorOperator(bpy.types.Operator):
    bl_idname = "mhx.error"
    bl_label = "Error when loading MHX file"

    def execute(self, context):
        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        global theErrorLines
        maxlen = 0
        for line in theErrorLines:
            if len(line) > maxlen:
                maxlen = len(line)
        width = 20+5*maxlen
        height = 20+5*len(theErrorLines)
        #self.report({'INFO'}, theMessage)
        wm = context.window_manager
        return wm.invoke_props_dialog(self, width=width, height=height)

    def draw(self, context):
        global theErrorLines
        for line in theErrorLines:
            self.layout.label(line)

def MyError(message):
    global theMessage, theErrorLines, theErrorStatus
    theMessage = message
    theErrorLines = message.split('\n')
    theErrorStatus = True
    bpy.ops.mhx.error('INVOKE_DEFAULT')
    raise MhxError(theMessage)

class MhxError(Exception):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return repr(self.value)

class SuccessOperator(bpy.types.Operator):
    bl_idname = "mhx.success"
    bl_label = "MHX file successfully loaded:"
    message = StringProperty()

    def execute(self, context):
        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        wm = context.window_manager
        return wm.invoke_props_dialog(self)

    def draw(self, context):
        self.layout.label(self.message + theMessage)

###################################################################################
#
#    User interface
#
###################################################################################

from bpy_extras.io_utils import ImportHelper, ExportHelper

MhxBoolProps = [
    ("enforce", "Enforce version", "Only accept MHX files of correct version", T_EnforceVersion),
    #("crash_safe", "Crash-safe", "Disable features that have caused Blender crashes", T_CrashSafe),
    ("mesh", "Mesh", "Use main mesh", T_Mesh),
    ("proxy", "Proxies", "Use proxies", T_Proxy),
    #("armature", "Armature", "Use armature", T_Armature),
    #("replace", "Replace scene", "Replace scene", T_Replace),
    ("cage", "Cage", "Load mesh deform cage", T_Cage),
    ("clothes", "Clothes", "Include clothes", T_Clothes),
    ("shapekeys", "Shapekeys", "Include shapekeys", T_Shapekeys),
    ("shapedrivers", "Shapekey drivers", "Include shapekey drivers", T_ShapeDrivers),
    #("symm", "Symmetric shapes", "Keep shapekeys symmetric", T_Symm),
    ("diamond", "Helper geometry", "Keep helper geometry", T_Diamond),
    ("rigify", "Rigify", "Create rigify control rig", T_Rigify),
]

class ImportMhx(bpy.types.Operator, ImportHelper):
    """Import from MHX file format (.mhx)"""
    bl_idname = "import_scene.makehuman_mhx"
    bl_description = 'Import from MHX file format (.mhx)'
    bl_label = "Import MHX"
    bl_space_type = "PROPERTIES"
    bl_region_type = "WINDOW"
    bl_options = {'UNDO'}

    filename_ext = ".mhx"
    filter_glob = StringProperty(default="*.mhx", options={'HIDDEN'})
    filepath = StringProperty(subtype='FILE_PATH')

    scale = FloatProperty(name="Scale", description="Default meter, decimeter = 1.0", default = theScale)
    advanced = BoolProperty(name="Advanced settings", description="Use advanced import settings", default=False)
    for (prop, name, desc, flag) in MhxBoolProps:
        expr = '%s = BoolProperty(name="%s", description="%s", default=(toggleSettings&%s != 0))' % (prop, name, desc, flag)
        exec(expr)   # Trusted source: this file.


    def draw(self, context):
        layout = self.layout
        layout.prop(self, "scale")
        layout.prop(self, "advanced")
        if self.advanced:
            for (prop, name, desc, flag) in MhxBoolProps:
                layout.prop(self, prop)


    def execute(self, context):
        global toggle, toggleSettings, theScale, MhxBoolProps
        if not self.advanced:
            toggle = DefaultToggle
        else:
            toggle = T_Armature
            for (prop, name, desc, flag) in MhxBoolProps:
                expr = '(%s if self.%s else 0)' % (flag, prop)
                toggle |=  eval(expr)   # trusted source: this file
            toggleSettings = toggle
        print("execute flags %x" % toggle)
        theScale = self.scale

        #filepathname = self.filepath.encode('utf-8', 'strict')
        try:
            if not context.user_preferences.system.use_scripts_auto_execute:
                MyError("Auto Run Python Scripts must be turned on.\nIt is found under\n File > User Preferences > File")
            readMhxFile(self.filepath)
            #bpy.ops.mhx.success('INVOKE_DEFAULT', message = self.filepath)
        except MhxError:
            print("Error when loading MHX file %s:\n" % self.filepath + theMessage)

        if self.advanced:
            writeDefaults()
            self.advanced = False
        return {'FINISHED'}


    def invoke(self, context, event):
        global toggle, theScale, MhxBoolProps
        readDefaults()
        self.scale = theScale
        for (prop, name, desc, flag) in MhxBoolProps:
            setattr(self, prop, mhxEval('(toggle&%s != 0)' % flag))
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}


###################################################################################
#
#    Main panel
#
###################################################################################

MhxLayers = [
    (( 0,    'Root', 'MhxRoot'),
     ( 1,    'Spine', 'MhxFKSpine')),
    ((10,    'Head', 'MhxHead'),
     ( 8,    'Face', 'MhxFace')),
    (( 9,    'Tweak', 'MhxTweak'),
     (16,    'Clothes', 'MhxClothes')),
    #((17,    'IK Spine', 'MhxIKSpine'),
     #((13,    'Inv FK Spine', 'MhxInvFKSpine')),
    ('Left', 'Right'),
    (( 2,    'IK Arm', 'MhxIKArm'),
     (18,    'IK Arm', 'MhxIKArm')),
    (( 3,    'FK Arm', 'MhxFKArm'),
     (19,    'FK Arm', 'MhxFKArm')),
    (( 4,    'IK Leg', 'MhxIKLeg'),
     (20,    'IK Leg', 'MhxIKLeg')),
    (( 5,    'FK Leg', 'MhxFKLeg'),
     (21,    'FK Leg', 'MhxFKLeg')),
    ((12,    'Extra', 'MhxExtra'),
     (28,    'Extra', 'MhxExtra')),
    (( 6,    'Fingers', 'MhxFingers'),
     (22,    'Fingers', 'MhxFingers')),
    (( 7,    'Links', 'MhxLinks'),
     (23,    'Links', 'MhxLinks')),
    ((11,    'Palm', 'MhxPalm'),
     (27,    'Palm', 'MhxPalm')),
]

OtherLayers = [
    (( 1,    'Spine', 'MhxFKSpine'),
     ( 10,    'Head', 'MhxHead')),
    (( 9,    'Tweak', 'MhxTweak'),
     ( 8,    'Face', 'MhxFace')),
    ('Left', 'Right'),
    (( 3,    'Arm', 'MhxFKArm'),
     (19,    'Arm', 'MhxFKArm')),
    (( 5,    'Leg', 'MhxFKLeg'),
     (21,    'Leg', 'MhxFKLeg')),
    (( 7,    'Fingers', 'MhxLinks'),
     (23,    'Fingers', 'MhxLinks')),
    ((11,    'Palm', 'MhxPalm'),
     (27,    'Palm', 'MhxPalm')),
]


#
#    class MhxMainPanel(bpy.types.Panel):
#

class MhxMainPanel(bpy.types.Panel):
    bl_label = "MHX Main v %s" % bl_info["version"]
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    #bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(cls, context):
        return (context.object and context.object.MhxRig)

    def draw(self, context):
        layout = self.layout
        layout.label("Layers")
        layout.operator("mhx.pose_enable_all_layers")
        layout.operator("mhx.pose_disable_all_layers")

        rig = context.object
        if rig.MhxRig == 'MHX':
            layers = MhxLayers
        else:
            layers = OtherLayers

        for (left,right) in layers:
            row = layout.row()
            if type(left) == str:
                row.label(left)
                row.label(right)
            else:
                for (n, name, prop) in [left,right]:
                    row.prop(rig.data, "layers", index=n, toggle=True, text=name)

        layout.separator()
        layout.label("Export/Import MHP")
        layout.operator("mhx.saveas_mhp")
        layout.operator("mhx.load_mhp")


class VIEW3D_OT_MhxEnableAllLayersButton(bpy.types.Operator):
    bl_idname = "mhx.pose_enable_all_layers"
    bl_label = "Enable all layers"
    bl_options = {'UNDO'}

    def execute(self, context):
        rig,mesh = getMhxRigMesh(context.object)
        for (left,right) in MhxLayers:
            if type(left) != str:
                for (n, name, prop) in [left,right]:
                    rig.data.layers[n] = True
        return{'FINISHED'}


class VIEW3D_OT_MhxDisableAllLayersButton(bpy.types.Operator):
    bl_idname = "mhx.pose_disable_all_layers"
    bl_label = "Disable all layers"
    bl_options = {'UNDO'}

    def execute(self, context):
        rig,mesh = getMhxRigMesh(context.object)
        layers = 32*[False]
        pb = context.active_pose_bone
        if pb:
            for n in range(32):
                if pb.bone.layers[n]:
                    layers[n] = True
                    break
        else:
            layers[0] = True
        if rig:
            rig.data.layers = layers
        return{'FINISHED'}



def saveMhpFile(rig, scn, filepath):
    roots = []
    for pb in rig.pose.bones:
        if pb.parent is None:
            roots.append(pb)

    (pname, ext) = os.path.splitext(filepath)
    mhppath = pname + ".mhp"

    fp = open(mhppath, "w", encoding="utf-8", newline="\n")
    for root in roots:
        writeMhpBones(fp, root, None)
    fp.close()
    print("Mhp file %s saved" % mhppath)


def writeMhpBones(fp, pb, log):
    if not isMuscleBone(pb):
        b = pb.bone
        if pb.parent:
            mat = b.matrix_local.inverted() * b.parent.matrix_local * pb.parent.matrix.inverted() * pb.matrix
        else:
            mat = pb.matrix.copy()
            maty = mat[1].copy()
            matz = mat[2].copy()
            mat[1] = matz
            mat[2] = -maty

        diff = mat - Matrix()
        nonzero = False
        for i in range(4):
            if abs(diff[i].length) > 5e-3:
                nonzero = True
                break

        if nonzero:
            fp.write("%s\tmatrix" % pb.name)
            for i in range(4):
                row = mat[i]
                fp.write("\t%.4f\t%.4f\t%.4f\t%.4f" % (row[0], row[1], row[2], row[3]))
            fp.write("\n")

    for child in pb.children:
        writeMhpBones(fp, child, log)


def isMuscleBone(pb):
    layers = pb.bone.layers
    if (layers[14] or layers[15] or layers[30] or layers[31]):
        return True
    for cns in pb.constraints:
        if (cns.type == 'STRETCH_TO' or
            cns.type == 'TRANSFORM' or
            cns.type == 'TRACK_TO' or
            cns.type == 'IK' or
            cns.type[0:5] == 'COPY_'):
            return True
    return False


def loadMhpFile(rig, scn, filepath):
    unit = Matrix()
    for pb in rig.pose.bones:
        pb.matrix_basis = unit

    (pname, ext) = os.path.splitext(filepath)
    mhppath = pname + ".mhp"

    fp = open(mhppath, "rU")
    for line in fp:
        words = line.split()
        if len(words) < 4:
            continue

        try:
            pb = rig.pose.bones[words[0]]
        except KeyError:
            print("Warning: Did not find bone %s" % words[0])
            continue

        if isMuscleBone(pb):
            pass
        elif words[1] == "quat":
            q = Quaternion((float(words[2]), float(words[3]), float(words[4]), float(words[5])))
            mat = q.to_matrix().to_4x4()
            pb.matrix_basis = mat
        elif words[1] == "gquat":
            q = Quaternion((float(words[2]), float(words[3]), float(words[4]), float(words[5])))
            mat = q.to_matrix().to_4x4()
            maty = mat[1].copy()
            matz = mat[2].copy()
            mat[1] = -matz
            mat[2] = maty
            pb.matrix_basis = pb.bone.matrix_local.inverted() * mat
        elif words[1] == "matrix":
            rows = []
            n = 2
            for i in range(4):
                rows.append((float(words[n]), float(words[n+1]), float(words[n+2]), float(words[n+3])))
                n += 4
            mat = Matrix(rows)
            if pb.parent:
                pb.matrix_basis = mat
            else:
                maty = mat[1].copy()
                matz = mat[2].copy()
                mat[1] = -matz
                mat[2] = maty
                pb.matrix_basis = pb.bone.matrix_local.inverted() * mat
        elif words[1] == "scale":
            pass
        else:
            print("WARNING: Unknown line in mcp file:\n%s" % line)
    fp.close()
    print("Mhp file %s loaded" % mhppath)


class VIEW3D_OT_LoadMhpButton(bpy.types.Operator):
    bl_idname = "mhx.load_mhp"
    bl_label = "Load MHP File"
    bl_description = "Load a pose in MHP format"
    bl_options = {'UNDO'}

    filename_ext = ".mhp"
    filter_glob = StringProperty(default="*.mhp", options={'HIDDEN'})
    filepath = bpy.props.StringProperty(
        name="File Path",
        description="File path used for mhp file",
        maxlen= 1024, default= "")

    def execute(self, context):
        loadMhpFile(context.object, context.scene, self.properties.filepath)
        return {'FINISHED'}

    def invoke(self, context, event):
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}


class VIEW3D_OT_SaveasMhpFileButton(bpy.types.Operator, ExportHelper):
    bl_idname = "mhx.saveas_mhp"
    bl_label = "Save MHP File"
    bl_description = "Save current pose in MHP format"
    bl_options = {'UNDO'}

    filename_ext = ".mhp"
    filter_glob = StringProperty(default="*.mhp", options={'HIDDEN'})
    filepath = bpy.props.StringProperty(
        name="File Path",
        description="File path used for mhp file",
        maxlen= 1024, default= "")

    def execute(self, context):
        saveMhpFile(context.object, context.scene, self.properties.filepath)
        return {'FINISHED'}

    def invoke(self, context, event):
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}

###################################################################################
#
#    Lipsync panel
#
###################################################################################

#
#    visemes
#

bodyLanguageVisemes = ({
    'Rest' : [
        ('MouthWidth_L', 0),
        ('MouthWidth_R', 0),
        ('MouthNarrow_L', 0),
        ('MouthNarrow_R', 0),
        ('LipsPart', 0.6),
        ('UpLipsMidHeight', 0),
        ('LoLipsMidHeight', 0),
        ('LoLipsIn', 0),
        ('MouthOpen', 0),
        ('TongueBackHeight', 0),
        ('TongueHeight', 0),
        ],
    'Etc' : [
        ('MouthWidth_L', 0),
        ('MouthWidth_R', 0),
        ('MouthNarrow_L', 0),
        ('MouthNarrow_R', 0),
        ('LipsPart', 0.4),
        ('UpLipsMidHeight', 0),
        ('LoLipsMidHeight', 0),
        ('LoLipsIn', 0),
        ('MouthOpen', 0),
        ('TongueBackHeight', 0),
        ('TongueHeight', 0),
        ],
    'MBP' : [
        ('MouthWidth_L', 0),
        ('MouthWidth_R', 0),
        ('MouthNarrow_L', 0),
        ('MouthNarrow_R', 0),
        ('LipsPart', 0),
        ('UpLipsMidHeight', 0),
        ('LoLipsMidHeight', 0),
        ('LoLipsIn', 0),
        ('MouthOpen', 0),
        ('TongueBackHeight', 0),
        ('TongueHeight', 0),
        ],
    'OO' : [
        ('MouthWidth_L', 0),
        ('MouthWidth_R', 0),
        ('MouthNarrow_L', 1.0),
        ('MouthNarrow_R', 1.0),
        ('LipsPart', 0),
        ('UpLipsMidHeight', 0),
        ('LoLipsMidHeight', 0),
        ('LoLipsIn', 0),
        ('MouthOpen', 0.4),
        ('TongueBackHeight', 0),
        ('TongueHeight', 0),
        ],
    'O' : [
        ('MouthWidth_L', 0),
        ('MouthWidth_R', 0),
        ('MouthNarrow_L', 0.9),
        ('MouthNarrow_R', 0.9),
        ('LipsPart', 0),
        ('UpLipsMidHeight', 0),
        ('LoLipsMidHeight', 0),
        ('LoLipsIn', 0),
        ('MouthOpen', 0.8),
        ('TongueBackHeight', 0),
        ('TongueHeight', 0),
        ],
    'R' : [
        ('MouthWidth_L', 0),
        ('MouthWidth_R', 0),
        ('MouthNarrow_L', 0.5),
        ('MouthNarrow_R', 0.5),
        ('LipsPart', 0),
        ('UpLipsMidHeight', 0.2),
        ('LoLipsMidHeight', -0.2),
        ('LoLipsIn', 0),
        ('MouthOpen', 0),
        ('TongueBackHeight', 0),
        ('TongueHeight', 0),
        ],
    'FV' : [
        ('MouthWidth_L', 0.2),
        ('MouthWidth_R', 0.2),
        ('MouthNarrow_L', 0),
        ('MouthNarrow_R', 0),
        ('LipsPart', 1.0),
        ('UpLipsMidHeight', 0),
        ('LoLipsMidHeight', 0.3),
        ('LoLipsIn', 0.6),
        ('MouthOpen', 0),
        ('TongueBackHeight', 0),
        ('TongueHeight', 0),
        ],
    'S' : [
        ('MouthWidth_L', 0),
        ('MouthWidth_R', 0),
        ('MouthNarrow_L', 0),
        ('MouthNarrow_R', 0),
        ('LipsPart', 0),
        ('UpLipsMidHeight', 0.5),
        ('LoLipsMidHeight', -0.7),
        ('LoLipsIn', 0),
        ('MouthOpen', 0),
        ('TongueBackHeight', 0),
        ('TongueHeight', 0),
        ],
    'SH' : [
        ('MouthWidth_L', 0.8),
        ('MouthWidth_R', 0.8),
        ('MouthNarrow_L', 0),
        ('MouthNarrow_R', 0),
        ('LipsPart', 0),
        ('UpLipsMidHeight', 1.0),
        ('LoLipsMidHeight', 0),
        ('LoLipsIn', 0),
        ('MouthOpen', 0),
        ('TongueBackHeight', 0),
        ('TongueHeight', 0),
        ],
    'EE' : [
        ('MouthWidth_L', 0.2),
        ('MouthWidth_R', 0.2),
        ('MouthNarrow_L', 0),
        ('MouthNarrow_R', 0),
        ('LipsPart', 0),
        ('UpLipsMidHeight', 0.6),
        ('LoLipsMidHeight', -0.6),
        ('LoLipsIn', 0),
        ('MouthOpen', 0.5),
        ('TongueBackHeight', 0),
        ('TongueHeight', 0),
        ],
    'AH' : [
        ('MouthWidth_L', 0),
        ('MouthWidth_R', 0),
        ('MouthNarrow_L', 0),
        ('MouthNarrow_R', 0),
        ('LipsPart', 0),
        ('UpLipsMidHeight', 0.4),
        ('LoLipsMidHeight', 0),
        ('LoLipsIn', 0),
        ('MouthOpen', 0.7),
        ('TongueBackHeight', 0),
        ('TongueHeight', 0),
        ],
    'EH' : [
        ('MouthWidth_L', 0),
        ('MouthWidth_R', 0),
        ('MouthNarrow_L', 0),
        ('MouthNarrow_R', 0),
        ('LipsPart', 0),
        ('UpLipsMidHeight', 0.5),
        ('LoLipsMidHeight', -0.6),
        ('LoLipsIn', 0),
        ('MouthOpen', 0.25),
        ('TongueBackHeight', 0),
        ('TongueHeight', 0),
        ],
    'TH' : [
        ('MouthWidth_L', 0),
        ('MouthWidth_R', 0),
        ('MouthNarrow_L', 0),
        ('MouthNarrow_R', 0),
        ('LipsPart', 0),
        ('UpLipsMidHeight', 0),
        ('LoLipsMidHeight', 0),
        ('LoLipsIn', 0),
        ('MouthOpen', 0.2),
        ('TongueBackHeight', 1.0),
        ('TongueHeight', 1.0)
        ],
    'L' : [
        ('MouthWidth_L', 0),
        ('MouthWidth_R', 0),
        ('MouthNarrow_L', 0),
        ('MouthNarrow_R', 0),
        ('LipsPart', 0),
        ('UpLipsMidHeight', 0.5),
        ('LoLipsMidHeight', -0.5),
        ('LoLipsIn', 0),
        ('MouthOpen', -0.2),
        ('TongueBackHeight', 1.0),
        ('TongueHeight', 1.0),
        ],
    'G' : [
        ('MouthWidth_L', 0),
        ('MouthWidth_R', 0),
        ('MouthNarrow_L', 0),
        ('MouthNarrow_R', 0),
        ('LipsPart', 0),
        ('UpLipsMidHeight', 0.5),
        ('LoLipsMidHeight', -0.5),
        ('LoLipsIn', 0),
        ('MouthOpen', -0.2),
        ('TongueBackHeight', 1.0),
        ('TongueHeight', 0),
        ],

    'Blink' : [
        ('UpLidUp_L', 1),
        ('UpLidUp_R', 1),
        ('LoLidDown_L', 1),
        ('LoLidDown_R', 1)
        ],

    'Unblink' : [
        ('UpLidUp_L', 0),
        ('UpLidUp_R', 0),
        ('LoLidDown_L', 0),
        ('LoLidDown_R', 0)
        ],
})

VisemePanelBones = {
    'MouthOpen' :       ('PJaw', (0,0.25)),
    'UpLipsMidHeight' : ('PUpLipMid', (0,-0.25)),
    'LoLipsMidHeight' : ('PLoLipMid', (0,-0.25)),
    'LoLipsIn':         ('PLoLipMid', (-0.25,0)),
    'MouthWidth_L' :    ('PMouth_L', (0.25,0)),
    'MouthWidth_R' :    ('PMouth_R', (-0.25,0)),
    'MouthNarrow_L' :   ('PMouth_L', (-0.25,0)),
    'MouthNarrow_R' :   ('PMouth_R', (0.25,0)),
    'LipsPart' :        ('PMouthMid', (0, -0.25)),
    'TongueBackHeight': ('PTongue', (-0.25, 0)),
    'TongueHeight' :    ('PTongue', (0, -0.25)),

    'UpLidUp_L' :       ('PUpLid_L', (0,1.0)),
    'UpLidUp_R' :       ('PUpLid_R', (0,1.0)),
    'LoLidDown_L' :     ('PLoLid_L', (0,-1.0)),
    'LoLidDown_R' :     ('PLoLid_R', (0,-1.0)),
}

VisemeList = [
    ('Rest', 'Etc', 'AH'),
    ('MBP', 'OO', 'O'),
    ('R', 'FV', 'S'),
    ('SH', 'EE', 'EH'),
    ('TH', 'L', 'G')
]

#
#   makeVisemes(ob, scn):
#   class VIEW3D_OT_MhxMakeVisemesButton(bpy.types.Operator):
#

def makeVisemes(ob, scn):
    rig = ob.parent
    if ob.type != 'MESH':
        print("Active object %s is not a mesh" % ob)
        return
    if not ob.data.shape_keys:
        print("%s has no shapekeys" % ob)
        return
    try:
        ob.data.shape_keys.key_blocks["VIS_Rest"]
        print("Visemes already created")
        return
    except KeyError:
        pass
    try:
        ob.data.shape_keys.key_blocks["MouthOpen"]
    except KeyError:
        print("Mesh does not have face shapes")
        return

    verts = ob.data.vertices
    for (vis,shapes) in bodyLanguageVisemes.items():
        if vis in ['Blink', 'Unblink']:
            continue
        vkey = ob.shape_key_add(name="VIS_%s" % vis)
        print(vkey.name)
        for n,v in enumerate(verts):
            vkey.data[n].co = v.co
        for (name,value) in shapes:
            if name[-2:] == "_R":
                continue
            skey = ob.data.shape_keys.key_blocks[name]
            factor = 0.75*value
            for n,v in enumerate(verts):
                vkey.data[n].co += factor*(skey.data[n].co - v.co)
    print("Visemes made")
    return

class VIEW3D_OT_MhxMakeVisemesButton(bpy.types.Operator):
    bl_idname = "mhx.make_visemes"
    bl_label = "Generate viseme shapekeys"
    bl_options = {'UNDO'}

    def execute(self, context):
        makeVisemes(context.object, context.scene)
        return{'FINISHED'}

#
#    mohoVisemes
#    magpieVisemes
#

MohoVisemes = dict({
    "rest" : "Rest",
    "etc" : "Etc",
    "AI" : "AH",
    "O" : "O",
    "U" : "OO",
    "WQ" : "AH",
    "L" : "L",
    "E" : "EH",
    "MBP" : "MBP",
    "FV" : "FV",
})

MagpieVisemes = dict({
    "CONS" : "Etc",
    "AI" : "AH",
    "E" : "EH",
    "O" : "O",
    "UW" : "AH",
    "MBP" : "MBP",
    "L" : "L",
    "FV" : "FV",
    "Sh" : "SH",
})

#
#    setViseme(context, vis, setKey, frame):
#    setBoneLocation(context, pbone, loc, mirror, setKey, frame):
#    class VIEW3D_OT_MhxVisemeButton(bpy.types.Operator):
#

def getVisemeSet(context, rig):
    if rig.MhxVisemeSet == "StopStaring":
        return stopStaringVisemes
    elif rig.MhxVisemeSet == "BodyLanguage":
        return bodyLanguageVisemes
    else:
        raise MhxError("Unknown viseme set %s" % visset)


def setVisemeAlpha7(context, vis, visemes, setKey, frame):
    (rig, mesh) = getMhxRigMesh(context.object)
    isPanel = False
    isProp = False
    shapekeys = None
    scale = 0.75
    if rig.MhxShapekeyDrivers:
        try:
            scale *= rig.pose.bones['PFace'].bone.length
            isPanel = True
        except:
            isProp = True
    elif mesh:
        shapekeys = mesh.data.shape_keys.key_blocks

    for (skey, value) in visemes[vis]:
        if isPanel:
            (b, (x,z)) = VisemePanelBones[skey]
            loc = mathutils.Vector((float(x*value),0,float(z*value)))
            pb = rig.pose.bones[b]
            pb.location = loc*scale
            if setKey or context.tool_settings.use_keyframe_insert_auto:
                for n in range(3):
                    pb.keyframe_insert('location', index=n, frame=frame, group=pb.name)
        elif isProp:
            skey = 'Mhf' + skey
            try:
                prop = rig[skey]
            except:
                continue
            rig[skey] = value*scale
            if setKey or context.tool_settings.use_keyframe_insert_auto:
                rig.keyframe_insert('["%s"]' % skey, frame=frame, group="Visemes")
        elif shapekeys:
            try:
                shapekeys[skey].value = value*scale
            except:
                continue
            if setKey or context.tool_settings.use_keyframe_insert_auto:
                shapekeys[skey].keyframe_insert("value", frame=frame)
    updatePose(context)
    return


class VIEW3D_OT_MhxVisemeButton(bpy.types.Operator):
    bl_idname = 'mhx.pose_viseme'
    bl_label = 'Viseme'
    bl_options = {'UNDO'}
    viseme = StringProperty()

    def invoke(self, context, event):
        (rig, mesh) = getMhxRigMesh(context.object)
        visemes = getVisemeSet(context, rig)
        setVisemeAlpha7(context, self.viseme, visemes, False, context.scene.frame_current)
        return{'FINISHED'}


def readLipsync(context, filepath, offs, struct):
    (rig, mesh) = getMhxRigMesh(context.object)
    if rig.MhxVisemeSet:
        visemes = getVisemeSet(context, rig)
    else:
        props = getProps(rig, "Mhv")
        visemes = {}
        oldKeys = []
        for prop in props:
            dummy,units = getUnitsFromString("x;"+rig[prop])
            visemes[prop] = units
        props = getProps(rig, "Mhsmouth")
        auto = context.tool_settings.use_keyframe_insert_auto
        auto = True
        factor = rig.MhxStrength
        shapekeys = getMhmShapekeys(rig, mesh)
    context.scene.objects.active = rig
    bpy.ops.object.mode_set(mode='POSE')

    fp = open(filepath, "rU")
    for line in fp:
        words= line.split()
        if len(words) < 2:
            continue
        else:
            vis = struct[words[1]]
            frame = int(words[0])+offs
        if rig.MhxVisemeSet:
            setVisemeAlpha7(context, vis, visemes, True, frame)
        else:
            setMhmProps(rig, shapekeys, "Mhsmouth", visemes["Mhv"+vis], factor, auto, frame)
    fp.close()

    #setInterpolation(rig)
    updatePose(context)
    print("Lipsync file %s loaded" % filepath)


class VIEW3D_OT_MhxMohoButton(bpy.types.Operator, ImportHelper):
    bl_idname = "mhx.pose_load_moho"
    bl_label = "Load Moho (.dat)"
    bl_options = {'UNDO'}

    filename_ext = ".dat"
    filter_glob = StringProperty(default="*.dat", options={'HIDDEN'})
    filepath = StringProperty(subtype='FILE_PATH')

    def execute(self, context):
        readLipsync(context, self.properties.filepath, context.scene.frame_start - 1, MohoVisemes)
        return{'FINISHED'}

    def invoke(self, context, event):
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}


class MhxLipsyncPanel(bpy.types.Panel):
    bl_label = "MHX Lipsync"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(cls, context):
        return hasProps(context.object, "Mhv")

    def draw(self, context):
        rig,mesh = getMhxRigMesh(context.object)
        if not rig:
            layout.label("No MHX rig found")
            return
        layout = self.layout

        if not rig.MhxVisemeSet:
            visemes = getProps(rig, "Mhv")
            if not visemes:
                layout.label("No visemes found")
                return

            layout.operator("mhx.pose_reset_expressions", text="Reset visemes").prefix="Mhsmouth"
            layout.operator("mhx.pose_key_expressions", text="Key visemes").prefix="Mhsmouth"
            layout.prop(rig, "MhxStrength")
            layout.separator()
            n = 0
            for prop in visemes:
                if n % 3 == 0:
                    row = layout.row()
                    n = 0
                row.operator("mhx.pose_mhm", text=prop[3:]).data="Mhsmouth;"+rig[prop]
                n += 1
            while n % 3 != 0:
                row.label("")
                n += 1
            layout.separator()
            row = layout.row()
            row.operator("mhx.pose_mhm", text="Blink").data="Mhsmouth;eye_left_closure:1;eye_right_closure:1"
            row.operator("mhx.pose_mhm", text="Unblink").data="Mhsmouth;eye_left_closure:0;eye_right_closure:0"
        else:
            for (vis1, vis2, vis3) in VisemeList:
                row = layout.row()
                row.operator("mhx.pose_viseme", text=vis1).viseme = vis1
                row.operator("mhx.pose_viseme", text=vis2).viseme = vis2
                row.operator("mhx.pose_viseme", text=vis3).viseme = vis3
            layout.separator()
            row = layout.row()
            row.operator("mhx.pose_viseme", text="Blink").viseme = 'Blink'
            row.operator("mhx.pose_viseme", text="Unblink").viseme = 'Unblink'
            layout.separator()
            layout.operator("mhx.make_visemes")

        layout.separator()
        row = layout.row()
        row.operator("mhx.pose_load_moho")
        #layout.operator("mhx.update")

#
#   updatePose(context):
#   class VIEW3D_OT_MhxUpdateButton(bpy.types.Operator):
#

def updatePose(context):
    scn = context.scene
    scn.frame_current = scn.frame_current
    bpy.ops.object.posemode_toggle()
    bpy.ops.object.posemode_toggle()
    return

class VIEW3D_OT_MhxUpdateButton(bpy.types.Operator):
    bl_idname = "mhx.update"
    bl_label = "Update"

    def execute(self, context):
        updatePose(context)
        return{'FINISHED'}


###################################################################################
#
#    Expression panels
#
###################################################################################

class VIEW3D_OT_MhxResetExpressionsButton(bpy.types.Operator):
    bl_idname = "mhx.pose_reset_expressions"
    bl_label = "Reset expressions"
    bl_options = {'UNDO'}
    prefix = StringProperty()

    def execute(self, context):
        rig,mesh = getMhxRigMesh(context.object)
        shapekeys = getMhmShapekeys(rig, mesh)
        clearMhmProps(rig, shapekeys, self.prefix, context.tool_settings.use_keyframe_insert_auto, context.scene.frame_current)
        updatePose(context)
        return{'FINISHED'}


class VIEW3D_OT_MhxKeyExpressionsButton(bpy.types.Operator):
    bl_idname = "mhx.pose_key_expressions"
    bl_label = "Key expressions"
    bl_options = {'UNDO'}
    prefix = StringProperty()

    def execute(self, context):
        rig,mesh = getMhxRigMesh(context.object)
        props = getProps(rig, self.prefix)
        frame = context.scene.frame_current
        for prop in props:
            rig.keyframe_insert('["%s"]'%prop, frame=frame)
        updatePose(context)
        return{'FINISHED'}


class VIEW3D_OT_MhxPinExpressionButton(bpy.types.Operator):
    bl_idname = "mhx.pose_pin_expression"
    bl_label = "Pin"
    bl_options = {'UNDO'}
    data = StringProperty()

    def execute(self, context):
        rig,mesh = getMhxRigMesh(context.object)
        words = self.data.split(";")
        prefix = words[0]
        expression = words[1]

        props = getProps(rig, prefix)
        if context.tool_settings.use_keyframe_insert_auto:
            frame = context.scene.frame_current
            for prop in props:
                old = rig[prop]
                if prop == expression:
                    rig[prop] = 1.0
                else:
                    rig[prop] = 0.0
                if abs(rig[prop] - old) > 1e-3:
                    rig.keyframe_insert('["%s"]'%prop, frame=frame)
        else:
            for prop in props:
                if prop == expression:
                    rig[prop] = 1.0
                else:
                    rig[prop] = 0.0
        updatePose(context)
        return{'FINISHED'}


def getMhmShapekeys(rig, mesh):
    if rig.MhxShapekeyDrivers:
        return None
    else:
        return mesh.data.shape_keys.key_blocks


def setMhmProps(rig, shapekeys, prefix, units, factor, auto, frame):
    clearMhmProps(rig, shapekeys, prefix, auto, frame)
    for (prop, value) in units:
        if shapekeys:
            skey = prop[3:].replace("_","-")
            shapekeys[skey].value = factor*value
            if auto:
                shapekeys[skey].keyframe_insert("value", frame=frame)
        else:
            rig[prop] = factor*value
            if auto:
                rig.keyframe_insert('["%s"]'%prop, frame=frame)


def clearMhmProps(rig, shapekeys, prefix, auto, frame):
    props = getProps(rig, prefix)
    for prop in props:
        if shapekeys:
            skey = prop[3:].replace("_","-")
            shapekeys[skey].value = 0.0
            if auto:
                shapekeys[skey].keyframe_insert("value", frame=frame)
        else:
            rig[prop] = 0.0
            if auto:
                rig.keyframe_insert('["%s"]'%prop, frame=frame)


def getUnitsFromString(string):
    words = string.split(";")
    prefix = words[0]
    units = []
    for word in words[1:]:
        if word == "":
            continue
        unit = word.split(":")
        prop = "Mhs" + unit[0]
        value = float(unit[1])
        units.append((prop, value))
    return prefix,units


class VIEW3D_OT_MhxMhmButton(bpy.types.Operator):
    bl_idname = "mhx.pose_mhm"
    bl_label = "Mhm"
    bl_options = {'UNDO'}
    data = StringProperty()

    def execute(self, context):
        rig,mesh = getMhxRigMesh(context.object)
        auto = context.tool_settings.use_keyframe_insert_auto
        frame = context.scene.frame_current
        shapekeys = getMhmShapekeys(rig, mesh)
        prefix,units = getUnitsFromString(self.data)
        setMhmProps(rig, shapekeys, prefix, units, rig.MhxStrength, auto, frame)
        updatePose(context)
        return{'FINISHED'}


def getProps(rig, prefix):
    props = []
    for prop in rig.keys():
        if prop.startswith(prefix):
            props.append(prop)
    props.sort()
    return props


def hasProps(ob, prefix):
    if ob is None:
        return False
    if ob.type == 'MESH' and ob.parent:
        rig = ob.parent
    elif ob.type == 'ARMATURE':
        rig = ob
    else:
        return False
    for prop in rig.keys():
        if prop.startswith(prefix):
            return True
    return False


class MhxExpressionsPanel(bpy.types.Panel):
    bl_label = "MHX Expressions"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(cls, context):
        return hasProps(context.object, "Mhe")

    def draw(self, context):
        layout = self.layout
        rig,mesh = getMhxRigMesh(context.object)
        if not rig:
            layout.label("No MHX rig found")
            return
        exprs = getProps(rig, "Mhe")
        if not exprs:
            layout.label("No expressions found")
            return

        layout.operator("mhx.pose_reset_expressions").prefix="Mhs"
        layout.operator("mhx.pose_key_expressions").prefix="Mhs"
        layout.prop(rig, "MhxStrength")
        layout.separator()
        for prop in exprs:
            layout.operator("mhx.pose_mhm", text=prop[3:]).data="Mhs;"+rig[prop]


def drawShapePanel(self, context, prefix, name):
    layout = self.layout
    rig,mesh = getMhxRigMesh(context.object)
    if not rig:
        print("No MHX rig found")
        return
    if not rig.MhxShapekeyDrivers:
        layout.label("No shapekey drivers.")
        layout.label("Set %s values in mesh context instead" % name)
        return
    props = getProps(rig, prefix)
    if not props:
        layout.label("No %ss found" % name)
        return

    layout.operator("mhx.pose_reset_expressions", text="Reset %ss" % name).prefix=prefix
    layout.operator("mhx.pose_key_expressions", text="Key %ss" % name).prefix=prefix
    #layout.operator("mhx.update")

    layout.separator()
    for prop in props:
        row = layout.split(0.85)
        row.prop(rig, '["%s"]' % prop, text=prop[3:])
        row.operator("mhx.pose_pin_expression", text="", icon='UNPINNED').data = (prefix + ";" + prop)
    return


class MhxExpressionUnitsPanel(bpy.types.Panel):
    bl_label = "MHX Expression Tuning"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(cls, context):
        return hasProps(context.object, "Mhs")

    def draw(self, context):
        drawShapePanel(self, context, "Mhs", "expression")


class MhxCustomShapePanel(bpy.types.Panel):
    bl_label = "MHX Custom Shapes"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(cls, context):
        return hasProps(context.object, "Mhc")

    def draw(self, context):
        drawShapePanel(self, context, "Mhc", "custom shape")


#########################################
#
#   FK-IK snapping.
#
#########################################

def getPoseMatrix(gmat, pb):
    restInv = pb.bone.matrix_local.inverted()
    if pb.parent:
        parInv = pb.parent.matrix.inverted()
        parRest = pb.parent.bone.matrix_local
        return restInv * (parRest * (parInv * gmat))
    else:
        return restInv * gmat


def getGlobalMatrix(mat, pb):
    gmat = pb.bone.matrix_local * mat
    if pb.parent:
        parMat = pb.parent.matrix
        parRest = pb.parent.bone.matrix_local
        return parMat * (parRest.inverted() * gmat)
    else:
        return gmat


def matchPoseTranslation(pb, src, auto):
    pmat = getPoseMatrix(src.matrix, pb)
    insertLocation(pb, pmat, auto)


def insertLocation(pb, mat, auto):
    pb.location = mat.to_translation()
    if auto:
        pb.keyframe_insert("location", group=pb.name)
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.mode_set(mode='POSE')


def matchPoseRotation(pb, src, auto):
    pmat = getPoseMatrix(src.matrix, pb)
    insertRotation(pb, pmat, auto)


def printMatrix(string,mat):
    print(string)
    for i in range(4):
        print("    %.4g %.4g %.4g %.4g" % tuple(mat[i]))


def insertRotation(pb, mat, auto):
    q = mat.to_quaternion()
    if pb.rotation_mode == 'QUATERNION':
        pb.rotation_quaternion = q
        if auto:
            pb.keyframe_insert("rotation_quaternion", group=pb.name)
    else:
        pb.rotation_euler = q.to_euler(pb.rotation_mode)
        if auto:
            pb.keyframe_insert("rotation_euler", group=pb.name)
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.mode_set(mode='POSE')


def matchIkLeg(legIk, toeFk, mBall, mToe, mHeel, auto):
    rmat = toeFk.matrix.to_3x3()
    tHead = Vector(toeFk.matrix.col[3][:3])
    ty = rmat.col[1]
    tail = tHead + ty * toeFk.bone.length

    try:
        zBall = mBall.matrix.col[3][2]
    except AttributeError:
        return
    zToe = mToe.matrix.col[3][2]
    zHeel = mHeel.matrix.col[3][2]

    x = Vector(rmat.col[0])
    y = Vector(rmat.col[1])
    z = Vector(rmat.col[2])

    if zHeel > zBall and zHeel > zToe:
        # 1. foot.ik is flat
        if abs(y[2]) > abs(z[2]):
            y = -z
        y[2] = 0
    else:
        # 2. foot.ik starts at heel
        hHead = Vector(mHeel.matrix.col[3][:3])
        y = tail - hHead

    y.normalize()
    x -= x.dot(y)*y
    x.normalize()
    z = x.cross(y)
    head = tail - y * legIk.bone.length

    # Create matrix
    gmat = Matrix()
    gmat.col[0][:3] = x
    gmat.col[1][:3] = y
    gmat.col[2][:3] = z
    gmat.col[3][:3] = head
    pmat = getPoseMatrix(gmat, legIk)

    insertLocation(legIk, pmat, auto)
    insertRotation(legIk, pmat, auto)


def matchPoleTarget(pb, above, below, auto):
    x = Vector(above.matrix.col[1][:3])
    y = Vector(below.matrix.col[1][:3])
    p0 = Vector(below.matrix.col[3][:3])
    n = x.cross(y)
    if abs(n.length) > 1e-4:
        z = x - y
        n = n/n.length
        z -= z.dot(n)*n
        p = p0 + z/z.length*3.0
    else:
        p = p0
    gmat = Matrix.Translation(p)
    pmat = getPoseMatrix(gmat, pb)
    insertLocation(pb, pmat, auto)


def matchPoseReverse(pb, src, auto):
    gmat = src.matrix
    tail = gmat.col[3] + src.length * gmat.col[1]
    rmat = Matrix((gmat.col[0], -gmat.col[1], -gmat.col[2], tail))
    rmat.transpose()
    pmat = getPoseMatrix(rmat, pb)
    pb.matrix_basis = pmat
    insertRotation(pb, pmat, auto)


def matchPoseScale(pb, src, auto):
    pmat = getPoseMatrix(src.matrix, pb)
    pb.scale = pmat.to_scale()
    if auto:
        pb.keyframe_insert("scale", group=pb.name)
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.mode_set(mode='POSE')


def snapFkArm(context, data):
    rig = context.object
    prop,old,suffix = setSnapProp(rig, data, 1.0, context, False)
    auto = context.scene.tool_settings.use_keyframe_insert_auto

    print("Snap FK Arm%s" % suffix)
    snapFk,cnsFk = getSnapBones(rig, "ArmFK", suffix)
    (uparmFk, loarmFk, handFk) = snapFk
    muteConstraints(cnsFk, True)
    snapIk,cnsIk = getSnapBones(rig, "ArmIK", suffix)
    (uparmIk, loarmIk, elbow, elbowPt, handIk) = snapIk

    matchPoseRotation(uparmFk, uparmIk, auto)
    matchPoseScale(uparmFk, uparmIk, auto)

    matchPoseRotation(loarmFk, loarmIk, auto)
    matchPoseScale(loarmFk, loarmIk, auto)

    restoreSnapProp(rig, prop, old, context)

    try:
        matchHand = rig["MhaHandFollowsWrist" + suffix]
    except KeyError:
        matchHand = True
    if matchHand:
        matchPoseRotation(handFk, handIk, auto)
        matchPoseScale(handFk, handIk, auto)

    #muteConstraints(cnsFk, False)
    return


def snapIkArm(context, data):
    rig = context.object
    prop,old,suffix = setSnapProp(rig, data, 0.0, context, True)
    auto = context.scene.tool_settings.use_keyframe_insert_auto

    print("Snap IK Arm%s" % suffix)
    snapIk,cnsIk = getSnapBones(rig, "ArmIK", suffix)
    (uparmIk, loarmIk, elbow, elbowPt, handIk) = snapIk
    snapFk,cnsFk = getSnapBones(rig, "ArmFK", suffix)
    (uparmFk, loarmFk, handFk) = snapFk
    muteConstraints(cnsIk, True)

    matchPoseTranslation(handIk, handFk, auto)
    matchPoseRotation(handIk, handFk, auto)

    matchPoleTarget(elbowPt, uparmFk, loarmFk, auto)

    #matchPoseRotation(uparmIk, uparmFk, auto)
    #matchPoseRotation(loarmIk, loarmFk, auto)

    restoreSnapProp(rig, prop, old, context)
    #muteConstraints(cnsIk, False)
    return


def snapFkLeg(context, data):
    rig = context.object
    prop,old,suffix = setSnapProp(rig, data, 1.0, context, False)
    auto = context.scene.tool_settings.use_keyframe_insert_auto

    print("Snap FK Leg%s" % suffix)
    snap,_ = getSnapBones(rig, "Leg", suffix)
    (upleg, loleg, foot, toe) = snap
    snapIk,cnsIk = getSnapBones(rig, "LegIK", suffix)
    (uplegIk, lolegIk, kneePt, ankleIk, legIk, footRev, toeRev, mBall, mToe, mHeel) = snapIk
    snapFk,cnsFk = getSnapBones(rig, "LegFK", suffix)
    (uplegFk, lolegFk, footFk, toeFk) = snapFk
    muteConstraints(cnsFk, True)

    matchPoseRotation(uplegFk, uplegIk, auto)
    matchPoseScale(uplegFk, uplegIk, auto)

    matchPoseRotation(lolegFk, lolegIk, auto)
    matchPoseScale(lolegFk, lolegIk, auto)

    restoreSnapProp(rig, prop, old, context)

    if not rig["MhaLegIkToAnkle" + suffix]:
        matchPoseReverse(footFk, footRev, auto)
        matchPoseReverse(toeFk, toeRev, auto)

    #muteConstraints(cnsFk, False)
    return


def snapIkLeg(context, data):
    rig = context.object
    scn = context.scene
    prop,old,suffix = setSnapProp(rig, data, 0.0, context, True)
    auto = scn.tool_settings.use_keyframe_insert_auto

    print("Snap IK Leg%s" % suffix)
    snapIk,cnsIk = getSnapBones(rig, "LegIK", suffix)
    (uplegIk, lolegIk, kneePt, ankleIk, legIk, footRev, toeRev, mBall, mToe, mHeel) = snapIk
    snapFk,cnsFk = getSnapBones(rig, "LegFK", suffix)
    (uplegFk, lolegFk, footFk, toeFk) = snapFk
    muteConstraints(cnsIk, True)

    legIkToAnkle = rig["MhaLegIkToAnkle" + suffix]
    if legIkToAnkle:
        matchPoseTranslation(ankleIk, footFk, auto)

    #matchPoseTranslation(legIk, legFk, auto)
    #matchPoseRotation(legIk, legFk, auto)
    matchIkLeg(legIk, toeFk, mBall, mToe, mHeel, auto)

    matchPoseReverse(toeRev, toeFk, auto)
    matchPoseReverse(footRev, footFk, auto)

    matchPoleTarget(kneePt, uplegFk, lolegFk, auto)

    #matchPoseRotation(uplegIk, uplegFk, auto)
    #matchPoseRotation(lolegIk, lolegFk, auto)

    if not legIkToAnkle:
        matchPoseTranslation(ankleIk, footFk, auto)

    restoreSnapProp(rig, prop, old, context)
    #muteConstraints(cnsIk, False)
    return


SnapBonesAlpha8 = {
    "Arm"   : ["upper_arm", "forearm", "hand"],
    "ArmFK" : ["upper_arm.fk", "forearm.fk", "hand.fk"],
    "ArmIK" : ["upper_arm.ik", "forearm.ik", None, "elbow.pt.ik", "hand.ik"],
    "Leg"   : ["thigh", "shin", "foot", "toe"],
    "LegFK" : ["thigh.fk", "shin.fk", "foot.fk", "toe.fk"],
    "LegIK" : ["thigh.ik", "shin.ik", "knee.pt.ik", "ankle.ik", "foot.ik", "foot.rev", "toe.rev", "ball.marker", "toe.marker", "heel.marker"],
}


def getSnapBones(rig, key, suffix):
    try:
        pb = rig.pose.bones["UpLeg_L"]
    except KeyError:
        pb = None

    if pb is not None:
        raise NameError("MakeHuman alpha 7 not supported after Blender 2.68")

    try:
        rig.pose.bones["thigh.fk.L"]
        names = SnapBonesAlpha8[key]
        suffix = '.' + suffix[1:]
    except KeyError:
        names = None

    if not names:
        raise NameError("Not an mhx armature")

    pbones = []
    constraints = []
    for name in names:
        if name:
            try:
                pb = rig.pose.bones[name+suffix]
            except KeyError:
                pb = None
            pbones.append(pb)
            if pb is not None:
                for cns in pb.constraints:
                    if cns.type == 'LIMIT_ROTATION' and not cns.mute:
                        constraints.append(cns)
        else:
            pbones.append(None)
    return tuple(pbones),constraints


def muteConstraints(constraints, value):
    for cns in constraints:
        cns.mute = value


class VIEW3D_OT_MhxSnapFk2IkButton(bpy.types.Operator):
    bl_idname = "mhx.snap_fk_ik"
    bl_label = "Snap FK"
    bl_options = {'UNDO'}
    data = StringProperty()

    def execute(self, context):
        bpy.ops.object.mode_set(mode='POSE')
        rig = context.object
        if rig.MhxSnapExact:
            rig["MhaRotationLimits"] = 0.0
        if self.data[:6] == "MhaArm":
            snapFkArm(context, self.data)
        elif self.data[:6] == "MhaLeg":
            snapFkLeg(context, self.data)
        return{'FINISHED'}


class VIEW3D_OT_MhxSnapIk2FkButton(bpy.types.Operator):
    bl_idname = "mhx.snap_ik_fk"
    bl_label = "Snap IK"
    bl_options = {'UNDO'}
    data = StringProperty()

    def execute(self, context):
        bpy.ops.object.mode_set(mode='POSE')
        rig = context.object
        if rig.MhxSnapExact:
            rig["MhaRotationLimits"] = 0.0
        if self.data[:6] == "MhaArm":
            snapIkArm(context, self.data)
        elif self.data[:6] == "MhaLeg":
            snapIkLeg(context, self.data)
        return{'FINISHED'}


def setSnapProp(rig, data, value, context, isIk):
    words = data.split()
    prop = words[0]
    oldValue = rig[prop]
    rig[prop] = value
    ik = int(words[1])
    fk = int(words[2])
    extra = int(words[3])
    oldIk = rig.data.layers[ik]
    oldFk = rig.data.layers[fk]
    oldExtra = rig.data.layers[extra]
    rig.data.layers[ik] = True
    rig.data.layers[fk] = True
    rig.data.layers[extra] = True
    updatePose(context)
    if isIk:
        oldValue = 1.0
        oldIk = True
        oldFk = False
    else:
        oldValue = 0.0
        oldIk = False
        oldFk = True
        oldExtra = False
    return (prop, (oldValue, ik, fk, extra, oldIk, oldFk, oldExtra), prop[-2:])


def restoreSnapProp(rig, prop, old, context):
    updatePose(context)
    (oldValue, ik, fk, extra, oldIk, oldFk, oldExtra) = old
    rig[prop] = oldValue
    rig.data.layers[ik] = oldIk
    rig.data.layers[fk] = oldFk
    rig.data.layers[extra] = oldExtra
    updatePose(context)
    return


class VIEW3D_OT_MhxToggleFkIkButton(bpy.types.Operator):
    bl_idname = "mhx.toggle_fk_ik"
    bl_label = "FK - IK"
    bl_options = {'UNDO'}
    toggle = StringProperty()

    def execute(self, context):
        words = self.toggle.split()
        rig = context.object
        prop = words[0]
        value = float(words[1])
        onLayer = int(words[2])
        offLayer = int(words[3])
        rig.data.layers[onLayer] = True
        rig.data.layers[offLayer] = False
        rig[prop] = value
        # Don't do autokey - confusing.
        #if context.tool_settings.use_keyframe_insert_auto:
        #    rig.keyframe_insert('["%s"]' % prop, frame=scn.frame_current)
        updatePose(context)
        return{'FINISHED'}

#
#   MHX FK/IK Switch panel
#

class MhxFKIKPanel(bpy.types.Panel):
    bl_label = "MHX FK/IK Switch"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    #bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(cls, context):
        return (context.object and context.object.MhxRig == 'MHX')

    def draw(self, context):
        rig = context.object
        layout = self.layout

        row = layout.row()
        row.label("")
        row.label("Left")
        row.label("Right")

        layout.label("FK/IK switch")
        row = layout.row()
        row.label("Arm")
        self.toggleButton(row, rig, "MhaArmIk_L", " 3", " 2")
        self.toggleButton(row, rig, "MhaArmIk_R", " 19", " 18")
        row = layout.row()
        row.label("Leg")
        self.toggleButton(row, rig, "MhaLegIk_L", " 5", " 4")
        self.toggleButton(row, rig, "MhaLegIk_R", " 21", " 20")

        layout.label("IK Influence")
        row = layout.row()
        row.label("Arm")
        row.prop(rig, '["MhaArmIk_L"]', text="")
        row.prop(rig, '["MhaArmIk_R"]', text="")
        row = layout.row()
        row.label("Leg")
        row.prop(rig, '["MhaLegIk_L"]', text="")
        row.prop(rig, '["MhaLegIk_R"]', text="")

        try:
            ok = (rig["MhxVersion"] >= 12)
        except:
            ok = False
        if not ok:
            layout.label("Snapping only works with MHX version 1.12 and later.")
            return

        layout.separator()
        layout.label("Snapping")
        row = layout.row()
        row.label("Rotation Limits")
        row.prop(rig, '["MhaRotationLimits"]', text="")
        row.prop(rig, "MhxSnapExact", text="Exact Snapping")

        layout.label("Snap Arm bones")
        row = layout.row()
        row.label("FK Arm")
        row.operator("mhx.snap_fk_ik", text="Snap L FK Arm").data = "MhaArmIk_L 2 3 12"
        row.operator("mhx.snap_fk_ik", text="Snap R FK Arm").data = "MhaArmIk_R 18 19 28"
        row = layout.row()
        row.label("IK Arm")
        row.operator("mhx.snap_ik_fk", text="Snap L IK Arm").data = "MhaArmIk_L 2 3 12"
        row.operator("mhx.snap_ik_fk", text="Snap R IK Arm").data = "MhaArmIk_R 18 19 28"

        layout.label("Snap Leg bones")
        row = layout.row()
        row.label("FK Leg")
        row.operator("mhx.snap_fk_ik", text="Snap L FK Leg").data = "MhaLegIk_L 4 5 12"
        row.operator("mhx.snap_fk_ik", text="Snap R FK Leg").data = "MhaLegIk_R 20 21 28"
        row = layout.row()
        row.label("IK Leg")
        row.operator("mhx.snap_ik_fk", text="Snap L IK Leg").data = "MhaLegIk_L 4 5 12"
        row.operator("mhx.snap_ik_fk", text="Snap R IK Leg").data = "MhaLegIk_R 20 21 28"


    def toggleButton(self, row, rig, prop, fk, ik):
        if rig[prop] > 0.5:
            row.operator("mhx.toggle_fk_ik", text="IK").toggle = prop + " 0" + fk + ik
        else:
            row.operator("mhx.toggle_fk_ik", text="FK").toggle = prop + " 1" + ik + fk


###################################################################################
#
#    Posing panel
#
###################################################################################
#
#    class MhxDriversPanel(bpy.types.Panel):
#

class MhxDriversPanel(bpy.types.Panel):
    bl_label = "MHX Drivers"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(cls, context):
        return (context.object and context.object.MhxRig == 'MHX')

    def draw(self, context):
        lrProps = []
        props = []
        lrFaceProps = []
        faceProps = []
        plist = list(context.object.keys())
        plist.sort()
        for prop in plist:
            if prop[0:3] == 'Mha':
                if prop[-2:] == '_L':
                    lrProps.append(prop[:-2])
                elif prop[-2:] != '_R':
                    props.append(prop)
            elif prop[0:3] == 'Mhf':
                if prop[-2:] == '_L':
                    lrFaceProps.append(prop[:-2])
                elif prop[-2:] != '_R':
                    faceProps.append(prop)

        ob = context.object
        layout = self.layout
        for prop in props:
            layout.prop(ob, '["%s"]' % prop, text=prop[3:])

        layout.separator()
        row = layout.row()
        row.label("Left")
        row.label("Right")
        for prop in lrProps:
            row = layout.row()
            row.prop(ob, '["%s"]' % (prop+"_L"), text=prop[3:])
            row.prop(ob, '["%s"]' % (prop+"_R"), text=prop[3:])

        if faceProps:
            layout.separator()
            layout.label("Face shapes")
            for prop in faceProps:
                layout.prop(ob, '["%s"]' % prop, text=prop[3:])

            layout.separator()
            row = layout.row()
            row.label("Left")
            row.label("Right")
            for prop in lrFaceProps:
                row = layout.row()
                row.prop(ob, '["%s"]' % (prop+"_L"), text=prop[3:])
                row.prop(ob, '["%s"]' % (prop+"_R"), text=prop[3:])

        return

###################################################################################
#
#    Visibility panel
#
###################################################################################
#
#    class MhxVisibilityPanel(bpy.types.Panel):
#

class MhxVisibilityPanel(bpy.types.Panel):
    bl_label = "MHX Visibility"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(cls, context):
        return (context.object and context.object.MhxRig)

    def draw(self, context):
        ob = context.object
        layout = self.layout
        props = list(ob.keys())
        props.sort()
        for prop in props:
            if prop[0:3] == "Mhh":
                layout.prop(ob, '["%s"]' % prop, text="Hide %s" % prop[3:])
        layout.separator()
        layout.operator("mhx.update_textures")
        layout.separator()
        layout.operator("mhx.add_hiders")
        layout.operator("mhx.remove_hiders")
        return

class VIEW3D_OT_MhxUpdateTexturesButton(bpy.types.Operator):
    bl_idname = "mhx.update_textures"
    bl_label = "Update"
    bl_options = {'UNDO'}

    def execute(self, context):
        scn = context.scene
        for mat in bpy.data.materials:
            if mat.animation_data:
                try:
                    mat["MhxDriven"]
                except:
                    continue
                for driver in mat.animation_data.drivers:
                    prop = mat.path_resolve(driver.data_path)
                    value = driver.evaluate(scn.frame_current)
                    prop[driver.array_index] = value
        return{'FINISHED'}

class VIEW3D_OT_MhxAddHidersButton(bpy.types.Operator):
    bl_idname = "mhx.add_hiders"
    bl_label = "Add Hide Property"
    bl_options = {'UNDO'}

    def execute(self, context):
        rig = context.object
        for ob in context.scene.objects:
            if ob.select and ob != rig:
                prop = "Mhh%s" % ob.name
                defNewProp(prop, "Bool", "default=False")
                rig[prop] = False
                addHider(ob, "hide", rig, prop)
                addHider(ob, "hide_render", rig, prop)
        return{'FINISHED'}

def addHider(ob, attr, rig, prop):
    fcu = ob.driver_add(attr)
    drv = fcu.driver
    drv.type = 'SCRIPTED'
    drv.expression = "x"
    drv.show_debug_info = True
    var = drv.variables.new()
    var.name = "x"
    targ = var.targets[0]
    targ.id = rig
    targ.data_path = '["%s"]' % prop
    return

class VIEW3D_OT_MhxRemoveHidersButton(bpy.types.Operator):
    bl_idname = "mhx.remove_hiders"
    bl_label = "Remove Hide Property"
    bl_options = {'UNDO'}

    def execute(self, context):
        rig = context.object
        for ob in context.scene.objects:
            if ob.select and ob != rig:
                ob.driver_remove("hide")
                ob.driver_remove("hide_render")
                del rig["Mhh%s" % ob.name]
        return{'FINISHED'}

###################################################################################
#
#    Common functions
#
###################################################################################
#
#   getMhxRigMesh(ob):
#

def pollMhx(ob):
    if not ob:
        return False
    elif ob.type == 'ARMATURE':
        return ob.MhxRig
    elif ob.type == 'MESH':
        par = ob.parent
        return (par and (par.type == 'ARMATURE') and par.MhxRig)
    else:
        return False

def getMhxRigMesh(ob):
    if ob.type == 'ARMATURE':
        for mesh in ob.children:
            if mesh.MhxMesh and ob.MhxRig:
                return (ob, mesh)
        return (ob, None)
    elif ob.type == 'MESH':
        par = ob.parent
        if (par and par.type == 'ARMATURE' and par.MhxRig):
            if ob.MhxMesh:
                return (par, ob)
            else:
                return (par, None)
        else:
            return (None, None)
    return (None, None)


#
#    setInterpolation(rig):
#

def setInterpolation(rig):
    if not rig.animation_data:
        return
    act = rig.animation_data.action
    if not act:
        return
    for fcu in act.fcurves:
        for pt in fcu.keyframe_points:
            pt.interpolation = 'LINEAR'
        fcu.extrapolation = 'CONSTANT'
    return

###################################################################################
#
#    initialize and register
#
###################################################################################

def menu_func(self, context):
    self.layout.operator(ImportMhx.bl_idname, text="MakeHuman (.mhx)...")

def register():
    bpy.types.Object.MhxVersionStr = StringProperty(name="Version", default="", maxlen=128)
    bpy.types.Object.MhAlpha8 = BoolProperty(default=True)
    bpy.types.Object.MhxMesh = BoolProperty(default=False)
    bpy.types.Object.MhxRig = StringProperty(default="")
    bpy.types.Object.MhxVisemeSet = StringProperty(default="")
    bpy.types.Object.MhxRigify = BoolProperty(default=False)
    bpy.types.Object.MhxSnapExact = BoolProperty(default=False)
    bpy.types.Object.MhxShapekeyDrivers = BoolProperty(default=True)
    bpy.types.Object.MhxStrength = FloatProperty(
        name = "Expression strength",
        description = "Multiply expression with this factor",
        default=1.0, min=-1.0, max=2.0
        )
    bpy.utils.register_module(__name__)
    bpy.types.INFO_MT_file_import.append(menu_func)

def unregister():
    try:
        bpy.utils.unregister_module(__name__)
    except:
        pass
    try:
        bpy.types.INFO_MT_file_import.remove(menu_func)
    except:
        pass

if __name__ == "__main__":
    unregister()
    register()





