OSDN Git Service

add import_scene_pmd.py.
authorousttrue <ousttrue@gmail.com>
Wed, 12 May 2010 14:21:35 +0000 (23:21 +0900)
committerousttrue <ousttrue@gmail.com>
Wed, 12 May 2010 14:21:35 +0000 (23:21 +0900)
blender25/import_scene_pmd.py [new file with mode: 0644]

diff --git a/blender25/import_scene_pmd.py b/blender25/import_scene_pmd.py
new file mode 100644 (file)
index 0000000..0b580e2
--- /dev/null
@@ -0,0 +1,576 @@
+# coding: utf-8
+
+__author__= ['ousttrue']
+__url__ = ("")
+__version__= '20100508 0.1:'
+__bpydoc__= '''\
+pmd Importer
+
+This script imports a pmd into Blender for editing.
+'''
+
+import bpy
+from bpy.props import *
+import mathutils
+import sys
+import os
+import math
+from meshio import pmd, englishmap
+
+
+def to_radian(degree):
+    return math.pi * degree / 180
+
+
+def convert_coord(pos):
+    """
+    Left handed y-up to Right handed z-up
+    """
+    return (pos.x, pos.z, pos.y)
+
+
+def convert_uv(uv):
+    return (uv.x, 1.0 - uv.y)
+
+
+def get_bone_name(l, index):
+    name=englishmap.getEnglishBoneName(l.bones[index].getName())
+    return name if name else l.bones[index].getName()
+
+
+def create_texture(directory, texture_name):
+    texture=bpy.data.textures.new(texture_name)
+    texture.type='IMAGE'
+    texture=texture.recast_type()
+    texturePath="%s/%s" % (directory, texture_name)
+    print('create_texture', texturePath)
+    image=bpy.data.images.load(texturePath)
+    texture.image=image
+    texture.mipmap = True
+    texture.interpolation = True
+    texture.use_alpha = True
+    return texture
+
+
+def createMaterial():
+    """
+    create default materil 
+    """
+    material = bpy.data.materials.new("Material")
+    material.diffuse_shader='TOON'
+    material.specular_shader='TOON'
+    # temporary
+    material.emit=1.0
+    return material
+
+
+def importMesh(scene, l, tex_dir):
+    """
+    @param l[in] mmd.PMDLoader
+    @param filename[in]
+    """
+
+    ############################################################
+    # shpae\e$B%-!<$G;H$o$l$k%^%F%j%"%kM%@hE*$KA0$KJB$Y$k\e(B
+    ############################################################
+    # shape\e$B%-!<$G;H$o$l$kD:E@%$%s%G%C%/%9$r=8$a$k\e(B
+    shape_key_used_vertices=set()
+    if len(l.morph_list)>0:
+        # base 
+        base=None
+        for s in l.morph_list:
+            if s.type!=0:
+                continue
+            base=s
+            break
+        assert(base)
+
+        for index in base.indices:
+            shape_key_used_vertices.add(index)
+
+    # \e$B%^%F%j%"%k$K4^$^$l$kD:E@$,\e(Bshape_key\e$B$K4^$^$l$k$+H]$+!)\e(B
+    def isMaterialUsedInShape(offset, m):
+        for i in range(offset, offset+m.vertex_count): 
+            if l.indices[i] in shape_key_used_vertices:
+                return True
+
+    # shape\e$B%-!<$G;H$o$l$k%^%F%j%"%k$r5-O?$9$k\e(B
+    shape_key_materials=set()
+    # \e$B3F%^%F%j%"%k$N3+;OD:E@%$%s%G%C%/%9$r5-O?$9$k\e(B
+    face_map={}
+    face_count=0
+    for i, m in enumerate(l.materials):
+        face_map[i]=face_count
+        if isMaterialUsedInShape(face_count, m):
+            shape_key_materials.add(i)
+        face_count+=m.vertex_count
+
+    # shape\e$B%-!<$G;H$o$l$k%^%F%j%"%k$rA0$KJB$Y$k%$%s%G%C%/%9%^%C%W$r:n$k\e(B
+    material_map={}
+    used_index=0
+    not_used_index=len(shape_key_materials)
+    for i, m in enumerate(l.materials):
+        if i in shape_key_materials:
+            material_map[i]=used_index
+            used_index+=1
+        else:
+            material_map[i]=not_used_index
+            not_used_index+=1
+
+    # \e$B%^%F%j%"%k\e(B16\e$B8D$4$H$KJ,3d$7$?%a%C%7%e$r:n@.$9$k\e(B
+    material_index=0
+    mesh_objects=[]
+    while material_index<len(l.materials):
+        # shape\e$B%-!<$G;H$o$l$k=g$KJB$Y$J$*$7$?%^%F%j%"%k\e(B16\e$B8DJ,$N\e(B
+        # \e$B%a%C%7%e$r:n@.$9$k\e(B
+        meshObject, used_vertices=import16MaerialAndMesh(l, 
+                material_index, material_map, face_map, tex_dir)
+        scene.objects.link(meshObject)
+        scene.update()
+        mesh_objects.append(meshObject)
+
+        # enter Edit Mode
+        bpy.ops.object.select_all(action='DESELECT')
+        meshObject.selected=True
+        scene.objects.active=meshObject
+        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
+        # crete shape key
+        importShape(meshObject, l)
+
+        ############################################################
+        # clean up not used vertices
+        ############################################################
+        #progress_print('clean up vertices not used')
+        vertex_map={}
+        mesh=meshObject.data
+        for i, v in enumerate(mesh.verts):
+            if i in used_vertices:
+                vertex_map[i]=len(vertex_map)
+                v.selected=False
+            else:
+                v.selected=True
+                assert(mesh.verts[i].selected)
+        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
+        print("%d vertices selected" % mesh.total_vert_sel)
+        print("used %d/%d" % (len(vertex_map), len(mesh.verts)))
+        bpy.ops.mesh.delete(type='VERT')
+
+        ############################################################
+        # flip face
+        ############################################################
+        bpy.ops.mesh.select_all(action='SELECT')
+        bpy.ops.mesh.flip_normals()
+        bpy.ops.mesh.select_all(action='DESELECT')
+
+        # exit Edit Mode
+        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
+
+        mesh.update()
+        material_index+=16
+
+    return mesh_objects
+
+
+def import16MaerialAndMesh(l, 
+        material_offset, material_map, face_map, tex_dir):
+    # create mesh
+    mesh=bpy.data.meshes.new("Mesh")
+
+    # create object
+    meshObject= bpy.data.objects.new("Mesh", mesh)
+    meshObject.layers[0]=True
+
+    ############################################################
+    # material
+    ############################################################
+    #progress_print('create materials')
+    mesh_material_map={}
+    materials=[]
+    textureMap={}
+    imageMap={}
+    index=0
+    for i in range(material_offset, material_offset+16):
+        try:
+            material_index=material_map[i]
+            m=l.materials[material_index]
+            mesh_material_map[material_index]=index
+        except KeyError:
+            break
+
+        material=createMaterial()
+        material.diffuse_color=([m.diffuse.r, m.diffuse.g, m.diffuse.b])
+        material.alpha=m.diffuse.a
+        material.specular_hardness=int(m.shinness)
+        material.specular_color=([m.specular.r, m.specular.g, m.specular.b])
+        material.mirror_color=([m.ambient.r, m.ambient.g, m.ambient.b])
+        texture_name=m.getTexture()
+        if texture_name!='':
+            if texture_name in textureMap:
+                texture=textureMap[texture_name]
+            else:
+                texture=create_texture(tex_dir, texture_name)
+                textureMap[texture_name]=texture
+                imageMap[material_index]=texture.image
+            #material.add_texture(texture, "UV", {"COLOR", "ALPHA"})
+            material.add_texture(texture, "UV", "COLOR")
+
+        materials.append(material)
+        mesh.add_material(material)
+        # lookup table for assign
+        index+=1
+
+    ############################################################
+    # vertex
+    ############################################################
+    #progress_print('create vertices')
+    # create vertices
+    vertex_groups={}
+    unpackedVertices=[]
+    for v in l.each_vertex():
+        unpackedVertices.extend(
+                convert_coord(v.pos))
+        vertex_groups[v.bone0]=True
+        vertex_groups[v.bone1]=True
+
+    ############################################################
+    # face
+    ############################################################
+    #progress_print('create faces')
+    # create faces
+    mesh_face_indices=[]
+    mesh_face_materials=[]
+    used_vertices=set()
+    for i in range(material_offset, material_offset+16):
+        try:
+            material_index=material_map[i]
+        except KeyError:
+            break
+        face_offset=face_map[material_index]
+        m=l.materials[material_index]
+        material_faces=l.indices[face_offset:face_offset+m.vertex_count]
+        for j in range(0, len(material_faces), 3):
+            i0=material_faces[j]
+            i1=material_faces[j+1]
+            i2=material_faces[j+2]
+            if i2==0:
+                mesh_face_indices.extend([i2, i0, i1, 0])
+            else:
+                mesh_face_indices.extend([i0, i1, i2, 0])
+            mesh_face_materials.append(material_index)
+            used_vertices.add(i0)
+            used_vertices.add(i1)
+            used_vertices.add(i2)
+
+    ############################################################
+    # create vertices & faces
+    ############################################################
+    mesh.add_geometry(
+            int(len(unpackedVertices)/3), 0, int(len(mesh_face_indices)/4))
+    mesh.verts.foreach_set("co", unpackedVertices)
+    mesh.faces.foreach_set("verts_raw", mesh_face_indices)
+    assert(len(l.vertices)==len(mesh.verts))
+
+    ############################################################
+    # face params
+    ############################################################
+    used_map={}
+    mesh.add_uv_texture()
+
+    for face, uv_face, material_index in zip(mesh.faces, 
+            mesh.uv_textures[0].data,
+            mesh_face_materials,
+            ):
+        try:
+            index=mesh_material_map[material_index]
+        except KeyError as message:
+            print(message, mesh_material_map, m)
+            assert(False)
+        face.material_index=index
+        material=mesh.materials[index]
+        used_map[index]=True
+        if material.texture_slots[0]:
+            #texture=material.texture_slots[0].texture
+            #face.image=texture.image
+            #texture.imageFlags|=Blender.Texture.ImageFlags.USEALPHA
+            uv=l.getUV(face.verts[0])
+            uv_face.uv1=[uv.x, 1.0-uv.y]
+
+            uv=l.getUV(face.verts[1])
+            uv_face.uv2=[uv.x, 1.0-uv.y]
+
+            uv=l.getUV(face.verts[2])
+            uv_face.uv3=[uv.x, 1.0-uv.y]
+        if face.material_index in imageMap:
+            uv_face.image=imageMap[face.material_index]
+            uv_face.tex=True
+
+        # set smooth
+        face.smooth = 1
+
+    ############################################################
+    # vertex weight
+    ############################################################
+    # create vertex group
+    for i in vertex_groups.keys():
+        meshObject.add_vertex_group(get_bone_name(l, i))
+
+    # vertex params
+    for i, v, mvert in zip(range(len(l.vertices)), 
+            l.each_vertex(), mesh.verts):
+        mvert.normal=mathutils.Vector(convert_coord(v.normal))
+        #mvert.uvco=convert_uv(v.uv)
+        w1=float(v.weight0)/100.0
+        w2=1.0-w1
+
+        meshObject.add_vertex_to_group(i, 
+                meshObject.vertex_groups[get_bone_name(l, v.bone0)], w1, 'ADD')
+        meshObject.add_vertex_to_group(i, 
+                meshObject.vertex_groups[get_bone_name(l, v.bone1)], w2, 'ADD')
+    mesh.update()
+
+    #progress_print('%s created' % mesh.name)
+    return meshObject, used_vertices
+
+
+def build_bone(armature, b, parent=None):
+    if b.tail_index==0:
+        return
+
+    name=englishmap.getEnglishBoneName(b.getName())
+    bone = armature.edit_bones.new(name if name else b.getName())
+    if parent:
+        bone.head = mathutils.Vector(convert_coord(b.pos))
+        bone.parent=parent
+        bone.connected=True if parent.tail==bone.head else False
+        bone.tail = mathutils.Vector(convert_coord(b.tail))
+        if bone.head==bone.tail:
+            bone.tail=bone.head-mathutils.Vector((0, 1, 0))
+    elif b.__class__ is pmd.BONE_IK:
+        bone.head = mathutils.Vector(convert_coord(b.pos))
+        bone.tail = mathutils.Vector(convert_coord(b.tail))
+    else:
+        # center
+        tail=mathutils.Vector(convert_coord(b.pos))
+        bone.tail = tail
+        bone.head = tail-mathutils.Vector((0, 1, 0))
+
+    for child in b.children:
+        build_bone(armature, child, bone)
+
+
+def importArmature(scene, l):
+    # create armature
+    armature = bpy.data.armatures.new('Armature')
+    # link to object
+    armature_object=bpy.data.objects.new('Armature', armature)
+    scene.objects.link(armature_object)
+    armature_object.x_ray=True
+
+    # armature settings
+    armature.drawtype='OCTAHEDRAL'
+    armature.deform_envelope=False
+    armature.deform_vertexgroups=True
+    armature.x_axis_mirror=True
+
+    # create action
+    #act = Blender.Armature.NLA.NewAction()
+    #act.setActive(armature_object)
+
+    # select only armature object and set edit mode
+    scene.objects.active=armature_object
+    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
+    bpy.ops.object.mode_set(mode='EDIT', toggle=False)
+
+    # create armature
+    for b in l.bones:
+        if not b.parent:
+            build_bone(armature, b)
+
+    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
+    bpy.ops.object.select_all(action='DESELECT')
+
+    ############################################################
+    # IK
+    ############################################################
+    pose = armature_object.pose
+    for ik in l.ik_list:
+        effector=l.bones[ik.target]
+        parent=l.bones[effector.parent_index]
+        name = englishmap.getEnglishBoneName(parent.getName())
+        p_bone = pose.bones[name]
+        if not p_bone:
+            print('not found', name)
+            continue
+        if len(ik.children) >= 16:
+            print('over MAX_CHAINLEN', ik, len(ik.children))
+            continue
+        # IK
+        ik_const = p_bone.constraints.new('IK')
+        ik_const.chain_length=len(ik.children)
+        ik_const.target=armature_object
+        ik_const.subtarget=englishmap.getEnglishBoneName(l.bones[ik.index].getName())
+        # ROT
+        rot_const = p_bone.constraints.new('LIMIT_ROTATION')
+        rot_const.influence = ik.weight
+        rot_const.owner_space = 'LOCAL'
+        rot_const.use_limit_x=True
+        rot_const.use_limit_z=True
+        rot_const.minimum_x=to_radian(ik.iterations)
+        rot_const.maximum_x=to_radian(180)
+        rot_const.minimum_z=to_radian(180 - ik.iterations)
+        rot_const.maximum_z=to_radian(0)
+
+    return armature_object
+    
+
+def importShape(meshObject, l):
+    if len(l.morph_list)==0:
+        return
+
+    # base 
+    base=None
+    for s in l.morph_list:
+        if s.type!=0:
+            continue
+        base=s
+        break
+    assert(base)
+
+    # create base key
+    baseblock=meshObject.add_shape_key("Basis")
+
+    # mesh
+    mesh=meshObject.data
+
+    # each skin
+    for s in l.morph_list:
+        if s.getName()==base.name:
+            # skip base
+            continue
+
+        # restore
+        #for v, base_pos in zip(mesh.verts, baseblock.data):
+        #    v.co=base_pos.co
+        #mesh.update()
+
+        # name
+        name=englishmap.getEnglishSkinName(s.getName())
+        if not name:
+            name=s.getName()
+        new_shape_key=meshObject.add_shape_key(name)
+        #new_shape_key.value=1.0
+
+        # morph
+        for i, offset in zip(s.indices, s.pos_list):
+            try:
+                vertex_index=base.indices[i]
+                new_shape_key.data[vertex_index].co=[p+o for p, o in zip(
+                    mesh.verts[vertex_index].co, convert_coord(offset))]
+            except IndexError as msg:
+                print(IndexError, msg)
+                print(i, len(base.indices))
+                print(vertex_index, len(mesh.verts))
+                print(base.indices[i])
+                break
+            except KeyError:
+                #print 'this mesh not has shape vertices'
+                break
+        
+        # set ipo curve
+        #icu=ipo.addCurve(name)
+        #icu.interpolation = Blender.IpoCurve.InterpTypes.LINEAR
+        #icu.append( (0.0, 0.0) )
+        
+
+def load(filename, context):
+    """
+    load pmd file to context.
+    """
+    io=pmd.IO()
+    if not io.read(filename):
+        print("fail to read", filename)
+        return
+
+    scene=context.scene
+
+    # create root object
+    root=bpy.data.objects.new(os.path.basename(filename), None)
+    scene.objects.link(root)
+
+    # import mesh
+    mesh_objects=importMesh(scene, io, os.path.dirname(filename))
+    for o in mesh_objects:
+        o.parent=root
+
+    # import armature
+    armature_object=importArmature(scene, io)
+    if armature_object:
+        armature_object.parent=root
+        armature = armature_object.data
+        armature.draw_names=True
+
+        # add armature modifier
+        for o in mesh_objects:
+            mod=o.modifiers.new("Modifier", "ARMATURE")
+            mod.object = armature_object
+            mod.use_bone_envelopes=False
+            #o.makeDisplayList()
+    # redraw
+    scene.update()
+
+    print("finised")
+
+
+###############################################################################
+# import operator
+###############################################################################
+class IMPORT_OT_pmd(bpy.types.Operator):
+    '''Import from Metasequoia file format (.pmd)'''
+    bl_idname = "import_scene.pmd"
+    bl_label = 'Import PMD'
+
+    # List of operator properties, the attributes will be assigned
+    # to the class instance from the operator settings before calling.
+
+    path = StringProperty(
+            name="File Path", 
+            description="File path used for importing the PMD file", 
+            maxlen= 1024, default= "")
+    filename = StringProperty(
+            name="File Name", 
+            description="Name of the file.")
+    directory = StringProperty(
+            name="Directory", 
+            description="Directory of the file.")
+
+    def execute(self, context):
+        load(self.properties.path, context)
+        return {'FINISHED'}
+
+    def invoke(self, context, event):
+        wm = context.manager
+        wm.add_fileselect(self)
+        return {'RUNNING_MODAL'}
+
+
+###############################################################################
+# register menu
+###############################################################################
+menu_func = lambda self, context: self.layout.operator(
+        IMPORT_OT_pmd.bl_idname, text="MikuMikuDance model (.pmd)")
+
+
+def register():
+    bpy.types.register(IMPORT_OT_pmd)
+    bpy.types.INFO_MT_file_import.append(menu_func)
+
+def unregister():
+    bpy.types.unregister(IMPORT_OT_pmd)
+    bpy.types.INFO_MT_file_import.remove(menu_func)
+
+
+if __name__=="__main__":
+    register()
+