OSDN Git Service

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