OSDN Git Service

implement pmd_import.py.
[meshio/meshio.git] / blender24 / pmd_export.py
1 #!BPY
2 # coding: utf-8
3 """
4  Name: 'MikuMikuDance model (.pmd)...'
5  Blender: 248
6  Group: 'Export'
7  Tooltip: 'Export PMD file for MikuMikuDance.'
8 """
9 __author__= ["ousttrue"]
10 __version__= "0.1"
11 __url__=()
12 __bpydoc__="""
13 0.1: 20100318 first implementation.
14 """
15 import Blender
16 import os
17
18 import mmd
19
20 if os.path.exists(os.path.dirname(sys.argv[0])+"/utf8"):
21     FS_ENCODING='utf-8'
22 else:
23     FS_ENCODING=sys.getfilesystemencoding()
24 print FS_ENCODING
25
26 FILE_EXT = '.pmd'
27
28
29 ###############################################################################
30 # outlineの木構造からエクスポートするオブジェクトを選び出す。
31 # (Blender.Objectがparentしか持たず下っていくことができないため
32 # wrapして扱いやすくする)
33 ###############################################################################
34 class Node(object):
35     __slots__=['name', 'children']
36     def __init__(self, name="__scene__"):
37         self.name=name
38         self.children=[]
39
40     def addChild(self, child):
41         self.children.append(child)
42
43     def printTree(self, level=0):
44         print " " * (level*2) + self.name
45         for child in self.children:
46             child.printTree(level+1)
47
48
49 class OutlineTree(object):
50     __slots__=["root", "dict"]
51     def __init__(self):
52         self.root=Node()
53         self.dict={}
54
55     def addObject(self, object):
56         # already added
57         if object.name in self.dict:
58             return
59
60         parent=object.getParent()
61         node=Node(object.name)
62         if parent:
63             if not parent.name in self.dict:
64                 addNode(self.dict, parent)
65             self.dict[parent.name].addChild(node)
66         else:
67             self.root.addChild(node)
68         self.dict[object.name]=node
69
70     def get(self, name):
71         if name in self.dict:
72             return self.dict[name]
73         else:
74             return None
75
76
77 ###############################################################################
78 # Blenderのメッシュをワンスキンメッシュ化する
79 ###############################################################################
80 def near(x, y, EPSILON=1e-5):
81     d=x-y
82     return d>=-EPSILON and d<=EPSILON
83
84 class VertexKey(object):
85     """
86     重複頂点の検索キー
87     """
88     __slots__=[
89             'x', 'y', 'z', # 位置
90             'nx', 'ny', 'nz', # 法線
91             'u', 'v', # uv
92             ]
93
94     def __init__(self, x, y, z, nx, ny, nz, u, v):
95         self.x=x
96         self.y=y
97         self.z=z
98         self.nx=nx
99         self.ny=ny
100         self.nz=nz
101         self.u=u
102         self.v=v
103
104     def __hash__(self):
105         return int((self.x+self.y+self.z+self.nx+self.ny+self.nz+self.u+self.v)*100)
106
107     def __eq__(self, rhs):
108         return near(self.x, rhs.x) and near(self.y, rhs.y) and near(self.z, rhs.z) and near(self.nx, rhs.nx) and near(self.ny, rhs.ny) and near(self.nz, rhs.nz) and near(self.u, rhs.u) and near(self.v, rhs.v)
109
110
111 class VertexArray(object):
112     """
113     頂点配列
114     """
115     def __init__(self):
116         # マテリアル毎に分割したインデックス配列
117         self.indexArrays={}
118         # 頂点属性
119         self.vertices=[]
120         self.normals=[]
121         self.uvs=[]
122         # skinning属性
123         self.b0=[]
124         self.b1=[]
125         self.weight=[]
126
127         self.vertexMap={}
128
129     def __str__(self):
130         return "<VertexArray %d vertices, %d indexArrays>" % (
131                 len(self.vertices), len(self.indexArrays))
132
133     def getIndex(self, pos, normal, uv, b0, b1, weight0):
134         """
135         頂点属性からその頂点のインデックスを得る
136         """
137         key=VertexKey(
138                 pos[0], pos[1], pos[2],
139                 normal[0], normal[1], normal[2],
140                 uv[0], uv[1])
141         if not key in self.vertexMap:
142             self.vertexMap[key]=len(self.vertices)
143             self.vertices.append((pos.x, pos.y, pos.z))
144             self.normals.append((normal.x, normal.y, normal.z))
145             self.uvs.append((uv.x, uv.y))
146             # ToDo
147             self.b0.append(b0)
148             self.b1.append(b1)
149             self.weight.append(weight0)
150         return self.vertexMap[key]
151
152     def addTriangle(self,
153             material,
154             pos0, pos1, pos2,
155             n0, n1, n2,
156             uv0, uv1, uv2,
157             b0_0, b0_1, b0_2,
158             b1_0, b1_1, b1_2,
159             weight0, weight1, weight2
160             ):
161         if not material in self.indexArrays:
162             self.indexArrays[material]=[]
163
164         index0=self.getIndex(pos0, n0, uv0, b0_0, b1_0, weight0)
165         index1=self.getIndex(pos1, n1, uv1, b0_1, b1_1, weight1)
166         index2=self.getIndex(pos2, n2, uv2, b0_2, b1_2, weight2)
167
168         self.indexArrays[material]+=[index0, index1, index2]
169
170
171 class OneSkinMesh(object):
172     __slots__=['armatureObj', 'vertexArray']
173     def __init__(self):
174         self.armatureObj=None
175         self.vertexArray=VertexArray()
176
177     def create(self, node):
178         obj=Blender.Object.Get(node.name)
179
180         type=obj.getType()
181         if type=='Mesh':
182             self.addMesh(obj)
183
184         for child in node.children:
185             self.create(child)
186
187     def addMesh(self, obj):
188         if obj.restrictDisplay:
189             return
190
191         if len(obj.getData(mesh=True).verts)==0:
192             return
193
194         print("export", obj.name)
195
196         # search modifier
197         for m in obj.modifiers:
198             if m.name=="Armature":
199                 armatureObj=m[Blender.Modifier.Settings.OBJECT]
200                 if not self.armatureObj:
201                     self.armatureObj=armatureObj
202                 elif self.armatureObj!=armatureObj:
203                     print "warning! found multiple armature. ignored.", armatureObj.name
204
205         # bone weight
206         mesh=obj.getData(mesh=True)
207         weightMap={}
208         secondWeightMap={}
209         for name in mesh.getVertGroupNames():
210             for i, w in mesh.getVertsFromGroup(name, 1):
211                 if w>0:
212                     if i in weightMap:
213                         if i in secondWeightMap:
214                             # 上位2つのweightを採用する
215                             if w<secondWeightMap[i]:
216                                 pass
217                             elif w<weightMap[i]:
218                                 # 2つ目を入れ替え
219                                 secondWeightMap[i]=(name, w)
220                             else:
221                                 # 1つ目を入れ替え
222                                 weightMap[i]=(name, w)
223                         else:
224                             if w>weightMap[i][1]:
225                                 # 多い方をweightMapに
226                                 secondWeightMap[i]=weightMap[i]
227                                 weightMap[i]=(name, w)
228                             else:
229                                 secondWeightMap[i]=(name, w)
230                     else:
231                         weightMap[i]=(name, w)
232
233         # 合計値が1になるようにする
234         for i in xrange(len(mesh.verts)):
235         #for i, name_weight in weightMap.items():
236             if i in secondWeightMap:
237                 secondWeightMap[i]=(secondWeightMap[i][0], 1.0-weightMap[i][1])
238             elif i in weightMap:
239                 weightMap[i]=(weightMap[i][0], 1.0)
240                 secondWeightMap[i]=("", 0)
241             else:
242                 print "no weight vertex"
243                 weightMap[i]=("", 0)
244                 secondWeightMap[i]=("", 0)
245                 
246
247         ############################################################
248         # faces
249         # 新たにメッシュを生成する
250         ############################################################
251         mesh=Blender.Mesh.New()
252         # not applied modifiers
253         mesh.getFromObject(obj.name, 1)
254         # apply object transform
255         mesh.transform(obj.getMatrix())
256         if len(mesh.verts)==0:
257             return
258
259         for face in mesh.faces:
260             faceVertexCount=len(face.v)
261             material=mesh.materials[face.mat]
262             if faceVertexCount==3:
263                 v0=face.v[0]
264                 v1=face.v[1]
265                 v2=face.v[2]
266                 # triangle
267                 self.vertexArray.addTriangle(
268                         material.name,
269                         v0.co, v1.co, v2.co,
270                         v0.no, v1.no, v2.no,
271                         face.uv[0], face.uv[1], face.uv[2],
272                         weightMap[v0.index][0],
273                         weightMap[v1.index][0],
274                         weightMap[v2.index][0],
275                         secondWeightMap[v0.index][0],
276                         secondWeightMap[v1.index][0],
277                         secondWeightMap[v2.index][0],
278                         weightMap[v0.index][1],
279                         weightMap[v1.index][1],
280                         weightMap[v2.index][1]
281                         )
282             elif faceVertexCount==4:
283                 v0=face.v[0]
284                 v1=face.v[1]
285                 v2=face.v[2]
286                 v3=face.v[3]
287                 # quadrangle
288                 self.vertexArray.addTriangle(
289                         material.name,
290                         v0.co, v1.co, v2.co,
291                         v0.no, v1.no, v2.no,
292                         face.uv[0], face.uv[1], face.uv[2],
293                         weightMap[v0.index][0],
294                         weightMap[v1.index][0],
295                         weightMap[v2.index][0],
296                         secondWeightMap[v0.index][0],
297                         secondWeightMap[v1.index][0],
298                         secondWeightMap[v2.index][0],
299                         weightMap[v0.index][1],
300                         weightMap[v1.index][1],
301                         weightMap[v2.index][1]
302                         )
303                 self.vertexArray.addTriangle(
304                         material.name,
305                         v2.co, v3.co, v0.co,
306                         v2.no, v3.no, v0.no,
307                         face.uv[2], face.uv[3], face.uv[0],
308                         weightMap[v2.index][0],
309                         weightMap[v3.index][0],
310                         weightMap[v0.index][0],
311                         secondWeightMap[v2.index][0],
312                         secondWeightMap[v3.index][0],
313                         secondWeightMap[v0.index][0],
314                         weightMap[v2.index][1],
315                         weightMap[v3.index][1],
316                         weightMap[v0.index][1]
317                         )
318
319
320 class BoneBuilder(object):
321     __slots__=['bones', 'boneMap']
322     def __init__(self):
323         self.bones=[]
324         self.boneMap={}
325
326     def build(self, armatureObj):
327         if armatureObj:
328             armature=armatureObj.getData()
329             for b in armature.bones.values():
330                 if not b.parent:
331                     # root bone
332                     bone=mmd.createBone(b.name, 0)
333                     self.addBone(bone)
334                     self.getBone(bone, b)
335
336     def getBone(self, parent, b):
337         if len(b.children)==0:
338             return
339
340         for i, c in enumerate(b.children):
341             bone=mmd.createBone(c.name, 0)
342             self.addBone(bone)
343             if parent:
344                 bone.parentIndex=parent.index
345                 if i==0:
346                     parent.tailIndex=bone.index
347             pos=c.head['ARMATURESPACE']
348             # convert to right-handed z-up to len-handed y-up
349             bone.pos=(pos.x, pos.z, pos.y)
350             self.getBone(bone, c)
351
352     def addBone(self, bone):
353         bone.index=len(self.bones)
354         self.bones.append(bone)
355         self.boneMap[bone.name]=bone.index
356
357
358 ###############################################################################
359 # エクスポータ
360 ###############################################################################
361 def export_pmd(filename):
362     filename=filename.decode(FS_ENCODING)
363     if not filename.lower().endswith(FILE_EXT):
364         filename += FILE_EXT
365     print "## pmd exporter ##", filename
366
367     Blender.Window.WaitCursor(1)
368     t = Blender.sys.time()
369
370     # parse scene
371     scene=Blender.Scene.GetCurrent()
372     tree=OutlineTree()
373     for object in scene.objects:
374         tree.addObject(object)
375
376     # 出力準備
377     mesh=OneSkinMesh()
378     mesh.create(tree.get(scene.objects.active.name))
379
380     # setup pmd
381     pmd=mmd.PMDLoader()
382     pmd.model_name="blender export"
383     pmd.comment="blender export"
384
385     # bones
386     builder=BoneBuilder()
387     builder.build(mesh.armatureObj)
388     for b in builder.bones:
389         pmd.bones.append(b)
390
391     # 頂点
392     vArray=mesh.vertexArray
393     for pos, normal, uv, b0, b1, weight  in zip(
394             vArray.vertices, vArray.normals, vArray.uvs,
395             vArray.b0, vArray.b1, vArray.weight):
396         # convert right-handed z-up to left-handed y-up
397         v=mmd.Vertex(
398             pos[0], pos[2], pos[1],
399             normal[0], normal[2], normal[1],
400             uv[0], uv[1],
401             builder.boneMap[b0] if b0 in builder.boneMap else 0,
402             builder.boneMap[b1] if b1 in builder.boneMap else 0,
403             int(100*weight), 
404             0 # edge flag, 0: enable edge, 1: not edge
405             )
406         pmd.vertices.append(v)
407
408     for m, indices in vArray.indexArrays.items():
409         material=Blender.Material.Get(m)
410         # マテリアル
411         material=mmd.Material(
412                 material.R, material.G, material.B, material.alpha,
413                 material.spec, material.specR, material.specG, material.specB,
414                 0, 0, 0
415                 #material.amb, material.amb, material.amb
416                 )
417         material.vertex_count=len(indices)
418         pmd.materials.append(material)
419         # 面
420         for i in xrange(0, len(indices), 3):
421             #pmd.faces.append(mmd.Face(indices[i], indices[i+1], indices[i+2]))
422             pmd.faces.append(mmd.Face(indices[i+2], indices[i+1], indices[i]))
423
424     # 書き込み
425     if not pmd.write(filename):
426         return
427
428     # clean up
429     print 'My Script finished in %.2f seconds' % (Blender.sys.time()-t)
430     Blender.Redraw()
431     Blender.Window.WaitCursor(0)
432     print
433
434
435 if __name__=="__main__":
436     Blender.Window.FileSelector(
437             export_pmd,
438             'Export Metasequoia PMD',
439             Blender.sys.makename(ext=FILE_EXT))
440