OSDN Git Service

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