Skip to content
Snippets Groups Projects
io_import_scene_mhx.py 105 KiB
Newer Older
  • Learn to ignore specific revisions
  •         ('PUpLipMid', (0,0)), 
            ('PLoLipMid', (0,0)), 
            ('PJaw', (0,0.8)), 
            ('PTongue', (0,0))], 
        'R' : [
            ('PMouth', (-0.5,0)), 
            ('PMouthMid', (0,0)), 
            ('PUpLipMid', (0,-0.2)), 
            ('PLoLipMid', (0,0.2)), 
            ('PJaw', (0,0)), 
            ('PTongue', (0,0))], 
        'FV' : [
            ('PMouth', (-0.2,0)), 
            ('PMouthMid', (0,1.0)), 
            ('PUpLipMid', (0,0)), 
            ('PLoLipMid', (-0.6,-0.3)), 
            ('PJaw', (0,0)), 
            ('PTongue', (0,0))], 
        'S' : [
            ('PMouth', (0,0)), 
            ('PMouthMid', (0,0)), 
            ('PUpLipMid', (0,-0.5)), 
            ('PLoLipMid', (0,0.7)), 
            ('PJaw', (0,0)), 
            ('PTongue', (0,0))], 
        'SH' : [
            ('PMouth', (-0.8,0)), 
            ('PMouthMid', (0,0)), 
            ('PUpLipMid', (0,-1.0)), 
            ('PLoLipMid', (0,1.0)), 
            ('PJaw', (0,0)), 
            ('PTongue', (0,0))], 
        'EE' : [
            ('PMouth', (0.2,0)), 
            ('PMouthMid', (0,0)), 
            ('PUpLipMid', (0,-0.6)), 
            ('PLoLipMid', (0,0.6)), 
            ('PJaw', (0,0.05)), 
            ('PTongue', (0,0))], 
        'AH' : [
            ('PMouth', (0,0)), 
            ('PMouthMid', (0,0)), 
            ('PUpLipMid', (0,-0.4)), 
            ('PLoLipMid', (0,0)), 
            ('PJaw', (0,0.7)), 
            ('PTongue', (0,0))], 
        'EH' : [
            ('PMouth', (0,0)), 
            ('PMouthMid', (0,0)), 
            ('PUpLipMid', (0,-0.5)), 
            ('PLoLipMid', (0,0.6)), 
            ('PJaw', (0,0.25)), 
            ('PTongue', (0,0))], 
        'TH' : [
            ('PMouth', (0,0)), 
            ('PMouthMid', (0,0)), 
            ('PUpLipMid', (0,0)), 
            ('PLoLipMid', (0,0)), 
            ('PJaw', (0,0.2)), 
            ('PTongue', (1.0,1.0))], 
        'L' : [
            ('PMouth', (0,0)), 
            ('PMouthMid', (0,0)), 
            ('PUpLipMid', (0,-0.5)), 
            ('PLoLipMid', (0,0.5)), 
            ('PJaw', (0,-0.2)), 
            ('PTongue', (1.0,1.0))], 
        'G' : [
            ('PMouth', (0,0)), 
            ('PMouthMid', (0,0)), 
            ('PUpLipMid', (0,-0.5)), 
            ('PLoLipMid', (0,0.5)), 
            ('PJaw', (0,-0.2)), 
            ('PTongue', (-1.0,0))], 
    
        'Blink' : [('PUpLid', (0,1.0)), ('PLoLid', (0,-1.0))], 
        'Unblink' : [('PUpLid', (0,0)), ('PLoLid', (0,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):
        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
        adata = ob.data.shape_keys.animation_data
        if not adata:
            print("Shapekeys have not drivers")
            return
        try:
            ob.data.shape_keys.key_blocks["VIS_Rest"]
            print("Visemes already created")
            return
        except:
            pass        
        rig = ob.parent
        scale = rig.data.bones['PFace'].length*0.2
    
        boneShapes = {}
        for fcu in adata.drivers:
            name = fcu.data_path.split('"')[1]
            for var in fcu.driver.variables:
                if var.type == 'TRANSFORMS':
                    targ = var.targets[0]  
                    fmod = fcu.modifiers[0]
                    try:
                        list = boneShapes[targ.bone_target]
                    except:
                        list = []
                        boneShapes[targ.bone_target] = list
                    list.append((targ.transform_type, fmod, ob.data.shape_keys.key_blocks[name]))
                
        verts = ob.data.vertices            
        for (vis,bones) 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 (bone, xz) in bones:
                try:
                    boneShapes[bone]
                    single = True
                except:
                    single = False
                if single:
                    addToVisShapeKey(boneShapes[bone], vkey, verts, xz, 1, scale)
                else:
                    try:
                        boneShapes[bone+"_L"]
                        double = True
                    except:
                        double = False
                    if double:
                        addToVisShapeKey(boneShapes[bone+"_L"], vkey, verts, xz, 1, scale)
                        #addToVisShapeKey(boneShapes[bone+"_R"], vkey, verts, xz, -1, scale)
        print("Visemes made")                    
        return
        
    def addToVisShapeKey(shapes, vkey, verts, xz, sign, scale):
        for (typ, fmod, skey) in shapes:
            factor = fmod.coefficients[1]*scale
            (x,z) = xz
            if typ == 'LOC_X':
                k = factor*sign*x
            elif typ == 'LOC_Z':
                k = factor*z
            if k < skey.slider_min:
                k = skey.slider_min
            if k > skey.slider_max:
                k = skey.slider_max
            if abs(k) < 0.001:
                continue
            print("  %s %.3f %.3f %.3f %.3f" % (skey.name, factor, x, z, k))
            for n,v in enumerate(verts):
                vkey.data[n].co += k*(skey.data[n].co - v.co)
        return            
           
    class VIEW3D_OT_MhxMakeVisemesButton(bpy.types.Operator):
        bl_idname = "mhx.make_visemes"
        bl_label = "Generate viseme shapekeys"
    
        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" : "t,d,k,g,T,D,s,z,S,Z,h,n,N,j,r,tS", 
        "AI" : "i,&,V,aU,I,0,@,aI", 
        "E" : "eI,3,e", 
        "O" : "O,@U,oI", 
        "UW" : "U,u,w", 
        "MBP" : "m,b,p", 
        "L" : "l", 
        "FV" : "f,v", 
        "Sh" : "dZ", 
    })
    
    #
    #    setViseme(context, vis, setKey, frame):
    #    setBoneLocation(context, pbone, loc, mirror, setKey, frame):
    #    class VIEW3D_OT_MhxVisemeButton(bpy.types.Operator):
    #
    
    def getVisemeSet(context, rig):
        try:
            visset = rig['MhxVisemeSet']
        except:
            return bodyLanguageVisemes
        if visset == 'StopStaring':
            return stopStaringVisemes
        elif visset == 'BodyLanguage':
            return bodyLanguageVisemes
        else:
            raise NameError("Unknown viseme set %s" % visset)
    
    def setViseme(context, vis, setKey, frame):
        rig = getMhxRig(context.object)
        pbones = rig.pose.bones
        try:
            scale = pbones['PFace'].bone.length
        except:
            return
        visemes = getVisemeSet(context, rig)
        for (b, (x, z)) in visemes[vis]:
            loc = mathutils.Vector((float(x),0,float(z)))
            try:
                pb = pbones[b]
            except:
    
                pb = None
                
            if pb:
                setBoneLocation(context, pb, scale, loc, False, setKey, frame)
            else:
                setBoneLocation(context, pbones[b+'_L'], scale, loc, False, setKey, frame)
                setBoneLocation(context, pbones[b+'_R'], scale, loc, True, setKey, frame)
    
        return
    
    def setBoneLocation(context, pb, scale, loc, mirror, setKey, frame):
        if mirror:
            loc[0] = -loc[0]
        pb.location = loc*scale*0.2
    
        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)
        return
    
    class VIEW3D_OT_MhxVisemeButton(bpy.types.Operator):
        bl_idname = 'mhx.pose_viseme'
        bl_label = 'Viseme'
        viseme = StringProperty()
    
        def invoke(self, context, event):
            setViseme(context, self.viseme, False, context.scene.frame_current)
            return{'FINISHED'}
    
    
    
    #
    #    openFile(context, filepath):
    #    readMoho(context, filepath, offs):
    #    readMagpie(context, filepath, offs):
    #
    
    def openFile(context, filepath):
        (path, fileName) = os.path.split(filepath)
        (name, ext) = os.path.splitext(fileName)
        return open(filepath, "rU")
    
    def readMoho(context, filepath, offs):
        rig = getMhxRig(context.object)
        context.scene.objects.active = rig
        bpy.ops.object.mode_set(mode='POSE')    
        fp = openFile(context, filepath)        
        for line in fp:
            words= line.split()
            if len(words) < 2:
                pass
            else:
                vis = mohoVisemes[words[1]]
                setViseme(context, vis, True, int(words[0])+offs)
        fp.close()
    
        setInterpolation(rig)
        updatePose(rig)
    
        print("Moho file %s loaded" % filepath)
        return
    
    def readMagpie(context, filepath, offs):
        rig = getMhxRig(context.object)
        context.scene.objects.active = rig
        bpy.ops.object.mode_set(mode='POSE')    
        fp = openFile(context, filepath)        
        for line in fp: 
            words= line.split()
            if len(words) < 3:
                pass
            elif words[2] == 'X':
                vis = magpieVisemes[words[3]]
                setViseme(context, vis, True, int(words[0])+offs)
        fp.close()
    
        setInterpolation(rig)
        updatePose(rig)
    
        print("Magpie file %s loaded" % filepath)
        return
    
    # 
    #    class VIEW3D_OT_MhxLoadMohoButton(bpy.types.Operator):
    #
    
    class VIEW3D_OT_MhxLoadMohoButton(bpy.types.Operator):
        bl_idname = "mhx.pose_load_moho"
        bl_label = "Moho (.dat)"
    
        filepath = StringProperty(subtype='FILE_PATH')
    
        startFrame = IntProperty(name="Start frame", description="First frame to import", default=1)
    
        def execute(self, context):
            import bpy, os, mathutils
            readMoho(context, self.properties.filepath, self.properties.startFrame-1)        
            return{'FINISHED'}    
    
        def invoke(self, context, event):
            context.window_manager.fileselect_add(self)
            return {'RUNNING_MODAL'}    
    
    #
    #    class VIEW3D_OT_MhxLoadMagpieButton(bpy.types.Operator):
    #
    
    class VIEW3D_OT_MhxLoadMagpieButton(bpy.types.Operator):
        bl_idname = "mhx.pose_load_magpie"
        bl_label = "Magpie (.mag)"
    
        filepath = StringProperty(subtype='FILE_PATH')
    
        startFrame = IntProperty(name="Start frame", description="First frame to import", default=1)
    
        def execute(self, context):
            import bpy, os, mathutils
            readMagpie(context, self.properties.filepath, self.properties.startFrame-1)        
            return{'FINISHED'}    
    
        def invoke(self, context, event):
            context.window_manager.fileselect_add(self)
            return {'RUNNING_MODAL'}    
    
    #
    #    class MhxLipsyncPanel(bpy.types.Panel):
    #
    
    class MhxLipsyncPanel(bpy.types.Panel):
        bl_label = "MHX Lipsync"
        bl_space_type = "VIEW_3D"
        bl_region_type = "UI"
        
        @classmethod
        def poll(cls, context):
            return context.object
    
        def draw(self, context):
            rig = getMhxRig(context.object)
            if not rig:
                return
    
            layout = self.layout        
            layout.label(text="Visemes")
            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.label(text="Load file")
            row = layout.row()
            row.operator("mhx.pose_load_moho")
            row.operator("mhx.pose_load_magpie")
    
            layout.separator()
            layout.operator("mhx.make_visemes")
    
            
    #
    #   updatePose(rig):
    #   class VIEW3D_OT_MhxUpdateButton(bpy.types.Operator):
    #
    
    def updatePose(rig):
        pb = rig.pose.bones["PFaceDisp"]
        pb.location = pb.location
        return
    
    class VIEW3D_OT_MhxUpdateButton(bpy.types.Operator):
        bl_idname = "mhx.update"
        bl_label = "Update"
    
        def execute(self, context):
            rig = getMhxRig(context.object)
            updatePose(rig)
            return{'FINISHED'}    
            
    
    
    ###################################################################################    
    #
    #    Expression panel
    #
    ###################################################################################    
    #
    #    class VIEW3D_OT_MhxResetExpressionsButton(bpy.types.Operator):
    #
    
    class VIEW3D_OT_MhxResetExpressionsButton(bpy.types.Operator):
        bl_idname = "mhx.pose_reset_expressions"
        bl_label = "Reset expressions"
    
        def execute(self, context):
            rig = getMhxRig(context.object)
            props = getShapeProps(rig)
            for (prop, name) in props:
                rig[prop] = 0.0
    
            return{'FINISHED'}    
    
    #
    #    class VIEW3D_OT_MhxKeyExpressionButton(bpy.types.Operator):
    #
    
    class VIEW3D_OT_MhxKeyExpressionsButton(bpy.types.Operator):
        bl_idname = "mhx.pose_key_expressions"
        bl_label = "Key expressions"
    
        def execute(self, context):
            rig = getMhxRig(context.object)
            props = getShapeProps(rig)
            frame = context.scene.frame_current
            for (prop, name) in props:
                rig.keyframe_insert('["%s"]' % prop, frame=frame)
    
            return{'FINISHED'}    
    #
    #    class VIEW3D_OT_MhxPinExpressionButton(bpy.types.Operator):
    #
    
    class VIEW3D_OT_MhxPinExpressionButton(bpy.types.Operator):
        bl_idname = "mhx.pose_pin_expression"
        bl_label = "Pin"
        expression = StringProperty()
    
        def execute(self, context):
            rig = getMhxRig(context.object)
            props = getShapeProps(rig)
            if context.tool_settings.use_keyframe_insert_auto:
                frame = context.scene.frame_current
                for (prop, name) in props:
                    old = rig[prop]
                    if prop == self.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, name) in props:
                    if prop == self.expression:
                        rig[prop] = 1.0
                    else:
                        rig[prop] = 0.0
    
            return{'FINISHED'}    
    
    #
    #   getShapeProps(ob):        
    #
    
    def getShapeProps(rig):
        props = []        
        plist = list(rig.keys())
        plist.sort()
        for prop in plist:
            if prop[0] == '*':
                props.append((prop, prop[1:]))
        return props                
    
    #
    #    class MhxExpressionsPanel(bpy.types.Panel):
    #
    
    class MhxExpressionsPanel(bpy.types.Panel):
        bl_label = "MHX Expressions"
        bl_space_type = "VIEW_3D"
        bl_region_type = "UI"
        
        @classmethod
        def poll(cls, context):
            return context.object
    
        def draw(self, context):
            rig = getMhxRig(context.object)
            if not rig:
                return
            props = getShapeProps(rig)
            if not props:
                return
            layout = self.layout
            layout.label(text="Expressions")
            layout.operator("mhx.pose_reset_expressions")
            layout.operator("mhx.pose_key_expressions")
    
            #layout.operator("mhx.update")
    
            layout.separator()
            for (prop, name) in props:
    
                row.prop(rig, '["%s"]' % prop, text=name)
    
                row.operator("mhx.pose_pin_expression", text="", icon='UNPINNED').expression = prop
    
            return
    
    ###################################################################################    
    #
    #    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"
        
        @classmethod
        def poll(cls, context):
            return pollMhxRig(context.object)
    
        def draw(self, context):
            lProps = []
            rProps = []
            props = []
            plist = list(context.object.keys())
            plist.sort()
            for prop in plist:
    
                elif prop[-2:] == '_R':
    
            ob = context.object
            layout = self.layout
    
            for (prop, pname) in props:
                layout.prop(ob, '["%s"]' % prop, text=pname)
    
            layout.label("Left")
            for (prop, pname) in lProps:
                layout.prop(ob, '["%s"]' % prop, text=pname)
            layout.label("Right")
            for (prop, pname) in rProps:
                layout.prop(ob, '["%s"]' % prop, text=pname)
            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"
        
        @classmethod
        def poll(cls, context):
            return pollMhxRig(context.object)
    
        def draw(self, context):
            ob = context.object
            props = list(ob.keys())
            props.sort()
            for prop in props:
                if prop[0:4] == "Hide": 
                    self.layout.prop(ob, '["%s"]' % prop)
            return
    
    
    ###################################################################################    
    #
    #    Layers panel
    #
    ###################################################################################    
    
    MhxLayers = [
        (( 0,    'Root', 'MhxRoot'),
         ( 8,    'Face', 'MhxFace')),
        (( 9,    'Tweak', 'MhxTweak'),
         (10,    'Head', 'MhxHead')),
        (( 1,    'FK Spine', 'MhxFKSpine'),
         (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')),
    ]
    
    #
    #    class MhxLayersPanel(bpy.types.Panel):
    #
    
    class MhxLayersPanel(bpy.types.Panel):
        bl_label = "MHX Layers"
        bl_space_type = "VIEW_3D"
        bl_region_type = "UI"
        
        @classmethod
        def poll(cls, context):
            return pollMhxRig(context.object)
    
        def draw(self, context):
            layout = self.layout
    
            layout.operator("mhx.pose_enable_all_layers")
            layout.operator("mhx.pose_disable_all_layers")
    
            amt = context.object.data
            for (left,right) in MhxLayers:
                row = layout.row()
                if type(left) == str:
                    row.label(left)
                    row.label(right)
                else:
                    for (n, name, prop) in [left,right]:
                        row.prop(amt, "layers", index=n, toggle=True, text=name)
            return
    
    
    class VIEW3D_OT_MhxEnableAllLayersButton(bpy.types.Operator):
        bl_idname = "mhx.pose_enable_all_layers"
        bl_label = "Enable all layers"
    
    
        def execute(self, context):
            rig = getMhxRig(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"
    
        def execute(self, context):
            rig = getMhxRig(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
            rig.data.layers = layers            
    
            return{'FINISHED'}    
                    
    ###################################################################################    
    #
    #    Common functions
    #
    ###################################################################################    
    #
    #   pollMhxRig(ob):
    #   getMhxRig(ob):
    #
    
    def pollMhxRig(ob):
        try:
            return (ob["MhxRig"] == "MHX")
        except:
            return False
            
    def getMhxRig(ob):
        if ob.type == 'ARMATURE':
            rig = ob
        elif ob.type == 'MESH':
            rig = ob.parent
        else:
            return None
        try:        
            if (rig["MhxRig"] == "MHX"):
                return rig
            else:
                return None
        except:
            return 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)...")
    
    Brendon Murphy's avatar
    Brendon Murphy committed
    def register():
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        bpy.types.INFO_MT_file_import.append(menu_func)
    
    Brendon Murphy's avatar
    Brendon Murphy committed
    def unregister():
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        bpy.types.INFO_MT_file_import.remove(menu_func)
    
    Brendon Murphy's avatar
    Brendon Murphy committed
    
    if __name__ == "__main__":
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        try:
            unregister()
        except:
            pass
        register()