OSDN Git Service

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