Skip to content
Snippets Groups Projects
Select Git revision
  • ca74197440127e56c9f6f2a277c30957f34fd07c
  • master default protected
  • blender-v3.6-release
  • main
  • blender-v4.1-release
  • blender-v4.0-release
  • blender-v3.3-release
  • asset-shelf
  • blender-v3.5-release
  • brush-assets-project
  • blender-v2.93-release
  • blender-v3.4-release
  • xr-dev
  • bholodeck-v3.3
  • blender-v3.2-release
  • temp-xr-tracker
  • blender-v3.1-release
  • screenshots-manual
  • gltf_vtree
  • blender-v2.83-release
  • blender-v3.0-release
  • v3.6.18
  • v3.6.19
  • v3.6.20
  • v3.6.21
  • v3.6.22
  • v3.6.23
  • v4.1.1
  • v4.1.0
  • v3.6.10
  • v3.6.11
  • v3.6.12
  • v3.6.13
  • v3.6.14
  • v3.6.15
  • v3.6.16
  • v3.6.17
  • v3.6.9
  • v3.3.16
  • v3.6.8
  • v3.3.15
41 results

rig_lists.py

Blame
  • io_import_scene_mhx.py 67.55 KiB
    """ 
    **Project Name:**      MakeHuman
    
    **Product Home Page:** http://www.makehuman.org/
    
    **Code Home Page:**    http://code.google.com/p/makehuman/
    
    **Authors:**           Thomas Larsson
    
    **Copyright(c):**      MakeHuman Team 2001-2010
    
    **Licensing:**         GPL3 (see also http://sites.google.com/site/makehumandocs/licensing)
    
    **Coding Standards:**  See http://sites.google.com/site/makehumandocs/developers-guide
    
    Abstract
    MHX (MakeHuman eXchange format) importer for Blender 2.5x.
    Version 1.0.3
    
    """
    
    bl_addon_info = {
        'name': 'Import: MakeHuman (.mhx)',
        'author': 'Thomas Larsson',
        'version': (1, 0, 3),
        'blender': (2, 5, 5),
        'api': 33590,
        'location': "File > Import",
        'description': 'Import files in the MakeHuman eXchange format (.mhx)',
        'warning': '',
        'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/'\
            'Scripts/Import-Export/Make_Human',
        'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
            'func=detail&aid=21872&group_id=153&atid=469',
        'category': 'Import-Export'}
    
    """
    Place this file in the .blender/scripts/addons dir
    You have to activated the script in the "Add-Ons" tab (user preferences).
    Access from the File > Import menu.
    """
    
    MAJOR_VERSION = 1
    MINOR_VERSION = 0
    SUB_VERSION = 3
    BLENDER_VERSION = (2, 55, 1)
    
    #
    #
    #
    
    import bpy
    import os
    import time
    import mathutils
    from mathutils import *
    #import geometry
    #import string
    
    MHX249 = False
    Blender24 = False
    Blender25 = True
    TexDir = "~/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
    
    todo = []
    
    #
    #    toggle flags
    #
    
    T_EnforceVersion = 0x01
    T_Clothes = 0x02
    T_Stretch = 0x04
    T_Bend = 0x08
    
    T_Diamond = 0x10
    T_Replace = 0x20
    T_Face = 0x40
    T_Shape = 0x80
    
    T_Mesh = 0x100
    T_Armature = 0x200
    T_Proxy = 0x400
    T_Cage = 0x800
    
    T_Rigify = 0x1000
    T_Preset = 0x2000
    T_Symm = 0x4000
    T_MHX = 0x8000
    
    toggle = T_EnforceVersion + T_Replace + T_Mesh + T_Armature + T_Face + T_Shape + T_Proxy + T_Clothes
    
    #
    #    setFlagsAndFloats(rigFlags):
    #
    #    Global floats
    #fFingerPanel = 0.0
    #fFingerIK = 0.0
    fNoStretch = 0.0
    
    #    rigLeg and rigArm flags
    T_Toes = 0x0001
    #T_GoboFoot = 0x0002
    
    #T_InvFoot = 0x0010
    #T_InvFootPT = 0x0020
    #T_InvFootNoPT = 0x0040
    
    #T_FingerPanel = 0x100
    #T_FingerRot = 0x0200
    #T_FingerIK = 0x0400
    
    
    #T_LocalFKIK = 0x8000
    
    #rigLeg = 0
    #rigArm = 0
    
    def setFlagsAndFloats():
        '''
        global toggle, rigLeg, rigArm
    
        (footRig, fingerRig) = rigFlags
        rigLeg = 0
        if footRig == 'Reverse foot': 
            rigLeg |= T_InvFoot
            if toggle & T_PoleTar:
                rigLeg |= T_InvFootPT
            else:
                rigLeg |= T_InvFootNoPT
        elif footRig == 'Gobo': rigLeg |= T_GoboFoot        
    
        rigArm = 0
        if fingerRig == 'Panel': rigArm |= T_FingerPanel
        elif fingerRig == 'Rotation': rigArm |= T_FingerRot
        elif fingerRig == 'IK': rigArm |= T_FingerIK
    
        toggle |= T_Panel
        '''
        global fNoStretch
        if toggle&T_Stretch: fNoStretch == 0.0
        else: fNoStretch = 1.0
    
        return
    
    
    #
    #    Dictionaries
    #
    
    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' : {},
    }
    
    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',
    }
    
    #
    #    checkBlenderVersion()
    #
    
    def checkBlenderVersion():
        print("Found Blender", bpy.app.version)
        (A, B, C) = bpy.app.version
        (a, b, c) = BLENDER_VERSION
        if a <= A: return
        if b <= B: return
        if c <= C: return
        msg = (
    "This version of the MHX importer only works with Blender (%d, %d, %d) or later. " % (a, b, c) +
    "Download a more recent Blender from www.blender.org or www.graphicall.org.\n"
        )
        raise NameError(msg)
        return
    
    #
    #    readMhxFile(filePath, scale):
    #
    
    def readMhxFile(filePath, scale):
        global todo, nErrors, theScale, defaultScale, One, toggle
    
        checkBlenderVersion()    
        
        theScale = scale
        defaultScale = scale
        One = 1.0/theScale
    
        fileName = os.path.expanduser(filePath)
        (shortName, ext) = os.path.splitext(fileName)
        if ext.lower() != ".mhx":
            print("Error: Not a mhx file: " + fileName)
            return
        print( "Opening MHX file "+ fileName )
        time1 = time.clock()
    
        ignore = False
        stack = []
        tokens = []
        key = "toplevel"
        level = 0
        nErrors = 0
        comment = 0
        nesting = 0
    
        setFlagsAndFloats()
    
        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 = eval(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" % lineNo )
                    print( line )
                    dummy = 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:
            raise NameError("Tokenizer out of kilter %d" % level)    
        clearScene()
        print( "Parsing" )
        parse(tokens)
        
        for (expr, glbals, lcals) in todo:
            try:
                print("Doing %s" % expr)
                exec(expr, glbals, lcals)
            except:
                msg = "Failed: "+expr
                print( msg )
                nErrors += 1
                #raise NameError(msg)
    
        print("Postprocess")
        postProcess()
        print("HideLayers")
        hideLayers()
        time2 = time.clock()
        print("toggle = %x" % toggle)
        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, glbals, lcals):
    #
    
    def getObject(name, var, glbals, lcals):
        try:
            ob = loadedData['Object'][name]
        except:
            if name != "None":
                pushOnTodoList(None, "ob = loadedData['Object'][name]" % globals(), locals())
            ob = None
        return ob
    
    #
    #    checkMhxVersion(major, minor):
    #
    
    def checkMhxVersion(major, minor):
        global warnedVersion
        if  major != MAJOR_VERSION or minor != MINOR_VERSION:
            if warnedVersion:
                return
            else:
                msg = (
    "Wrong MHX version\n" +
    "Expected MHX %d.%d but the loaded file has version MHX %d.%d\n" % (MAJOR_VERSION, MINOR_VERSION, major, minor) +
    "You can disable this error message by deselecting the Enforce version option when importing. " +
    "Alternatively, you can try to download the most recent nightly build from www.makehuman.org. " +
    "The current version of the import script is located in the importers/mhx/blender25x folder and is called import_scene_mhx.py. " +
    "The version distributed with Blender builds from www.graphicall.org may be out of date.\n"
    )
            if toggle & T_EnforceVersion:
                raise NameError(msg)
            else:
                print(msg)
                warnedVersion = True
        return
    
    #
    #    parse(tokens):
    #
    
    ifResult = False
    
    def parse(tokens):
        global MHX249, ifResult, theScale, defaultScale, One
        
        for (key, val, sub) in tokens:    
            # print("Parse %s" % key)
            data = None
            if key == 'MHX':
                checkMhxVersion(int(val[0]), int(val[1]))
            elif key == 'MHX249':
                MHX249 = eval(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)
                raise NameError(msg)    
            elif key == 'NoScale':
                if eval(val[0]):
                    theScale = 1.0
                else:
                    theScale = defaultScale        
                One = 1.0/theScale
            elif key == "Object":
                parseObject(val, sub)
            elif key == "Mesh":
                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 == "Process":
                parseProcess(val, sub)
            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])]
                    print("matanim", ob, mat)
                    parseAnimationData(mat, val, sub)
            elif key == 'ShapeKeys':
                try:
                    ob = loadedData['Object'][val[0]]
                except:
                    raise NameError("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)                
    
            if data and key != 'Mesh':
                print( data )
        return
    
    #
    #    parseDefaultType(typ, args, tokens):
    #
    
    def parseDefaultType(typ, args, tokens):
        global todo
    
        name = args[0]
        data = None
        expr = "bpy.data.%s.new('%s')" % (Plural[typ], name)
        print(expr)
        data = eval(expr)
        print("  ok", data)
    
        bpyType = typ.capitalize()
        print(bpyType, name, data)
        loadedData[bpyType][name] = data
        if data == None:
            return None
    
        for (key, val, sub) in tokens:
            #print("%s %s" % (key, val))
            defaultKey(key, val, sub, 'data', [], globals(), locals())
        print("Done ", 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 == 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', [], globals(), locals())
        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 = eval(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]
        # print(expr, channel, index)
        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)
                    #raise NameError("kp", fcu, n, len(fcu.keyframe_points), val)
            else:
                defaultKey(key, val, sub, 'fcu', [], globals(), locals())
        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 eval(args[1]):
            return
        if rna.animation_data == 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', [], globals(), locals())
        return adata
    
    def parseAnimDataFCurve(adata, rna, args, tokens):
        if invalid(args[2]):
            return
        dataPath = args[0]
        index = int(args[1])
        # print("parseAnimDataFCurve", adata, dataPath, index)
        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.add(n, 0)
                pt.interpolation = 'LINEAR'
                pt = parseKeyFramePoint(pt, val, sub)
                n += 1
            else:
                defaultKey(key, val, sub, 'fcu', [], globals(), locals())
        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]
            # print("prop", expr, prop)
            bone = eval(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
        
        #print("expr", rna, expr)
        fcu = eval(expr)
        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', [], globals(), locals())
        return fcu
    
    def parseDriverVariable(drv, rna, args, tokens):
        var = drv.variables.new()
        var.name = args[0]
        var.type = args[1]
        nTarget = 0
        # print("var", var, var.name, var.type)
        for (key, val, sub) in tokens:
            if key == 'Target':
                parseDriverTarget(var, nTarget, rna, val, sub)
                nTarget += 1
            else:
                defaultKey(key, val, sub, 'var', [], globals(), locals())
        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', [], globals(), locals())
        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]
        targ.id = loadedData['Object'][args[0]]
        for (key, val, sub) in tokens:
            defaultKey(key, val, sub, 'targ', [], globals(), locals())
        return targ
    
        
    #
    #    parseMaterial(args, ext, tokens):
    #    parseMTex(mat, args, tokens):
    #    parseTexture(args, tokens):
    #
    
    def parseMaterial(args, tokens):
        global todo
        name = args[0]
        mat = bpy.data.materials.new(name)
        if mat == 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)
            else:
                exclude = ['specular_intensity', 'tangent_shading']
                defaultKey(key, val, sub, 'mat', [], globals(), locals())
        
        return mat
    
    def parseMTex(mat, args, tokens):
        global todo
        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", [], globals(), locals())
    
        return mtex
    
    def parseTexture(args, tokens):
        global todo
        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'], globals(), locals())
    
        return tex
    
    def parseRamp(data, args, tokens):
        nvar = "data.%s" % args[0]
        use = "data.use_%s = True" % args[0]
        exec(use)
        ramp = eval(nvar)
        elts = ramp.elements
        n = 0
        for (key, val, sub) in tokens:
            # print("Ramp", key, val)
            if key == 'Element':
                elts[n].color = eval(val[0])
                elts[n].position = eval(val[1])
                n += 1
            else:
                defaultKey(key, val,  sub, "tex", ['use_nodes', 'use_textures', 'contrast'], globals(), locals())
        
    def parseSSS(mat, args, tokens):
        sss = mat.subsurface_scattering
        for (key, val, sub) in tokens:
            defaultKey(key, val, sub, "sss", [], globals(), locals())
    
    def parseStrand(mat, args, tokens):
        strand = mat.strand
        for (key, val, sub) in tokens:
            defaultKey(key, val, sub, "strand", [], globals(), locals())
    
    #
    #    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", [], globals(), locals())
    
    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", [], globals(), locals())
    
    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", [], globals(), locals())
    
    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", [], globals(), locals())
    
    
    
    #
    #    doLoadImage(filepath):
    #    loadImage(filepath):
    #    parseImage(args, tokens):
    #
    
    def doLoadImage(filepath):        
        path1 = os.path.expanduser(filepath)
        file1 = os.path.realpath(path1)
        if os.path.isfile(file1):
            print( "Found file "+file1 )
            try:
                img = bpy.data.images.load(file1)
                return img
            except:
                print( "Cannot read image" )
                return None
        else:
            print( "No file "+file1 )
            return None
    
    
    def loadImage(filepath):
        global TexDir, warnedTextureDir, loadedData
    
        texDir = os.path.expanduser(TexDir)
        path1 = os.path.expanduser(filepath)
        file1 = os.path.realpath(path1)
        (path, filename) = os.path.split(file1)
        (name, ext) = os.path.splitext(filename)
        print( "Loading ", filepath, " = ", filename )
    
        # img = doLoadImage(texDir+"/"+name+".png")
        # if img:
        #    return img
    
        img = doLoadImage(texDir+"/"+filename)
        if img:
            return img
    
        # img = doLoadImage(path+"/"+name+".png")
        # if img:
        #    return img
    
        img = doLoadImage(path+"/"+filename)
        if img:
            return img
    
        if warnedTextureDir:
            return None
        warnedTextureDir = True
        return None
        TexDir = Draw.PupStrInput("TexDir? ", path, 100)
    
        texDir = os.path.expanduser(TexDir)
        img =  doLoadImage(texDir+"/"+name+".png")
        if img:
            return img
    
        img = doLoadImage(TexDir+"/"+filename)
        return img
        
    def parseImage(args, tokens):
        global todo
        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 == None:
                    return None
                img.name = imgName
            else:
                defaultKey(key, val,  sub, "img", ['depth', 'dirty', 'has_data', 'size', 'type'], globals(), locals())
        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):
        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:
                raise NameError("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 == None:
            print("Create", name, data, datName)
            ob = createObject(typ, name, data, datName)
            print("created", ob)
            linkObject(ob, data)
    
        for (key, val, sub) in tokens:
            if key == 'Modifier':
                parseModifier(ob, val, sub)
            elif key == 'Constraint':
                parseConstraint(ob.constraints, 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'], globals(), locals())
    
        # Needed for updating layers
        if bpy.context.object == ob:
            pass
            '''
            if ob.data in ['MESH', 'ARMATURE']:
                print(ob, ob.data)
                bpy.ops.object.mode_set(mode='EDIT')
                bpy.ops.object.mode_set(mode='OBJECT')
            '''
        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 == None:
            ob.data = data
            print("Data linked", ob, ob.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:
            defaultKey(key, val, sub, 'mod', [], globals(), locals())
        return mod
    
    #
    #    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', [], globals(), locals())
        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', [], globals(), locals())
        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 = eval(val[0])
                h.time = int(val[1])
                h.weight = float(val[2])
                n += 1
            elif key == 'location':
                par.location = eval(val[0])
        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 todo
        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)
            
        mats = []
        for (key, val, sub) in tokens:
            if key == 'Verts' or key == 'Edges' or key == 'Faces':
                pass
            elif key == 'MeshTextureFaceLayer':
                parseUvTexture(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", [], globals(), locals())
    
        for (key, val, sub) in tokens:
            if key == 'Faces':
                parseFaces2(sub, me)
    
        for n,mat in enumerate(list(me.materials)):
            print(n, mat)
        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 parseFaces2(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 == '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 parseUvTexture(args, tokens, me):
        name = args[0]
        me.uv_textures.new(name = name)
        uvtex = me.uv_textures[-1]
        loadedData['MeshTextureFaceLayer'][name] = uvtex
        for (key, val, sub) in tokens:
            if key == 'Data':
                parseUvTexData(val, sub, uvtex.data)
            else:
                defaultKey(key, val,  sub, "uvtex", [], globals(), locals())
        return
    
    def parseUvTexData(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    
            else:
                pass
                #for i in range(n):
                #    defaultKey(key, val,  sub, "data[i]", [], globals(), locals())
        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", [], globals(), locals())
        return
    
    def parseVertColorData(args, tokens, data):
        n = 0
        for (key, val, sub) in tokens:
            if key == 'cv':
                data[n].color1 = eval(val[0])
                data[n].color2 = eval(val[1])
                data[n].color3 = eval(val[2])
                data[n].color4 = eval(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 = eval(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']):
            group = ob.vertex_groups.new(grpName)
            loadedData['VertexGroup'][grpName] = group
            for (key, val, sub) in tokens:
                if key == 'wv':
                    ob.vertex_groups.assign( [int(val[0])], group, 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_Shape+T_Face) 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)
        ob.active_shape_key_index = 0
        return
    
    
    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:
            raise NameError("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", [], globals(), locals())
    
        return    
    
        
    #
    #    parseArmature (obName, args, tokens)
    #
    
    def parseArmature (args, tokens):
        global toggle
        if verbosity > 2:
            print( "Parsing armature %s" % args )
        
        amtname = args[0]
        obname = args[1]
        mode = args[2]
        
        if mode == 'Rigify':
            toggle |= T_Rigify
            return parseRigify(amtname, obname, tokens)
    
        toggle &= ~T_Rigify
        amt = bpy.data.armatures.new(amtname)
        ob = createObject('ARMATURE', obname, amt, amtname)    
    
        linkObject(ob, amt)
        print("Linked")
    
        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
            else:
                defaultKey(key, val,  sub, "amt", ['MetaRig'], globals(), locals())
        bpy.ops.object.mode_set(mode='OBJECT')
        return amt
    
    #
    #    parseRigify(amtname, obname, tokens):        
    #
    
    def parseRigify(amtname, obname, tokens):        
        (key,val,sub) = tokens[0]
        if key != 'MetaRig':
            raise NameError("Expected MetaRig")
        typ = val[0]
        if typ == "human":
            bpy.ops.object.armature_human_advanced_add()
        else:
            bpy.ops.pose.metarig_sample_add(type = typ)
        ob = bpy.context.scene.objects.active
        amt = ob.data
        loadedData['Rigify'][obname] = ob
        loadedData['Armature'][amtname] = amt
        loadedData['Object'][obname] = ob
        print("Rigify object", ob, amt)
    
        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.mode_set(mode='EDIT')
    
        heads = {}
        tails = {}
        for (bname, bone) in amt.edit_bones.items():
            heads[bname] = 10*theScale*bone.head
            tails[bname] = 10*theScale*bone.tail
    
        for (key, val, sub) in tokens:
            if key == 'Bone':
                bname = val[0]
                print("Bone", bname)
                try:
                    bone = amt.edit_bones[bname]
                except:
                    print("Did not find bone %s" % bname)
                    bone = None
                print(" -> ", bone)
                if bone:
                    parseBone(bone, amt, sub, heads, tails)
            else:
                defaultKey(key, val,  sub, "amt", ['MetaRig'], globals(), locals())
        bpy.ops.object.mode_set(mode='OBJECT')
        return amt
            
    #
    #    parseBone(bone, amt, tokens, heads, tails):
    #
    
    def parseBone(bone, amt, tokens, heads, tails):
        global todo
    
        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
                '''
                #bpy.ops.object.mode_set(mode='OBJECT')
                pbone = amt.bones[name]
                pbone.hide = True
                print("Hide", pbone, pbone.hide)
                #bpy.ops.object.mode_set(mode='EDIT')            
                '''
            else:
                defaultKey(key, val,  sub, "bone", [], globals(), locals())
    
        return bone
    
    #
    #    parsePose (args, tokens):
    #
    
    def parsePose (args, tokens):
        global todo
        if toggle & T_Rigify:
            return
        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 = eval(val[2])
                pb = pbones[bone]
                print("Setting", pb, prop, val)
                pb[prop] = value
                print("Prop set", pb[prop])
            else:
                defaultKey(key, val,  sub, "ob.pose", [], globals(), locals())
        bpy.ops.object.mode_set(mode='OBJECT')
        return ob
    
    
    #
    #    parsePoseBone(pbones, args, tokens):
    #    parseArray(data, exts, args):
    #
    
    def parseBoneGroup(pose, nGrps, args, tokens):
        global todo
        print( "Parsing bonegroup %s" % args )
        name = args[0]
        bpy.ops.pose.group_add()
        print(dir(pose.bone_groups))
        bg = pose.bone_groups.active
        print("Created", bg)
        loadedData['BoneGroup'][name] = bg
        for (key, val, sub) in tokens:
            defaultKey(key, val,  sub, "bg", [], globals(), locals())
        return
    
    def parsePoseBone(pbones, ob, args, tokens):
        global todo
        if invalid(args[1]):
            return
        name = args[0]
        pb = pbones[name]
        amt = ob.data
    
        # Make posebone active - don't know how to do this in pose mode
        bpy.ops.object.mode_set(mode='OBJECT')
        amt.bones.active = amt.bones[name]
        bpy.ops.object.mode_set(mode='POSE')
    
        for (key, val, sub) in tokens:
            if key == 'Constraint':
                cns = parseConstraint(pb.constraints, val, sub)
            elif key == 'bpyops':
                bpy.ops.object.mode_set(mode='OBJECT')
                amt.bones.active = amt.bones[name]
                ob.constraints.active = cns            
                expr = "bpy.ops.%s" % val[0]
                # print(expr)
                exec(expr)
                bpy.ops.object.mode_set(mode='POSE')
            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 = eval(val[0])
                #bpy.ops.object.mode_set(mode='POSE')
                
            else:
                defaultKey(key, val,  sub, "pb", [], globals(), locals())
        #print("pb %s done" % name)
        return
    
    def parseArray(data, exts, args):
        n = 1
        for ext in exts:
            expr = "data.%s = %s" % (ext, args[n])
            # print(expr)
            exec(expr)
            n += 1
        return
            
    #
    #    parseConstraint(constraints, args, tokens)
    #
    
    def parseConstraint(constraints, args, tokens):
        if invalid(args[2]):
            return None
        cns = constraints.new(args[1])
        #bpy.ops.pose.constraint_add(type=args[1])
        #cns = pb.constraints[-1]
    
        cns.name = args[0]
        #print("cns", cns.name)
        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", [], globals(), locals())
    
    
        #print("cns %s done" % cns.name)
        return cns
    
    #
    
    
    #    parseCurve (args, tokens):
    #    parseSpline(cu, args, tokens):
    #    parseBezier(spline, n, args, tokens):
    #
    
    def parseCurve (args, tokens):
        global todo
        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", [], globals(), locals())
        return
    
    def parseTextCurve (args, tokens):
        global todo
        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", [], globals(), locals())
        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", [], globals(), locals())
        return
        
    def parseBezier(bez, args, tokens):
        bez.co = eval(args[0])
        bez.co = theScale*bez.co    
        bez.handle1 = eval(args[1])    
        bez.handle1_type = args[2]
        bez.handle2 = eval(args[3])    
        bez.handle2_type = args[4]
        return
    
    def parsePoint(pt, args, tokens):
        pt.co = eval(args[0])
        pt.co = theScale*pt.co
        return
    
    #
    #    parseLattice (args, tokens):
    #
    
    def parseLattice (args, tokens):
        global todo
        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", [], globals(), locals())
        return
    
    def parseLatticePoints(args, tokens, points):
        global todo
        n = 0
        for (key, val, sub) in tokens:
            if key == 'pt':
                v = points[n].co
                (x,y,z) = eval(val[0])
                v.x = theScale*x
                v.y = theScale*y
                v.z = theScale*z
    
                v = points[n].deformed_co
                (x,y,z) = eval(val[1])
                v.x = theScale*x
                v.y = theScale*y
                v.z = theScale*z
    
                n += 1
        return
    
    #
    #    parseLamp (args, tokens):
    #    parseFalloffCurve(focu, args, tokens):
    #
    
    def parseLamp (args, tokens):
        global todo
        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", [], globals(), locals())
        return
    
    def parseFalloffCurve(focu, args, tokens):
        return
    
    #
    #    parseGroup (args, tokens):
    #    parseGroupObjects(args, tokens, grp):
    #
    
    def parseGroup (args, tokens):
        global todo
        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", [], globals(), locals())
        return
    
    def parseGroupObjects(args, tokens, grp):
        global todo
        for (key, val, sub) in tokens:
            if key == 'ob':
                try:
                    ob = loadedData['Object'][val[0]]
                    grp.objects.link(ob)
                except:
                    pass
        return
    
    #
    #    parseWorld (args, tokens):
    #
    
    def parseWorld (args, tokens):
        global todo
        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", [], globals(), locals())
        return
    
    #
    #    parseScene (args, tokens):
    #    parseRenderSettings(render, args, tokens):
    #    parseToolSettings(tool, args, tokens):
    #
    
    def parseScene (args, tokens):
        global todo
        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", [], globals(), locals())
        return
    
    def parseRenderSettings(render, args, tokens):
        global todo
        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", [], globals(), locals())
        return
    
    #
    #    postProcess()
    #
    
    def postProcess():
        if not toggle & T_MHX:
            return
        try:
            ob = loadedData['Object']['HumanMesh']
        except:
            ob = None
        if toggle & T_Diamond == 0 and ob:
            deleteDiamonds(ob)
        if toggle & T_Rigify and False:
            for rig in loadedData['Rigify'].values():
                bpy.context.scene.objects.active = rig
                print("Rigify", rig)
                bpy.ops.pose.metarig_generate()
                print("Metarig generated")
                #bpy.context.scene.objects.unlink(rig)
    
                rig = bpy.context.scene.objects.active
                print("Rigged", rig, bpy.context.object)
                ob = loadedData['Object']['HumanMesh']
                mod = ob.modifiers[0]
                print(ob, mod, mod.object)
                mod.object = rig
                print("Rig changed", mod.object)
        return            
    
    #
    #    deleteDiamonds(ob)
    #    Delete joint diamonds in main mesh
    #
    
    def deleteDiamonds(ob):
        bpy.context.scene.objects.active = ob
        if not bpy.context.object:
            return
        print("Delete diamonds 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
        for f in me.faces:        
            if len(f.vertices) < 4:
                for vn in f.vertices:
                    me.vertices[vn].select = True
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.delete(type='VERT')
        bpy.ops.object.mode_set(mode='OBJECT')
        return
    
        
    #
    #    parseProcess(args, tokens):
    #    applyTransform(objects, rig, parents):
    #
    
    def parseProcess(args, tokens):
        if toggle & T_Bend == 0:
            return
        try:
            rig = loadedData['Object'][args[0]]
        except:
            rig = None
        if not rig:
            return
    
        parents = {}
        objects = []
    
        for (key, val, sub) in tokens:
            #print(key, val)
            if key == 'Reparent':
                bname = val[0]
                try:
                    eb = ebones[bname]
                    parents[bname] = eb.parent.name
                    eb.parent = ebones[val[1]]
                except:
                    pass
            elif key == 'Bend':
                axis = val[1]
                angle = float(val[2])
                mat = mathutils.Matrix.Rotation(angle, 4, axis)
                try:
                    pb = pbones[val[0]]
                    prod = pb.matrix_local * mat
                    for i in range(4):
                        for j in range(4):
                            pb.matrix_local[i][j] = prod[i][j]
                except:
                    print("No bone "+val[0])
                    pass
            elif key == 'Snap':
                try:
                    eb = ebones[val[0]]
                except:
                    eb = None
                tb = ebones[val[1]]
                typ = val[2]
                if eb == None:
                    pass
                elif typ == 'Inv':
                    eb.head = tb.tail
                    eb.tail = tb.head
                elif typ == 'Head':
                    eb.head = tb.head
                elif typ == 'Tail':
                    eb.tail = tb.tail
                elif typ == 'Both':
                    eb.head = tb.head
                    eb.tail = tb.tail
                    eb.roll = tb.roll
                else:
                    raise NameError("Snap type %s" % typ)
            elif key == 'PoseMode':
                bpy.context.scene.objects.active = rig
                bpy.ops.object.mode_set(mode='POSE')
                pbones = rig.pose.bones    
            elif key == 'ObjectMode':
                bpy.context.scene.objects.active = rig
                bpy.ops.object.mode_set(mode='POSE')
                pbones = rig.pose.bones    
            elif key == 'EditMode':
                bpy.context.scene.objects.active = rig
                bpy.ops.object.mode_set(mode='EDIT')
                ebones = rig.data.edit_bones    
                bpy.ops.armature.select_all(action='DESELECT')
            elif key == 'Roll':
                try:
                    eb = ebones[val[0]]
                except:
                    eb = None
                if eb:
                    eb.roll = float(val[1])
            elif key == 'Select':
                pass
            elif key == 'RollUp':
                pass
            elif key == 'Apply':
                applyTransform(objects, rig, parents)
            elif key == 'ApplyArmature':
                try:
                    ob = loadedData['Object'][val[0]]
                    objects.append((ob,sub))
                except:
                    ob = None
            elif key == 'Object':
                try:
                    ob = loadedData['Object'][val[0]]
                except:
                    ob = None
                if ob:
                    bpy.context.scene.objects.active = ob
                    #mod = ob.modifiers[0]
                    #ob.modifiers.remove(mod)
                    for (key1, val1, sub1) in sub:
                        if key1 == 'Modifier':
                            parseModifier(ob, val1, sub1)
        return
    
    def applyTransform(objects, rig, parents):
        for (ob,tokens) in objects:
            print("Applying transform to %s" % ob)
            bpy.context.scene.objects.active = ob        
            bpy.ops.object.visual_transform_apply()
            bpy.ops.object.modifier_apply(apply_as='DATA', modifier='Armature')
    
        bpy.context.scene.objects.active = rig
        bpy.ops.object.mode_set(mode='POSE')
        bpy.ops.pose.armature_apply()
        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.mode_set(mode='EDIT')
        ebones = rig.data.edit_bones
        for (bname, pname) in parents.items():
            eb = ebones[bname]
            par = ebones[pname]
            if eb.use_connect:
                par.tail = eb.head
            eb.parent = par
    
        bpy.ops.object.mode_set(mode='OBJECT')
        return            
    
    #
    #    defaultKey(ext, args, tokens, var, exclude, glbals, lcals):
    #
    
    def defaultKey(ext, args, tokens, var, exclude, glbals, lcals):
        global todo
    
        if ext == 'Property':
            try:
                expr = "%s['%s'] = %s" % (var, args[0], args[1])
            except:
                expr = None
            # print("Property", expr)
            if expr:
                exec(expr, glbals, lcals)
            return
            
        nvar = "%s.%s" % (var, ext)
        #print(ext)
        if ext in exclude:
            return
        #print("D", nvar)
    
        if len(args) == 0:
            raise NameError("Key length 0: %s" % ext)
            
        rnaType = args[0]
        if rnaType == 'Add':
            print("*** Cannot Add yet ***")
            return
    
        elif rnaType == 'Refer':
            typ = args[1]
            name = args[2]
            data = "loadedData['%s']['%s']" % (typ, name)
    
        elif rnaType == 'Struct' or rnaType == 'Define':
            typ = args[1]
            name = args[2]
            try:
                data = eval(nvar, glbals, lcals)
            except:
                data = None            
            # print("Old structrna", nvar, data)
    
            if data == None:
                try:
                    creator = args[3]
                except:
                    creator = None
                # print("Creator", creator, eval(var,glbals,lcals))
    
                try:
                    rna = eval(var,glbals,lcals)
                    data = eval(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", [], globals(), locals())
    
            print("Struct done", nvar)
            return
    
        elif rnaType == 'PropertyRNA':
            raise NameError("PropertyRNA!")
            #print("PropertyRNA ", ext, var)
            for (key, val, sub) in tokens:
                defaultKey(ext, val, sub, nvar, [], glbals, lcals)
            return
    
        elif rnaType == 'Array':
            for n in range(1, len(args)):
                expr = "%s[%d] = %s" % (nvar, n-1, args[n])
                exec(expr, glbals, lcals)
            if len(args) > 0:
                expr = "%s[0] = %s" % (nvar, args[1])
                exec(expr, glbals, lcals)            
            return
            
        elif rnaType == 'List':
            data = []
            for (key, val, sub) in tokens:
                elt = eval(val[1], glbals, lcals)
                data.append(elt)
    
        elif rnaType == 'Matrix':
            return
            i = 0
            n = len(tokens)
            for (key, val, sub) in tokens:
                if key == 'row':    
                    for j in range(n):
                        expr = "%s[%d][%d] = %g" % (nvar, i, j, float(val[j]))
                        exec(expr, glbals, lcals)
                    i += 1
            return
    
        else:
            try:
                data = loadedData[rnaType][args[1]]
                #print("From loaded", rnaType, args[1], data)
                return data
            except:
                data = rnaType
    
        #print(var, ext, data)
        expr = "%s = %s" % (nvar, data)
        try:
            exec(expr, glbals, lcals)
        except:
            pushOnTodoList(var, expr, glbals, lcals)
        return
    
    #
    #
    #
    
    def pushOnTodoList(var, expr, glbals, lcals):
        global todo
        print("Tdo", var)
        print(dir(eval(var, glbals, lcals)))
        raise NameError("Todo", expr)
        todo.append((expr, glbals, lcals))
        return
    
                
    #
    #    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 = Matrix( [1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1] )
        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:
                    defaultKey(key2, val2, sub2, "data.%s" % subkeys[key], [], globals(), locals())
            else:
                defaultKey(key, val, sub, "data", exclude, globals(), locals())
    
    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:
            raise NameError("Bool %s?" % string)
            
    #
    #    invalid(condition):
    #
    
    def invalid(condition):
        global rigLeg, rigArm, toggle
        res = eval(condition, globals())
        try:
            res = eval(condition, globals())
            #print("%s = %s" % (condition, res))
            return not res
        except:
            #print("%s invalid!" % condition)
            return True
    
    
        
    #
    #    clearScene(context):
    #    hideLayers():
    #
        
    def clearScene():
        global toggle
        scn = bpy.context.scene
        for n in range(len(scn.layers)):
            scn.layers[n] = True
        print("clearScene %s %s" % (toggle & T_Replace, scn))
        if not toggle & T_Replace:
            return scn
    
        for ob in scn.objects:
            if ob.type == "MESH" or ob.type == "ARMATURE" or ob.type == 'EMPTY':
                scn.objects.active = ob
                bpy.ops.object.mode_set(mode='OBJECT')
                scn.objects.unlink(ob)
                del ob
        #print(scn.objects)
        return scn
    
    def hideLayers():
        scn = bpy.context.scene
        for n in range(len(scn.layers)):
            if n < 8:
                scn.layers[n] = True
            else:
                scn.layers[n] = False
        return
    
    #
    #    User interface
    #
    
    DEBUG= False
    from bpy.props import *
    
    class IMPORT_OT_makehuman_mhx(bpy.types.Operator):
        '''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"
    
        filepath = StringProperty(name="File Path", description="File path used for importing the MHX file", maxlen= 1024, default= "")
    
        scale = FloatProperty(name="Scale", description="Default meter, decimeter = 1.0", default = theScale)
    
    
    
        enforce = BoolProperty(name="Enforce version", description="Only accept MHX files of correct version", default=toggle&T_EnforceVersion)
        mesh = BoolProperty(name="Mesh", description="Use main mesh", default=toggle&T_Mesh)
        proxy = BoolProperty(name="Proxies", description="Use proxies", default=toggle&T_Proxy)
        armature = BoolProperty(name="Armature", description="Use armature", default=toggle&T_Armature)
        replace = BoolProperty(name="Replace scene", description="Replace scene", default=toggle&T_Replace)
        cage = BoolProperty(name="Cage", description="Load mesh deform cage", default=toggle&T_Cage)
        clothes = BoolProperty(name="Clothes", description="Include clothes", default=toggle&T_Clothes)
        stretch = BoolProperty(name="Stretchy limbs", description="Stretchy limbs", default=toggle&T_Stretch)
        face = BoolProperty(name="Face shapes", description="Include facial shapekeys", default=toggle&T_Face)
        shape = BoolProperty(name="Body shapes", description="Include body shapekeys", default=toggle&T_Shape)
        symm = BoolProperty(name="Symmetric shapes", description="Keep shapekeys symmetric", default=toggle&T_Symm)
        diamond = BoolProperty(name="Diamonds", description="Keep joint diamonds", default=toggle&T_Diamond)
        bend = BoolProperty(name="Bend joints", description="Bend joints for better IK", default=toggle&T_Bend)
            
        def execute(self, context):
            global toggle
            O_EnforceVersion = T_EnforceVersion if self.properties.enforce else 0
            O_Mesh = T_Mesh if self.properties.mesh else 0
            O_Proxy = T_Proxy if self.properties.proxy else 0
            O_Armature = T_Armature if self.properties.armature else 0
            O_Replace = T_Replace if self.properties.replace else 0
            O_Cage = T_Cage if self.properties.cage else 0
            O_Clothes = T_Clothes if self.properties.clothes else 0
            O_Stretch = T_Stretch if self.properties.stretch else 0
            O_Face = T_Face if self.properties.face else 0
            O_Shape = T_Shape if self.properties.shape else 0
            O_Symm = T_Symm if self.properties.symm else 0
            O_Diamond = T_Diamond if self.properties.diamond else 0
            O_Bend = T_Bend if self.properties.bend else 0
            toggle = ( O_EnforceVersion | O_Mesh | O_Proxy | O_Armature | O_Replace | O_Stretch | O_Cage | 
                    O_Face | O_Shape | O_Symm | O_Diamond | O_Bend | O_Clothes | T_MHX )
    
            print("Load", self.properties.filepath)
            readMhxFile(self.properties.filepath, self.properties.scale)
            return {'FINISHED'}
    
        def invoke(self, context, event):
            context.window_manager.fileselect_add(self)
            return {'RUNNING_MODAL'}
    
    def menu_func(self, context):
        self.layout.operator(IMPORT_OT_makehuman_mhx.bl_idname, text="MakeHuman (.mhx)...")
    
    def register():
        bpy.types.INFO_MT_file_import.append(menu_func)
    
    def unregister():
        bpy.types.INFO_MT_file_import.remove(menu_func)
    
    if __name__ == "__main__":
        try:
            unregister()
        except:
            pass
        register()
    
    #
    #    Testing
    #
    """
    #readMhxFile("C:/Documents and Settings/xxxxxxxxxxxxxxxxxxxx/Mina dokument/makehuman/exports/foo-25.mhx", 'Classic')
    readMhxFile("/home/thomas/makehuman/exports/foo-25.mhx", 1.0)
    
    #toggle = T_Replace + T_Mesh + T_Armature + T_MHX
    #readMhxFile("/home/thomas/myblends/test.mhx", 1.0)
    """