OSDN Git Service

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