OSDN Git Service

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