--- /dev/null
+#!BPY
+# coding: utf-8
+"""
+Name: 'Metasequoia(.mqo)...'
+Blender: 245
+Group: 'Import'
+Tooltip: 'Import from Metasequoia file format (.mqo)'
+"""
+__author__=['ousttrue']
+__url__ = ["http://gunload.web.fc2.com/blender/"]
+__bpydoc__= '''\
+
+MQO Importer
+
+This script imports a mqo into Blender for editing.
+
+20080123: update.
+20091125: modify for linux.
+20100310: rewrite.
+20100311: create armature from mikoto bone.
+20100505: C extension.
+20100606: integrate 2.4 and 2.5.
+20100619: fix multibyte object name.
+20100626: refactoring.
+20100724: update for Blender2.53.
+20100731: add full python module.
+20101005: update for Blender2.54.
+20101228: update for Blender2.55.
+20110429: update for Blender2.57b.
+20110918: update for Blender2.59.
+20111002: update for pymeshio-2.1.0
+'''
+
+bl_addon_info = {
+ 'category': 'Import/Export',
+ 'name': 'Import: Metasequioa Model Format (.mqo)',
+ 'author': 'ousttrue',
+ 'version': (2, 0),
+ 'blender': (2, 5, 3),
+ 'location': 'File > Import',
+ 'description': 'Import from the Metasequioa Model Format (.mqo)',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://sourceforge.jp/projects/meshio/wiki/FrontPage',
+ 'tracker_url': 'http://sourceforge.jp/ticket/newticket.php?group_id=5081',
+ }
+
+import os
+import sys
+from .pymeshio.mqo import reader
+
+# for 2.5
+import bpy
+
+# wrapper
+from . import bl
+
+def createMqoMaterial(m):
+ material = bpy.data.materials.new(m.name.decode("cp932"))
+ # shader
+ if m.shader==1:
+ material.diffuse_shader='FRESNEL'
+ else:
+ material.diffuse_shader='LAMBERT'
+ # diffuse
+ material.diffuse_color=[m.color.r, m.color.g, m.color.b]
+ material.diffuse_intensity=m.diffuse
+ material.alpha=m.color.a
+ # other
+ material.ambient = m.ambient
+ #material.specular = m.specular
+ material.emit=m.emit
+ material.use_shadeless=True
+ return material
+
+
+def has_mikoto(mqo):
+ return False
+
+
+def __createMaterials(mqo, directory):
+ """
+ create blender materials and renturn material list.
+ """
+ materials = []
+ textureMap={}
+ imageMap={}
+ if len(mqo.materials)>0:
+ for material_index, m in enumerate(mqo.materials):
+ # material
+ material=createMqoMaterial(m)
+ materials.append(material)
+ # texture
+ texture_name=m.tex.decode("cp932")
+ if texture_name!=b'':
+ if texture_name in textureMap:
+ texture=textureMap[texture_name]
+ else:
+ # load texture image
+ if os.path.isabs(texture_name):
+ # absolute
+ path = texture_name
+ else:
+ # relative
+ path = os.path.join(directory, texture_name)
+ # texture
+ path=path.replace("\\", "/")
+ if os.path.exists(path):
+ print("create texture:", path)
+ texture, image=bl.texture.create(path)
+ textureMap[texture_name]=texture
+ imageMap[material_index]=image
+ else:
+ print("%s not exits" % path)
+ continue
+ bl.material.addTexture(material, texture)
+ else:
+ # default material
+ pass
+ return materials, imageMap
+
+
+def __createObjects(mqo, root, materials, imageMap, scale):
+ """
+ create blender mesh objects.
+ """
+ # tree stack
+ stack=[root]
+ objects=[]
+ for o in mqo.objects:
+ mesh, mesh_object=bl.mesh.create(o.name.decode("cp932"))
+
+ # add hierarchy
+ stack_depth=len(stack)-1
+ #print(o.depth, stack_depth)
+ if o.depth<stack_depth:
+ for i in range(stack_depth-o.depth):
+ stack.pop()
+ bl.object.makeParent(stack[-1], mesh_object)
+ stack.append(mesh_object)
+
+ obj_name=o.name.decode("cp932")
+ if obj_name.startswith('sdef'):
+ objects.append(mesh_object)
+ elif obj_name.startswith('anchor'):
+ bl.object.setLayerMask(mesh_object, [0, 1])
+ elif obj_name.startswith('bone'):
+ bl.object.setLayerMask(mesh_object, [0, 1])
+
+ # geometry
+ vertices=[(v.x * scale, -v.z * scale, v.y * scale) for v in o.vertices]
+ faces=[]
+ materialMap={}
+ for f in o.faces:
+ face_indices=[]
+ # flip face
+ for i in reversed(range(f.index_count)):
+ face_indices.append(f.getIndex(i))
+ faces.append(face_indices)
+ materialMap[f.material_index]=True
+ bl.mesh.addGeometry(mesh, vertices, faces)
+
+ # blender limits 16 materials per mesh
+ for i, material_index in enumerate(materialMap.keys()):
+ if i>=16:
+ # split a mesh ?
+ print("over 16 materials!")
+ break
+ bl.mesh.addMaterial(mesh, materials[material_index])
+ materialMap[material_index]=i
+
+ # set face params
+ assert(len(o.faces)==len(mesh.faces))
+ bl.mesh.addUV(mesh)
+ for i, (f, face) in enumerate(zip(o.faces, mesh.faces)):
+ uv_array=[]
+ # ToDo FIX
+ # flip face
+ for j in reversed(range(f.index_count)):
+ uv_array.append((f.getUV(j).x, 1.0-f.getUV(j).y))
+ bl.mesh.setFaceUV(mesh, i, face, uv_array,
+ imageMap.get(f.material_index, None))
+ if f.material_index in materialMap:
+ bl.face.setMaterial(face, materialMap[f.material_index])
+ bl.face.setSmooth(face, True)
+
+ # mirror modifier
+ if o.mirror:
+ bl.modifier.addMirror(mesh_object)
+
+ # set smoothing
+ bl.mesh.setSmooth(mesh, o.smoothing)
+
+ # calc normal
+ bl.mesh.recalcNormals(mesh_object)
+
+ return objects
+
+
+###############################################################################
+# for mqo mikoto bone.
+###############################################################################
+class MikotoBone(object):
+ __slots__=[
+ 'name',
+ 'iHead', 'iTail', 'iUp',
+ 'vHead', 'vTail', 'vUp',
+ 'parent', 'isFloating',
+ 'children',
+ ]
+ def __init__(self, face=None, vertices=None, materials=None):
+ self.parent=None
+ self.isFloating=False
+ self.children=[]
+ if not face:
+ self.name='root'
+ return
+
+ self.name=materials[face.material_index].name.encode('utf-8')
+
+ i0=face.getIndex(0)
+ i1=face.getIndex(1)
+ i2=face.getIndex(2)
+ v0=vertices[i0]
+ v1=vertices[i1]
+ v2=vertices[i2]
+ e01=v1-v0
+ e12=v2-v1
+ e20=v0-v2
+ sqNorm0=e01.getSqNorm()
+ sqNorm1=e12.getSqNorm()
+ sqNorm2=e20.getSqNorm()
+ if sqNorm0>sqNorm1:
+ if sqNorm1>sqNorm2:
+ # e01 > e12 > e20
+ self.iHead=i2
+ self.iTail=i1
+ self.iUp=i0
+ else:
+ if sqNorm0>sqNorm2:
+ # e01 > e20 > e12
+ self.iHead=i2
+ self.iTail=i0
+ self.iUp=i1
+ else:
+ # e20 > e01 > e12
+ self.iHead=i1
+ self.iTail=i0
+ self.iUp=i2
+ else:
+ # 0 < 1
+ if sqNorm1<sqNorm2:
+ # e20 > e12 > e01
+ self.iHead=i1
+ self.iTail=i2
+ self.iUp=i0
+ else:
+ if sqNorm0<sqNorm2:
+ # e12 > e20 > e01
+ self.iHead=i0
+ self.iTail=i2
+ self.iUp=i1
+ else:
+ # e12 > e01 > e20
+ self.iHead=i0
+ self.iTail=i1
+ self.iUp=i2
+ self.vHead=vertices[self.iHead]
+ self.vTail=vertices[self.iTail]
+ self.vUp=vertices[self.iUp]
+
+ if self.name.endswith('[]'):
+ basename=self.name[0:-2]
+ # expand LR name
+ if self.vTail.x>0:
+ self.name="%s_L" % basename
+ else:
+ self.name="%s_R" % basename
+
+
+ def setParent(self, parent, floating=False):
+ if floating:
+ self.isFloating=True
+ self.parent=parent
+ parent.children.append(self)
+
+ def printTree(self, indent=''):
+ print("%s%s" % (indent, self.name))
+ for child in self.children:
+ child.printTree(indent+' ')
+
+
+def build_armature(armature, mikotoBone, parent=None):
+ """
+ create a armature bone.
+ """
+ bone = Armature.Editbone()
+ bone.name = mikotoBone.name.encode('utf-8')
+ armature.bones[bone.name] = bone
+
+ bone.head = Mathutils.Vector(*mikotoBone.vHead.to_a())
+ bone.tail = Mathutils.Vector(*mikotoBone.vTail.to_a())
+ if parent:
+ bone.parent=parent
+ if mikotoBone.isFloating:
+ pass
+ else:
+ bone.options=[Armature.CONNECTED]
+
+ for child in mikotoBone.children:
+ build_armature(armature, child, bone)
+
+
+def create_armature(mqo):
+ """
+ create armature
+ """
+ boneObject=None
+ for o in mqo.objects:
+ if o.name.startswith('bone'):
+ boneObject=o
+ break
+ if not boneObject:
+ return
+
+ tailMap={}
+ for f in boneObject.faces:
+ if f.index_count!=3:
+ print("invalid index_count: %d" % f.index_count)
+ continue
+ b=MikotoBone(f, boneObject.vertices, mqo.materials)
+ tailMap[b.iTail]=b
+
+ ####################
+ # build mikoto bone tree
+ ####################
+ mikotoRoot=MikotoBone()
+
+ for b in tailMap.values():
+ # each bone has unique parent or is root bone.
+ if b.iHead in tailMap:
+ b.setParent(tailMap[b.iHead])
+ else:
+ isFloating=False
+ for e in boneObject.edges:
+ if b.iHead==e.indices[0]:
+ # floating bone
+ if e.indices[1] in tailMap:
+ b.setParent(tailMap[e.indices[1]], True)
+ isFloating=True
+ break
+ elif b.iHead==e.indices[1]:
+ # floating bone
+ if e.indices[0] in tailMap:
+ b.setParent(tailMap[e.indices[0]], True)
+ isFloating=True
+ break
+ if isFloating:
+ continue
+
+ # no parent bone
+ b.setParent(mikotoRoot, True)
+
+ if len(mikotoRoot.children)==0:
+ print("no root bone")
+ return
+
+ if len(mikotoRoot.children)==1:
+ # single root
+ mikotoRoot=mikotoRoot.children[0]
+ mikotoRoot.parent=None
+ else:
+ mikotoRoot.vHead=Vector3(0, 10, 0)
+ mikotoRoot.vTail=Vector3(0, 0, 0)
+
+ ####################
+ # create armature
+ ####################
+ armature = Armature.New()
+ # link to object
+ armature_object = scene.objects.new(armature)
+ # create action
+ act = Armature.NLA.NewAction()
+ act.setActive(armature_object)
+ # set XRAY
+ armature_object.drawMode |= Object.DrawModes.XRAY
+ # armature settings
+ armature.drawType = Armature.OCTAHEDRON
+ armature.envelopes = False
+ armature.vertexGroups = True
+ armature.mirrorEdit = True
+ armature.drawNames=True
+
+ # edit bones
+ armature.makeEditable()
+ build_armature(armature, mikotoRoot)
+ armature.update()
+
+ return armature_object
+
+
+class TrianglePlane(object):
+ """
+ mikoto\e$BJ}<0%\!<%s$N%"%s%+!<%&%'%$%H7W;;MQ!#\e(B
+ (\e$BIT40A4\e(B)
+ """
+ __slots__=['normal',
+ 'v0', 'v1', 'v2',
+ ]
+ def __init__(self, v0, v1, v2):
+ self.v0=v0
+ self.v1=v1
+ self.v2=v2
+
+ def isInsideXY(self, p):
+ v0=Vector2(self.v0.x, self.v0.y)
+ v1=Vector2(self.v1.x, self.v1.y)
+ v2=Vector2(self.v2.x, self.v2.y)
+ e01=v1-v0
+ e12=v2-v1
+ e20=v0-v2
+ c0=Vector2.cross(e01, p-v0)
+ c1=Vector2.cross(e12, p-v1)
+ c2=Vector2.cross(e20, p-v2)
+ if c0>=0 and c1>=0 and c2>=0:
+ return True
+ if c0<=0 and c1<=0 and c2<=0:
+ return True
+
+ def isInsideYZ(self, p):
+ v0=Vector2(self.v0.y, self.v0.z)
+ v1=Vector2(self.v1.y, self.v1.z)
+ v2=Vector2(self.v2.y, self.v2.z)
+ e01=v1-v0
+ e12=v2-v1
+ e20=v0-v2
+ c0=Vector2.cross(e01, p-v0)
+ c1=Vector2.cross(e12, p-v1)
+ c2=Vector2.cross(e20, p-v2)
+ if c0>=0 and c1>=0 and c2>=0:
+ return True
+ if c0<=0 and c1<=0 and c2<=0:
+ return True
+
+ def isInsideZX(self, p):
+ v0=Vector2(self.v0.z, self.v0.x)
+ v1=Vector2(self.v1.z, self.v1.x)
+ v2=Vector2(self.v2.z, self.v2.x)
+ e01=v1-v0
+ e12=v2-v1
+ e20=v0-v2
+ c0=Vector2.cross(e01, p-v0)
+ c1=Vector2.cross(e12, p-v1)
+ c2=Vector2.cross(e20, p-v2)
+ if c0>=0 and c1>=0 and c2>=0:
+ return True
+ if c0<=0 and c1<=0 and c2<=0:
+ return True
+
+
+class MikotoAnchor(object):
+ """
+ mikoto\e$BJ}<0%9%1%k%H%s$N%"%s%+!<!#\e(B
+ """
+ __slots__=[
+ "triangles", "bbox",
+ ]
+ def __init__(self):
+ self.triangles=[]
+ self.bbox=None
+
+ def push(self, face, vertices):
+ if face.index_count==3:
+ self.triangles.append(TrianglePlane(
+ vertices[face.indices[0]],
+ vertices[face.indices[1]],
+ vertices[face.indices[2]]
+ ))
+ elif face.index_count==4:
+ self.triangles.append(TrianglePlane(
+ vertices[face.indices[0]],
+ vertices[face.indices[1]],
+ vertices[face.indices[2]]
+ ))
+ self.triangles.append(TrianglePlane(
+ vertices[face.indices[2]],
+ vertices[face.indices[3]],
+ vertices[face.indices[0]]
+ ))
+ # bounding box
+ if not self.bbox:
+ self.bbox=BoundingBox(vertices[face.indices[0]])
+ for i in face.indices:
+ self.bbox.expand(vertices[i])
+
+
+ def calcWeight(self, v):
+ if not self.bbox.isInside(v):
+ return 0
+
+ if self.anyXY(v.x, v.y) and self.anyYZ(v.y, v.z) and self.anyZX(v.z, v.x):
+ return 1.0
+ else:
+ return 0
+
+ def anyXY(self, x, y):
+ for t in self.triangles:
+ if t.isInsideXY(Vector2(x, y)):
+ return True
+ return False
+
+ def anyYZ(self, y, z):
+ for t in self.triangles:
+ if t.isInsideYZ(Vector2(y, z)):
+ return True
+ return False
+
+ def anyZX(self, z, x):
+ for t in self.triangles:
+ if t.isInsideZX(Vector2(z, x)):
+ return True
+ return False
+
+
+def create_bone_weight(scene, mqo, armature_object, objects):
+ """
+ create mikoto bone weight.
+ """
+ anchorMap={}
+ # setup mikoto anchors
+ for o in mqo.objects:
+ if o.name.startswith("anchor"):
+ for f in o.faces:
+ name=mqo.materials[f.material_index].name
+ if name.endswith('[]'):
+ basename=name[0:-2]
+ v=o.vertices[f.indices[0]]
+ if(v.x>0):
+ # L
+ name_L=basename+'_L'
+ if not name_L in anchorMap:
+ anchorMap[name_L]=MikotoAnchor()
+ anchorMap[name_L].push(f, o.vertices)
+ elif(v.x<0):
+ # R
+ name_R=basename+'_R'
+ if not name_R in anchorMap:
+ anchorMap[name_R]=MikotoAnchor()
+ anchorMap[name_R].push(f, o.vertices)
+ else:
+ print("no side", v)
+ else:
+ if not name in anchorMap:
+ anchorMap[name]=MikotoAnchor()
+ anchorMap[name].push(f, o.vertices)
+
+ for o in objects:
+ # add armature modifier
+ mod=o.modifiers.append(Modifier.Types.ARMATURE)
+ mod[Modifier.Settings.OBJECT] = armature_object
+ mod[Modifier.Settings.ENVELOPES] = False
+ o.makeDisplayList()
+ # create vertex group
+ mesh=o.getData(mesh=True)
+ for name in anchorMap.keys():
+ mesh.addVertGroup(name)
+ mesh.update()
+
+ # assing vertices to vertex group
+ for o in objects:
+ mesh=o.getData(mesh=True)
+ for i, mvert in enumerate(mesh.verts):
+ hasWeight=False
+ for name, anchor in anchorMap.items():
+ weight=anchor.calcWeight(mvert.co)
+ if weight>0:
+ mesh.assignVertsToGroup(
+ name, [i], weight, Mesh.AssignModes.ADD)
+ hasWeight=True
+ if not hasWeight:
+ # debug orphan vertex
+ print('orphan', mvert)
+ mesh.update()
+
+
+def _execute(filepath='', scale=0.1):
+ # read mqo model
+ model=reader.read_from_file(filepath)
+ if not model:
+ bl.message("fail to load %s" % filepath)
+ return
+
+ # create materials
+ materials, imageMap=__createMaterials(model, os.path.dirname(filepath))
+ if len(materials)==0:
+ materials.append(bl.material.create('default'))
+
+ # create objects
+ root=bl.object.createEmpty(os.path.basename(filepath))
+ objects=__createObjects(model, root, materials, imageMap, scale)
+
+ if has_mikoto(model):
+ # create mikoto bone
+ armature_object=create_armature(model)
+ if armature_object:
+ root.makeParent([armature_object])
+
+ # create bone weight
+ create_bone_weight(model, armature_object, objects)
+