OSDN Git Service

pmd. sort material order.
[meshio/meshio.git] / swig / blender24 / 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.0"
11 __url__=()
12 __bpydoc__="""
13 0.1: 20091126
14 0.2: 20091209 implement IK.
15 0.3: 20091210 implement morph target.
16 0.4: 20100305 use english name.
17 0.5: 20100408 cleanup not used vertices.
18 0.6: 20100416 fix fornt face. texture load fail safe. add progress.
19 0.7: 20100506 C extension.
20 0.8: 20100521 add shape_key group.
21 1.0: 20100530 add invisilbe bone tail(armature layer 2).
22 """
23 import Blender
24 from Blender import Mathutils
25 import bpy
26
27 import os
28 import sys
29 import re
30
31 # extension
32 from meshio import pmd, englishmap
33
34
35 FS_ENCODING=sys.getfilesystemencoding()
36 if os.path.exists(os.path.dirname(sys.argv[0])+"/utf8"):
37     INTERNAL_ENCODING='utf-8'
38 else:
39     INTERNAL_ENCODING=FS_ENCODING
40
41
42 MMD_SHAPE_GROUP_NAME='_MMD_SHAPE'
43
44
45 ###############################################################################
46 # ProgressBar
47 ###############################################################################
48 class ProgressBar(object):
49     def __init__(self, base):
50         print "#### %s ####" % base
51         self.base=base
52         self.start=Blender.sys.time() 
53         self.set('<start>', 0)
54
55     def advance(self, message, progress):
56         self.progress+=float(progress)
57         self._print(message)
58
59     def set(self, message, progress):
60         self.progress=float(progress)
61         self._print(message)
62
63     def _print(self, message):
64         print message
65         message="%s: %s" % (self.base, message)
66         if message.__class__ is unicode:
67             message=message.encode(FS_ENCODING)
68         Blender.Window.DrawProgressBar(self.progress, message)
69
70     def finish(self):
71         self.progress=1.0
72         message='finished in %.2f sec' % (Blender.sys.time()-self.start)
73         self.set(message, 1.0)
74
75 def progress_start(base):
76     global progressBar
77     progressBar=ProgressBar(base)
78
79 def progress_finish():
80     global progressBar
81     progressBar.finish()
82
83 def progress_print(message, progress=0.05):
84     global progressBar
85     progressBar.advance(message, progress)
86
87 def progress_set(message, progress):
88     global progressBar
89     progressBar.set(message, progress)
90
91
92 ###############################################################################
93 # functions
94 ###############################################################################
95 def convert_coord(pos):
96     """
97     Left handed y-up to Right handed z-up
98     """
99     return (pos.x, pos.z, pos.y)
100
101
102 def convert_uv(uv):
103     return (uv.x, 1.0 - uv.y)
104
105
106 def get_bone_name(l, index):
107     name=englishmap.getEnglishBoneName(l.bones[index].getName())
108     return name if name else l.bones[index].getName().encode(INTERNAL_ENCODING)
109
110
111 def createMaterial():
112     """
113     create default materil 
114     """
115     material=Blender.Material.New()
116     material.setDiffuseShader(Blender.Material.Shaders.DIFFUSE_TOON)
117     material.setRef(1)
118     material.diffuseSize = 3.14/2
119     material.setDiffuseSmooth(0)
120     material.setSpecShader(Blender.Material.Shaders.SPEC_TOON)
121     material.setSpecSize(0)
122     material.setSpec(0)
123     return material
124
125
126 def importMesh(scene, l, tex_dir):
127     """
128     @param l[in] mmd.PMDLoader
129     @param filename[in]
130     """
131
132     ############################################################
133     # shpaeキーで使われるマテリアル優先的に前に並べる
134     ############################################################
135     # shapeキーで使われる頂点インデックスを集める
136     shape_key_used_vertices=set()
137     if len(l.morph_list)>0:
138         # base 
139         base=None
140         for s in l.morph_list:
141             if s.type!=0:
142                 continue
143             base=s
144             break
145         assert(base)
146
147         for index in base.indices:
148             shape_key_used_vertices.add(index)
149
150     # マテリアルに含まれる頂点がshape_keyに含まれるか否か?
151     def isMaterialUsedInShape(offset, m):
152         for i in xrange(offset, offset+m.vertex_count): 
153             if l.indices[i] in shape_key_used_vertices:
154                 return True
155
156     # shapeキーで使われるマテリアルを記録する
157     shape_key_materials=set()
158     # 各マテリアルの開始頂点インデックスを記録する
159     face_map={}
160     face_count=0
161     for i, m in enumerate(l.materials):
162         face_map[i]=face_count
163         if isMaterialUsedInShape(face_count, m):
164             shape_key_materials.add(i)
165         face_count+=m.vertex_count
166
167     # list化
168     material_order=list(shape_key_materials)
169
170     # shapeキーに使われていないマテリアルを後ろに追加
171     for i in range(len(l.materials)):
172         if not i in material_order:
173             material_order.append(i)
174
175     # マテリアル16個ごとに分割したメッシュを作成する
176     material_offset=0
177     mesh_objects=[]
178     while material_offset<len(l.materials):
179         # create mesh
180         mesh = Blender.Mesh.New()
181         mesh.vertexUV = 1
182         # create object
183         obj = scene.objects.new(mesh)
184         obj.layers = [1]
185         mesh_objects.append(obj)
186
187         # shapeキーで使われる順に並べなおしたマテリアル16個分の
188         # メッシュを作成する
189         vertex_map=import16MaerialAndMesh(
190                 mesh, l, 
191                 material_order[material_offset:material_offset+16], 
192                 face_map, tex_dir)
193
194         # crete shape key
195         importShape(obj, l, vertex_map)
196
197         mesh.update()
198         material_offset+=16
199
200     return mesh_objects
201
202
203 def import16MaerialAndMesh(mesh, l, material_order, face_map, tex_dir):
204     ############################################################
205     # material
206     ############################################################
207     progress_print('create materials')
208     mesh_material_map={}
209     materials=[]
210     index=0
211     for material_index in material_order:
212         try:
213             m=l.materials[material_index]
214             mesh_material_map[material_index]=index
215         except KeyError:
216             break
217
218         material=createMaterial()
219         material.setRGBCol([m.diffuse.r, m.diffuse.g, m.diffuse.b])
220         material.setAlpha(m.diffuse.a)
221         material.setSpec(m.shinness*0.1)
222         material.setSpecCol([m.specular.r, m.specular.g, m.specular.b])
223         material.setMirCol([m.ambient.r, m.ambient.g, m.ambient.b])
224         material.enableSSS=True if m.flag==1 else False
225         # set texture
226         if m.getTexture()!='':
227             tex_file=re.compile('\*.*.spa$').sub('', m.getTexture())
228             tex_path = os.path.join(tex_dir, tex_file).encode(
229                     INTERNAL_ENCODING)
230             tex = Blender.Texture.New()
231             tex.setType("Image")
232             try:
233                 tex.image = Blender.Image.Load(tex_path)
234                 material.setTexture(0, tex)
235                 material.getTextures()[0].texco = Blender.Texture.TexCo.UV
236             except IOError:
237                 print material.name, "fail to load", tex_path
238         materials.append(material)
239         # lookup table for assign
240         index+=1
241     mesh.materials=materials
242
243     ############################################################
244     # vertex
245     ############################################################
246     progress_print('create vertices')
247     # create vertices
248     vertex_groups={}
249     vertices=[]
250     for v in l.each_vertex():
251         vertices.append(convert_coord(v.pos))
252         vertex_groups[v.bone0]=True
253         vertex_groups[v.bone1]=True
254     mesh.verts.extend(vertices)
255
256     # create vertex group
257     for i in vertex_groups.keys():
258         mesh.addVertGroup(get_bone_name(l, i))
259
260     # vertex params
261     for i, v, mvert in zip(xrange(len(l.vertices)), l.each_vertex(), mesh.verts):
262         mvert.no=Mathutils.Vector(*convert_coord(v.normal))
263         mvert.uvco=convert_uv(v.uv)
264         w1=float(v.weight0)/100.0
265         w2=1.0-w1
266         mesh.assignVertsToGroup(get_bone_name(l, v.bone0), [i], w1, 
267                 Blender.Mesh.AssignModes.ADD)
268         mesh.assignVertsToGroup(get_bone_name(l, v.bone1), [i], w2, 
269                 Blender.Mesh.AssignModes.ADD)    
270
271     ############################################################
272     # face
273     ############################################################
274     progress_print('create faces')
275     # create faces
276     mesh_face_indices=[]
277     mesh_face_materials=[]
278     used_vertices=set()
279
280     def degenerate(i0, i1, i2):
281         return i0==i1 or i1==i2 or i2==i0
282
283     for material_index in material_order:
284         face_offset=face_map[material_index]
285         m=l.materials[material_index]
286         material_faces=l.indices[face_offset:face_offset+m.vertex_count]
287         for j in xrange(0, len(material_faces), 3):
288             i0=material_faces[j]
289             i1=material_faces[j+1]
290             i2=material_faces[j+2]
291             triangle=[i0, i1, i2]
292             if degenerate(*triangle):
293                 continue
294             mesh_face_indices.append(triangle)
295             mesh_face_materials.append(material_index)
296             used_vertices.add(i0)
297             used_vertices.add(i1)
298             used_vertices.add(i2)
299
300     mesh.faces.extend(mesh_face_indices, ignoreDups=True)
301
302     # face params
303     used_map={}
304     mesh.addUVLayer('NewUV')
305     for face, material_index in zip(mesh.faces, mesh_face_materials):
306         try:
307             index=mesh_material_map[material_index]
308         except KeyError, message:
309             print message, mesh_material_map, m
310             assert(False)
311         face.mat=index
312         material=mesh.materials[index]
313         texture=material.getTextures()[0]
314         used_map[index]=True
315         if texture:
316             face.image=texture.tex.image
317             texture.tex.imageFlags|=Blender.Texture.ImageFlags.USEALPHA
318             face.uv=[face.verts[0].uvco, face.verts[1].uvco, face.verts[2].uvco]
319         # set smooth
320         face.smooth = 1
321     # flip
322     mesh.flipNormals()
323
324     ############################################################
325     # clean up not used vertices
326     ############################################################
327     progress_print('clean up vertices not used')
328     remove_vertices=[]
329     vertex_map={}
330     for i, v in enumerate(l.each_vertex()):
331         if i in used_vertices:
332             vertex_map[i]=len(vertex_map)
333         else:
334             remove_vertices.append(i)
335     mesh.verts.delete(remove_vertices)
336
337     progress_print('%s created' % mesh.name)
338     return vertex_map
339
340
341 class Builder(object):
342     def __init__(self):
343         self.boneMap={}
344
345     def build(self, armature, bones):
346         for b in bones:
347             if not b.parent:
348                 self.__build(armature, b, None, None)
349         armature.update()
350
351     def __build(self, armature, b, p, parent):
352         name=englishmap.getEnglishBoneName(b.getName())
353         if not name:
354             name=b.getName().encode(INTERNAL_ENCODING)
355         self.boneMap[name]=b
356
357         bone=Blender.Armature.Editbone()
358         bone.name=name
359         armature.bones[name]=bone
360
361         if b.tail_index==0:
362             # 先端
363             assert(b.type==6 or b.type==7)
364             bone.head = Mathutils.Vector(*convert_coord(b.pos))
365             bone.tail=bone.head+Mathutils.Vector(0, 1, 0)
366             assert(parent)
367             bone.parent=parent
368             if bone.name=="center_t":
369                 # センターボーンは(0, 1, 0)の方向を向いていないと具合が悪い
370                 parent.tail=parent.head+Mathutils.Vector(0, 1, 0)
371                 bone.head=parent.tail
372                 bone.tail=bone.head+Mathutils.Vector(0, 1, 0)
373             else:
374                 assert(parent.tail==bone.head)
375             bone.options=[Blender.Armature.CONNECTED]
376             # armature layer 2
377             bone.layerMask = (1<<1)
378         else:
379             bone.head = Mathutils.Vector(*convert_coord(b.pos))
380             bone.tail = Mathutils.Vector(*convert_coord(b.tail))
381             if parent:
382                 bone.parent=parent
383                 if parent.tail==bone.head:
384                     bone.options=[Blender.Armature.CONNECTED]
385
386         if bone.head==bone.tail:
387             bone.tail=bone.head+Mathutils.Vector(0, 1, 0)
388
389         for c in b.children:
390             self.__build(armature, c, b, bone)
391
392
393 def importArmature(scene, l):
394     # create armature
395     armature = Blender.Armature.New()
396     # link to object
397     armature_object = scene.objects.new(armature)
398     # create action
399     act = Blender.Armature.NLA.NewAction()
400     act.setActive(armature_object)
401     # set XRAY
402     armature_object.drawMode = (
403             armature_object.drawMode | Blender.Object.DrawModes.XRAY)
404     # armature settings
405     armature.drawType = Blender.Armature.OCTAHEDRON
406     armature.drawNames=True
407     armature.envelopes = False
408     armature.vertexGroups = True
409     armature.mirrorEdit = True
410
411     # create armature
412     armature.makeEditable()
413
414     ############################################################
415     # build bone
416     ############################################################
417     builder=Builder()
418     builder.build(armature, l.bones)
419
420     ############################################################
421     # IK
422     ############################################################
423     pose = armature_object.getPose()
424     cSetting = Blender.Constraint.Settings
425     for ik in l.ik_list:
426         # IKtarget->parent(=IK).name
427         target=l.bones[ik.target]
428         name = englishmap.getEnglishBoneName(target.getName())
429         p_bone = pose.bones[name]
430         if not p_bone:
431             print 'not found', name
432             continue
433         if len(ik.children) >= 16:
434             print 'over MAX_CHAINLEN', ik, len(ik.children)
435             continue
436         # IK solver
437         ik_solver = p_bone.constraints.append(Blender.Constraint.Type.IKSOLVER)
438         ik_solver[cSetting.CHAINLEN]=len(ik.children)
439         ik_solver[cSetting.TARGET]=armature_object
440         ik_solver[cSetting.USETIP]=False
441
442         effector_name=englishmap.getEnglishBoneName(
443                 l.bones[ik.index].getName())
444         if not effector_name:
445             effector_name=l.bones[ik.index].getName()
446
447         ik_solver[cSetting.BONE]=effector_name
448         #ik_solver.influence=ik.weight
449         # not used. place folder when export.
450         ik_solver[cSetting.ROTWEIGHT]=ik.weight
451         ik_solver[cSetting.ITERATIONS]=ik.iterations * 10
452
453     armature.makeEditable()
454     armature.update()
455
456     return armature_object
457     
458
459 def importShape(obj, l, vertex_map):
460     if len(l.morph_list)==0:
461         return
462     obj.pinShape=True
463     mesh=obj.getData(mesh=True)
464
465     # find base 
466     base=None
467     for s in l.morph_list:
468         if s.type==0:
469             base=s
470
471             # create vertex group
472             mesh.addVertGroup(MMD_SHAPE_GROUP_NAME)
473             indices=[]
474             hasShape=False
475             for i in s.indices:
476                 if i in vertex_map:
477                     hasShape=True
478                     indices.append(vertex_map[i])
479             mesh.assignVertsToGroup(MMD_SHAPE_GROUP_NAME, indices, 0, 
480                     Blender.Mesh.AssignModes.ADD)
481             if not hasShape:
482                 return
483
484             # create base key
485             mesh.insertKey()
486             assert(len(mesh.key.blocks)==1)
487             baseShapeIndex=0
488             baseShapeBlock=mesh.key.blocks[baseShapeIndex]
489             baseShapeBlock.name='Basis'
490             obj.activeShape=baseShapeIndex
491             mesh.update()
492             break
493
494     assert(base)
495
496     # each skin
497     for s in l.morph_list:
498         if s.name==base.name:
499             continue
500
501         for index, offset in zip(s.indices, s.pos_list):
502             try:
503                 vertex_index=vertex_map[base.indices[index]]
504                 v=mesh.verts[vertex_index].co
505                 offset=convert_coord(offset)
506                 v[0]+=offset[0]
507                 v[1]+=offset[1]
508                 v[2]+=offset[2]
509             except IndexError, msg:
510                 print msg
511                 print index, len(base.indices), len(vertex_map)
512                 print len(mesh.verts)
513                 print base.indices[index]
514                 print vertex_index
515                 break
516             except KeyError:
517                 #print 'this mesh not has shape vertices'
518                 break
519
520         # get skin name
521         name=englishmap.getEnglishSkinName(s.getName())
522         if not name:
523             name=s.getName().encode(INTERNAL_ENCODING)
524             print(name)
525
526         # create shapekey block
527         mesh.insertKey()
528         shapeIndex=len(mesh.key.blocks)-1
529         keyBlock=mesh.key.blocks[shapeIndex]
530         keyBlock.name=name
531
532         # copy vertex to shape key
533         mesh.update()
534         
535         # restore
536         for mv, v in zip(mesh.verts, baseShapeBlock.getData()):
537             mv.co[0] = v[0]
538             mv.co[1] = v[1]
539             mv.co[2] = v[2]
540         mesh.update()
541
542     # select base shape
543     obj.activeShape=baseShapeIndex
544
545 def run(filename):
546     """
547     @param filename
548     """
549     filename=filename.decode(INTERNAL_ENCODING)
550     tex_dir=os.path.dirname(filename)
551
552     # progress
553     progress_start('pmd_import')
554     print(INTERNAL_ENCODING, FS_ENCODING)
555
556     # load pmd
557     progress_set('load %s' % filename, 0.0)
558
559     l=pmd.IO()
560     if not l.read(filename):
561         print "fail to load %s" % filename
562         return
563     progress_set('loaded %s' % filename, 0.1)
564
565     # set object mode
566     mode_edit = Blender.Window.EditMode() 
567     if mode_edit: 
568         Blender.Window.EditMode(0)
569         
570     scene = bpy.data.scenes.active
571
572     # import objects container
573     root=scene.objects.new("Empty")
574     root.setName(
575             l.english_name if len(l.english_name)>0 else l.getName().encode(INTERNAL_ENCODING))
576
577     # import mesh
578     mesh_objects=importMesh(scene, l, tex_dir)
579     root.makeParent(mesh_objects)
580
581     # import armature
582     armature_object=importArmature(scene, l)
583     if armature_object:
584         armature = armature_object.getData()
585         root.makeParent([armature_object])
586
587         # add armature modifier
588         for o in mesh_objects:
589             mod=o.modifiers.append(Blender.Modifier.Types.ARMATURE)
590             mod[Blender.Modifier.Settings.OBJECT] = armature_object
591             mod[Blender.Modifier.Settings.ENVELOPES] = False
592             #o.makeDisplayList()
593
594         ############################################################
595         # Limitation
596         ############################################################
597         for n, b in armature_object.getPose().bones.items():
598             if n.endswith("_t"):
599                 continue
600
601             if n.startswith("knee_"):
602                 b.lockYRot=True
603                 b.lockZRot=True
604                 b.limitX=True
605                 b.limitMin=[0, 0, 0]
606                 b.limitMax=[180, 0, 0]
607             elif n.startswith("ankle_"):
608                 b.lockYRot=True
609
610     # redraw
611     scene.update(0)
612
613     # restore edit mode
614     if mode_edit: 
615         Blender.Window.EditMode(1)
616
617     progress_finish()
618     Blender.Window.RedrawAll()
619
620
621 if __name__=="__main__":
622     Blender.Window.FileSelector(
623             run, 
624             'Import PMD file', 
625             Blender.sys.makename(ext='.pmd'))
626