OSDN Git Service

fix relative import
[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 __bpydoc__= '''\\r
12 \r
13 MQO Importer\r
14 \r
15 This script imports a mqo into Blender for editing.\r
16 \r
17 20080123: update.\r
18 20091125: modify for linux.\r
19 20100310: rewrite.\r
20 20100311: create armature from mikoto bone.\r
21 20100505: C extension.\r
22 20100606: integrate 2.4 and 2.5.\r
23 20100619: fix multibyte object name.\r
24 20100626: refactoring.\r
25 20100724: update for Blender2.53.\r
26 20100731: add full python module.\r
27 20101005: update for Blender2.54.\r
28 20101228: update for Blender2.55.\r
29 20110429: update for Blender2.57b.\r
30 20110918: update for Blender2.59.\r
31 20111002: update for pymeshio-2.1.0\r
32 '''\r
33 \r
34 bl_addon_info = {\r
35         'category': 'Import/Export',\r
36         'name': 'Import: Metasequioa Model Format (.mqo)',\r
37         'author': 'ousttrue',\r
38         'version': (2, 0),\r
39         'blender': (2, 5, 3),\r
40         'location': 'File > Import',\r
41         'description': 'Import from the Metasequioa Model Format (.mqo)',\r
42         'warning': '', # used for warning icon and text in addons panel\r
43         'wiki_url': 'http://sourceforge.jp/projects/meshio/wiki/FrontPage',\r
44         'tracker_url': 'http://sourceforge.jp/ticket/newticket.php?group_id=5081',\r
45         }\r
46 \r
47 import os\r
48 import sys\r
49 from .pymeshio.mqo import reader\r
50 \r
51 # for 2.5\r
52 import bpy\r
53 \r
54 # wrapper\r
55 from . import bl25 as bl\r
56 \r
57 def createMqoMaterial(m):\r
58     material = bpy.data.materials.new(m.name.decode("cp932"))\r
59     # shader\r
60     if m.shader==1:\r
61         material.diffuse_shader='FRESNEL'\r
62     else:\r
63         material.diffuse_shader='LAMBERT'\r
64     # diffuse\r
65     material.diffuse_color=[m.color.r, m.color.g, m.color.b]\r
66     material.diffuse_intensity=m.diffuse\r
67     material.alpha=m.color.a\r
68     # other\r
69     material.ambient = m.ambient\r
70     #material.specular = m.specular\r
71     material.emit=m.emit\r
72     material.use_shadeless=True\r
73     return material\r
74 \r
75 \r
76 def has_mikoto(mqo):\r
77     return False\r
78 \r
79 \r
80 def __createMaterials(mqo, directory):\r
81     """\r
82     create blender materials and renturn material list.\r
83     """\r
84     materials = []\r
85     textureMap={}\r
86     imageMap={}\r
87     if len(mqo.materials)>0:\r
88         for material_index, m in enumerate(mqo.materials):\r
89             # material\r
90             material=createMqoMaterial(m)\r
91             materials.append(material)\r
92             # texture\r
93             texture_name=m.tex.decode("cp932")\r
94             if texture_name!=b'':\r
95                 if texture_name in textureMap:\r
96                     texture=textureMap[texture_name]\r
97                 else:\r
98                     # load texture image\r
99                     if os.path.isabs(texture_name):\r
100                         # absolute\r
101                         path = texture_name\r
102                     else:\r
103                         # relative\r
104                         path = os.path.join(directory, texture_name)\r
105                     # texture\r
106                     path=path.replace("\\", "/")\r
107                     if os.path.exists(path):\r
108                         print("create texture:", path)\r
109                         texture, image=bl.texture.create(path)\r
110                         textureMap[texture_name]=texture\r
111                         imageMap[material_index]=image\r
112                     else:\r
113                         print("%s not exits" % path)\r
114                         continue\r
115                 bl.material.addTexture(material, texture)\r
116     else:\r
117         # default material\r
118         pass\r
119     return materials, imageMap\r
120 \r
121 \r
122 def __createObjects(mqo, root, materials, imageMap, scale):\r
123     """\r
124     create blender mesh objects.\r
125     """\r
126     # tree stack\r
127     stack=[root]    \r
128     objects=[]\r
129     for o in mqo.objects:\r
130         mesh, mesh_object=bl.mesh.create(o.name.decode("cp932"))\r
131 \r
132         # add hierarchy\r
133         stack_depth=len(stack)-1\r
134         #print(o.depth, stack_depth)\r
135         if o.depth<stack_depth:\r
136             for i in range(stack_depth-o.depth):\r
137                 stack.pop()\r
138         bl.object.makeParent(stack[-1], mesh_object)\r
139         stack.append(mesh_object)\r
140 \r
141         obj_name=o.name.decode("cp932")\r
142         if obj_name.startswith('sdef'):\r
143             objects.append(mesh_object)\r
144         elif obj_name.startswith('anchor'):\r
145             bl.object.setLayerMask(mesh_object, [0, 1])\r
146         elif obj_name.startswith('bone'):\r
147             bl.object.setLayerMask(mesh_object, [0, 1])\r
148 \r
149         # geometry\r
150         vertices=[(v.x * scale, -v.z * scale, v.y * scale) for v in o.vertices]\r
151         faces=[]\r
152         materialMap={}\r
153         for f in o.faces:\r
154             face_indices=[]\r
155             # flip face\r
156             for i in reversed(range(f.index_count)):\r
157                 face_indices.append(f.getIndex(i))\r
158             faces.append(face_indices)\r
159             materialMap[f.material_index]=True\r
160         bl.mesh.addGeometry(mesh, vertices, faces)\r
161 \r
162         # blender limits 16 materials per mesh\r
163         for i, material_index in enumerate(materialMap.keys()):\r
164             if i>=16:\r
165                 # split a mesh ?\r
166                 print("over 16 materials!")\r
167                 break\r
168             bl.mesh.addMaterial(mesh, materials[material_index])\r
169             materialMap[material_index]=i\r
170  \r
171         # set face params\r
172         assert(len(o.faces)==len(mesh.faces))\r
173         bl.mesh.addUV(mesh)\r
174         for i, (f, face) in enumerate(zip(o.faces, mesh.faces)):\r
175             uv_array=[]\r
176             # ToDo FIX\r
177             # flip face\r
178             for j in reversed(range(f.index_count)):\r
179                 uv_array.append((f.getUV(j).x, 1.0-f.getUV(j).y))\r
180             bl.mesh.setFaceUV(mesh, i, face, uv_array, \r
181                     imageMap.get(f.material_index, None))\r
182             if f.material_index in materialMap:\r
183                 bl.face.setMaterial(face, materialMap[f.material_index])\r
184             bl.face.setSmooth(face, True)\r
185 \r
186         # mirror modifier\r
187         if o.mirror:\r
188             bl.modifier.addMirror(mesh_object)\r
189 \r
190         # set smoothing\r
191         bl.mesh.setSmooth(mesh, o.smoothing)\r
192 \r
193         # calc normal\r
194         bl.mesh.recalcNormals(mesh_object)\r
195 \r
196     return objects\r
197 \r
198 \r
199 ###############################################################################\r
200 # for mqo mikoto bone.\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.getIndex(0)\r
221         i1=face.getIndex(1)\r
222         i2=face.getIndex(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(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\e$BJ}<0%\!<%s$N%"%s%+!<%&%'%$%H7W;;MQ!#\e(B\r
404     (\e$BIT40A4\e(B)\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\e$BJ}<0%9%1%k%H%s$N%"%s%+!<!#\e(B\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 _execute(filepath='', scale=0.1):\r
586     # read mqo model\r
587     model=reader.read_from_file(filepath)\r
588     if not model:\r
589         bl.message("fail to load %s" % filepath)\r
590         return\r
591 \r
592     # create materials\r
593     materials, imageMap=__createMaterials(model, os.path.dirname(filepath))\r
594     if len(materials)==0:\r
595         materials.append(bl.material.create('default'))\r
596 \r
597     # create objects\r
598     root=bl.object.createEmpty(os.path.basename(filepath))\r
599     objects=__createObjects(model, root, materials, imageMap, scale)\r
600 \r
601     if has_mikoto(model):\r
602         # create mikoto bone\r
603         armature_object=create_armature(model)\r
604         if armature_object:\r
605             root.makeParent([armature_object])\r
606 \r
607             # create bone weight\r
608             create_bone_weight(model, armature_object, objects)\r
609 \r