OSDN Git Service

08c1d63fec36deded448dd060bb8a4a41676a83c
[meshio/meshio.git] / swig / blender / pmd_import.py
1 #!BPY
2 # coding:utf-8
3 """
4  Name: 'MikuMikuDance model (.pmd)...'
5  Blender: 248
6  Group: 'Import'
7  Tooltip: 'Import PMD file for MikuMikuDance.'
8 """
9 __author__= ["ousttrue"]
10 __version__= "1.1"
11 __url__=()
12 __bpydoc__="""
13 pmd Importer
14
15 This script imports a pmd into Blender for editing.
16
17 0.1: 20091126: first implement.
18 0.2: 20091209: implement IK.
19 0.3: 20091210: implement morph target.
20 0.4: 20100305: use english name.
21 0.5: 20100408: cleanup not used vertices.
22 0.6: 20100416: fix fornt face. texture load fail safe. add progress.
23 0.7: 20100506: C extension.
24 0.8: 20100521: add shape_key group.
25 1.0: 20100530: add invisilbe bone tail(armature layer 2).
26 1.1: 20100608: integrate 2.4 and 2.5.
27 """
28
29 MMD_SHAPE_GROUP_NAME='_MMD_SHAPE'
30 BASE_SHAPE_NAME='Basis'
31
32
33 ###############################################################################
34 # import
35 ###############################################################################
36 import os
37 import sys
38 import re
39 import math
40
41 # C extension
42 from meshio import pmd, englishmap
43
44 def isBlender24():
45     return sys.version_info[0]<3
46
47 if isBlender24():
48     # for 2.4
49     import Blender
50     from Blender import Mathutils
51     import bpy
52
53     # wrapper
54     import bl24 as bl
55 else:
56     # for 2.5
57     import bpy
58     from bpy.props import *
59     import mathutils
60
61     # wrapper
62     import bl25 as bl
63
64     xrange=range
65
66 ###############################################################################
67 # progress bar
68 ###############################################################################
69 def progress_start(base):
70     global progressBar
71     progressBar=bl.ProgressBar(base)
72
73 def progress_finish():
74     global progressBar
75     progressBar.finish()
76
77 def progress_print(message, progress=0.05):
78     global progressBar
79     progressBar.advance(message, progress)
80
81 def progress_set(message, progress):
82     global progressBar
83     progressBar.set(message, progress)
84
85
86 ###############################################################################
87 def convert_coord(pos):
88     """
89     Left handed y-up to Right handed z-up
90     """
91     return (pos.x, pos.z, pos.y)
92
93
94 def convert_uv(uv):
95     return (uv.x, 1.0 - uv.y)
96
97
98 def to_radian(degree):
99     return math.pi * degree / 180
100
101
102 def get_bone_name(l, index):
103     name=englishmap.getEnglishBoneName(l.bones[index].getName())
104     return name if name else l.bones[index].getName()
105
106 def __importShape(obj, l, vertex_map):
107     if len(l.morph_list)==0:
108         return
109
110     # set shape_key pin
111     bl.objectPinShape(obj)
112
113     # find base 
114     base=None
115     for s in l.morph_list:
116         if s.type==0:
117             base=s
118
119             # create vertex group
120             bl.meshAddVertexGroup(obj, MMD_SHAPE_GROUP_NAME)
121             hasShape=False
122             for i in s.indices:
123                 if isBlender24():
124                     if i in vertex_map:
125                         hasShape=True
126                         bl.meshAssignVertexGroup(
127                                 obj, MMD_SHAPE_GROUP_NAME, vertex_map[i], 0)
128                 else:
129                     hasShape=True
130                     bl.meshAssignVertexGroup(
131                             obj, MMD_SHAPE_GROUP_NAME, i, 0)
132             if not hasShape:
133                 return
134     assert(base)
135
136     # create base key
137     baseShapeBlock=bl.objectAddShapeKey(obj, BASE_SHAPE_NAME)
138     # mesh
139     mesh=bl.objectGetData(obj)
140     mesh.update()
141
142     # each skin
143     for s in l.morph_list:
144         if s.type==0:
145             continue
146
147         # name
148         name=englishmap.getEnglishSkinName(s.getName())
149         if not name:
150             name=s.getName()
151
152         if isBlender24():
153             # 24
154             for index, offset in zip(s.indices, s.pos_list):
155                 try:
156                     vertex_index=vertex_map[base.indices[index]]
157                     v=mesh.verts[vertex_index].co
158                     offset=convert_coord(offset)
159                     v[0]+=offset[0]
160                     v[1]+=offset[1]
161                     v[2]+=offset[2]
162                 except IndexError as msg:
163                     print(msg)
164                     print(index, len(base.indices), len(vertex_map))
165                     print(len(mesh.verts))
166                     print(base.indices[index])
167                     print(vertex_index)
168                     break
169                 except KeyError:
170                     #print 'this mesh not has shape vertices'
171                     break
172
173             # create shapekey block
174             new_shape_key=bl.objectAddShapeKey(obj, name)
175
176             # copy vertex to shape key
177             mesh.update()
178             
179             # restore
180             for mv, v in zip(mesh.verts, baseShapeBlock.getData()):
181                 mv.co[0] = v[0]
182                 mv.co[1] = v[1]
183                 mv.co[2] = v[2]
184             mesh.update()
185
186         else:
187             # 25
188             new_shape_key=bl.objectAddShapeKey(obj, name)
189
190             for index, offset in zip(s.indices, s.pos_list):
191                 try:
192                     vertex_index=base.indices[index]
193                     bl.shapeKeyAssign(new_shape_key, vertex_index,
194                             mesh.verts[vertex_index].co+
195                             bl.createVector(*convert_coord(offset)))
196                 except IndexError as msg:
197                     print(msg)
198                     print(index, len(base.indices), len(vertex_map))
199                     print(len(mesh.verts))
200                     print(base.indices[index])
201                     print(vertex_index)
202                     break
203                 except KeyError:
204                     #print 'this mesh not has shape vertices'
205                     break
206
207     # select base shape
208     bl.objectActivateShapeKey(obj, 0)
209
210
211 def __build(armature, b, p, parent):
212     name=englishmap.getEnglishBoneName(b.getName())
213     if not name:
214         name=b.getName()
215
216     bone=bl.createArmatureBone(armature, name)
217
218     if b.tail_index==0:
219         # 先端
220         assert(b.type==6 or b.type==7)
221         bone.head = bl.createVector(*convert_coord(b.pos))
222         bone.tail=bone.head+bl.createVector(0, 1, 0)
223         assert(parent)
224         bone.parent=parent
225         if bone.name=="center_t":
226             # センターボーンは(0, 1, 0)の方向を向いていないと具合が悪い
227             parent.tail=parent.head+bl.createVector(0, 1, 0)
228             bone.head=parent.tail
229             bone.tail=bone.head+bl.createVector(0, 1, 0)
230         else:
231             assert(parent.tail==bone.head)
232         bl.boneSetConnected(bone)
233         # armature layer 2
234         bl.boneLayerMask(bone, [0, 1])
235     else:
236         bone.head = bl.createVector(*convert_coord(b.pos))
237         bone.tail = bl.createVector(*convert_coord(b.tail))
238         if parent:
239             bone.parent=parent
240             if parent.tail==bone.head:
241                 bl.boneSetConnected(bone)
242
243     if bone.head==bone.tail:
244         bone.tail=bone.head+bl.createVector(0, 1, 0)
245
246     for c in b.children:
247         __build(armature, c, b, bone)
248
249
250 def __importArmature(scene, l):
251     # build bone
252     armature, armature_object=bl.createArmature(scene)
253     bl.armatureMakeEditable(scene, armature_object)
254     for b in l.bones:
255         if not b.parent:
256             __build(armature, b, None, None)
257     bl.armatureUpdate(armature)
258     bl.exitEditMode()
259
260     # IK constraint
261     pose = bl.objectGetPose(armature_object)
262     for ik in l.ik_list:
263         target=l.bones[ik.target]
264         name = englishmap.getEnglishBoneName(target.getName())
265         if not name:
266             name=target.getName()
267         p_bone = pose.bones[name]
268         if not p_bone:
269             print('not found', name)
270             continue
271         if len(ik.children) >= 16:
272             print('over MAX_CHAINLEN', ik, len(ik.children))
273             continue
274         effector_name=englishmap.getEnglishBoneName(
275                 l.bones[ik.index].getName())
276         if not effector_name:
277             effector_name=l.bones[ik.index].getName()
278
279         constraint=bl.createIkConstraint(armature_object, 
280                 p_bone, effector_name, ik)
281
282     bl.armatureMakeEditable(scene, armature_object)
283     bl.armatureUpdate(armature)
284     bl.exitEditMode()
285
286     return armature_object
287         
288
289 def __import16MaerialAndMesh(meshObject, l, 
290         material_order, face_map, tex_dir):
291
292     mesh=bl.objectGetData(meshObject)
293     ############################################################
294     # material
295     ############################################################
296     progress_print('create materials')
297     mesh_material_map={}
298     textureMap={}
299     imageMap={}
300     index=0
301
302     for material_index in material_order:
303         try:
304             m=l.materials[material_index]
305             mesh_material_map[material_index]=index
306         except KeyError:
307             break
308
309         material=bl.createPmdMaterial(m)
310
311         texture_name=m.getTexture()
312         if texture_name!='':
313             if texture_name in textureMap:
314                 texture=textureMap[texture_name]
315             else:
316                 try:
317                     texture, image=bl.createTexture(
318                             os.path.join(tex_dir, texture_name))
319                     textureMap[texture_name]=texture
320                     imageMap[material_index]=image
321                 except:
322                     continue
323             bl.materialAddTexture(material, texture)
324         bl.meshAddMaterial(mesh, material)
325         index+=1
326
327     ############################################################
328     # vertex
329     ############################################################
330     progress_print('create vertices')
331     # create vertices
332     vertices=[]
333     if isBlender24():
334         for v in l.each_vertex():
335             vertices.append(convert_coord(v.pos))
336     else:
337         for v in l.each_vertex():
338             vertices.extend(convert_coord(v.pos))
339
340     ############################################################
341     # face
342     ############################################################
343     progress_print('create faces')
344     # create faces
345     mesh_face_indices=[]
346     mesh_face_materials=[]
347     used_vertices=set()
348
349     for material_index in material_order:
350         face_offset=face_map[material_index]
351         m=l.materials[material_index]
352         material_faces=l.indices[face_offset:face_offset+m.vertex_count]
353
354         def degenerate(i0, i1, i2):
355             """
356             縮退しているか?
357             """
358             return i0==i1 or i1==i2 or i2==i0
359
360         for j in xrange(0, len(material_faces), 3):
361             i0=material_faces[j]
362             i1=material_faces[j+1]
363             i2=material_faces[j+2]
364             if i2==0:
365                 triangle=[i2, i0, i1]
366             else:
367                 triangle=[i0, i1, i2]
368             if degenerate(*triangle):
369                 continue
370             if isBlender24():
371                 mesh_face_indices.append(triangle[0:3])
372             else:
373                 mesh_face_indices.extend(
374                         [triangle[0], triangle[1], triangle[2], 0])
375             mesh_face_materials.append(material_index)
376             used_vertices.add(i0)
377             used_vertices.add(i1)
378             used_vertices.add(i2)
379
380     ############################################################
381     # create vertices & faces
382     ############################################################
383     bl.meshCreateVerteicesAndFaces(mesh, vertices, mesh_face_indices)
384
385     ############################################################
386     # vertex bone weight
387     ############################################################
388     # create vertex group
389     vertex_groups={}
390     for v in l.each_vertex():
391         vertex_groups[v.bone0]=True
392         vertex_groups[v.bone1]=True
393     for i in vertex_groups.keys():
394         bl.meshAddVertexGroup(meshObject, get_bone_name(l, i))
395
396     # vertex params
397     bl.meshUseVertexUv(mesh)
398     for i, v, mvert in zip(xrange(len(l.vertices)), 
399         l.each_vertex(), mesh.verts):
400         # normal, uv
401         bl.vertexSetNormal(mvert, convert_coord(v.normal))
402         bl.vertexSetUv(mvert, convert_uv(v.uv))
403         # bone weight
404         w1=float(v.weight0)/100.0
405         w2=1.0-w1
406         bl.meshAssignVertexGroup(meshObject, get_bone_name(l, v.bone0),
407             i,  w1)
408         bl.meshAssignVertexGroup(meshObject, get_bone_name(l, v.bone1),
409             i,  w2)
410
411     ############################################################
412     # face params
413     ############################################################
414     used_map={}
415     bl.meshAddUV(mesh)
416
417     if isBlender24():
418         for face, material_index in zip(mesh.faces, mesh_face_materials):
419             try:
420                 index=mesh_material_map[material_index]
421             except KeyError as message:
422                 print(message, mesh_material_map, m)
423                 assert(False)
424             face.mat=index
425             material=mesh.materials[index]
426             texture=material.getTextures()[0]
427             used_map[index]=True
428             if texture:
429                 face.image=texture.tex.image
430                 texture.tex.imageFlags|=Blender.Texture.ImageFlags.USEALPHA
431                 face.uv=[
432                     face.verts[0].uvco, face.verts[1].uvco, face.verts[2].uvco]
433             # set smooth
434             face.smooth = 1
435         # flip
436         mesh.flipNormals()
437     else:
438         for face, uv_face, material_index in zip(mesh.faces, 
439                 mesh.uv_textures[0].data,
440                 mesh_face_materials,
441                 ):
442             try:
443                 index=mesh_material_map[material_index]
444             except KeyError as message:
445                 print(message, mesh_material_map, m)
446                 assert(False)
447             face.material_index=index
448             material=mesh.materials[index]
449             used_map[index]=True
450             if material.texture_slots[0]:
451                 uv=l.getUV(face.verts[0])
452                 uv_face.uv1=[uv.x, 1.0-uv.y]
453
454                 uv=l.getUV(face.verts[1])
455                 uv_face.uv2=[uv.x, 1.0-uv.y]
456
457                 uv=l.getUV(face.verts[2])
458                 uv_face.uv3=[uv.x, 1.0-uv.y]
459             if face.material_index in imageMap:
460                 uv_face.image=imageMap[face.material_index]
461                 uv_face.tex=True
462
463             # set smooth
464             face.smooth = 1
465     mesh.update()
466
467     ############################################################
468     # clean up not used vertices
469     ############################################################
470     progress_print('clean up vertices not used')
471     remove_vertices=[]
472     vertex_map={}
473     for i, v in enumerate(l.each_vertex()):
474         if i in used_vertices:
475             vertex_map[i]=len(vertex_map)
476         else:
477             remove_vertices.append(i)
478
479     bl.meshVertsDelete(mesh, remove_vertices)
480
481     progress_print('%s created' % mesh.name)
482     return vertex_map
483
484
485 def __importMesh(scene, io, tex_dir):
486     """
487     @param l[in] mmd.PMDLoader
488     @param filename[in]
489     """
490     ############################################################
491     # shpaeキーで使われるマテリアル優先的に前に並べる
492     ############################################################
493     # shapeキーで使われる頂点インデックスを集める
494     shape_key_used_vertices=set()
495     if len(io.morph_list)>0:
496         # base 
497         base=None
498         for s in io.morph_list:
499             if s.type!=0:
500                 continue
501             base=s
502             break
503         assert(base)
504
505         for index in base.indices:
506             shape_key_used_vertices.add(index)
507
508     # マテリアルに含まれる頂点がshape_keyに含まれるか否か?
509     def isMaterialUsedInShape(offset, m):
510         for i in xrange(offset, offset+m.vertex_count): 
511             if io.indices[i] in shape_key_used_vertices:
512                 return True
513
514     # shapeキーで使われるマテリアルを記録する
515     shape_key_materials=set()
516     # 各マテリアルの開始頂点インデックスを記録する
517     face_map={}
518     face_count=0
519     for i, m in enumerate(io.materials):
520         face_map[i]=face_count
521         if isMaterialUsedInShape(face_count, m):
522             shape_key_materials.add(i)
523         face_count+=m.vertex_count
524
525     # list化
526     material_order=list(shape_key_materials)
527
528     # shapeキーに使われていないマテリアルを後ろに追加
529     for i in range(len(io.materials)):
530         if not i in material_order:
531             material_order.append(i)
532
533     # マテリアル16個ごとに分割したメッシュを作成する
534     material_offset=0
535     mesh_objects=[]
536     while material_offset<len(io.materials):
537         mesh, meshObject=bl.createMesh(scene, 'mesh')
538         # create object
539         #meshObject.layers = [1]
540         mesh_objects.append(meshObject)
541
542         # activate object
543         bl.objectDeselectAll()
544         bl.objectActivate(scene, meshObject)
545
546         # shapeキーで使われる順に並べなおしたマテリアル16個分の
547         # メッシュを作成する
548         vertex_map=__import16MaerialAndMesh(
549                 meshObject, io, 
550                 material_order[material_offset:material_offset+16], 
551                 face_map, tex_dir)
552
553         # enter Edit Mode
554         #bl.enterEditMode()
555
556         # crete shape key
557         __importShape(meshObject, io, vertex_map)
558
559         # exit Edit Mode
560         #bl.exitEditMode()
561
562         mesh.update()
563         material_offset+=16
564
565     return mesh_objects
566
567
568 def __execute(filename, scene):
569     """
570     load pmd file to context.
571     """
572     # load pmd
573     progress_set('load %s' % filename, 0.0)
574
575     io=pmd.IO()
576     if not io.read(filename):
577         print("fail to load %s" % filename)
578         return
579     progress_set('loaded %s' % filename, 0.1)
580
581     # create root object
582     model_name=io.getEnglishName()
583     if len(model_name)==0:
584         model_name=io.getName()
585     root=bl.createEmptyObject(scene, model_name)
586
587     # import mesh
588     mesh_objects=__importMesh(scene, io, os.path.dirname(filename))
589     for o in mesh_objects:
590         bl.objectMakeParent(root, o)
591
592     # import armature
593     armature_object=__importArmature(scene, io)
594     if armature_object:
595         bl.objectMakeParent(root, armature_object)
596         armature = bl.objectGetData(armature_object) 
597
598         # add armature modifier
599         for o in mesh_objects:
600             bl.objectAddArmatureModifier(o, armature_object)
601
602         # Limitation
603         for n, b in bl.objectGetPose(armature_object).bones.items():
604             bl.poseBoneLimit(n, b)
605
606     # select objects
607     bl.objectSelect(root)
608     for o in mesh_objects:
609         bl.objectSelect(o)
610     bl.objectSelect(armature_object)
611  
612
613 if isBlender24():
614     # for 2.4
615     def execute_24(filename):
616         """
617         @param filename
618         """
619         filename=filename.decode(bl.INTERNAL_ENCODING)
620         print(bl.INTERNAL_ENCODING, bl.FS_ENCODING)
621
622         # set object mode
623         mode_edit = Blender.Window.EditMode() 
624         if mode_edit: 
625             Blender.Window.EditMode(0)
626             
627         progress_start('pmd_import')
628         scene = bpy.data.scenes.active
629         __execute(filename, scene)
630         scene.update(0)
631         progress_finish()
632
633         # restore edit mode
634         if mode_edit: 
635             Blender.Window.EditMode(1)
636         Blender.Window.RedrawAll()
637
638     Blender.Window.FileSelector(
639             execute_24, 
640             'Import PMD file', 
641             Blender.sys.makename(ext='.pmd'))
642
643 else:
644     # for 2.5
645     def execute_25(*args):
646         progress_start('pmd_import')
647         __execute(*args)
648         progress_finish()
649
650     # import operator
651     class IMPORT_OT_pmd(bpy.types.Operator):
652         bl_idname = "import_scene.pmd"
653         bl_label = 'Import PMD'
654
655         # List of operator properties, the attributes will be assigned
656         # to the class instance from the operator settings before calling.
657
658         path = StringProperty(
659                 name="File Path", 
660                 description="File path used for importing the PMD file", 
661                 maxlen= 1024, default= "")
662         filename = StringProperty(
663                 name="File Name", 
664                 description="Name of the file.")
665         directory = StringProperty(
666                 name="Directory", 
667                 description="Directory of the file.")
668
669         def execute(self, context):
670             execute_25(self.properties.path, context.scene)
671             return 'FINISHED'
672
673         def invoke(self, context, event):
674             wm = context.manager
675             wm.add_fileselect(self)
676             return 'RUNNING_MODAL'
677
678     # register menu
679     def menu_func(self, context): 
680         self.layout.operator(IMPORT_OT_pmd.bl_idname, 
681                 text="MikuMikuDance model (.pmd)")
682
683     def register():
684         bpy.types.register(IMPORT_OT_pmd)
685         bpy.types.INFO_MT_file_import.append(menu_func)
686
687     def unregister():
688         bpy.types.unregister(IMPORT_OT_pmd)
689         bpy.types.INFO_MT_file_import.remove(menu_func)
690
691     if __name__=="__main__":
692         register()
693