diff --git a/io_import_scene_mhx.py b/io_import_scene_mhx.py index 00b7f513eb575f380868a34563d55bdb57405b14..20f5248af4f1b403db24492a6bc9c807f2885cce 100644 --- a/io_import_scene_mhx.py +++ b/io_import_scene_mhx.py @@ -38,7 +38,7 @@ Alternatively, run the script in the script editor (Alt-P), and access from the bl_info = { 'name': 'Import: MakeHuman (.mhx)', 'author': 'Thomas Larsson', - 'version': (1, 16, 8), + 'version': "1.16.9", "blender": (2, 68, 0), 'location': "File > Import > MakeHuman (.mhx)", 'description': 'Import files in the MakeHuman eXchange format (.mhx)', @@ -65,7 +65,7 @@ import os import time import math import mathutils -from mathutils import Vector, Matrix +from mathutils import Vector, Matrix, Quaternion from bpy.props import * MHX249 = False @@ -2909,7 +2909,7 @@ class SuccessOperator(bpy.types.Operator): # ################################################################################### -from bpy_extras.io_utils import ImportHelper +from bpy_extras.io_utils import ImportHelper, ExportHelper MhxBoolProps = [ ("enforce", "Enforce version", "Only accept MHX files of correct version", T_EnforceVersion), @@ -2994,6 +2994,247 @@ class ImportMhx(bpy.types.Operator, ImportHelper): return {'RUNNING_MODAL'} +################################################################################### +# +# Main 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'), + (16, 'Clothes', 'MhxClothes')), + ('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 MhxMainPanel(bpy.types.Panel): +# + +class MhxMainPanel(bpy.types.Panel): + bl_label = "MHX Main v %s" % bl_info["version"] + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + #bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + return (context.object and context.object.MhxRig) + + def draw(self, context): + layout = self.layout + layout.label("Layers") + layout.operator("mhx.pose_enable_all_layers") + layout.operator("mhx.pose_disable_all_layers") + 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) + + layout.separator() + layout.label("Export/Import MHP") + layout.operator("mhx.saveas_mhp") + layout.operator("mhx.load_mhp") + + +class VIEW3D_OT_MhxEnableAllLayersButton(bpy.types.Operator): + bl_idname = "mhx.pose_enable_all_layers" + bl_label = "Enable all layers" + bl_options = {'UNDO'} + + def execute(self, context): + rig,mesh = getMhxRigMesh(context.object) + for (left,right) in MhxLayers: + if type(left) != str: + for (n, name, prop) in [left,right]: + rig.data.layers[n] = True + return{'FINISHED'} + + +class VIEW3D_OT_MhxDisableAllLayersButton(bpy.types.Operator): + bl_idname = "mhx.pose_disable_all_layers" + bl_label = "Disable all layers" + bl_options = {'UNDO'} + + def execute(self, context): + rig,mesh = getMhxRigMesh(context.object) + layers = 32*[False] + pb = context.active_pose_bone + if pb: + for n in range(32): + if pb.bone.layers[n]: + layers[n] = True + break + else: + layers[0] = True + if rig: + rig.data.layers = layers + return{'FINISHED'} + + + +def saveMhpFile(rig, scn, filepath): + roots = [] + for pb in rig.pose.bones: + if pb.parent is None: + roots.append(pb) + + (pname, ext) = os.path.splitext(filepath) + mhppath = pname + ".mhp" + + fp = open(mhppath, "w", encoding="utf-8", newline="\n") + for root in roots: + writeMhpBones(fp, root, None) + fp.close() + print("Mhp file %s saved" % mhppath) + + +def writeMhpBones(fp, pb, log): + if not isMuscleBone(pb): + b = pb.bone + if pb.parent: + string = "quat" + mat = b.matrix_local.inverted() * b.parent.matrix_local * pb.parent.matrix.inverted() * pb.matrix + else: + string = "gquat" + mat = pb.matrix.copy() + maty = mat[1].copy() + matz = mat[2].copy() + mat[1] = matz + mat[2] = -maty + + t,q,s = mat.decompose() + magn = math.sqrt(q.x*q.x + q.y*q.y + q.z*q.z) + if magn > 1e-5: + fp.write("%s\t%s\t%.5f\t%.5f\t%.5f\t%.5f\n" % (pb.name, string, q.w, q.x, q.y, q.z)) + + for child in pb.children: + writeMhpBones(fp, child, log) + + +def isMuscleBone(pb): + layers = pb.bone.layers + if (layers[14] or layers[15] or layers[30] or layers[31]): + return True + for cns in pb.constraints: + if (cns.type == 'STRETCH_TO' or + cns.type == 'TRANSFORM' or + cns.type == 'TRACK_TO' or + cns.type == 'IK' or + cns.type[0:5] == 'COPY_'): + return True + return False + + +def loadMhpFile(rig, scn, filepath): + (pname, ext) = os.path.splitext(filepath) + mhppath = pname + ".mhp" + + fp = open(mhppath, "rU") + for line in fp: + words = line.split() + if len(words) < 4: + continue + elif words[1] == "quat": + try: + pb = rig.pose.bones[words[0]] + except KeyError: + print("Warning: Did not find bone %s" % words[0]) + continue + if not isMuscleBone(pb): + q = Quaternion((float(words[2]), float(words[3]), float(words[4]), float(words[5]))) + mat = q.to_matrix().to_4x4() + pb.matrix_basis = mat + elif words[1] == "gquat": + try: + pb = rig.pose.bones[words[0]] + except KeyError: + print("Warning: Did not find bone %s" % words[0]) + continue + q = Quaternion((float(words[2]), float(words[3]), float(words[4]), float(words[5]))) + mat = q.to_matrix().to_4x4() + maty = mat[1].copy() + matz = mat[2].copy() + mat[1] = -matz + mat[2] = maty + pb.matrix_basis = pb.bone.matrix_local.inverted() * mat + elif words[1] == "scale": + pass + else: + print("WARNING: Unknown line in mcp file:\n%s" % line) + fp.close() + print("Mhp file %s loaded" % mhppath) + + +class VIEW3D_OT_LoadMhpButton(bpy.types.Operator): + bl_idname = "mhx.load_mhp" + bl_label = "Load MHP File" + bl_description = "Load a pose in MHP format" + bl_options = {'UNDO'} + + filename_ext = ".mhp" + filter_glob = StringProperty(default="*.mhp", options={'HIDDEN'}) + filepath = bpy.props.StringProperty( + name="File Path", + description="File path used for mhp file", + maxlen= 1024, default= "") + + def execute(self, context): + loadMhpFile(context.object, context.scene, self.properties.filepath) + return {'FINISHED'} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {'RUNNING_MODAL'} + + +class VIEW3D_OT_SaveasMhpFileButton(bpy.types.Operator, ExportHelper): + bl_idname = "mhx.saveas_mhp" + bl_label = "Save MHP File" + bl_description = "Save current pose in MHP format" + bl_options = {'UNDO'} + + filename_ext = ".mhp" + filter_glob = StringProperty(default="*.mhp", options={'HIDDEN'}) + filepath = bpy.props.StringProperty( + name="File Path", + description="File path used for mhp file", + maxlen= 1024, default= "") + + def execute(self, context): + saveMhpFile(context.object, context.scene, self.properties.filepath) + return {'FINISHED'} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {'RUNNING_MODAL'} + ################################################################################### # # Lipsync panel @@ -4193,7 +4434,6 @@ class VIEW3D_OT_MhxToggleFkIkButton(bpy.types.Operator): updatePose(context) return{'FINISHED'} - # # MHX FK/IK Switch panel # @@ -4445,102 +4685,6 @@ class VIEW3D_OT_MhxRemoveHidersButton(bpy.types.Operator): del rig["Mhh%s" % ob.name] return{'FINISHED'} -################################################################################### -# -# 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'), - (16, 'Clothes', 'MhxClothes')), - ('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" - #bl_options = {'DEFAULT_CLOSED'} - - @classmethod - def poll(cls, context): - return (context.object and context.object.MhxRig == 'MHX') - - 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" - bl_options = {'UNDO'} - - def execute(self, context): - rig,mesh = getMhxRigMesh(context.object) - for (left,right) in MhxLayers: - if type(left) != str: - for (n, name, prop) in [left,right]: - rig.data.layers[n] = True - return{'FINISHED'} - -class VIEW3D_OT_MhxDisableAllLayersButton(bpy.types.Operator): - bl_idname = "mhx.pose_disable_all_layers" - bl_label = "Disable all layers" - bl_options = {'UNDO'} - - def execute(self, context): - rig,mesh = getMhxRigMesh(context.object) - layers = 32*[False] - pb = context.active_pose_bone - if pb: - for n in range(32): - if pb.bone.layers[n]: - layers[n] = True - break - else: - layers[0] = True - if rig: - rig.data.layers = layers - return{'FINISHED'} - ################################################################################### # # Common functions