OSDN Git Service

integrate 2.4 and 2.5.
[meshio/meshio.git] / swig / blender / mqo_import.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__= '0.6 2010/05/05'\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 '''\r
25 import os\r
26 import sys\r
27 import math\r
28 \r
29 # C extension\r
30 from meshio import mqo\r
31 \r
32 def isBlender24():\r
33     return sys.version_info[0]<3\r
34 \r
35 \r
36 if isBlender24():\r
37     # for 2.4\r
38     import Blender\r
39     from Blender import Mathutils\r
40     import bpy\r
41 \r
42     # ファイルシステムの文字コード\r
43     # 改造版との共用のため\r
44     FS_ENCODING=sys.getfilesystemencoding()\r
45     if os.path.exists(os.path.dirname(sys.argv[0])+"/utf8"):\r
46         INTERNAL_ENCODING='utf-8'\r
47     else:\r
48         INTERNAL_ENCODING=FS_ENCODING\r
49 else:\r
50     # for 2.5\r
51     import bpy\r
52     from bpy.props import *\r
53 \r
54 \r
55 def has_mikoto(mqo):\r
56     return False\r
57 \r
58 \r
59 def create_materials(scene, mqo, directory):\r
60     """\r
61     create blender materials and renturn material list.\r
62     """\r
63     materials = []\r
64     images = []\r
65     for m in mqo.materials:\r
66         material = Blender.Material.New(m.getName().encode(INTERNAL_ENCODING))\r
67         materials.append(material)\r
68 \r
69         material.mode |= Blender.Material.Modes.SHADELESS\r
70         material.rgbCol = [m.color.r, m.color.g, m.color.b]\r
71         material.alpha = m.color.a\r
72         material.amb = m.ambient\r
73         material.spec = m.specular\r
74         material.hard = int(255 * m.power)\r
75         if m.texture!="":\r
76             texture_path=m.getTexture()\r
77 \r
78             # load texture image\r
79             if os.path.isabs(texture_path):\r
80                 # absolute\r
81                 path = texture_path\r
82             else:\r
83                 # relative\r
84                 path = os.path.join(directory, texture_path)\r
85 \r
86             # backslash to slash\r
87             #path = path.replace('\\', '/')\r
88 \r
89             # texture\r
90             if os.path.exists(path):\r
91                 image = Blender.Image.Load(path.encode(INTERNAL_ENCODING))\r
92                 images.append(image)\r
93                 material.mode = material.mode | Blender.Material.Modes.TEXFACE\r
94                 tex = Blender.Texture.New(path.encode(INTERNAL_ENCODING))\r
95                 tex.type = Blender.Texture.Types.IMAGE\r
96                 tex.image = image\r
97                 material.setTexture(0, tex, Blender.Texture.TexCo.UV)\r
98             else:\r
99                 print("%s not exits" % path)\r
100             \r
101     return materials\r
102 \r
103 \r
104 def create_objects(scene, root, mqo, materials):\r
105     """\r
106     create blender mesh objects.\r
107     """\r
108     # store hierarchy\r
109     stack=[root]    \r
110 \r
111     objects=[]\r
112     for o in mqo.objects:\r
113         #print "%s:v(%d),f(%d)" % (o.name, len(o.vertices), len(o.faces))\r
114         # create mesh\r
115         mesh = Blender.Mesh.New()\r
116         mesh_object=scene.objects.new(mesh, o.name.encode('utf-8'))\r
117 \r
118         # add hierarchy\r
119         stack_depth=len(stack)-1\r
120         print(o.depth, stack_depth)\r
121         if o.depth<stack_depth:\r
122             for i in range(stack_depth-o.depth):\r
123                 stack.pop()\r
124         stack[-1].makeParent([mesh_object])\r
125         stack.append(mesh_object)\r
126 \r
127         if o.name.startswith('sdef'):\r
128             # add sdef object\r
129             objects.append(mesh_object)\r
130         elif o.name.startswith('anchor'):\r
131             #print("hide %s" % o.name)\r
132             #mesh_object.restrictDisplay=False\r
133             mesh_object.layers=[2]\r
134         elif o.name.startswith('bone'):\r
135             mesh_object.layers=[2]\r
136 \r
137         # add vertices\r
138         mesh.verts.extend(Mathutils.Vector(0, 0, 0)) # dummy\r
139         mesh.verts.extend([(v.x, -v.z, v.y) for v in o.vertices])\r
140         # add faces\r
141         mesh_faces=[]\r
142         for face in o.faces:\r
143             face_indices=[]\r
144             for i in xrange(face.index_count):\r
145                 face_indices.append(face.getIndex(i)+1)\r
146             mesh_faces.append(face_indices)\r
147         #new_faces=mesh.faces.extend([face.indices for face in o.faces], \r
148         new_faces=mesh.faces.extend(mesh_faces,\r
149                 #ignoreDups=True, \r
150                 indexList=True)\r
151         mesh.update()\r
152         \r
153         # gather used materials\r
154         usedMaterials = {}\r
155         if new_faces:\r
156             for i in new_faces:\r
157                 if type(i) is int:\r
158                     usedMaterials[o.faces[i].material_index]=True\r
159 \r
160         # blender limits 16 materials per mesh\r
161         # separate mesh ?\r
162         for i, material_index in enumerate(usedMaterials.keys()):\r
163             if i>=16:\r
164                 print("over 16 materials!")\r
165                 break\r
166             mesh.materials+=[materials[material_index]]\r
167             usedMaterials[material_index]=i\r
168         \r
169         # set face params\r
170         for i, f in enumerate(o.faces):       \r
171             if not type(new_faces[i]) is int:\r
172                 continue\r
173 \r
174             face=mesh.faces[new_faces[i]]\r
175 \r
176             uv_array=[]\r
177             for i in xrange(f.index_count):\r
178                 uv_array.append(Blender.Mathutils.Vector(\r
179                     f.getUV(i).x, \r
180                     1.0-f.getUV(i).y)\r
181                     )\r
182             try:\r
183                 face.uv=uv_array\r
184             except Exception, msg:\r
185                 #print msg\r
186                 #print face.index, uv_array\r
187                 pass\r
188         \r
189             if f.material_index in usedMaterials:\r
190                 face.mat = usedMaterials[f.material_index]\r
191 \r
192             face.smooth = 1\r
193 \r
194         # rmeove dummy 0 vertex\r
195         mesh.verts.delete(0)\r
196             \r
197         mesh.mode |= Blender.Mesh.Modes.AUTOSMOOTH\r
198         mesh.maxSmoothAngle = int(o.smoothing)\r
199         mesh.smooth()\r
200         mesh.calcNormals()\r
201         mesh.flipNormals()\r
202         mesh.update()\r
203 \r
204         # mirror modifier\r
205         if o.mirror:\r
206             mod=mesh_object.modifiers.append(Blender.Modifier.Types.MIRROR)\r
207 \r
208     return objects\r
209 \r
210 \r
211 class MikotoBone(object):\r
212     __slots__=[\r
213             'name',\r
214             'iHead', 'iTail', 'iUp',\r
215             'vHead', 'vTail', 'vUp',\r
216             'parent', 'isFloating',\r
217             'children',\r
218             ]\r
219     def __init__(self, face=None, vertices=None, materials=None):\r
220         self.parent=None\r
221         self.isFloating=False\r
222         self.children=[]\r
223         if not face:\r
224             self.name='root'\r
225             return\r
226 \r
227         self.name=materials[face.material_index].name.encode('utf-8')\r
228 \r
229         i0=face.indices[0]\r
230         i1=face.indices[1]\r
231         i2=face.indices[2]\r
232         v0=vertices[i0]\r
233         v1=vertices[i1]\r
234         v2=vertices[i2]\r
235         e01=v1-v0\r
236         e12=v2-v1\r
237         e20=v0-v2\r
238         sqNorm0=e01.getSqNorm()\r
239         sqNorm1=e12.getSqNorm()\r
240         sqNorm2=e20.getSqNorm()\r
241         if sqNorm0>sqNorm1:\r
242             if sqNorm1>sqNorm2:\r
243                 # e01 > e12 > e20\r
244                 self.iHead=i2\r
245                 self.iTail=i1\r
246                 self.iUp=i0\r
247             else:\r
248                 if sqNorm0>sqNorm2:\r
249                     # e01 > e20 > e12\r
250                     self.iHead=i2\r
251                     self.iTail=i0\r
252                     self.iUp=i1\r
253                 else:\r
254                     # e20 > e01 > e12\r
255                     self.iHead=i1\r
256                     self.iTail=i0\r
257                     self.iUp=i2\r
258         else:\r
259             # 0 < 1\r
260             if sqNorm1<sqNorm2:\r
261                 # e20 > e12 > e01\r
262                 self.iHead=i1\r
263                 self.iTail=i2\r
264                 self.iUp=i0\r
265             else:\r
266                 if sqNorm0<sqNorm2:\r
267                     # e12 > e20 > e01\r
268                     self.iHead=i0\r
269                     self.iTail=i2\r
270                     self.iUp=i1\r
271                 else:\r
272                     # e12 > e01 > e20\r
273                     self.iHead=i0\r
274                     self.iTail=i1\r
275                     self.iUp=i2\r
276         self.vHead=vertices[self.iHead]\r
277         self.vTail=vertices[self.iTail]\r
278         self.vUp=vertices[self.iUp]\r
279 \r
280         if self.name.endswith('[]'):\r
281             basename=self.name[0:-2]\r
282             # expand LR name\r
283             if self.vTail.x>0:\r
284                 self.name="%s_L" % basename\r
285             else:\r
286                 self.name="%s_R" % basename\r
287 \r
288 \r
289     def setParent(self, parent, floating=False):\r
290         if floating:\r
291             self.isFloating=True\r
292         self.parent=parent\r
293         parent.children.append(self)\r
294 \r
295     def printTree(self, indent=''):\r
296         print("%s%s" % (indent, self.name))\r
297         for child in self.children:\r
298             child.printTree(indent+'  ')\r
299 \r
300 \r
301 def build_armature(armature, mikotoBone, parent=None):\r
302     """\r
303     create a armature bone.\r
304     """\r
305     bone = Armature.Editbone()\r
306     bone.name = mikotoBone.name.encode('utf-8')\r
307     armature.bones[bone.name] = bone\r
308 \r
309     bone.head = Mathutils.Vector(*mikotoBone.vHead.to_a())\r
310     bone.tail = Mathutils.Vector(*mikotoBone.vTail.to_a())\r
311     if parent:\r
312         bone.parent=parent\r
313         if mikotoBone.isFloating:\r
314             pass\r
315         else:\r
316             bone.options=[Armature.CONNECTED]\r
317 \r
318     for child in mikotoBone.children:\r
319         build_armature(armature, child, bone)\r
320 \r
321 \r
322 def create_armature(scene, mqo):\r
323     """\r
324     create armature\r
325     """\r
326     boneObject=None\r
327     for o in mqo.objects:\r
328         if o.name.startswith('bone'):\r
329             boneObject=o\r
330             break\r
331     if not boneObject:\r
332         return\r
333 \r
334     tailMap={}\r
335     for f in boneObject.faces:\r
336         if f.index_count!=3:\r
337             print("invalid index_count: %d" % f.index_count)\r
338             continue\r
339         b=MikotoBone(f, boneObject.vertices, mqo.materials)\r
340         tailMap[b.iTail]=b\r
341 \r
342     #################### \r
343     # build mikoto bone tree\r
344     #################### \r
345     mikotoRoot=MikotoBone()\r
346 \r
347     for b in tailMap.values():\r
348         # each bone has unique parent or is root bone.\r
349         if b.iHead in tailMap:\r
350             b.setParent(tailMap[b.iHead])\r
351         else: \r
352             isFloating=False\r
353             for e in boneObject.edges:\r
354                 if  b.iHead==e.indices[0]:\r
355                     # floating bone\r
356                     if e.indices[1] in tailMap:\r
357                         b.setParent(tailMap[e.indices[1]], True)\r
358                         isFloating=True\r
359                         break\r
360                 elif b.iHead==e.indices[1]:\r
361                     # floating bone\r
362                     if e.indices[0] in tailMap:\r
363                         b.setParent(tailMap[e.indices[0]], True)\r
364                         isFloating=True\r
365                         break\r
366             if isFloating:\r
367                 continue\r
368 \r
369             # no parent bone\r
370             b.setParent(mikotoRoot, True)\r
371 \r
372     if len(mikotoRoot.children)==0:\r
373         print("no root bone")\r
374         return\r
375 \r
376     if len(mikotoRoot.children)==1:\r
377         # single root\r
378         mikotoRoot=mikotoRoot.children[0]\r
379         mikotoRoot.parent=None\r
380     else:\r
381         mikotoRoot.vHead=Vector3(0, 10, 0)\r
382         mikotoRoot.vTail=Vector3(0, 0, 0)\r
383 \r
384     #################### \r
385     # create armature\r
386     #################### \r
387     armature = Armature.New()\r
388     # link to object\r
389     armature_object = scene.objects.new(armature)\r
390     # create action\r
391     act = Armature.NLA.NewAction()\r
392     act.setActive(armature_object)\r
393     # set XRAY\r
394     armature_object.drawMode |= Object.DrawModes.XRAY\r
395     # armature settings\r
396     armature.drawType = Armature.OCTAHEDRON\r
397     armature.envelopes = False\r
398     armature.vertexGroups = True\r
399     armature.mirrorEdit = True\r
400     armature.drawNames=True\r
401 \r
402     # edit bones\r
403     armature.makeEditable()\r
404     build_armature(armature, mikotoRoot)\r
405     armature.update()\r
406 \r
407     return armature_object\r
408         \r
409 \r
410 class TrianglePlane(object):\r
411     """\r
412     mikoto方式ボーンのアンカーウェイト計算用。\r
413     (不完全)\r
414     """\r
415     __slots__=['normal', \r
416             'v0', 'v1', 'v2',\r
417             ]\r
418     def __init__(self, v0, v1, v2):\r
419         self.v0=v0\r
420         self.v1=v1\r
421         self.v2=v2\r
422 \r
423     def isInsideXY(self, p):\r
424         v0=Vector2(self.v0.x, self.v0.y)\r
425         v1=Vector2(self.v1.x, self.v1.y)\r
426         v2=Vector2(self.v2.x, self.v2.y)\r
427         e01=v1-v0\r
428         e12=v2-v1\r
429         e20=v0-v2\r
430         c0=Vector2.cross(e01, p-v0)\r
431         c1=Vector2.cross(e12, p-v1)\r
432         c2=Vector2.cross(e20, p-v2)\r
433         if c0>=0 and c1>=0 and c2>=0:\r
434             return True\r
435         if c0<=0 and c1<=0 and c2<=0:\r
436             return True\r
437 \r
438     def isInsideYZ(self, p):\r
439         v0=Vector2(self.v0.y, self.v0.z)\r
440         v1=Vector2(self.v1.y, self.v1.z)\r
441         v2=Vector2(self.v2.y, self.v2.z)\r
442         e01=v1-v0\r
443         e12=v2-v1\r
444         e20=v0-v2\r
445         c0=Vector2.cross(e01, p-v0)\r
446         c1=Vector2.cross(e12, p-v1)\r
447         c2=Vector2.cross(e20, p-v2)\r
448         if c0>=0 and c1>=0 and c2>=0:\r
449             return True\r
450         if c0<=0 and c1<=0 and c2<=0:\r
451             return True\r
452 \r
453     def isInsideZX(self, p):\r
454         v0=Vector2(self.v0.z, self.v0.x)\r
455         v1=Vector2(self.v1.z, self.v1.x)\r
456         v2=Vector2(self.v2.z, self.v2.x)\r
457         e01=v1-v0\r
458         e12=v2-v1\r
459         e20=v0-v2\r
460         c0=Vector2.cross(e01, p-v0)\r
461         c1=Vector2.cross(e12, p-v1)\r
462         c2=Vector2.cross(e20, p-v2)\r
463         if c0>=0 and c1>=0 and c2>=0:\r
464             return True\r
465         if c0<=0 and c1<=0 and c2<=0:\r
466             return True\r
467 \r
468 \r
469 class MikotoAnchor(object):\r
470     """\r
471     mikoto方式スケルトンのアンカー。\r
472     """\r
473     __slots__=[\r
474             "triangles", "bbox",\r
475             ]\r
476     def __init__(self):\r
477         self.triangles=[]\r
478         self.bbox=None\r
479 \r
480     def push(self, face, vertices):\r
481         if face.index_count==3:\r
482             self.triangles.append(TrianglePlane(\r
483                 vertices[face.indices[0]],\r
484                 vertices[face.indices[1]],\r
485                 vertices[face.indices[2]]\r
486                 ))\r
487         elif face.index_count==4:\r
488             self.triangles.append(TrianglePlane(\r
489                 vertices[face.indices[0]],\r
490                 vertices[face.indices[1]],\r
491                 vertices[face.indices[2]]\r
492                 ))\r
493             self.triangles.append(TrianglePlane(\r
494                 vertices[face.indices[2]],\r
495                 vertices[face.indices[3]],\r
496                 vertices[face.indices[0]]\r
497                 ))\r
498         # bounding box\r
499         if not self.bbox:\r
500             self.bbox=BoundingBox(vertices[face.indices[0]])\r
501         for i in face.indices:\r
502             self.bbox.expand(vertices[i])\r
503 \r
504 \r
505     def calcWeight(self, v):\r
506         if not self.bbox.isInside(v):\r
507             return 0\r
508 \r
509         if self.anyXY(v.x, v.y) and self.anyYZ(v.y, v.z) and self.anyZX(v.z, v.x):\r
510             return 1.0\r
511         else:\r
512             return 0\r
513         \r
514     def anyXY(self, x, y):\r
515         for t in self.triangles:\r
516             if t.isInsideXY(Vector2(x, y)):\r
517                 return True\r
518         return False\r
519 \r
520     def anyYZ(self, y, z):\r
521         for t in self.triangles:\r
522             if t.isInsideYZ(Vector2(y, z)):\r
523                 return True\r
524         return False\r
525 \r
526     def anyZX(self, z, x):\r
527         for t in self.triangles:\r
528             if t.isInsideZX(Vector2(z, x)):\r
529                 return True\r
530         return False\r
531 \r
532 \r
533 def create_bone_weight(scene, mqo, armature_object, objects):\r
534     """\r
535     create mikoto bone weight.\r
536     """\r
537     anchorMap={}\r
538     # setup mikoto anchors\r
539     for o in mqo.objects:\r
540         if o.name.startswith("anchor"):\r
541             for f in o.faces:\r
542                 name=mqo.materials[f.material_index].name\r
543                 if name.endswith('[]'):\r
544                     basename=name[0:-2]\r
545                     v=o.vertices[f.indices[0]]\r
546                     if(v.x>0):\r
547                         # L\r
548                         name_L=basename+'_L'\r
549                         if not name_L in anchorMap:\r
550                             anchorMap[name_L]=MikotoAnchor()\r
551                         anchorMap[name_L].push(f, o.vertices)\r
552                     elif(v.x<0):\r
553                         # R\r
554                         name_R=basename+'_R'\r
555                         if not name_R in anchorMap:\r
556                             anchorMap[name_R]=MikotoAnchor()\r
557                         anchorMap[name_R].push(f, o.vertices)\r
558                     else:\r
559                         print("no side", v)\r
560                 else:\r
561                     if not name in anchorMap:\r
562                         anchorMap[name]=MikotoAnchor()\r
563                     anchorMap[name].push(f, o.vertices)\r
564 \r
565     for o in objects:\r
566         # add armature modifier\r
567         mod=o.modifiers.append(Modifier.Types.ARMATURE)\r
568         mod[Modifier.Settings.OBJECT] = armature_object\r
569         mod[Modifier.Settings.ENVELOPES] = False\r
570         o.makeDisplayList()\r
571         # create vertex group\r
572         mesh=o.getData(mesh=True)\r
573         for name in anchorMap.keys():\r
574             mesh.addVertGroup(name)\r
575         mesh.update()\r
576                  \r
577     # assing vertices to vertex group\r
578     for o in objects:\r
579         mesh=o.getData(mesh=True)\r
580         for i, mvert in enumerate(mesh.verts):\r
581             hasWeight=False\r
582             for name, anchor in anchorMap.items():\r
583                 weight=anchor.calcWeight(mvert.co)\r
584                 if weight>0:\r
585                     mesh.assignVertsToGroup(\r
586                             name, [i], weight, Mesh.AssignModes.ADD)\r
587                     hasWeight=True\r
588             if not hasWeight:\r
589                 # debug orphan vertex\r
590                 print('orphan', mvert)\r
591         mesh.update()\r
592     \r
593 \r
594 def execute_24(filename):\r
595     """\r
596     import a mqo file.\r
597     """\r
598     filename=filename.decode(INTERNAL_ENCODING)\r
599     print "##start mqo_import.py##"\r
600     print INTERNAL_ENCODING, FS_ENCODING\r
601     print "parse mqo file: %s" % (filename)\r
602 \r
603     Blender.Window.WaitCursor(1) \r
604     t = Blender.sys.time() \r
605 \r
606     # parse file\r
607     io=mqo.IO()\r
608     \r
609     if not io.read(filename):\r
610         return\r
611 \r
612     # get active scene\r
613     scene = Blender.Scene.GetCurrent()\r
614 \r
615     # create materials\r
616     materials=create_materials(scene, io, os.path.dirname(filename))\r
617  \r
618     # create objects\r
619     root=scene.objects.new("Empty")\r
620     root.setName(os.path.basename(filename))\r
621     objects=create_objects(scene, root, io, materials)\r
622 \r
623     if has_mikoto(io):\r
624         # create mikoto bone\r
625         armature_object=create_armature(scene, io)\r
626         if armature_object:\r
627             root.makeParent([armature_object])\r
628 \r
629             # create bone weight\r
630             create_bone_weight(scene, io, armature_object, objects)\r
631 \r
632 \r
633     print('finished in %.2f seconds' % (Blender.sys.time()-t))\r
634     print('')\r
635     Blender.Redraw()\r
636     Blender.Window.WaitCursor(0) \r
637 \r
638 \r
639 if isBlender24():\r
640     # for 2.4\r
641     Blender.Window.FileSelector(execute_24, 'Import MQO', '*.mqo')\r
642 else:\r
643     # for 2.5\r
644     pass\r
645 \r