4 Name: 'MikuMikuDance model (.pmd)...'
7 Tooltip: 'Export PMD file for MikuMikuDance.'
9 __author__= ["ousttrue"]
13 0.1 20100318 first implementation.
14 0.2 20100519 refactoring. use C extension.
20 from meshio import pmd, englishmap
25 FS_ENCODING=sys.getfilesystemencoding()
26 if os.path.exists(os.path.dirname(sys.argv[0])+"/utf8"):
27 INTERNAL_ENCODING='utf-8'
29 INTERNAL_ENCODING=FS_ENCODING
36 __slots__=['o', 'children']
37 def __init__(self, o):
42 ###############################################################################
43 # Blenderのメッシュをワンスキンメッシュ化する
44 ###############################################################################
45 def near(x, y, EPSILON=1e-5):
47 return d>=-EPSILON and d<=EPSILON
50 class VertexKey(object):
56 'nx', 'ny', 'nz', # 法線
60 def __init__(self, x, y, z, nx, ny, nz, u, v):
71 return int((self.x+self.y+self.z+self.nx+self.ny+self.nz+self.u+self.v)*100)
73 def __eq__(self, rhs):
74 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)
77 class VertexArray(object):
96 return "<VertexArray %d vertices, %d indexArrays>" % (
97 len(self.vertices), len(self.indexArrays))
101 self.vertices, self.normals, self.uvs,
102 self.b0, self.b1, self.weight)
104 def getIndex(self, pos, normal, uv, b0, b1, weight0):
109 pos[0], pos[1], pos[2],
110 normal[0], normal[1], normal[2],
112 if not key in self.vertexMap:
113 self.vertexMap[key]=len(self.vertices)
114 self.vertices.append((pos.x, pos.y, pos.z))
115 self.normals.append((normal.x, normal.y, normal.z))
116 self.uvs.append((uv.x, uv.y))
120 self.weight.append(weight0)
121 return self.vertexMap[key]
123 def addTriangle(self,
130 weight0, weight1, weight2
132 if not material in self.indexArrays:
133 self.indexArrays[material]=[]
135 index0=self.getIndex(pos0, n0, uv0, b0_0, b1_0, weight0)
136 index1=self.getIndex(pos1, n1, uv1, b0_1, b1_1, weight1)
137 index2=self.getIndex(pos2, n2, uv2, b0_2, b1_2, weight2)
139 self.indexArrays[material]+=[index0, index1, index2]
142 class OneSkinMesh(object):
143 __slots__=['armatureObj', 'vertexArray']
144 def __init__(self, root):
145 self.armatureObj=None
146 self.vertexArray=VertexArray()
150 return "<OneSkinMesh armature:%s, %s>" % (
151 self.armatureObj.name if self.armatureObj else None,
154 def __create(self, node):
155 if node.o.getType()=='Mesh':
156 self.__addMesh(node.o)
158 for child in node.children:
161 def __addMesh(self, obj):
162 if obj.restrictDisplay:
166 print("export", obj.name)
168 ############################################################
169 # search armature modifier
170 ############################################################
171 for m in obj.modifiers:
172 if m.name=="Armature":
173 armatureObj=m[Blender.Modifier.Settings.OBJECT]
174 if not self.armatureObj:
175 self.armatureObj=armatureObj
176 elif self.armatureObj!=armatureObj:
177 print "warning! found multiple armature. ignored.", armatureObj.name
179 ############################################################
181 ############################################################
182 mesh=obj.getData(mesh=True)
185 for name in mesh.getVertGroupNames():
186 for i, w in mesh.getVertsFromGroup(name, 1):
189 if i in secondWeightMap:
191 if w<secondWeightMap[i]:
195 secondWeightMap[i]=(name, w)
198 weightMap[i]=(name, w)
200 if w>weightMap[i][1]:
202 secondWeightMap[i]=weightMap[i]
203 weightMap[i]=(name, w)
205 secondWeightMap[i]=(name, w)
207 weightMap[i]=(name, w)
210 for i in xrange(len(mesh.verts)):
211 #for i, name_weight in weightMap.items():
212 if i in secondWeightMap:
213 secondWeightMap[i]=(secondWeightMap[i][0], 1.0-weightMap[i][1])
215 weightMap[i]=(weightMap[i][0], 1.0)
216 secondWeightMap[i]=("", 0)
218 print "no weight vertex"
220 secondWeightMap[i]=("", 0)
223 ############################################################
226 ############################################################
227 mesh=Blender.Mesh.New()
228 # not applied modifiers
229 mesh.getFromObject(obj.name, 1)
230 # apply object transform
231 mesh.transform(obj.getMatrix())
232 if len(mesh.verts)==0:
235 for face in mesh.faces:
236 faceVertexCount=len(face.v)
237 material=mesh.materials[face.mat]
238 if faceVertexCount==3:
243 self.vertexArray.addTriangle(
247 face.uv[0], face.uv[1], face.uv[2],
248 weightMap[v0.index][0],
249 weightMap[v1.index][0],
250 weightMap[v2.index][0],
251 secondWeightMap[v0.index][0],
252 secondWeightMap[v1.index][0],
253 secondWeightMap[v2.index][0],
254 weightMap[v0.index][1],
255 weightMap[v1.index][1],
256 weightMap[v2.index][1]
258 elif faceVertexCount==4:
264 self.vertexArray.addTriangle(
268 face.uv[0], face.uv[1], face.uv[2],
269 weightMap[v0.index][0],
270 weightMap[v1.index][0],
271 weightMap[v2.index][0],
272 secondWeightMap[v0.index][0],
273 secondWeightMap[v1.index][0],
274 secondWeightMap[v2.index][0],
275 weightMap[v0.index][1],
276 weightMap[v1.index][1],
277 weightMap[v2.index][1]
279 self.vertexArray.addTriangle(
283 face.uv[2], face.uv[3], face.uv[0],
284 weightMap[v2.index][0],
285 weightMap[v3.index][0],
286 weightMap[v0.index][0],
287 secondWeightMap[v2.index][0],
288 secondWeightMap[v3.index][0],
289 secondWeightMap[v0.index][0],
290 weightMap[v2.index][1],
291 weightMap[v3.index][1],
292 weightMap[v0.index][1]
295 def getVertexCount(self):
296 return len(self.vertexArray.vertices)
300 def __init__(self, bone):
302 pos=bone.head['ARMATURESPACE']
303 self.pos=[pos.x, pos.y, pos.z]
304 self.parent_index=None
308 return "<Bone %s>" % self.name
310 class BoneBuilder(object):
311 __slots__=['bones', 'boneMap']
316 def build(self, armatureObj):
318 armature=armatureObj.getData()
319 for b in armature.bones.values():
324 self.getBone(bone, b)
326 def getBone(self, parent, b):
327 if len(b.children)==0:
330 for i, c in enumerate(b.children):
334 bone.parent_index=parent.index
336 parent.tail_index=bone.index
337 self.getBone(bone, c)
339 def addBone(self, bone):
340 bone.index=len(self.bones)
341 self.bones.append(bone)
342 self.boneMap[bone.name]=bone.index
345 class PmdExporter(object):
347 def setup(self, scene):
350 for o in scene.objects:
351 object_node_map[o]=Node(o)
352 for node in object_node_map.values():
354 object_node_map[node.o.parent].children.append(node)
356 root=object_node_map[scene.objects.active]
359 self.oneSkinMesh=OneSkinMesh(root)
360 print(self.oneSkinMesh)
361 self.name=root.o.name
363 def write(self, path):
366 io.comment="blender export"
370 builder=BoneBuilder()
371 builder.build(self.oneSkinMesh.armatureObj)
372 for b in builder.bones:
376 bone.parent_index=b.parent_index if b.parent_index!=None else 0xFFFF
377 bone.tail_index=b.tail_index if b.tail_index!=None else 0xFFFF
380 # convert right-handed z-up to left-handed y-up
386 for pos, normal, uv, b0, b1, weight in self.oneSkinMesh.vertexArray.zip():
387 # convert right-handed z-up to left-handed y-up
397 v.bone0=builder.boneMap[b0] if b0 in builder.boneMap else 0
398 v.bone1=builder.boneMap[b1] if b1 in builder.boneMap else 0
399 v.weight0=int(100*weight)
400 v.edge_flag=0 # edge flag, 0: enable edge, 1: not edge
403 vertexCount=self.oneSkinMesh.getVertexCount()
404 for m, indices in self.oneSkinMesh.vertexArray.indexArrays.items():
405 print(m, len(indices))
406 m=Blender.Material.Get(m)
408 material=io.addMaterial()
409 material.diffuse.r=m.R
410 material.diffuse.g=m.G
411 material.diffuse.b=m.B
412 material.diffuse.a=m.alpha
413 material.sinness=m.spec
414 material.specular.r=m.specR
415 material.specular.g=m.specG
416 material.specular.b=m.specB
417 material.ambient.r=m.amb
418 material.ambient.g=m.amb
419 material.ambient.b=m.amb
420 material.vertex_count=len(indices)
421 material.toon_index=0
426 assert(i<vertexCount)
427 for i in xrange(0, len(indices), 3):
429 io.indices.append(indices[i+2])
430 io.indices.append(indices[i+1])
431 io.indices.append(indices[i])
433 #io.indices.append(indices[i])
434 #io.indices.append(indices[i+1])
435 #io.indices.append(indices[i+2])
438 return io.write(path.encode(FS_ENCODING))
441 def export_pmd(filename):
442 filename=filename.decode(INTERNAL_ENCODING)
444 Blender.Window.WaitCursor(1)
445 t = Blender.sys.time()
447 if not filename.lower().endswith(EXTENSION):
448 filename += EXTENSION
449 print "pmd exporter: %s" % filename
451 exporter=PmdExporter()
454 exporter.setup(Blender.Scene.GetCurrent())
457 exporter.write(filename)
459 print 'finished in %.2f seconds' % (Blender.sys.time()-t)
461 Blender.Window.WaitCursor(0)
464 Blender.Window.FileSelector(
466 'Export Metasequoia PMD',
467 Blender.sys.makename(ext=EXTENSION))