OSDN Git Service

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