OSDN Git Service

replace blender25-meshio to blender26-meshio
[meshio/pymeshio.git] / blender26-meshio / import_mqo.py
diff --git a/blender26-meshio/import_mqo.py b/blender26-meshio/import_mqo.py
new file mode 100755 (executable)
index 0000000..a755632
--- /dev/null
@@ -0,0 +1,609 @@
+#!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)
+