From 5d19c719e669ec5245bc167eac5b571a15fd3b8d Mon Sep 17 00:00:00 2001 From: ousttrue Date: Sun, 6 Jun 2010 19:37:32 +0900 Subject: [PATCH] implement mqo_export. --- swig/blender/Makefile | 2 +- swig/blender/bl24.py | 67 +++++++++ swig/blender/bl25.py | 79 +++++++++++ swig/blender/cp.py | 1 + swig/blender/mqo_export.py | 331 +++++++++++++++++++++++++++++++++++++++++++++ swig/blender/mqo_import.py | 22 +-- 6 files changed, 490 insertions(+), 12 deletions(-) create mode 100644 swig/blender/mqo_export.py diff --git a/swig/blender/Makefile b/swig/blender/Makefile index 660e56f..1e2dfbb 100644 --- a/swig/blender/Makefile +++ b/swig/blender/Makefile @@ -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 $^ diff --git a/swig/blender/bl24.py b/swig/blender/bl24.py index fb180c1..0cce8a3 100644 --- a/swig/blender/bl24.py +++ b/swig/blender/bl24.py @@ -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): + # $BAjBP%Q%9$KJQ49$9$k(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 + diff --git a/swig/blender/bl25.py b/swig/blender/bl25.py index e344c8e..27ff977 100644 --- a/swig/blender/bl25.py +++ b/swig/blender/bl25.py @@ -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): + # $BAjBP%Q%9$KJQ49$9$k(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 diff --git a/swig/blender/cp.py b/swig/blender/cp.py index a3b595e..10fd049 100644 --- a/swig/blender/cp.py +++ b/swig/blender/cp.py @@ -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 index 0000000..54dd51c --- /dev/null +++ b/swig/blender/mqo_export.py @@ -0,0 +1,331 @@ +#!BPY +# coding: utf-8 + +""" +Name: 'Metasequoia (.mqo)...' +Blender: 245 +Group: 'Export' +Tooltip: 'Save as Metasequoia MQO File' +""" +__author__= 'ousttrue' +__url__ = ["http://gunload.web.fc2.com/blender/"] +__version__= '0.3 2010/06/06' +__bpydoc__ = """\ +This script is an exporter to MQO file format. + +Usage: + +Run this script from "File->Export" menu. + +0.1 20080128: +0.2 20100518: refactoring. +0.3 20100606: integrate 2.4 and 2.5. +""" + + +############################################################################### +# import +############################################################################### +import os +import sys + +def isBlender24(): + return sys.version_info[0]<3 + +if isBlender24(): + # for 2.4 + import Blender + from Blender import Mathutils + import bpy + + # wrapper + import bl24 as bl + +else: + # for 2.5 + import bpy + from bpy.props import * + + # wrapper + import bl25 as bl + +def apply_transform(vec, matrix): + x, y, z = vec + xloc, yloc, zloc = matrix[3][0], matrix[3][1], matrix[3][2] + return x*matrix[0][0] + y*matrix[1][0] + z*matrix[2][0] + xloc,\ + x*matrix[0][1] + y*matrix[1][1] + z*matrix[2][1] + yloc,\ + x*matrix[0][2] + y*matrix[1][2] + z*matrix[2][2] + zloc + +def convert_to_mqo(vec): + return vec.x, vec.z, -vec.y + + +class OutlineNode(object): + __slots__=['o', 'children'] + def __init__(self, o): + self.o=o + self.children=[] + + def __str__(self): + return "" % self.o + + +class ObjectInfo(object): + __slots__=['object', 'depth', 'material_map'] + def __init__(self, o, depth): + self.object=o + self.depth=depth + self.material_map={} + + def __str__(self): + return "" % (self.depth, self.object) + + +class MqoExporter(object): + __slots__=["materials", "objects"] + def __init__(self): + self.objects=[] + self.materials=[] + + def setup(self, scene): + # 木構造を構築する + object_node_map={} + for o in scene.objects: + object_node_map[o]=OutlineNode(o) + for node in object_node_map.values(): + if node.o.parent: + object_node_map[node.o.parent].children.append(node) + + # ルートを得る + root=object_node_map[scene.objects.active] + + # 情報を集める + if root.o.type.upper()=='EMPTY': + # depth調整 + for node in root.children: + self.__setup(node) + else: + self.__setup(root) + + def __setup(self, node, depth=0): + info=ObjectInfo(node.o, depth) + self.objects.append(info) + if node.o.type.upper()=='MESH': + # set material index + for i, m in enumerate(node.o.data.materials): + info.material_map[i]=self.__getOrAddMaterial(m) + # recursive + for child in node.children: + self.__setup(child, depth+1) + + def __getOrAddMaterial(self, material): + for i, m in enumerate(self.materials): + if m==material: + return i + index=len(self.materials) + self.materials.append(material) + return index + + def write(self, path, scene): + io=bl.Writer(path, 'cp932') + self.__write_header(io) + self.__write_scene(io) + print("Writing MaterialChunk") + self.__write_materials(io, os.path.dirname(path)) + print("Writing ObjectChunk") + for info in self.objects: + self.__write_object(io, info, scene) + io.write("Eof\r\n") + io.flush() + io.close() + + def __write_header(self, io): + io.write("Metasequoia Document\r\n") + io.write("Format Text Ver 1.0\r\n") + io.write("\r\n") + + def __write_scene(self, io): + print("Writing SceneChunk") + io.write("Scene {\r\n") + io.write("}\r\n") + + def __write_materials(self, io, dirname): + # each material + io.write("Material %d {\r\n" % (len(self.materials))) + for m in self.materials: + tex, aplane=bl.getTexture(m, dirname) + if len(tex): + # textureがある場合は下地を白に + io.write("\"%s\" shader(3) col(1 1 1 1)" % m.name) + else: + # 無い場合はそのまま + io.write(bl.materialToMqo(m)) + io.write(tex) + io.write(aplane) + io.write("\r\n") + # end of chunk + io.write("}\r\n") + + def __write_object(self, io, info, scene): + print(info) + + obj=info.object + if obj.type.upper()!='MESH': + return + + # duplicate and applyMatrix + mesh, dumy=bl.objectDuplicate(scene, obj) + + ############################################################ + # write + ############################################################ + io.write("Object \""+obj.name+"\" {\r\n") + + # depth + io.write("\tdepth %d\r\n" % info.depth) + + # mirroring + isMirrorring=False + for mod in obj.modifiers: + if mod.name=="Mirror": + isMirrorring=True + break + if isMirrorring: + io.write("\tmirror 1\r\n") + io.write("\tmirror_axis 1\r\n") + + # vertices + io.write("\tvertex %d {\r\n" % len(mesh.verts)) + for vert in mesh.verts: + x, y, z = convert_to_mqo(vert.co) + io.write("\t\t%f %f %f\r\n" % (x, y, z)) # rotate to y-up + io.write("\t}\r\n") + + # faces + io.write("\tface %d {\r\n" % len(mesh.faces)) + for i, face in enumerate(mesh.faces): + count=bl.faceVertexCount(face) + # V + io.write("\t\t%d V(" % count) + for j in bl.faceVertices(face): + io.write("%d " % j) + io.write(")") + # mat + if len(mesh.materials): + io.write(" M(%d)" % + info.material_map[bl.faceMaterialIndex(face)]) + # UV + if bl.meshHasUV(mesh) and bl.faceHasUV(mesh, i, face): + io.write(" UV(") + for uv in bl.faceGetUV(mesh, i, face, count): + # reverse vertical value + io.write("%f %f " % (uv[0], 1.0-uv[1])) + io.write(")") + io.write("\r\n") + io.write("\t}\r\n") # end of faces + io.write("}\r\n") # end of object + ############################################################ + + # 削除する + scene.objects.unlink(dumy) + + +def __execute(filename, scene, scale): + exporter=MqoExporter() + exporter.setup(scene) + exporter.write(filename, scene) + + +if isBlender24(): + # for 2.4 + def execute_24(filename): + """ + export mqo. + """ + filename=filename.decode(bl.INTERNAL_ENCODING) + print("mqo exporter: %s" % filename) + + Blender.Window.WaitCursor(1) + t = Blender.sys.time() + + __execute(filename, Blender.Scene.GetCurrent(), 1.0) + + print('finished in %.2f seconds' % (Blender.sys.time()-t) ) + Blender.Redraw() + Blender.Window.WaitCursor(0) + + # execute + Blender.Window.FileSelector( + execute_24, + 'Export Metasequoia MQO', + Blender.sys.makename(ext='.mqo')) + +else: + # for 2.5 + def execute_25(*args): + __execute(*args) + + # operator + class EXPORT_OT_mqo(bpy.types.Operator): + '''Save a Metasequoia MQO file.''' + bl_idname = "export_scene.mqo" + bl_label = 'Export MQO' + + # List of operator properties, the attributes will be assigned + # to the class instance from the operator settings before calling. + + path = StringProperty( + name="File Path", + description="File path used for exporting the MQO file", + maxlen= 1024, + default= "" + ) + filename = StringProperty( + name="File Name", + description="Name of the file.") + directory = StringProperty( + name="Directory", + description="Directory of the file.") + + check_existing = BoolProperty( + name="Check Existing", + description="Check and warn on overwriting existing files", + default=True, + options=set('HIDDEN')) + + scale = FloatProperty( + name="Scale", + description="Scale the MQO by this value", + min=0.0001, max=1000000.0, + soft_min=0.001, soft_max=100.0, default=1.0) + + def execute(self, context): + execute_25( + self.properties.path, + context.scene, + self.properties.scale) + return 'FINISHED' + + def invoke(self, context, event): + wm=context.manager + wm.add_fileselect(self) + return 'RUNNING_MODAL' + + # register menu + def menu_func(self, context): + default_path=bpy.data.filename.replace(".blend", ".mqo") + self.layout.operator( + EXPORT_OT_mqo.bl_idname, + text="Metasequoia (.mqo)").path=default_path + + def register(): + bpy.types.register(EXPORT_OT_mqo) + bpy.types.INFO_MT_file_export.append(menu_func) + + def unregister(): + bpy.types.unregister(EXPORT_OT_mqo) + bpy.types.INFO_MT_file_export.remove(menu_func) + + if __name__ == "__main__": + register() + diff --git a/swig/blender/mqo_import.py b/swig/blender/mqo_import.py index 34334bd..7f38f0e 100644 --- a/swig/blender/mqo_import.py +++ b/swig/blender/mqo_import.py @@ -29,7 +29,6 @@ This script imports a mqo into Blender for editing. ############################################################################### import os import sys -import math # C extension from meshio import mqo @@ -161,6 +160,7 @@ def __execute(filename, scene, scale=1.0): # register ############################################################################### if isBlender24(): + # for 2.4 def execute_24(filename): """ import a mqo file. @@ -183,18 +183,14 @@ if isBlender24(): Blender.Redraw() Blender.Window.WaitCursor(0) - # for 2.4 # execute Blender.Window.FileSelector(execute_24, 'Import MQO', '*.mqo') else: - def execute_25(filename, context, scale): - """ - import a mqo file. - """ - __execute(filename, context.scene, scale) - # for 2.5 - # import operator + def execute_25(*args): + __execute(*args) + + # operator class IMPORT_OT_mqo(bpy.types.Operator): '''Import from Metasequoia file format (.mqo)''' bl_idname = "import_scene.mqo" @@ -221,7 +217,10 @@ else: soft_min=0.001, soft_max=100.0, default=1.0) def execute(self, context): - execute_25(self.properties.path, context, self.properties.scale) + execute_25( + self.properties.path, + context.scene, + self.properties.scale) return 'FINISHED' def invoke(self, context, event): @@ -232,7 +231,8 @@ else: # register menu def menu_func(self, context): - self.layout.operator(IMPORT_OT_mqo.bl_idname, + self.layout.operator( + IMPORT_OT_mqo.bl_idname, text="Metasequoia (.mqo)") def register(): -- 2.11.0