OSDN Git Service

implementing export_pmx...
[meshio/pymeshio.git] / blender26-meshio / import_pmx.py
old mode 100755 (executable)
new mode 100644 (file)
index 453355b..2fc2766
@@ -5,10 +5,164 @@ PMXモデルをインポートする。
 1マテリアル、1オブジェクトで作成する。
 """
 import os
+import bpy
 from . import bl
+from .pymeshio import pmx
+from .pymeshio.pmx import reader
 
 
+def convert_coord(pos):
+    """
+    Left handed y-up to Right handed z-up
+    """
+    return (pos.x, pos.z, pos.y)
+
+def VtoV(v):
+    return bl.createVector(v.x, v.y, v.z)
+
+def get_object_name(fmt, index, name):
+    """
+    object名を作る。最大21バイト
+    """
+    len_list=[len(name[:i].encode('utf-8')) for i in range(1, len(name)+1, 1)]
+    letter_count=0
+    prefix=fmt.format(index)
+    max_length=21-len(prefix)
+    for str_len in len_list:
+        if str_len>max_length:
+            break
+        letter_count+=1
+    name=prefix+name[:letter_count]
+    print("%s(%d)" % (name, letter_count))
+    return name
+
+def __import_joints(joints, rigidbodies):
+    print("create joints")
+    container=bl.object.createEmpty('Joints')
+    layers=[
+        True, False, False, False, False, False, False, False, False, False,
+        False, False, False, False, False, False, False, False, False, False,
+            ]
+    material=bl.material.create('joint')
+    material.diffuse_color=(1, 0, 0)
+    constraintMeshes=[]
+    for i, c in enumerate(joints):
+        bpy.ops.mesh.primitive_uv_sphere_add(
+                segments=8,
+                ring_count=4,
+                size=0.1,
+                location=(c.position.x, c.position.z, c.position.y),
+                layers=layers
+                )
+        meshObject=bl.object.getActive()
+        constraintMeshes.append(meshObject)
+        mesh=bl.object.getData(meshObject)
+        bl.mesh.addMaterial(mesh, material)
+        meshObject.name=get_object_name("j{0:02}:", i, c.name)
+        #meshObject.draw_transparent=True
+        #meshObject.draw_wire=True
+        meshObject.draw_type='SOLID'
+        rot=c.rotation
+        meshObject.rotation_euler=(-rot.x, -rot.z, -rot.y)
+
+        meshObject[bl.CONSTRAINT_NAME]=c.name
+        meshObject[bl.CONSTRAINT_A]=rigidbodies[c.rigidbody_index_a].name
+        meshObject[bl.CONSTRAINT_B]=rigidbodies[c.rigidbody_index_b].name
+        meshObject[bl.CONSTRAINT_POS_MIN]=VtoV(c.translation_limit_min)
+        meshObject[bl.CONSTRAINT_POS_MAX]=VtoV(c.translation_limit_max)
+        meshObject[bl.CONSTRAINT_ROT_MIN]=VtoV(c.rotation_limit_min)
+        meshObject[bl.CONSTRAINT_ROT_MAX]=VtoV(c.rotation_limit_max)
+        meshObject[bl.CONSTRAINT_SPRING_POS]=VtoV(c.spring_constant_translation)
+        meshObject[bl.CONSTRAINT_SPRING_ROT]=VtoV(c.spring_constant_rotation)
+
+    for meshObject in reversed(constraintMeshes):
+        bl.object.makeParent(container, meshObject)
+
+    return container
+
+def __importRigidBodies(rigidbodies, bones):
+    print("create rigid bodies")
+
+    container=bl.object.createEmpty('RigidBodies')
+    layers=[
+        True, False, False, False, False, False, False, False, False, False,
+        False, False, False, False, False, False, False, False, False, False,
+            ]
+    material=bl.material.create('rigidBody')
+    rigidMeshes=[]
+    for i, rigid in enumerate(rigidbodies):
+        if rigid.bone_index==-1:
+            # no reference bone
+            bone=bones[0]
+        else:
+            bone=bones[rigid.bone_index]
+        pos=rigid.shape_position
+        size=rigid.shape_size
+
+        if rigid.shape_type==0:
+            bpy.ops.mesh.primitive_ico_sphere_add(
+                    location=(pos.x, pos.z, pos.y),
+                    layers=layers
+                    )
+            bpy.ops.transform.resize(
+                    value=(size.x, size.x, size.x))
+        elif rigid.shape_type==1:
+            bpy.ops.mesh.primitive_cube_add(
+                    location=(pos.x, pos.z, pos.y),
+                    layers=layers
+                    )
+            bpy.ops.transform.resize(
+                    value=(size.x, size.z, size.y))
+        elif rigid.shape_type==2:
+            bpy.ops.mesh.primitive_cylinder_add(
+                    location=(pos.x, pos.z, pos.y),
+                    layers=layers
+                    )
+            bpy.ops.transform.resize(
+                    value=(size.x, size.x, size.y))
+        else:
+            assert(False)
+
+        meshObject=bl.object.getActive()
+        mesh=bl.object.getData(meshObject)
+        rigidMeshes.append(meshObject)
+        bl.mesh.addMaterial(mesh, material)
+        meshObject.name=get_object_name("r{0:02}:", i, rigid.name)
+        #meshObject.draw_transparent=True
+        #meshObject.draw_wire=True
+        meshObject.draw_type='WIRE'
+        rot=rigid.shape_rotation
+        meshObject.rotation_euler=(-rot.x, -rot.z, -rot.y)
+
+        meshObject[bl.RIGID_NAME]=rigid.name
+        meshObject[bl.RIGID_SHAPE_TYPE]=rigid.shape_type
+        meshObject[bl.RIGID_PROCESS_TYPE]=rigid.mode
+        meshObject[bl.RIGID_BONE_NAME]=bone.name
+        meshObject[bl.RIGID_GROUP]=rigid.collision_group
+        meshObject[bl.RIGID_INTERSECTION_GROUP]=rigid.no_collision_group
+        meshObject[bl.RIGID_WEIGHT]=rigid.param.mass
+        meshObject[bl.RIGID_LINEAR_DAMPING]=rigid.param.linear_damping
+        meshObject[bl.RIGID_ANGULAR_DAMPING]=rigid.param.angular_damping
+        meshObject[bl.RIGID_RESTITUTION]=rigid.param.restitution
+        meshObject[bl.RIGID_FRICTION]=rigid.param.friction
+
+    for meshObject in reversed(rigidMeshes):
+        bl.object.makeParent(container, meshObject)
+
+    return container
+
 def __create_a_material(m, name, textures_and_images):
+    """
+    materialを作成する
+
+    :Params:
+        m
+            pymeshio.pmx.Material
+        name
+            material name
+        textures_and_images
+            list of (texture, image)
+    """
     material = bl.material.create(name)
     # diffuse
     material.diffuse_shader='FRESNEL'
@@ -28,61 +182,227 @@ def __create_a_material(m, name, textures_and_images):
     material.preview_render_type='FLAT'
     material.use_transparency=True
     # texture
-    texture_index=bl.material.addTexture(material, textures_and_images[m.texture_index][0])
+    if m.texture_index!=-1:
+        bl.material.addTexture(material, textures_and_images[m.texture_index][0])
     return material
 
+def __create_armature(bones, display_slots):
+    """
+    armatureを作成する
+
+    :Params:
+        bones
+            list of pymeshio.pmx.Bone
+    """
+    armature, armature_object=bl.armature.create()
+
+    # create bones
+    bl.armature.makeEditable(armature_object)
+    def create_bone(b):
+        bone=bl.armature.createBone(armature, b.name)
+        # bone position
+        bone.head=bl.createVector(*convert_coord(b.position))
+        if not b.getConnectionFlag():
+            bone.tail=bl.createVector(*convert_coord(b.position))
+        elif not b.getVisibleFlag():
+            bone.tail=bone.head+bl.createVector(0, 1, 0)
+
+        return bone
+    bl_bones=[create_bone(b) for b in bones]
+
+    # build skeleton
+    for b, bone in zip(bones, bl_bones):
+        assert(b.name==bone.name)
+        if b.parent_index!=-1:
+            #print("%s -> %s" % (bones[b.parent_index].name, b.name))
+            parent_bone=bl_bones[b.parent_index]
+            bone.parent=parent_bone
+            if b.getConnectionFlag() and b.tail_index!=-1:
+                assert(b.tail_index!=0)
+                tail_bone=bl_bones[b.tail_index]
+                bone.tail=tail_bone.head
+                bl.bone.setConnected(tail_bone)
+        else:
+            print("no parent %s" % b.name)
+    bl.armature.update(armature)
+
+    # create ik constraint
+    bl.enterObjectMode()
+    pose = bl.object.getPose(armature_object)
+    for b, bone in zip(bones, bl_bones):
+        if b.getIkFlag():
+            ik=b.ik
+            assert(len(ik.link)<16)
+            p_bone=pose.bones[bones[ik.target_index].name]
+            assert(p_bone)
+            constraint=bl.armature.createIkConstraint(
+                    armature_object, p_bone, bone.name,
+                    ik.link, ik.limit_radian, ik.loop)
+    bl.armature.makeEditable(armature_object)
+    bl.armature.update(armature)
+
+    # create bone group
+    bl.enterObjectMode()
+    pose = bl.object.getPose(armature_object)
+    for i, ds in enumerate(display_slots):
+        print(ds)
+        g=bl.object.createBoneGroup(armature_object, ds.name, "THEME%02d" % (i+1))
+        for t, index in ds.references:
+            if t==0:
+                name=bones[index].name
+                try:
+                    pose.bones[name].bone_group=g
+                except KeyError as e:
+                    print("pose %s is not found" % name)
+
+    bl.enterObjectMode()
+    return armature_object
+
 def _execute(filepath):
-    bl.progress_set('load %s' % filepath, 0.0)
+    """
+    importerr 本体
+    """
     print(filepath)
 
-    from .pymeshio.pmx import reader
     model=reader.read_from_file(filepath)
     if not model:
         print("fail to load %s" % filepath)
         return
     print(model)
-    bl.progress_set('loaded', 0.1)
 
     # メッシュをまとめるエンプティオブジェクト
     model_name=model.english_name
     if len(model_name)==0:
         model_name=os.path.basename(filepath)
-    root=bl.object.createEmpty(model_name)
-    root[bl.MMD_MB_NAME]=model.name
-    root[bl.MMD_MB_COMMENT]=model.comment
-    root[bl.MMD_COMMENT]=model.english_comment
+    root_object=bl.object.createEmpty(model_name)
+    root_object[bl.MMD_MB_NAME]=model.name
+    root_object[bl.MMD_MB_COMMENT]=model.comment
+    root_object[bl.MMD_COMMENT]=model.english_comment
+
+    # armatureを作る
+    armature_object=__create_armature(model.bones, model.display_slots)
+    if armature_object:
+        bl.object.makeParent(root_object, armature_object)
 
     # テクスチャを作る
     texture_dir=os.path.dirname(filepath)
-    textures_and_images=[bl.texture.create(os.path.join(texture_dir, t)) 
+    textures_and_images=[bl.texture.create(os.path.join(texture_dir, t))
             for t in model.textures]
     print(textures_and_images)
 
-    def get_name(name, fmt, *args):
-        if len(name.encode("utf-8"))<16:
-            return name
-        else:
-            return fmt.format(*args)
     index_generator=(i for i in model.indices)
     # 頂点配列。(Left handed y-up) to (Right handed z-up)
-    vertices=[(pos.x, pos.z, pos.y) 
+    vertices=[convert_coord(pos)
             for pos in (v.position for v in model.vertices)]
+
     for i, m in enumerate(model.materials):
-        # マテリアル毎にメッシュを作成する
-        print(m.name)
-        #material=__create_a_material(m, get_name(m.name, "material:{0:02}", i), textures_and_images)
-        material=__create_a_material(m, m.name, textures_and_images)
-        mesh, mesh_object=bl.mesh.create("object:{0:02}".format(i))
+        name=get_object_name("{0:02}:", i, m.name)
+        ####################
+        # material
+        ####################
+        material=__create_a_material(m, name, textures_and_images)
+
+        ####################
+        # mesh object
+        ####################
+        # object名はutf-8で21byteまで
+        mesh, mesh_object=bl.mesh.create(name)
         bl.mesh.addMaterial(mesh, material)
         # activate object
         bl.object.deselectAll()
         bl.object.activate(mesh_object)
-        bl.object.makeParent(root, mesh_object)
-        indices=[next(index_generator) 
+        bl.object.makeParent(root_object, mesh_object)
+
+        ####################
+        # vertices & faces
+        ####################
+        indices=[next(index_generator)
                     for _ in range(m.vertex_count)]
-        bl.mesh.addGeometry(mesh, vertices, 
-                [(indices[i], indices[i+1], indices[i+2]) 
+        used_indices=set(indices)
+        bl.mesh.addGeometry(mesh, vertices,
+                [(indices[i], indices[i+1], indices[i+2])
                     for i in range(0, len(indices), 3)])
+        assert(len(model.vertices), len(mesh.vertices))
+
+        # assign material
+        bl.mesh.addUV(mesh)
+        hasTexture=bl.material.hasTexture(material)
+        if hasTexture:
+            index_gen=(i for i in indices)
+            image=(textures_and_images.get[m.texture_index] 
+                    if m.texture_index in textures_and_images
+                    else None)
+        for i, face in enumerate(mesh.faces):
+            bl.face.setMaterial(face, 0)
+            if hasTexture:
+                uv0=model.vertices[next(index_gen)].uv
+                uv1=model.vertices[next(index_gen)].uv
+                uv2=model.vertices[next(index_gen)].uv
+                bl.mesh.setFaceUV(mesh, i, face, [# fix uv
+                    (uv0.x, 1.0-uv0.y),
+                    (uv1.x, 1.0-uv1.y),
+                    (uv2.x, 1.0-uv2.y)
+                    ],
+                    image)
+
+        ####################
+        # armature
+        ####################
+        if armature_object:
+            # armature modifirer
+            bl.modifier.addArmature(mesh_object, armature_object)
+            # set vertex attributes(normal, bone weights)
+            bl.mesh.useVertexUV(mesh)
+            for i, (v,  mvert) in enumerate(zip(model.vertices, mesh.vertices)):
+                bl.vertex.setNormal(mvert, convert_coord(v.normal))
+                if isinstance(v.deform, pmx.Bdef1):
+                    bl.object.assignVertexGroup(mesh_object,
+                            model.bones[v.deform.index0].name, i, 1.0)
+                elif isinstance(v.deform, pmx.Bdef2):
+                    bl.object.assignVertexGroup(mesh_object,
+                            model.bones[v.deform.index0].name, i, v.deform.weight0)
+                    bl.object.assignVertexGroup(mesh_object,
+                            model.bones[v.deform.index1].name, i, 1.0-v.deform.weight0)
+                else:
+                    raise Exception("unknown deform: %s" % v.deform)
+
+        ####################
+        # shape keys
+        ####################
+        # set shape_key pin
+        bl.object.pinShape(mesh_object, True)
+        # create base key
+        baseShapeBlock=bl.object.addShapeKey(mesh_object, bl.BASE_SHAPE_NAME)
+        mesh.update()
+        for m in model.morphs:
+            new_shape_key=bl.object.addShapeKey(mesh_object, m.name)
+            for o in m.offsets:
+                if isinstance(o, pmx.VertexMorphOffset):
+                    bl.shapekey.assign(new_shape_key, 
+                            o.vertex_index, 
+                            mesh.vertices[o.vertex_index].co+
+                            bl.createVector(*convert_coord(o.position_offset)))
+                else:
+                    raise Exception("unknown morph type: %s" % o)
+        # select base shape
+        bl.object.setActivateShapeKey(mesh_object, 0)
+
+        #############################
+        # clean up not used vertices
+        # in the material.
+        #############################
+        bl.mesh.vertsDelete(mesh, [i for i in range(len(mesh.vertices))
+            if i not in used_indices])
+
+    # import rigid bodies
+    rigidbody_object=__importRigidBodies(model.rigidbodies, model.bones)
+    if rigidbody_object:
+        bl.object.makeParent(root_object, rigidbody_object)
+
+    # import joints
+    joint_object=__import_joints(model.joints, model.rigidbodies)
+    if joint_object:
+        bl.object.makeParent(root_object, joint_object)
 
     return {'FINISHED'}