OSDN Git Service

remove pmd_diff_test
[meshio/pymeshio.git] / blender25-meshio / import_mqo.py
1 #!BPY\r
2 # coding: utf-8\r
3 """ \r
4 Name: 'Metasequoia(.mqo)...'\r
5 Blender: 245\r
6 Group: 'Import'\r
7 Tooltip: 'Import from Metasequoia file format (.mqo)'\r
8 """\r
9 __author__=['ousttrue']\r
10 __url__ = ["http://gunload.web.fc2.com/blender/"]\r
11 __version__= '2.4'\r
12 __bpydoc__= '''\\r
13 \r
14 MQO Importer\r
15 \r
16 This script imports a mqo into Blender for editing.\r
17 \r
18 0.2 20080123: update.\r
19 0.3 20091125: modify for linux.\r
20 0.4 20100310: rewrite.\r
21 0.5 20100311: create armature from mikoto bone.\r
22 0.6 20100505: C extension.\r
23 0.7 20100606: integrate 2.4 and 2.5.\r
24 0.8 20100619: fix multibyte object name.\r
25 0.9 20100626: refactoring.\r
26 2.0 20100724: update for Blender2.53.\r
27 2.1 20100731: add full python module.\r
28 2.2 20101005: update for Blender2.54.\r
29 2.3 20101228: update for Blender2.55.\r
30 2.4 20110429: update for Blender2.57b.\r
31 2.6 20110918: update for Blender2.59.\r
32 '''\r
33 \r
34 bl_addon_info = {\r
35         'category': 'Import/Export',\r
36         'name': 'Import: Metasequioa Model Format (.mqo)',\r
37         'author': 'ousttrue',\r
38         'version': (2, 0),\r
39         'blender': (2, 5, 3),\r
40         'location': 'File > Import',\r
41         'description': 'Import from the Metasequioa Model Format (.mqo)',\r
42         'warning': '', # used for warning icon and text in addons panel\r
43         'wiki_url': 'http://sourceforge.jp/projects/meshio/wiki/FrontPage',\r
44         'tracker_url': 'http://sourceforge.jp/ticket/newticket.php?group_id=5081',\r
45         }\r
46 \r
47 import os\r
48 import sys\r
49 \r
50 try:\r
51     # C extension\r
52     from .meshio import mqo\r
53     print('use meshio C module')\r
54 except ImportError:\r
55     # full python\r
56     from .pymeshio import mqo\r
57 \r
58 # for 2.5\r
59 import bpy\r
60 \r
61 # wrapper\r
62 from . import bl25 as bl\r
63 \r
64 def createMqoMaterial(m):\r
65     material = bpy.data.materials.new(m.getName())\r
66     # shader\r
67     if m.shader==1:\r
68         material.diffuse_shader='FRESNEL'\r
69     else:\r
70         material.diffuse_shader='LAMBERT'\r
71     # diffuse\r
72     material.diffuse_color=[m.color.r, m.color.g, m.color.b]\r
73     material.diffuse_intensity=m.diffuse\r
74     material.alpha=m.color.a\r
75     # other\r
76     material.ambient = m.ambient\r
77     #material.specular = m.specular\r
78     material.emit=m.emit\r
79     material.use_shadeless=True\r
80     return material\r
81 \r
82 \r
83 def has_mikoto(mqo):\r
84     #for o in mqo.objects:\r
85     #    if o.getName().startswith('bone'):\r
86     #        return True\r
87     #    if o.getName().startswith('sdef'):\r
88     #        return True\r
89     #    if o.getName().startswith('anchor'):\r
90     #        return True\r
91     return False\r
92 \r
93 \r
94 def __createMaterials(mqo, directory):\r
95     """\r
96     create blender materials and renturn material list.\r
97     """\r
98     materials = []\r
99     textureMap={}\r
100     imageMap={}\r
101     if len(mqo.materials)>0:\r
102         for material_index, m in enumerate(mqo.materials):\r
103             # material\r
104             material=createMqoMaterial(m)\r
105             materials.append(material)\r
106             # texture\r
107             texture_name=m.getTexture()\r
108             if texture_name!='':\r
109                 if texture_name in textureMap:\r
110                     texture=textureMap[texture_name]\r
111                 else:\r
112                     # load texture image\r
113                     if os.path.isabs(texture_name):\r
114                         # absolute\r
115                         path = texture_name\r
116                     else:\r
117                         # relative\r
118                         path = os.path.join(directory, texture_name)\r
119                     # texture\r
120                     path=path.replace("\\", "/")\r
121                     if os.path.exists(path):\r
122                         print("create texture:", path)\r
123                         texture, image=bl.texture.create(path)\r
124                         textureMap[texture_name]=texture\r
125                         imageMap[material_index]=image\r
126                     else:\r
127                         print("%s not exits" % path)\r
128                         continue\r
129                 bl.material.addTexture(material, texture)\r
130     else:\r
131         # default material\r
132         pass\r
133     return materials, imageMap\r
134 \r
135 \r
136 def __createObjects(mqo, root, materials, imageMap, scale):\r
137     """\r
138     create blender mesh objects.\r
139     """\r
140     # tree stack\r
141     stack=[root]    \r
142     objects=[]\r
143     for o in mqo.objects:\r
144         mesh, mesh_object=bl.mesh.create(o.getName())\r
145 \r
146         # add hierarchy\r
147         stack_depth=len(stack)-1\r
148         #print(o.depth, stack_depth)\r
149         if o.depth<stack_depth:\r
150             for i in range(stack_depth-o.depth):\r
151                 stack.pop()\r
152         bl.object.makeParent(stack[-1], mesh_object)\r
153         stack.append(mesh_object)\r
154 \r
155         if o.getName().startswith('sdef'):\r
156             objects.append(mesh_object)\r
157         elif o.getName().startswith('anchor'):\r
158             bl.object.setLayerMask(mesh_object, [0, 1])\r
159         elif o.getName().startswith('bone'):\r
160             bl.object.setLayerMask(mesh_object, [0, 1])\r
161 \r
162         # geometry\r
163         vertices=[(v.x * scale, -v.z * scale, v.y * scale) for v in o.vertices]\r
164         faces=[]\r
165         materialMap={}\r
166         for f in o.faces:\r
167             face_indices=[]\r
168             # flip face\r
169             for i in reversed(range(f.index_count)):\r
170                 face_indices.append(f.getIndex(i))\r
171             faces.append(face_indices)\r
172             materialMap[f.material_index]=True\r
173         bl.mesh.addGeometry(mesh, vertices, faces)\r
174 \r
175         # blender limits 16 materials per mesh\r
176         for i, material_index in enumerate(materialMap.keys()):\r
177             if i>=16:\r
178                 # split a mesh ?\r
179                 print("over 16 materials!")\r
180                 break\r
181             bl.mesh.addMaterial(mesh, materials[material_index])\r
182             materialMap[material_index]=i\r
183  \r
184         # set face params\r
185         assert(len(o.faces)==len(mesh.faces))\r
186         bl.mesh.addUV(mesh)\r
187         for i, (f, face) in enumerate(zip(o.faces, mesh.faces)):\r
188             uv_array=[]\r
189             # ToDo FIX\r
190             # flip face\r
191             for j in reversed(range(f.index_count)):\r
192                 uv_array.append((f.getUV(j).x, 1.0-f.getUV(j).y))\r
193             bl.mesh.setFaceUV(mesh, i, face, uv_array, \r
194                     imageMap.get(f.material_index, None))\r
195             if f.material_index in materialMap:\r
196                 bl.face.setMaterial(face, materialMap[f.material_index])\r
197             bl.face.setSmooth(face, True)\r
198 \r
199         # mirror modifier\r
200         if o.mirror:\r
201             bl.modifier.addMirror(mesh_object)\r
202 \r
203         # set smoothing\r
204         bl.mesh.setSmooth(mesh, o.smoothing)\r
205 \r
206         # calc normal\r
207         bl.mesh.recalcNormals(mesh_object)\r
208 \r
209     return objects\r
210 \r
211 \r
212 ###############################################################################\r
213 # for mqo mikoto bone.\r
214 ###############################################################################\r
215 class MikotoBone(object):\r
216     __slots__=[\r
217             'name',\r
218             'iHead', 'iTail', 'iUp',\r
219             'vHead', 'vTail', 'vUp',\r
220             'parent', 'isFloating',\r
221             'children',\r
222             ]\r
223     def __init__(self, face=None, vertices=None, materials=None):\r
224         self.parent=None\r
225         self.isFloating=False\r
226         self.children=[]\r
227         if not face:\r
228             self.name='root'\r
229             return\r
230 \r
231         self.name=materials[face.material_index].name.encode('utf-8')\r
232 \r
233         i0=face.getIndex(0)\r
234         i1=face.getIndex(1)\r
235         i2=face.getIndex(2)\r
236         v0=vertices[i0]\r
237         v1=vertices[i1]\r
238         v2=vertices[i2]\r
239         e01=v1-v0\r
240         e12=v2-v1\r
241         e20=v0-v2\r
242         sqNorm0=e01.getSqNorm()\r
243         sqNorm1=e12.getSqNorm()\r
244         sqNorm2=e20.getSqNorm()\r
245         if sqNorm0>sqNorm1:\r
246             if sqNorm1>sqNorm2:\r
247                 # e01 > e12 > e20\r
248                 self.iHead=i2\r
249                 self.iTail=i1\r
250                 self.iUp=i0\r
251             else:\r
252                 if sqNorm0>sqNorm2:\r
253                     # e01 > e20 > e12\r
254                     self.iHead=i2\r
255                     self.iTail=i0\r
256                     self.iUp=i1\r
257                 else:\r
258                     # e20 > e01 > e12\r
259                     self.iHead=i1\r
260                     self.iTail=i0\r
261                     self.iUp=i2\r
262         else:\r
263             # 0 < 1\r
264             if sqNorm1<sqNorm2:\r
265                 # e20 > e12 > e01\r
266                 self.iHead=i1\r
267                 self.iTail=i2\r
268                 self.iUp=i0\r
269             else:\r
270                 if sqNorm0<sqNorm2:\r
271                     # e12 > e20 > e01\r
272                     self.iHead=i0\r
273                     self.iTail=i2\r
274                     self.iUp=i1\r
275                 else:\r
276                     # e12 > e01 > e20\r
277                     self.iHead=i0\r
278                     self.iTail=i1\r
279                     self.iUp=i2\r
280         self.vHead=vertices[self.iHead]\r
281         self.vTail=vertices[self.iTail]\r
282         self.vUp=vertices[self.iUp]\r
283 \r
284         if self.name.endswith('[]'):\r
285             basename=self.name[0:-2]\r
286             # expand LR name\r
287             if self.vTail.x>0:\r
288                 self.name="%s_L" % basename\r
289             else:\r
290                 self.name="%s_R" % basename\r
291 \r
292 \r
293     def setParent(self, parent, floating=False):\r
294         if floating:\r
295             self.isFloating=True\r
296         self.parent=parent\r
297         parent.children.append(self)\r
298 \r
299     def printTree(self, indent=''):\r
300         print("%s%s" % (indent, self.name))\r
301         for child in self.children:\r
302             child.printTree(indent+'  ')\r
303 \r
304 \r
305 def build_armature(armature, mikotoBone, parent=None):\r
306     """\r
307     create a armature bone.\r
308     """\r
309     bone = Armature.Editbone()\r
310     bone.name = mikotoBone.name.encode('utf-8')\r
311     armature.bones[bone.name] = bone\r
312 \r
313     bone.head = Mathutils.Vector(*mikotoBone.vHead.to_a())\r
314     bone.tail = Mathutils.Vector(*mikotoBone.vTail.to_a())\r
315     if parent:\r
316         bone.parent=parent\r
317         if mikotoBone.isFloating:\r
318             pass\r
319         else:\r
320             bone.options=[Armature.CONNECTED]\r
321 \r
322     for child in mikotoBone.children:\r
323         build_armature(armature, child, bone)\r
324 \r
325 \r
326 def create_armature(mqo):\r
327     """\r
328     create armature\r
329     """\r
330     boneObject=None\r
331     for o in mqo.objects:\r
332         if o.name.startswith('bone'):\r
333             boneObject=o\r
334             break\r
335     if not boneObject:\r
336         return\r
337 \r
338     tailMap={}\r
339     for f in boneObject.faces:\r
340         if f.index_count!=3:\r
341             print("invalid index_count: %d" % f.index_count)\r
342             continue\r
343         b=MikotoBone(f, boneObject.vertices, mqo.materials)\r
344         tailMap[b.iTail]=b\r
345 \r
346     #################### \r
347     # build mikoto bone tree\r
348     #################### \r
349     mikotoRoot=MikotoBone()\r
350 \r
351     for b in tailMap.values():\r
352         # each bone has unique parent or is root bone.\r
353         if b.iHead in tailMap:\r
354             b.setParent(tailMap[b.iHead])\r
355         else: \r
356             isFloating=False\r
357             for e in boneObject.edges:\r
358                 if  b.iHead==e.indices[0]:\r
359                     # floating bone\r
360                     if e.indices[1] in tailMap:\r
361                         b.setParent(tailMap[e.indices[1]], True)\r
362                         isFloating=True\r
363                         break\r
364                 elif b.iHead==e.indices[1]:\r
365                     # floating bone\r
366                     if e.indices[0] in tailMap:\r
367                         b.setParent(tailMap[e.indices[0]], True)\r
368                         isFloating=True\r
369                         break\r
370             if isFloating:\r
371                 continue\r
372 \r
373             # no parent bone\r
374             b.setParent(mikotoRoot, True)\r
375 \r
376     if len(mikotoRoot.children)==0:\r
377         print("no root bone")\r
378         return\r
379 \r
380     if len(mikotoRoot.children)==1:\r
381         # single root\r
382         mikotoRoot=mikotoRoot.children[0]\r
383         mikotoRoot.parent=None\r
384     else:\r
385         mikotoRoot.vHead=Vector3(0, 10, 0)\r
386         mikotoRoot.vTail=Vector3(0, 0, 0)\r
387 \r
388     #################### \r
389     # create armature\r
390     #################### \r
391     armature = Armature.New()\r
392     # link to object\r
393     armature_object = scene.objects.new(armature)\r
394     # create action\r
395     act = Armature.NLA.NewAction()\r
396     act.setActive(armature_object)\r
397     # set XRAY\r
398     armature_object.drawMode |= Object.DrawModes.XRAY\r
399     # armature settings\r
400     armature.drawType = Armature.OCTAHEDRON\r
401     armature.envelopes = False\r
402     armature.vertexGroups = True\r
403     armature.mirrorEdit = True\r
404     armature.drawNames=True\r
405 \r
406     # edit bones\r
407     armature.makeEditable()\r
408     build_armature(armature, mikotoRoot)\r
409     armature.update()\r
410 \r
411     return armature_object\r
412         \r
413 \r
414 class TrianglePlane(object):\r
415     """\r
416     mikoto\e$BJ}<0%\!<%s$N%"%s%+!<%&%'%$%H7W;;MQ!#\e(B\r
417     (\e$BIT40A4\e(B)\r
418     """\r
419     __slots__=['normal', \r
420             'v0', 'v1', 'v2',\r
421             ]\r
422     def __init__(self, v0, v1, v2):\r
423         self.v0=v0\r
424         self.v1=v1\r
425         self.v2=v2\r
426 \r
427     def isInsideXY(self, p):\r
428         v0=Vector2(self.v0.x, self.v0.y)\r
429         v1=Vector2(self.v1.x, self.v1.y)\r
430         v2=Vector2(self.v2.x, self.v2.y)\r
431         e01=v1-v0\r
432         e12=v2-v1\r
433         e20=v0-v2\r
434         c0=Vector2.cross(e01, p-v0)\r
435         c1=Vector2.cross(e12, p-v1)\r
436         c2=Vector2.cross(e20, p-v2)\r
437         if c0>=0 and c1>=0 and c2>=0:\r
438             return True\r
439         if c0<=0 and c1<=0 and c2<=0:\r
440             return True\r
441 \r
442     def isInsideYZ(self, p):\r
443         v0=Vector2(self.v0.y, self.v0.z)\r
444         v1=Vector2(self.v1.y, self.v1.z)\r
445         v2=Vector2(self.v2.y, self.v2.z)\r
446         e01=v1-v0\r
447         e12=v2-v1\r
448         e20=v0-v2\r
449         c0=Vector2.cross(e01, p-v0)\r
450         c1=Vector2.cross(e12, p-v1)\r
451         c2=Vector2.cross(e20, p-v2)\r
452         if c0>=0 and c1>=0 and c2>=0:\r
453             return True\r
454         if c0<=0 and c1<=0 and c2<=0:\r
455             return True\r
456 \r
457     def isInsideZX(self, p):\r
458         v0=Vector2(self.v0.z, self.v0.x)\r
459         v1=Vector2(self.v1.z, self.v1.x)\r
460         v2=Vector2(self.v2.z, self.v2.x)\r
461         e01=v1-v0\r
462         e12=v2-v1\r
463         e20=v0-v2\r
464         c0=Vector2.cross(e01, p-v0)\r
465         c1=Vector2.cross(e12, p-v1)\r
466         c2=Vector2.cross(e20, p-v2)\r
467         if c0>=0 and c1>=0 and c2>=0:\r
468             return True\r
469         if c0<=0 and c1<=0 and c2<=0:\r
470             return True\r
471 \r
472 \r
473 class MikotoAnchor(object):\r
474     """\r
475     mikoto\e$BJ}<0%9%1%k%H%s$N%"%s%+!<!#\e(B\r
476     """\r
477     __slots__=[\r
478             "triangles", "bbox",\r
479             ]\r
480     def __init__(self):\r
481         self.triangles=[]\r
482         self.bbox=None\r
483 \r
484     def push(self, face, vertices):\r
485         if face.index_count==3:\r
486             self.triangles.append(TrianglePlane(\r
487                 vertices[face.indices[0]],\r
488                 vertices[face.indices[1]],\r
489                 vertices[face.indices[2]]\r
490                 ))\r
491         elif face.index_count==4:\r
492             self.triangles.append(TrianglePlane(\r
493                 vertices[face.indices[0]],\r
494                 vertices[face.indices[1]],\r
495                 vertices[face.indices[2]]\r
496                 ))\r
497             self.triangles.append(TrianglePlane(\r
498                 vertices[face.indices[2]],\r
499                 vertices[face.indices[3]],\r
500                 vertices[face.indices[0]]\r
501                 ))\r
502         # bounding box\r
503         if not self.bbox:\r
504             self.bbox=BoundingBox(vertices[face.indices[0]])\r
505         for i in face.indices:\r
506             self.bbox.expand(vertices[i])\r
507 \r
508 \r
509     def calcWeight(self, v):\r
510         if not self.bbox.isInside(v):\r
511             return 0\r
512 \r
513         if self.anyXY(v.x, v.y) and self.anyYZ(v.y, v.z) and self.anyZX(v.z, v.x):\r
514             return 1.0\r
515         else:\r
516             return 0\r
517         \r
518     def anyXY(self, x, y):\r
519         for t in self.triangles:\r
520             if t.isInsideXY(Vector2(x, y)):\r
521                 return True\r
522         return False\r
523 \r
524     def anyYZ(self, y, z):\r
525         for t in self.triangles:\r
526             if t.isInsideYZ(Vector2(y, z)):\r
527                 return True\r
528         return False\r
529 \r
530     def anyZX(self, z, x):\r
531         for t in self.triangles:\r
532             if t.isInsideZX(Vector2(z, x)):\r
533                 return True\r
534         return False\r
535 \r
536 \r
537 def create_bone_weight(scene, mqo, armature_object, objects):\r
538     """\r
539     create mikoto bone weight.\r
540     """\r
541     anchorMap={}\r
542     # setup mikoto anchors\r
543     for o in mqo.objects:\r
544         if o.name.startswith("anchor"):\r
545             for f in o.faces:\r
546                 name=mqo.materials[f.material_index].name\r
547                 if name.endswith('[]'):\r
548                     basename=name[0:-2]\r
549                     v=o.vertices[f.indices[0]]\r
550                     if(v.x>0):\r
551                         # L\r
552                         name_L=basename+'_L'\r
553                         if not name_L in anchorMap:\r
554                             anchorMap[name_L]=MikotoAnchor()\r
555                         anchorMap[name_L].push(f, o.vertices)\r
556                     elif(v.x<0):\r
557                         # R\r
558                         name_R=basename+'_R'\r
559                         if not name_R in anchorMap:\r
560                             anchorMap[name_R]=MikotoAnchor()\r
561                         anchorMap[name_R].push(f, o.vertices)\r
562                     else:\r
563                         print("no side", v)\r
564                 else:\r
565                     if not name in anchorMap:\r
566                         anchorMap[name]=MikotoAnchor()\r
567                     anchorMap[name].push(f, o.vertices)\r
568 \r
569     for o in objects:\r
570         # add armature modifier\r
571         mod=o.modifiers.append(Modifier.Types.ARMATURE)\r
572         mod[Modifier.Settings.OBJECT] = armature_object\r
573         mod[Modifier.Settings.ENVELOPES] = False\r
574         o.makeDisplayList()\r
575         # create vertex group\r
576         mesh=o.getData(mesh=True)\r
577         for name in anchorMap.keys():\r
578             mesh.addVertGroup(name)\r
579         mesh.update()\r
580                  \r
581     # assing vertices to vertex group\r
582     for o in objects:\r
583         mesh=o.getData(mesh=True)\r
584         for i, mvert in enumerate(mesh.verts):\r
585             hasWeight=False\r
586             for name, anchor in anchorMap.items():\r
587                 weight=anchor.calcWeight(mvert.co)\r
588                 if weight>0:\r
589                     mesh.assignVertsToGroup(\r
590                             name, [i], weight, Mesh.AssignModes.ADD)\r
591                     hasWeight=True\r
592             if not hasWeight:\r
593                 # debug orphan vertex\r
594                 print('orphan', mvert)\r
595         mesh.update()\r
596 \r
597 \r
598 def _execute(filepath='', scale=0.1):\r
599     # parse file\r
600     io=mqo.IO()\r
601     if not io.read(filepath):\r
602         bl.message("fail to load %s" % filepath)\r
603         return\r
604 \r
605     # create materials\r
606     materials, imageMap=__createMaterials(io, os.path.dirname(filepath))\r
607     if len(materials)==0:\r
608         materials.append(bl.material.create('default'))\r
609 \r
610     # create objects\r
611     root=bl.object.createEmpty(os.path.basename(filepath))\r
612     objects=__createObjects(io, root, materials, imageMap, scale)\r
613 \r
614     if has_mikoto(io):\r
615         # create mikoto bone\r
616         armature_object=create_armature(io)\r
617         if armature_object:\r
618             root.makeParent([armature_object])\r
619 \r
620             # create bone weight\r
621             create_bone_weight(io, armature_object, objects)\r
622 \r
623 \r