OSDN Git Service

update for blender2.53.
[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         io=bl.Writer(path, 'cp932')
186         self.__write_header(io)
187         self.__write_scene(io)
188         print("Writing MaterialChunk")
189         self.__write_materials(io, os.path.dirname(path))
190         print("Writing ObjectChunk")
191         for info in self.objects:
192             self.__write_object(io, info)
193         io.write("Eof\r\n")
194         io.flush()
195         io.close()
196
197     def __write_header(self, io):
198         io.write("Metasequoia Document\r\n")
199         io.write("Format Text Ver 1.0\r\n")
200         io.write("\r\n")
201
202     def __write_scene(self, io):
203         print("Writing SceneChunk")
204         io.write("Scene {\r\n")
205         io.write("}\r\n")
206
207     def __write_materials(self, io, dirname):
208         # each material    
209         io.write("Material %d {\r\n" % (len(self.materials)))
210         for m in self.materials:
211             io.write(str(materialToMqo(m)))
212             # ToDo separated alpha texture
213             for filename in bl.material.eachTexturePath(m):
214                 if len(dirname)>0 and filename.startswith(dirname):
215                     # 相対パスに変換する
216                     filename=filename[len(dirname)+1:]
217                 io.write(" tex(\"%s\")" % filename)
218                 break
219             io.write("\r\n") 
220         # end of chunk
221         io.write("}\r\n") 
222
223     def __write_object(self, io, info):
224         print(info)
225
226         obj=info.object
227         if obj.type.upper()=='MESH' or obj.type.upper()=='EMPTY':
228             pass
229         else:
230             print(obj.type)
231             return
232
233         io.write("Object \""+obj.name+"\" {\r\n")
234
235         # depth
236         io.write("\tdepth %d\r\n" % info.depth)
237
238         # mirror
239         if not self.apply_modifier:
240             if bl.modifier.hasType(obj, 'MIRROR'):
241                     io.write("\tmirror 1\r\n")
242                     io.write("\tmirror_axis 1\r\n")
243
244         if obj.type.upper()=='MESH':
245             # duplicate and applyMatrix
246             copyMesh, copyObj=bl.object.duplicate(obj)
247             # apply transform
248             copyObj.scale=obj.scale
249             bpy.ops.object.scale_apply()
250             copyObj.rotation_euler=obj.rotation_euler
251             bpy.ops.object.rotation_apply()
252             copyObj.location=obj.location
253             bpy.ops.object.location_apply()
254             # apply modifier
255             if self.apply_modifier:
256                 # remove shape key
257                 while bl.object.hasShapeKey(copyObj):
258                     bpy.ops.object.shape_key_remove()
259                 for m in [m for m in copyObj.modifiers]:
260                     if m.type=='SOLIDFY':
261                         continue
262                     elif m.type=='ARMATURE':
263                         bpy.ops.object.modifier_apply(modifier=m.name)
264                     elif m.type=='MIRROR':
265                         bpy.ops.object.modifier_apply(modifier=m.name)
266                     else:
267                         print(m.type)
268             # write mesh
269             self.__write_mesh(io, copyMesh, info.material_map)
270             bl.object.delete(copyObj)
271
272         io.write("}\r\n") # end of object
273
274     def __write_mesh(self, io, mesh, material_map):
275         # vertices
276         io.write("\tvertex %d {\r\n" % len(mesh.verts))
277         for vert in mesh.verts:
278             x, y, z = convert_to_mqo(vert.co)
279             io.write("\t\t%f %f %f\r\n" % 
280                     (x*self.scale, y*self.scale, z*self.scale)) # rotate to y-up
281         io.write("\t}\r\n")
282
283         # faces
284         io.write("\tface %d {\r\n" % len(mesh.faces))
285         for i, face in enumerate(mesh.faces):
286             count=bl.face.getVertexCount(face)
287             # V
288             io.write("\t\t%d V(" % count)
289             for j in reversed(bl.face.getVertices(face)):
290                 io.write("%d " % j)
291             io.write(")")
292             # mat
293             if len(mesh.materials):
294                 io.write(" M(%d)" % 
295                         material_map[bl.face.getMaterialIndex(face)])
296             # UV
297             if bl.mesh.hasUV(mesh) and bl.mesh.hasFaceUV(mesh, i, face):
298                 io.write(" UV(")
299                 for uv in reversed(bl.mesh.getFaceUV(mesh, i, face, count)):
300                     # reverse vertical value
301                     io.write("%f %f " % (uv[0], 1.0-uv[1])) 
302                 io.write(")")
303             io.write("\r\n")
304         io.write("\t}\r\n") # end of faces
305
306
307 def __execute(filename, scene, scale=10, apply_modifier=False):
308     if not scene.objects.active:
309         bl.message('no active object !')
310         return
311
312     exporter=MqoExporter(scale, apply_modifier)
313     exporter.setup(scene)
314     exporter.write(filename)
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