OSDN Git Service

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