OSDN Git Service

rename
[meshio/pymeshio.git] / meshio / io_export_scene_mqo.py
1 #!BPY
2 # coding: utf-8
3
4 """
5 Name: 'Metasequoia (.mqo)...'
6 Blender: 245
7 Group: 'Export'
8 Tooltip: 'Save as Metasequoia MQO File'
9 """
10 __author__= 'ousttrue'
11 __url__ = ["http://gunload.web.fc2.com/blender/"]
12 __version__= '2.2'
13 __bpydoc__ = """\
14 This script is an exporter to MQO file format.
15
16 Usage:
17
18 Run this script from "File->Export" menu.
19
20 0.1 20080128:
21 0.2 20100518: refactoring.
22 0.3 20100606: integrate 2.4 and 2.5.
23 0.4 20100626: refactoring.
24 0.5 20100710: add [apply_modifier] option(2.5 only).
25 0.6 20100714: remove shape_key when apply_modifier. fix material.
26 2.0 20100724: update for Blender2.53.
27 2.1 20101005: update for Blender2.54.
28 2.2 20101228: update for Blender2.55.
29 """
30
31 bl_addon_info = {
32         'category': 'Import/Export',
33         'name': 'Export: Metasequioa Model Format (.mqo)',
34         'author': 'ousttrue',
35         'version': (2, 1),
36         'blender': (2, 5, 3),
37         'location': 'File > Export',
38         'description': 'Export to the Metasequioa Model Format (.mqo)',
39         'warning': '', # used for warning icon and text in addons panel
40         'wiki_url': 'http://sourceforge.jp/projects/meshio/wiki/FrontPage',
41         'tracker_url': 'http://sourceforge.jp/ticket/newticket.php?group_id=5081',
42         }
43
44 import os
45 import sys
46
47 def isBlender24():
48     return sys.version_info[0]<3
49
50
51 class MQOMaterial(object):
52     __slots__=[
53             'name', 'shader', 'r', 'g', 'b', 'a',
54             'dif', 'amb', 'emi',
55             ]
56     def __init__(self, name, shader=3):
57         self.name=name
58         self.shader=shader
59         self.r=0.5
60         self.g=0.5
61         self.b=0.5
62         self.a=1
63         self.dif=1
64         self.amb=0
65         self.emi=0
66
67     def __str__(self):
68         return "\"%s\" shader(%d) col(%f %f %f %f) dif(%f) amb(%f) emi(%f)" % (
69                 self.name, self.shader, self.r, self.g, self.b, self.a,
70                 self.dif, self.amb, self.emi
71                 )
72
73
74 if isBlender24():
75     # for 2.4
76     import Blender
77     from Blender import Mathutils
78     import bpy
79
80     # wrapper
81     import bl24 as bl
82
83     def materialToMqo(m):
84         material=MQOMaterial(m.name, 3)
85         material.r=m.rgbCol[0]
86         material.g=m.rgbCol[1]
87         material.b=m.rgbCol[2]
88         material.a=m.alpha
89         return material
90
91 else:
92     # for 2.5
93     import bpy
94
95     # wrapper
96     import bl25 as bl
97
98     def materialToMqo(m):
99         material=MQOMaterial(m.name, 3)
100         material.r=m.diffuse_color[0]
101         material.g=m.diffuse_color[1]
102         material.b=m.diffuse_color[2]
103         material.a=m.alpha
104         material.amb=m.ambient
105         material.emi=m.emit
106         return material
107
108 def apply_transform(vec, matrix):
109     x, y, z = vec
110     xloc, yloc, zloc = matrix[3][0], matrix[3][1], matrix[3][2]
111     return    x*matrix[0][0] + y*matrix[1][0] + z*matrix[2][0] + xloc,\
112             x*matrix[0][1] + y*matrix[1][1] + z*matrix[2][1] + yloc,\
113             x*matrix[0][2] + y*matrix[1][2] + z*matrix[2][2] + zloc
114
115 def convert_to_mqo(vec):
116     return vec.x, vec.z, -vec.y
117
118
119 class OutlineNode(object):
120     __slots__=['o', 'children']
121     def __init__(self, o):
122         self.o=o
123         self.children=[]
124
125     def __str__(self):
126         return "<Node %s>" % self.o
127
128
129 class ObjectInfo(object):
130     __slots__=['object', 'depth', 'material_map']
131     def __init__(self, o, depth):
132         self.object=o
133         self.depth=depth
134         self.material_map={}
135
136     def __str__(self):
137         return "<ObjectInfo %d %s>" % (self.depth, self.object)
138
139
140 class MqoExporter(object):
141     __slots__=["materials", "objects", 'scale', 'apply_modifier',]
142     def __init__(self, scale, apply_modifier):
143         self.objects=[]
144         self.materials=[]
145         self.scale=scale
146         self.apply_modifier=apply_modifier
147
148     def setup(self, scene):
149         # 木構造を構築する
150         object_node_map={}
151         for o in scene.objects:
152             object_node_map[o]=OutlineNode(o)
153         for node in object_node_map.values():
154             if node.o.parent:
155                 object_node_map[node.o.parent].children.append(node)
156
157         # ルートを得る
158         root=object_node_map[scene.objects.active]
159
160         # 情報を集める
161         if root.o.type.upper()=='EMPTY':
162             # depth調整 
163             for node in root.children:
164                 self.__setup(node)
165         else:
166             self.__setup(root)
167
168     def __setup(self, node, depth=0):
169         info=ObjectInfo(node.o, depth)
170         self.objects.append(info)
171         if node.o.type.upper()=='MESH':
172             # set material index
173             for i, m in enumerate(node.o.data.materials):
174                 info.material_map[i]=self.__getOrAddMaterial(m)
175         # recursive
176         for child in node.children:
177             self.__setup(child, depth+1)
178             
179     def __getOrAddMaterial(self, material):
180         for i, m in enumerate(self.materials):
181             if m==material:
182                 return i
183         index=len(self.materials)
184         self.materials.append(material)
185         return index
186
187     def write(self, path):
188         bl.message("open: "+path)
189         io=bl.Writer(path, 'cp932')
190         self.__write_header(io)
191         self.__write_scene(io)
192         print("Writing MaterialChunk")
193         self.__write_materials(io, os.path.dirname(path))
194         print("Writing ObjectChunk")
195         for info in self.objects:
196             self.__write_object(io, info)
197         io.write("Eof\r\n")
198         io.flush()
199         io.close()
200
201     def __write_header(self, io):
202         io.write("Metasequoia Document\r\n")
203         io.write("Format Text Ver 1.0\r\n")
204         io.write("\r\n")
205
206     def __write_scene(self, io):
207         print("Writing SceneChunk")
208         io.write("Scene {\r\n")
209         io.write("}\r\n")
210
211     def __write_materials(self, io, dirname):
212         # each material    
213         io.write("Material %d {\r\n" % (len(self.materials)))
214         for m in self.materials:
215             io.write(str(materialToMqo(m)))
216             # ToDo separated alpha texture
217             for filename in bl.material.eachTexturePath(m):
218                 if len(dirname)>0 and filename.startswith(dirname):
219                     # 相対パスに変換する
220                     filename=filename[len(dirname)+1:]
221                 io.write(" tex(\"%s\")" % filename)
222                 break
223             io.write("\r\n") 
224         # end of chunk
225         io.write("}\r\n") 
226
227     def __write_object(self, io, info):
228         print(info)
229
230         obj=info.object
231         if obj.type.upper()=='MESH' or obj.type.upper()=='EMPTY':
232             pass
233         else:
234             print(obj.type)
235             return
236
237         io.write("Object \""+obj.name+"\" {\r\n")
238
239         # depth
240         io.write("\tdepth %d\r\n" % info.depth)
241
242         # mirror
243         if not self.apply_modifier:
244             if bl.modifier.hasType(obj, 'MIRROR'):
245                     io.write("\tmirror 1\r\n")
246                     io.write("\tmirror_axis 1\r\n")
247
248         if obj.type.upper()=='MESH':
249             # duplicate and applyMatrix
250             copyMesh, copyObj=bl.object.duplicate(obj)
251             # apply transform
252             copyObj.scale=obj.scale
253             bpy.ops.object.scale_apply()
254             copyObj.rotation_euler=obj.rotation_euler
255             bpy.ops.object.rotation_apply()
256             copyObj.location=obj.location
257             bpy.ops.object.location_apply()
258             # apply modifier
259             if self.apply_modifier:
260                 # remove shape key
261                 while bl.object.hasShapeKey(copyObj):
262                     bpy.ops.object.shape_key_remove()
263                 for m in [m for m in copyObj.modifiers]:
264                     if m.type=='SOLIDFY':
265                         continue
266                     elif m.type=='ARMATURE':
267                         bpy.ops.object.modifier_apply(modifier=m.name)
268                     elif m.type=='MIRROR':
269                         bpy.ops.object.modifier_apply(modifier=m.name)
270                     else:
271                         print(m.type)
272             # write mesh
273             self.__write_mesh(io, copyMesh, info.material_map)
274             bl.object.delete(copyObj)
275
276         io.write("}\r\n") # end of object
277
278     def __write_mesh(self, io, mesh, material_map):
279         # vertices
280         io.write("\tvertex %d {\r\n" % len(mesh.verts))
281         for vert in mesh.verts:
282             x, y, z = convert_to_mqo(vert.co)
283             io.write("\t\t%f %f %f\r\n" % 
284                     (x*self.scale, y*self.scale, z*self.scale)) # rotate to y-up
285         io.write("\t}\r\n")
286
287         # faces
288         io.write("\tface %d {\r\n" % len(mesh.faces))
289         for i, face in enumerate(mesh.faces):
290             count=bl.face.getVertexCount(face)
291             # V
292             io.write("\t\t%d V(" % count)
293             for j in reversed(bl.face.getVertices(face)):
294                 io.write("%d " % j)
295             io.write(")")
296             # mat
297             if len(mesh.materials):
298                 io.write(" M(%d)" % 
299                         material_map[bl.face.getMaterialIndex(face)])
300             # UV
301             if bl.mesh.hasUV(mesh) and bl.mesh.hasFaceUV(mesh, i, face):
302                 io.write(" UV(")
303                 for uv in reversed(bl.mesh.getFaceUV(mesh, i, face, count)):
304                     # reverse vertical value
305                     io.write("%f %f " % (uv[0], 1.0-uv[1])) 
306                 io.write(")")
307             io.write("\r\n")
308         io.write("\t}\r\n") # end of faces
309
310
311 def __execute(filename, scene, scale=10, apply_modifier=False):
312     if scene.objects.active:
313         exporter=MqoExporter(scale, apply_modifier)
314         exporter.setup(scene)
315         exporter.write(filename)
316     else:
317         bl.message('no active object !')
318
319
320 if isBlender24():
321     # for 2.4
322     def execute_24(filename):
323         scene=Blender.Scene.GetCurrent()
324         bl.initialize('mqo_export', scene)
325         __execute(
326                 filename.decode(bl.INTERNAL_ENCODING), 
327                 scene, False)
328         bl.finalize()
329
330     # execute
331     Blender.Window.FileSelector(
332             execute_24, 
333             'Export Metasequoia MQO', 
334             Blender.sys.makename(ext='.mqo'))
335
336 else:
337     # for 2.5
338     def execute_25(path, scene, scale, apply_modifier):
339         bl.initialize('mqo_export', scene)
340         __execute(path, scene, scale, apply_modifier)
341         bl.finalize()
342
343     # operator
344     class EXPORT_OT_mqo(bpy.types.Operator):
345         '''Save a Metasequoia MQO file.'''
346         bl_idname = "export_scene.mqo"
347         bl_label = 'Export MQO'
348
349         # List of operator properties, the attributes will be assigned
350         # to the class instance from the operator settings before calling.
351         filepath = bpy.props.StringProperty()
352         filename = bpy.props.StringProperty()
353         directory = bpy.props.StringProperty()
354
355         scale = bpy.props.FloatProperty(
356                 name="Scale", 
357                 description="Scale the MQO by this value", 
358                 min=0.0001, max=1000000.0, 
359                 soft_min=0.001, soft_max=100.0, default=10.0)
360
361         apply_modifier = bpy.props.BoolProperty(
362                 name="ApplyModifier", 
363                 description="Would apply modifiers", 
364                 default=False)
365
366         def execute(self, context):
367             execute_25(
368                     self.properties.filepath, 
369                     context.scene, 
370                     self.properties.scale,
371                     self.properties.apply_modifier)
372             return 'FINISHED'
373
374         def invoke(self, context, event):
375             wm=context.window_manager
376             try:
377                 wm.fileselect_add(self)
378             except:
379                 wm.add_fileselect(self)
380             return 'RUNNING_MODAL'
381
382     # register menu
383     def menu_func(self, context): 
384         default_path=bpy.data.filepath.replace(".blend", ".mqo")
385         self.layout.operator(
386                 EXPORT_OT_mqo.bl_idname, 
387                 text="Metasequoia (.mqo)",
388                 icon='PLUGIN'
389                 ).filepath=default_path
390
391     def register():
392         bpy.types.INFO_MT_file_export.append(menu_func)
393
394     def unregister():
395         bpy.types.INFO_MT_file_export.remove(menu_func)
396
397     if __name__ == "__main__":
398         register()
399