--- /dev/null
+#!BPY\r
+# coding: utf-8\r
+""" \r
+Name: 'Metasequoia(.mqo)...'\r
+Blender: 245\r
+Group: 'Import'\r
+Tooltip: 'Import from Metasequoia file format (.mqo)'\r
+"""\r
+__author__=['ousttrue']\r
+__url__ = ["http://gunload.web.fc2.com/blender/"]\r
+__version__= '0.6 2010/05/05'\r
+__bpydoc__= '''\\r
+\r
+MQO Importer\r
+\r
+This script imports a mqo into Blender for editing.\r
+\r
+0.2 20080123: update.\r
+0.3 20091125: modify for linux.\r
+0.4 20100310: rewrite.\r
+0.5 20100311: create armature from mikoto bone.\r
+0.6 20100505: C extension.\r
+0.7 20100606: integrate 2.4 and 2.5.\r
+'''\r
+import os\r
+import sys\r
+import math\r
+\r
+# C extension\r
+from meshio import mqo\r
+\r
+def isBlender24():\r
+ return sys.version_info[0]<3\r
+\r
+\r
+if isBlender24():\r
+ # for 2.4\r
+ import Blender\r
+ from Blender import Mathutils\r
+ import bpy\r
+\r
+ # ファイルシステムの文字コード\r
+ # 改造版との共用のため\r
+ FS_ENCODING=sys.getfilesystemencoding()\r
+ if os.path.exists(os.path.dirname(sys.argv[0])+"/utf8"):\r
+ INTERNAL_ENCODING='utf-8'\r
+ else:\r
+ INTERNAL_ENCODING=FS_ENCODING\r
+else:\r
+ # for 2.5\r
+ import bpy\r
+ from bpy.props import *\r
+\r
+\r
+def has_mikoto(mqo):\r
+ return False\r
+\r
+\r
+def create_materials(scene, mqo, directory):\r
+ """\r
+ create blender materials and renturn material list.\r
+ """\r
+ materials = []\r
+ images = []\r
+ for m in mqo.materials:\r
+ material = Blender.Material.New(m.getName().encode(INTERNAL_ENCODING))\r
+ materials.append(material)\r
+\r
+ material.mode |= Blender.Material.Modes.SHADELESS\r
+ material.rgbCol = [m.color.r, m.color.g, m.color.b]\r
+ material.alpha = m.color.a\r
+ material.amb = m.ambient\r
+ material.spec = m.specular\r
+ material.hard = int(255 * m.power)\r
+ if m.texture!="":\r
+ texture_path=m.getTexture()\r
+\r
+ # load texture image\r
+ if os.path.isabs(texture_path):\r
+ # absolute\r
+ path = texture_path\r
+ else:\r
+ # relative\r
+ path = os.path.join(directory, texture_path)\r
+\r
+ # backslash to slash\r
+ #path = path.replace('\\', '/')\r
+\r
+ # texture\r
+ if os.path.exists(path):\r
+ image = Blender.Image.Load(path.encode(INTERNAL_ENCODING))\r
+ images.append(image)\r
+ material.mode = material.mode | Blender.Material.Modes.TEXFACE\r
+ tex = Blender.Texture.New(path.encode(INTERNAL_ENCODING))\r
+ tex.type = Blender.Texture.Types.IMAGE\r
+ tex.image = image\r
+ material.setTexture(0, tex, Blender.Texture.TexCo.UV)\r
+ else:\r
+ print("%s not exits" % path)\r
+ \r
+ return materials\r
+\r
+\r
+def create_objects(scene, root, mqo, materials):\r
+ """\r
+ create blender mesh objects.\r
+ """\r
+ # store hierarchy\r
+ stack=[root] \r
+\r
+ objects=[]\r
+ for o in mqo.objects:\r
+ #print "%s:v(%d),f(%d)" % (o.name, len(o.vertices), len(o.faces))\r
+ # create mesh\r
+ mesh = Blender.Mesh.New()\r
+ mesh_object=scene.objects.new(mesh, o.name.encode('utf-8'))\r
+\r
+ # add hierarchy\r
+ stack_depth=len(stack)-1\r
+ print(o.depth, stack_depth)\r
+ if o.depth<stack_depth:\r
+ for i in range(stack_depth-o.depth):\r
+ stack.pop()\r
+ stack[-1].makeParent([mesh_object])\r
+ stack.append(mesh_object)\r
+\r
+ if o.name.startswith('sdef'):\r
+ # add sdef object\r
+ objects.append(mesh_object)\r
+ elif o.name.startswith('anchor'):\r
+ #print("hide %s" % o.name)\r
+ #mesh_object.restrictDisplay=False\r
+ mesh_object.layers=[2]\r
+ elif o.name.startswith('bone'):\r
+ mesh_object.layers=[2]\r
+\r
+ # add vertices\r
+ mesh.verts.extend(Mathutils.Vector(0, 0, 0)) # dummy\r
+ mesh.verts.extend([(v.x, -v.z, v.y) for v in o.vertices])\r
+ # add faces\r
+ mesh_faces=[]\r
+ for face in o.faces:\r
+ face_indices=[]\r
+ for i in xrange(face.index_count):\r
+ face_indices.append(face.getIndex(i)+1)\r
+ mesh_faces.append(face_indices)\r
+ #new_faces=mesh.faces.extend([face.indices for face in o.faces], \r
+ new_faces=mesh.faces.extend(mesh_faces,\r
+ #ignoreDups=True, \r
+ indexList=True)\r
+ mesh.update()\r
+ \r
+ # gather used materials\r
+ usedMaterials = {}\r
+ if new_faces:\r
+ for i in new_faces:\r
+ if type(i) is int:\r
+ usedMaterials[o.faces[i].material_index]=True\r
+\r
+ # blender limits 16 materials per mesh\r
+ # separate mesh ?\r
+ for i, material_index in enumerate(usedMaterials.keys()):\r
+ if i>=16:\r
+ print("over 16 materials!")\r
+ break\r
+ mesh.materials+=[materials[material_index]]\r
+ usedMaterials[material_index]=i\r
+ \r
+ # set face params\r
+ for i, f in enumerate(o.faces): \r
+ if not type(new_faces[i]) is int:\r
+ continue\r
+\r
+ face=mesh.faces[new_faces[i]]\r
+\r
+ uv_array=[]\r
+ for i in xrange(f.index_count):\r
+ uv_array.append(Blender.Mathutils.Vector(\r
+ f.getUV(i).x, \r
+ 1.0-f.getUV(i).y)\r
+ )\r
+ try:\r
+ face.uv=uv_array\r
+ except Exception, msg:\r
+ #print msg\r
+ #print face.index, uv_array\r
+ pass\r
+ \r
+ if f.material_index in usedMaterials:\r
+ face.mat = usedMaterials[f.material_index]\r
+\r
+ face.smooth = 1\r
+\r
+ # rmeove dummy 0 vertex\r
+ mesh.verts.delete(0)\r
+ \r
+ mesh.mode |= Blender.Mesh.Modes.AUTOSMOOTH\r
+ mesh.maxSmoothAngle = int(o.smoothing)\r
+ mesh.smooth()\r
+ mesh.calcNormals()\r
+ mesh.flipNormals()\r
+ mesh.update()\r
+\r
+ # mirror modifier\r
+ if o.mirror:\r
+ mod=mesh_object.modifiers.append(Blender.Modifier.Types.MIRROR)\r
+\r
+ return objects\r
+\r
+\r
+class MikotoBone(object):\r
+ __slots__=[\r
+ 'name',\r
+ 'iHead', 'iTail', 'iUp',\r
+ 'vHead', 'vTail', 'vUp',\r
+ 'parent', 'isFloating',\r
+ 'children',\r
+ ]\r
+ def __init__(self, face=None, vertices=None, materials=None):\r
+ self.parent=None\r
+ self.isFloating=False\r
+ self.children=[]\r
+ if not face:\r
+ self.name='root'\r
+ return\r
+\r
+ self.name=materials[face.material_index].name.encode('utf-8')\r
+\r
+ i0=face.indices[0]\r
+ i1=face.indices[1]\r
+ i2=face.indices[2]\r
+ v0=vertices[i0]\r
+ v1=vertices[i1]\r
+ v2=vertices[i2]\r
+ e01=v1-v0\r
+ e12=v2-v1\r
+ e20=v0-v2\r
+ sqNorm0=e01.getSqNorm()\r
+ sqNorm1=e12.getSqNorm()\r
+ sqNorm2=e20.getSqNorm()\r
+ if sqNorm0>sqNorm1:\r
+ if sqNorm1>sqNorm2:\r
+ # e01 > e12 > e20\r
+ self.iHead=i2\r
+ self.iTail=i1\r
+ self.iUp=i0\r
+ else:\r
+ if sqNorm0>sqNorm2:\r
+ # e01 > e20 > e12\r
+ self.iHead=i2\r
+ self.iTail=i0\r
+ self.iUp=i1\r
+ else:\r
+ # e20 > e01 > e12\r
+ self.iHead=i1\r
+ self.iTail=i0\r
+ self.iUp=i2\r
+ else:\r
+ # 0 < 1\r
+ if sqNorm1<sqNorm2:\r
+ # e20 > e12 > e01\r
+ self.iHead=i1\r
+ self.iTail=i2\r
+ self.iUp=i0\r
+ else:\r
+ if sqNorm0<sqNorm2:\r
+ # e12 > e20 > e01\r
+ self.iHead=i0\r
+ self.iTail=i2\r
+ self.iUp=i1\r
+ else:\r
+ # e12 > e01 > e20\r
+ self.iHead=i0\r
+ self.iTail=i1\r
+ self.iUp=i2\r
+ self.vHead=vertices[self.iHead]\r
+ self.vTail=vertices[self.iTail]\r
+ self.vUp=vertices[self.iUp]\r
+\r
+ if self.name.endswith('[]'):\r
+ basename=self.name[0:-2]\r
+ # expand LR name\r
+ if self.vTail.x>0:\r
+ self.name="%s_L" % basename\r
+ else:\r
+ self.name="%s_R" % basename\r
+\r
+\r
+ def setParent(self, parent, floating=False):\r
+ if floating:\r
+ self.isFloating=True\r
+ self.parent=parent\r
+ parent.children.append(self)\r
+\r
+ def printTree(self, indent=''):\r
+ print("%s%s" % (indent, self.name))\r
+ for child in self.children:\r
+ child.printTree(indent+' ')\r
+\r
+\r
+def build_armature(armature, mikotoBone, parent=None):\r
+ """\r
+ create a armature bone.\r
+ """\r
+ bone = Armature.Editbone()\r
+ bone.name = mikotoBone.name.encode('utf-8')\r
+ armature.bones[bone.name] = bone\r
+\r
+ bone.head = Mathutils.Vector(*mikotoBone.vHead.to_a())\r
+ bone.tail = Mathutils.Vector(*mikotoBone.vTail.to_a())\r
+ if parent:\r
+ bone.parent=parent\r
+ if mikotoBone.isFloating:\r
+ pass\r
+ else:\r
+ bone.options=[Armature.CONNECTED]\r
+\r
+ for child in mikotoBone.children:\r
+ build_armature(armature, child, bone)\r
+\r
+\r
+def create_armature(scene, mqo):\r
+ """\r
+ create armature\r
+ """\r
+ boneObject=None\r
+ for o in mqo.objects:\r
+ if o.name.startswith('bone'):\r
+ boneObject=o\r
+ break\r
+ if not boneObject:\r
+ return\r
+\r
+ tailMap={}\r
+ for f in boneObject.faces:\r
+ if f.index_count!=3:\r
+ print("invalid index_count: %d" % f.index_count)\r
+ continue\r
+ b=MikotoBone(f, boneObject.vertices, mqo.materials)\r
+ tailMap[b.iTail]=b\r
+\r
+ #################### \r
+ # build mikoto bone tree\r
+ #################### \r
+ mikotoRoot=MikotoBone()\r
+\r
+ for b in tailMap.values():\r
+ # each bone has unique parent or is root bone.\r
+ if b.iHead in tailMap:\r
+ b.setParent(tailMap[b.iHead])\r
+ else: \r
+ isFloating=False\r
+ for e in boneObject.edges:\r
+ if b.iHead==e.indices[0]:\r
+ # floating bone\r
+ if e.indices[1] in tailMap:\r
+ b.setParent(tailMap[e.indices[1]], True)\r
+ isFloating=True\r
+ break\r
+ elif b.iHead==e.indices[1]:\r
+ # floating bone\r
+ if e.indices[0] in tailMap:\r
+ b.setParent(tailMap[e.indices[0]], True)\r
+ isFloating=True\r
+ break\r
+ if isFloating:\r
+ continue\r
+\r
+ # no parent bone\r
+ b.setParent(mikotoRoot, True)\r
+\r
+ if len(mikotoRoot.children)==0:\r
+ print("no root bone")\r
+ return\r
+\r
+ if len(mikotoRoot.children)==1:\r
+ # single root\r
+ mikotoRoot=mikotoRoot.children[0]\r
+ mikotoRoot.parent=None\r
+ else:\r
+ mikotoRoot.vHead=Vector3(0, 10, 0)\r
+ mikotoRoot.vTail=Vector3(0, 0, 0)\r
+\r
+ #################### \r
+ # create armature\r
+ #################### \r
+ armature = Armature.New()\r
+ # link to object\r
+ armature_object = scene.objects.new(armature)\r
+ # create action\r
+ act = Armature.NLA.NewAction()\r
+ act.setActive(armature_object)\r
+ # set XRAY\r
+ armature_object.drawMode |= Object.DrawModes.XRAY\r
+ # armature settings\r
+ armature.drawType = Armature.OCTAHEDRON\r
+ armature.envelopes = False\r
+ armature.vertexGroups = True\r
+ armature.mirrorEdit = True\r
+ armature.drawNames=True\r
+\r
+ # edit bones\r
+ armature.makeEditable()\r
+ build_armature(armature, mikotoRoot)\r
+ armature.update()\r
+\r
+ return armature_object\r
+ \r
+\r
+class TrianglePlane(object):\r
+ """\r
+ mikoto方式ボーンのアンカーウェイト計算用。\r
+ (不完全)\r
+ """\r
+ __slots__=['normal', \r
+ 'v0', 'v1', 'v2',\r
+ ]\r
+ def __init__(self, v0, v1, v2):\r
+ self.v0=v0\r
+ self.v1=v1\r
+ self.v2=v2\r
+\r
+ def isInsideXY(self, p):\r
+ v0=Vector2(self.v0.x, self.v0.y)\r
+ v1=Vector2(self.v1.x, self.v1.y)\r
+ v2=Vector2(self.v2.x, self.v2.y)\r
+ e01=v1-v0\r
+ e12=v2-v1\r
+ e20=v0-v2\r
+ c0=Vector2.cross(e01, p-v0)\r
+ c1=Vector2.cross(e12, p-v1)\r
+ c2=Vector2.cross(e20, p-v2)\r
+ if c0>=0 and c1>=0 and c2>=0:\r
+ return True\r
+ if c0<=0 and c1<=0 and c2<=0:\r
+ return True\r
+\r
+ def isInsideYZ(self, p):\r
+ v0=Vector2(self.v0.y, self.v0.z)\r
+ v1=Vector2(self.v1.y, self.v1.z)\r
+ v2=Vector2(self.v2.y, self.v2.z)\r
+ e01=v1-v0\r
+ e12=v2-v1\r
+ e20=v0-v2\r
+ c0=Vector2.cross(e01, p-v0)\r
+ c1=Vector2.cross(e12, p-v1)\r
+ c2=Vector2.cross(e20, p-v2)\r
+ if c0>=0 and c1>=0 and c2>=0:\r
+ return True\r
+ if c0<=0 and c1<=0 and c2<=0:\r
+ return True\r
+\r
+ def isInsideZX(self, p):\r
+ v0=Vector2(self.v0.z, self.v0.x)\r
+ v1=Vector2(self.v1.z, self.v1.x)\r
+ v2=Vector2(self.v2.z, self.v2.x)\r
+ e01=v1-v0\r
+ e12=v2-v1\r
+ e20=v0-v2\r
+ c0=Vector2.cross(e01, p-v0)\r
+ c1=Vector2.cross(e12, p-v1)\r
+ c2=Vector2.cross(e20, p-v2)\r
+ if c0>=0 and c1>=0 and c2>=0:\r
+ return True\r
+ if c0<=0 and c1<=0 and c2<=0:\r
+ return True\r
+\r
+\r
+class MikotoAnchor(object):\r
+ """\r
+ mikoto方式スケルトンのアンカー。\r
+ """\r
+ __slots__=[\r
+ "triangles", "bbox",\r
+ ]\r
+ def __init__(self):\r
+ self.triangles=[]\r
+ self.bbox=None\r
+\r
+ def push(self, face, vertices):\r
+ if face.index_count==3:\r
+ self.triangles.append(TrianglePlane(\r
+ vertices[face.indices[0]],\r
+ vertices[face.indices[1]],\r
+ vertices[face.indices[2]]\r
+ ))\r
+ elif face.index_count==4:\r
+ self.triangles.append(TrianglePlane(\r
+ vertices[face.indices[0]],\r
+ vertices[face.indices[1]],\r
+ vertices[face.indices[2]]\r
+ ))\r
+ self.triangles.append(TrianglePlane(\r
+ vertices[face.indices[2]],\r
+ vertices[face.indices[3]],\r
+ vertices[face.indices[0]]\r
+ ))\r
+ # bounding box\r
+ if not self.bbox:\r
+ self.bbox=BoundingBox(vertices[face.indices[0]])\r
+ for i in face.indices:\r
+ self.bbox.expand(vertices[i])\r
+\r
+\r
+ def calcWeight(self, v):\r
+ if not self.bbox.isInside(v):\r
+ return 0\r
+\r
+ if self.anyXY(v.x, v.y) and self.anyYZ(v.y, v.z) and self.anyZX(v.z, v.x):\r
+ return 1.0\r
+ else:\r
+ return 0\r
+ \r
+ def anyXY(self, x, y):\r
+ for t in self.triangles:\r
+ if t.isInsideXY(Vector2(x, y)):\r
+ return True\r
+ return False\r
+\r
+ def anyYZ(self, y, z):\r
+ for t in self.triangles:\r
+ if t.isInsideYZ(Vector2(y, z)):\r
+ return True\r
+ return False\r
+\r
+ def anyZX(self, z, x):\r
+ for t in self.triangles:\r
+ if t.isInsideZX(Vector2(z, x)):\r
+ return True\r
+ return False\r
+\r
+\r
+def create_bone_weight(scene, mqo, armature_object, objects):\r
+ """\r
+ create mikoto bone weight.\r
+ """\r
+ anchorMap={}\r
+ # setup mikoto anchors\r
+ for o in mqo.objects:\r
+ if o.name.startswith("anchor"):\r
+ for f in o.faces:\r
+ name=mqo.materials[f.material_index].name\r
+ if name.endswith('[]'):\r
+ basename=name[0:-2]\r
+ v=o.vertices[f.indices[0]]\r
+ if(v.x>0):\r
+ # L\r
+ name_L=basename+'_L'\r
+ if not name_L in anchorMap:\r
+ anchorMap[name_L]=MikotoAnchor()\r
+ anchorMap[name_L].push(f, o.vertices)\r
+ elif(v.x<0):\r
+ # R\r
+ name_R=basename+'_R'\r
+ if not name_R in anchorMap:\r
+ anchorMap[name_R]=MikotoAnchor()\r
+ anchorMap[name_R].push(f, o.vertices)\r
+ else:\r
+ print("no side", v)\r
+ else:\r
+ if not name in anchorMap:\r
+ anchorMap[name]=MikotoAnchor()\r
+ anchorMap[name].push(f, o.vertices)\r
+\r
+ for o in objects:\r
+ # add armature modifier\r
+ mod=o.modifiers.append(Modifier.Types.ARMATURE)\r
+ mod[Modifier.Settings.OBJECT] = armature_object\r
+ mod[Modifier.Settings.ENVELOPES] = False\r
+ o.makeDisplayList()\r
+ # create vertex group\r
+ mesh=o.getData(mesh=True)\r
+ for name in anchorMap.keys():\r
+ mesh.addVertGroup(name)\r
+ mesh.update()\r
+ \r
+ # assing vertices to vertex group\r
+ for o in objects:\r
+ mesh=o.getData(mesh=True)\r
+ for i, mvert in enumerate(mesh.verts):\r
+ hasWeight=False\r
+ for name, anchor in anchorMap.items():\r
+ weight=anchor.calcWeight(mvert.co)\r
+ if weight>0:\r
+ mesh.assignVertsToGroup(\r
+ name, [i], weight, Mesh.AssignModes.ADD)\r
+ hasWeight=True\r
+ if not hasWeight:\r
+ # debug orphan vertex\r
+ print('orphan', mvert)\r
+ mesh.update()\r
+ \r
+\r
+def execute_24(filename):\r
+ """\r
+ import a mqo file.\r
+ """\r
+ filename=filename.decode(INTERNAL_ENCODING)\r
+ print "##start mqo_import.py##"\r
+ print INTERNAL_ENCODING, FS_ENCODING\r
+ print "parse mqo file: %s" % (filename)\r
+\r
+ Blender.Window.WaitCursor(1) \r
+ t = Blender.sys.time() \r
+\r
+ # parse file\r
+ io=mqo.IO()\r
+ \r
+ if not io.read(filename):\r
+ return\r
+\r
+ # get active scene\r
+ scene = Blender.Scene.GetCurrent()\r
+\r
+ # create materials\r
+ materials=create_materials(scene, io, os.path.dirname(filename))\r
+ \r
+ # create objects\r
+ root=scene.objects.new("Empty")\r
+ root.setName(os.path.basename(filename))\r
+ objects=create_objects(scene, root, io, materials)\r
+\r
+ if has_mikoto(io):\r
+ # create mikoto bone\r
+ armature_object=create_armature(scene, io)\r
+ if armature_object:\r
+ root.makeParent([armature_object])\r
+\r
+ # create bone weight\r
+ create_bone_weight(scene, io, armature_object, objects)\r
+\r
+\r
+ print('finished in %.2f seconds' % (Blender.sys.time()-t))\r
+ print('')\r
+ Blender.Redraw()\r
+ Blender.Window.WaitCursor(0) \r
+\r
+\r
+if isBlender24():\r
+ # for 2.4\r
+ Blender.Window.FileSelector(execute_24, 'Import MQO', '*.mqo')\r
+else:\r
+ # for 2.5\r
+ pass\r
+\r