OSDN Git Service

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