OSDN Git Service

implement mqo_export.
authorousttrue <ousttrue@gmail.com>
Sun, 6 Jun 2010 10:37:32 +0000 (19:37 +0900)
committerousttrue <ousttrue@gmail.com>
Sun, 6 Jun 2010 10:57:02 +0000 (19:57 +0900)
swig/blender/Makefile
swig/blender/bl24.py
swig/blender/bl25.py
swig/blender/cp.py
swig/blender/mqo_export.py [new file with mode: 0644]
swig/blender/mqo_import.py

index 660e56f..1e2dfbb 100644 (file)
@@ -1,5 +1,5 @@
 all: copy
 
-copy: mqo_import.py bl24.py bl25.py
+copy: bl24.py bl25.py mqo_import.py mqo_export.py
        /cygdrive/C/Python26/python cp.py $^
 
index fb180c1..0cce8a3 100644 (file)
@@ -14,6 +14,21 @@ else:
     INTERNAL_ENCODING=FS_ENCODING
 
 
+class Writer(object):
+    def __init__(self, path, encoding):
+        self.io=open(path, "wb")
+        self.encoding=encoding
+
+    def write(self, s):
+        self.io.write(s)
+
+    def flush(self):
+        self.io.flush()
+
+    def close(self):
+        self.io.close()
+
+
 def createEmptyObject(scene, name):
     empty=scene.objects.new("Empty")
     empty.setName(name)
@@ -511,3 +526,55 @@ def create_bone_weight(scene, mqo, armature_object, objects):
                 print('orphan', mvert)
         mesh.update()
 
+###############################################################################
+def getTexture(m, dirname):
+    tex=""
+    aplane=""
+    # texture
+    for texture in m.getTextures():
+        if texture and texture.tex and texture.tex.getImage():
+            image=texture.tex.getImage()
+            if not image:
+                continue
+            imagePath=Blender.sys.expandpath(image.getFilename())
+            if len(dirname)>0 and imagePath.startswith(dirname):
+                # \e$BAjBP%Q%9$KJQ49$9$k\e(B
+                imagePath=imagePath[len(dirname)+1:len(imagePath)]
+            if texture.mtCol>0:
+                tex=" tex(\"%s\")" % imagePath
+            elif texture.mtAlpha>0:
+                aplane=" aplane(\"%s\")" % imagePath
+    return tex, aplane
+
+def objectDuplicate(scene, obj):
+    mesh, dumy=createMesh(scene, obj.name.decode(INTERNAL_ENCODING))
+    # not apply modifiers
+    mesh.getFromObject(obj.name, 1)
+    # apply matrix
+    dumy.setMatrix(obj.matrixWorld)
+    return mesh, dumy
+
+def faceVertexCount(face):
+    return len(face.v)
+
+def faceVertices(face):
+    # flip
+    return [v.index for v in reversed(face.v)]
+
+def meshHasUV(mesh):
+    return mesh.faceUV
+
+def faceHasUV(mesh, i, face):
+    return len(face.uv)>0
+
+def faceGetUV(mesh, i, face, count):
+    # flip
+    return reversed(face.uv)
+
+def materialToMqo(m):
+    return "\"%s\" shader(3) col(%f %f %f %f)" % (
+            m.name, m.rgbCol[0], m.rgbCol[1], m.rgbCol[2], m.alpha)
+
+def faceMaterialIndex(face):
+    return face.mat
+
index e344c8e..27ff977 100644 (file)
@@ -2,6 +2,21 @@ import bpy
 import os
 
 
+class Writer(object):
+    def __init__(self, path, encoding):
+        self.io=open(path, "wb")
+        self.encoding=encoding
+
+    def write(self, s):
+        self.io.write(s.encode(self.encoding))
+
+    def flush(self):
+        self.io.flush()
+
+    def close(self):
+        self.io.close()
+
+
 def createEmptyObject(scene, name):
     empty=bpy.data.objects.new(name, None)
     scene.objects.link(empty)
@@ -126,4 +141,68 @@ def meshAddMqoGeometry(mesh, o, materials, imageMap, scale):
 
     mesh.update()
 
+def getTexture(m, dirname):
+    tex=""
+    aplane=""
+    # texture
+    for slot in m.texture_slots:
+        if slot and slot.texture:
+            texture=slot.texture
+            if  texture.type=="IMAGE":
+                image=texture.image
+                if not image:
+                    continue
+                imagePath=image.filename
+                if len(dirname)>0 and imagePath.startswith(dirname):
+                    # \e$BAjBP%Q%9$KJQ49$9$k\e(B
+                    imagePath=imagePath[len(dirname)+1:len(imagePath)]
+                #imagePath=Blender.sys.expandpath(
+                #        imagePath).replace("\\", '/')
+                if slot.map_colordiff:
+                    tex=" tex(\"%s\")" % imagePath
+                elif slot.map_alpha:
+                    aplane=" aplane(\"%s\")" % imagePath
+    return tex, aplane
+
+def objectDuplicate(scene, obj):
+    bpy.ops.object.select_all(action='DESELECT')
+    obj.selected=True
+    scene.objects.active=obj
+    bpy.ops.object.duplicate()
+    dumy=scene.objects.active
+    bpy.ops.object.rotation_apply()
+    bpy.ops.object.scale_apply()
+    bpy.ops.object.location_apply()
+    return dumy.data, dumy
+
+def faceVertexCount(face):
+    return len(face.verts)
+
+def faceVertices(face):
+    return face.verts[:]
+
+def meshHasUV(mesh):
+    return mesh.active_uv_texture
+
+def faceHasUV(mesh, i, face):
+    return mesh.active_uv_texture.data[i]
+
+def faceGetUV(mesh, i, faces, count):
+    uvFace=mesh.active_uv_texture.data[i]
+    if count==3:
+        return (uvFace.uv1, uvFace.uv2, uvFace.uv3)
+    elif count==4:
+        return (uvFace.uv1, uvFace.uv2, uvFace.uv3, uvFace.uv4)
+    else:
+        print(count)
+        assert(False)
+
+def materialToMqo(m):
+    return "\"%s\" shader(3) col(%f %f %f %f)" % (
+            m.name, 
+            m.diffuse_color[0], m.diffuse_color[1], m.diffuse_color[2], 
+            m.alpha)
+
+def faceMaterialIndex(face):
+    return face.material_index
 
index a3b595e..10fd049 100644 (file)
@@ -8,6 +8,7 @@ DST_24=[
 
 MAP_25={
         "mqo_import.py": "import_scene_mqo.py",
+        "mqo_export.py": "export_scene_mqo.py",
         "bl25.py": "bl25.py",
         }
 
diff --git a/swig/blender/mqo_export.py b/swig/blender/mqo_export.py
new file mode 100644 (file)
index 0000000..54dd51c
--- /dev/null
@@ -0,0 +1,331 @@
+#!BPY\r
+# coding: utf-8\r
+\r
+"""\r
+Name: 'Metasequoia (.mqo)...'\r
+Blender: 245\r
+Group: 'Export'\r
+Tooltip: 'Save as Metasequoia MQO File'\r
+"""\r
+__author__= 'ousttrue'\r
+__url__ = ["http://gunload.web.fc2.com/blender/"]\r
+__version__= '0.3 2010/06/06'\r
+__bpydoc__ = """\\r
+This script is an exporter to MQO file format.\r
+\r
+Usage:\r
+\r
+Run this script from "File->Export" menu.\r
+\r
+0.1 20080128:\r
+0.2 20100518: refactoring.\r
+0.3 20100606: integrate 2.4 and 2.5.\r
+"""\r
+\r
+\r
+###############################################################################\r
+# import\r
+###############################################################################\r
+import os\r
+import sys\r
+\r
+def isBlender24():\r
+    return sys.version_info[0]<3\r
+\r
+if isBlender24():\r
+    # for 2.4\r
+    import Blender\r
+    from Blender import Mathutils\r
+    import bpy\r
+\r
+    # wrapper\r
+    import bl24 as bl\r
+\r
+else:\r
+    # for 2.5\r
+    import bpy\r
+    from bpy.props import *\r
+\r
+    # wrapper\r
+    import bl25 as bl\r
+\r
+def apply_transform(vec, matrix):\r
+    x, y, z = vec\r
+    xloc, yloc, zloc = matrix[3][0], matrix[3][1], matrix[3][2]\r
+    return    x*matrix[0][0] + y*matrix[1][0] + z*matrix[2][0] + xloc,\\r
+            x*matrix[0][1] + y*matrix[1][1] + z*matrix[2][1] + yloc,\\r
+            x*matrix[0][2] + y*matrix[1][2] + z*matrix[2][2] + zloc\r
+\r
+def convert_to_mqo(vec):\r
+    return vec.x, vec.z, -vec.y\r
+\r
+\r
+class OutlineNode(object):\r
+    __slots__=['o', 'children']\r
+    def __init__(self, o):\r
+        self.o=o\r
+        self.children=[]\r
+\r
+    def __str__(self):\r
+        return "<Node %s>" % self.o\r
+\r
+\r
+class ObjectInfo(object):\r
+    __slots__=['object', 'depth', 'material_map']\r
+    def __init__(self, o, depth):\r
+        self.object=o\r
+        self.depth=depth\r
+        self.material_map={}\r
+\r
+    def __str__(self):\r
+        return "<ObjectInfo %d %s>" % (self.depth, self.object)\r
+\r
+\r
+class MqoExporter(object):\r
+    __slots__=["materials", "objects"]\r
+    def __init__(self):\r
+        self.objects=[]\r
+        self.materials=[]\r
+\r
+    def setup(self, scene):\r
+        # 木構造を構築する\r
+        object_node_map={}\r
+        for o in scene.objects:\r
+            object_node_map[o]=OutlineNode(o)\r
+        for node in object_node_map.values():\r
+            if node.o.parent:\r
+                object_node_map[node.o.parent].children.append(node)\r
+\r
+        # ルートを得る\r
+        root=object_node_map[scene.objects.active]\r
+\r
+        # 情報を集める\r
+        if root.o.type.upper()=='EMPTY':\r
+            # depth調整 \r
+            for node in root.children:\r
+                self.__setup(node)\r
+        else:\r
+            self.__setup(root)\r
+\r
+    def __setup(self, node, depth=0):\r
+        info=ObjectInfo(node.o, depth)\r
+        self.objects.append(info)\r
+        if node.o.type.upper()=='MESH':\r
+            # set material index\r
+            for i, m in enumerate(node.o.data.materials):\r
+                info.material_map[i]=self.__getOrAddMaterial(m)\r
+        # recursive\r
+        for child in node.children:\r
+            self.__setup(child, depth+1)\r
+            \r
+    def __getOrAddMaterial(self, material):\r
+        for i, m in enumerate(self.materials):\r
+            if m==material:\r
+                return i\r
+        index=len(self.materials)\r
+        self.materials.append(material)\r
+        return index\r
+\r
+    def write(self, path, scene):\r
+        io=bl.Writer(path, 'cp932')\r
+        self.__write_header(io)\r
+        self.__write_scene(io)\r
+        print("Writing MaterialChunk")\r
+        self.__write_materials(io, os.path.dirname(path))\r
+        print("Writing ObjectChunk")\r
+        for info in self.objects:\r
+            self.__write_object(io, info, scene)\r
+        io.write("Eof\r\n")\r
+        io.flush()\r
+        io.close()\r
+\r
+    def __write_header(self, io):\r
+        io.write("Metasequoia Document\r\n")\r
+        io.write("Format Text Ver 1.0\r\n")\r
+        io.write("\r\n")\r
+\r
+    def __write_scene(self, io):\r
+        print("Writing SceneChunk")\r
+        io.write("Scene {\r\n")\r
+        io.write("}\r\n")\r
+\r
+    def __write_materials(self, io, dirname):\r
+        # each material    \r
+        io.write("Material %d {\r\n" % (len(self.materials)))\r
+        for m in self.materials:\r
+            tex, aplane=bl.getTexture(m, dirname)\r
+            if len(tex):\r
+                # textureがある場合は下地を白に\r
+                io.write("\"%s\" shader(3) col(1 1 1 1)" % m.name)\r
+            else:\r
+                # 無い場合はそのまま\r
+                io.write(bl.materialToMqo(m))\r
+            io.write(tex)\r
+            io.write(aplane)\r
+            io.write("\r\n") \r
+        # end of chunk\r
+        io.write("}\r\n") \r
+\r
+    def __write_object(self, io, info, scene):\r
+        print(info)\r
+\r
+        obj=info.object\r
+        if obj.type.upper()!='MESH':\r
+            return\r
+\r
+        # duplicate and applyMatrix\r
+        mesh, dumy=bl.objectDuplicate(scene, obj)\r
+\r
+        ############################################################\r
+        # write\r
+        ############################################################\r
+        io.write("Object \""+obj.name+"\" {\r\n")\r
+\r
+        # depth\r
+        io.write("\tdepth %d\r\n" % info.depth)\r
+\r
+        # mirroring                \r
+        isMirrorring=False\r
+        for mod in obj.modifiers:\r
+                if mod.name=="Mirror":\r
+                        isMirrorring=True\r
+                        break\r
+        if isMirrorring:\r
+                io.write("\tmirror 1\r\n")\r
+                io.write("\tmirror_axis 1\r\n")\r
+\r
+        # vertices\r
+        io.write("\tvertex %d {\r\n" % len(mesh.verts))\r
+        for vert in mesh.verts:\r
+            x, y, z = convert_to_mqo(vert.co)\r
+            io.write("\t\t%f %f %f\r\n" % (x, y, z)) # rotate to y-up\r
+        io.write("\t}\r\n")\r
+\r
+        # faces\r
+        io.write("\tface %d {\r\n" % len(mesh.faces))\r
+        for i, face in enumerate(mesh.faces):\r
+            count=bl.faceVertexCount(face)\r
+            # V\r
+            io.write("\t\t%d V(" % count)\r
+            for j in bl.faceVertices(face):\r
+                io.write("%d " % j)\r
+            io.write(")")\r
+            # mat\r
+            if len(mesh.materials):\r
+                io.write(" M(%d)" % \r
+                        info.material_map[bl.faceMaterialIndex(face)])\r
+            # UV\r
+            if bl.meshHasUV(mesh) and bl.faceHasUV(mesh, i, face):\r
+                io.write(" UV(")\r
+                for uv in bl.faceGetUV(mesh, i, face, count):\r
+                    # reverse vertical value\r
+                    io.write("%f %f " % (uv[0], 1.0-uv[1])) \r
+                io.write(")")\r
+            io.write("\r\n")\r
+        io.write("\t}\r\n") # end of faces\r
+        io.write("}\r\n") # end of object\r
+        ############################################################\r
+\r
+        # 削除する\r
+        scene.objects.unlink(dumy)\r
+\r
+\r
+def __execute(filename, scene, scale):\r
+    exporter=MqoExporter()\r
+    exporter.setup(scene)\r
+    exporter.write(filename, scene)\r
+\r
+\r
+if isBlender24():\r
+    # for 2.4\r
+    def execute_24(filename):\r
+        """\r
+        export mqo.\r
+        """\r
+        filename=filename.decode(bl.INTERNAL_ENCODING)\r
+        print("mqo exporter: %s" % filename)\r
+\r
+        Blender.Window.WaitCursor(1) \r
+        t = Blender.sys.time() \r
+\r
+        __execute(filename, Blender.Scene.GetCurrent(), 1.0)\r
+\r
+        print('finished in %.2f seconds' % (Blender.sys.time()-t) )\r
+        Blender.Redraw()\r
+        Blender.Window.WaitCursor(0) \r
+\r
+    # execute\r
+    Blender.Window.FileSelector(\r
+            execute_24, \r
+            'Export Metasequoia MQO', \r
+            Blender.sys.makename(ext='.mqo'))\r
+\r
+else:\r
+    # for 2.5\r
+    def execute_25(*args):\r
+        __execute(*args)\r
+\r
+    # operator\r
+    class EXPORT_OT_mqo(bpy.types.Operator):\r
+        '''Save a Metasequoia MQO file.'''\r
+        bl_idname = "export_scene.mqo"\r
+        bl_label = 'Export MQO'\r
+\r
+        # List of operator properties, the attributes will be assigned\r
+        # to the class instance from the operator settings before calling.\r
+\r
+        path = StringProperty(\r
+                name="File Path",\r
+                description="File path used for exporting the MQO file",\r
+                maxlen= 1024,\r
+                default= ""\r
+                )\r
+        filename = StringProperty(\r
+                name="File Name", \r
+                description="Name of the file.")\r
+        directory = StringProperty(\r
+                name="Directory", \r
+                description="Directory of the file.")\r
+\r
+        check_existing = BoolProperty(\r
+                name="Check Existing",\r
+                description="Check and warn on overwriting existing files",\r
+                default=True,\r
+                options=set('HIDDEN'))\r
+\r
+        scale = FloatProperty(\r
+                name="Scale", \r
+                description="Scale the MQO by this value", \r
+                min=0.0001, max=1000000.0, \r
+                soft_min=0.001, soft_max=100.0, default=1.0)\r
+\r
+        def execute(self, context):\r
+            execute_25(\r
+                    self.properties.path, \r
+                    context.scene, \r
+                    self.properties.scale)\r
+            return 'FINISHED'\r
+\r
+        def invoke(self, context, event):\r
+            wm=context.manager\r
+            wm.add_fileselect(self)\r
+            return 'RUNNING_MODAL'\r
+\r
+    # register menu\r
+    def menu_func(self, context): \r
+        default_path=bpy.data.filename.replace(".blend", ".mqo")\r
+        self.layout.operator(\r
+                EXPORT_OT_mqo.bl_idname, \r
+                text="Metasequoia (.mqo)").path=default_path\r
+\r
+    def register():\r
+        bpy.types.register(EXPORT_OT_mqo)\r
+        bpy.types.INFO_MT_file_export.append(menu_func)\r
+\r
+    def unregister():\r
+        bpy.types.unregister(EXPORT_OT_mqo)\r
+        bpy.types.INFO_MT_file_export.remove(menu_func)\r
+\r
+    if __name__ == "__main__":\r
+        register()\r
+\r
index 34334bd..7f38f0e 100644 (file)
@@ -29,7 +29,6 @@ This script imports a mqo into Blender for editing.
 ###############################################################################\r
 import os\r
 import sys\r
-import math\r
 \r
 # C extension\r
 from meshio import mqo\r
@@ -161,6 +160,7 @@ def __execute(filename, scene, scale=1.0):
 # register\r
 ###############################################################################\r
 if isBlender24():\r
+    # for 2.4\r
     def execute_24(filename):\r
         """\r
         import a mqo file.\r
@@ -183,18 +183,14 @@ if isBlender24():
         Blender.Redraw()\r
         Blender.Window.WaitCursor(0) \r
 \r
-    # for 2.4\r
     # execute\r
     Blender.Window.FileSelector(execute_24, 'Import MQO', '*.mqo')\r
 else:\r
-    def execute_25(filename, context, scale):\r
-        """\r
-        import a mqo file.\r
-        """\r
-        __execute(filename, context.scene, scale)\r
-\r
     # for 2.5\r
-    # import operator\r
+    def execute_25(*args):\r
+        __execute(*args)\r
+\r
+    # operator\r
     class IMPORT_OT_mqo(bpy.types.Operator):\r
         '''Import from Metasequoia file format (.mqo)'''\r
         bl_idname = "import_scene.mqo"\r
@@ -221,7 +217,10 @@ else:
                 soft_min=0.001, soft_max=100.0, default=1.0)\r
 \r
         def execute(self, context):\r
-            execute_25(self.properties.path, context, self.properties.scale)\r
+            execute_25(\r
+                    self.properties.path, \r
+                    context.scene, \r
+                    self.properties.scale)\r
             return 'FINISHED'\r
 \r
         def invoke(self, context, event):\r
@@ -232,7 +231,8 @@ else:
 \r
     # register menu\r
     def menu_func(self, context): \r
-        self.layout.operator(IMPORT_OT_mqo.bl_idname, \r
+        self.layout.operator(\r
+                IMPORT_OT_mqo.bl_idname, \r
                 text="Metasequoia (.mqo)")\r
 \r
     def register():\r