OSDN Git Service

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