Added option to export vertex colors as alpha transparency (not necessarily grayscale...
[blender-addons-contrib.git] / io_scene_cod / __init__.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 """
22 Blender-CoD: Blender Add-On for Call of Duty modding
23 Version: alpha 3
24
25 Copyright (c) 2011 CoDEmanX, Flybynyt -- blender-cod@online.de
26
27 http://code.google.com/p/blender-cod/
28
29 TODO
30 - UI for xmodel and xanim import (planned for alpha 4/5)
31
32 """
33
34 bl_info = {
35     "name": "Blender-CoD - Add-On for Call of Duty modding (alpha 3)",
36     "author": "CoDEmanX, Flybynyt",
37     "version": (0, 3, 5),
38     "blender": (2, 62, 3),
39     "location": "File > Import  |  File > Export",
40     "description": "Export models to *.XMODEL_EXPORT and animations to *.XANIM_EXPORT",
41     "warning": "Alpha version, please report any bugs!",
42     "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Import-Export/Call_of_Duty_IO",
43     "tracker_url": "http://projects.blender.org/tracker/index.php?func=detail&aid=30482",
44     "support": "TESTING",
45     "category": "Import-Export"
46 }
47
48 # To support reload properly, try to access a package var, if it's there, reload everything
49 if "bpy" in locals():
50     import imp
51     if "import_xmodel" in locals():
52         imp.reload(import_xmodel)
53     if "export_xmodel" in locals():
54         imp.reload(export_xmodel)
55     if "import_xanim" in locals():
56         imp.reload(import_xanim)
57     if "export_xanim" in locals():
58         imp.reload(export_xanim)
59
60 import bpy
61 from bpy.props import BoolProperty, IntProperty, FloatProperty, StringProperty, EnumProperty
62 import bpy_extras.io_utils
63 from bpy_extras.io_utils import ExportHelper, ImportHelper
64 import time
65
66 # Planned for alpha 4/5
67 class ImportXmodel(bpy.types.Operator, ImportHelper):
68     """Load a CoD XMODEL_EXPORT File"""
69     bl_idname = "import_scene.xmodel"
70     bl_label = "Import XMODEL_EXPORT"
71     bl_options = {'PRESET'}
72
73     filename_ext = ".XMODEL_EXPORT"
74     filter_glob = StringProperty(default="*.XMODEL_EXPORT", options={'HIDDEN'})
75
76     #use_meshes = BoolProperty(name="Meshes", description="Import meshes", default=True)
77     #use_armature = BoolProperty(name="Armature", description="Import Armature", default=True)
78     #use_bind_armature = BoolProperty(name="Bind Meshes to Armature", description="Parent imported meshes to armature", default=True)
79
80     #use_split_objects = BoolProperty(name="Object", description="Import OBJ Objects into Blender Objects", default=True)
81     #use_split_groups = BoolProperty(name="Group", description="Import OBJ Groups into Blender Objects", default=True)
82
83     #use_image_search = BoolProperty(name="Image Search", description="Search subdirs for any assosiated images (Warning, may be slow)", default=True)
84
85     def execute(self, context):
86         from . import import_xmodel
87         start_time = time.clock()
88         result = import_xmodel.load(self, context, **self.as_keywords(ignore=("filter_glob", "check_existing")))
89
90         if not result:
91             self.report({'INFO'}, "Import finished in %.4f sec." % (time.clock() - start_time))
92             return {'FINISHED'}
93         else:
94             self.report({'ERROR'}, result)
95             return {'CANCELLED'}
96
97     """
98     def draw(self, context):
99         layout = self.layout
100
101         col = layout.column()
102         col.prop(self, "use_meshes")
103         col.prop(self, "use_armature")
104
105         row = layout.row()
106         row.active = self.use_meshes and self.use_armature
107         row.prop(self, "use_bind_armature")
108     """
109
110     @classmethod
111     def poll(self, context):
112         return (context.scene is not None)
113
114 class ImportXanim(bpy.types.Operator, ImportHelper):
115     """Load a CoD XANIM_EXPORT File"""
116     bl_idname = "import_scene.xanim"
117     bl_label = "Import XANIM_EXPORT"
118     bl_options = {'PRESET'}
119
120     filename_ext = ".XANIM_EXPORT"
121     filter_glob = StringProperty(default="*.XANIM_EXPORT;*.NT_EXPORT", options={'HIDDEN'})
122
123     def execute(self, context):
124         # print("Selected: " + context.active_object.name)
125         from . import import_xanim
126
127         return import_xanim.load(self, context, **self.as_keywords(ignore=("filter_glob",)))
128
129 class ExportXmodel(bpy.types.Operator, ExportHelper):
130     """Save a CoD XMODEL_EXPORT File"""
131
132     bl_idname = "export_scene.xmodel"
133     bl_label = 'Export XMODEL_EXPORT'
134     bl_options = {'PRESET'}
135
136     filename_ext = ".XMODEL_EXPORT"
137     filter_glob = StringProperty(default="*.XMODEL_EXPORT", options={'HIDDEN'})
138
139     # List of operator properties, the attributes will be assigned
140     # to the class instance from the operator settings before calling.
141
142     use_version = EnumProperty(
143         name="Format Version",
144         description="XMODEL_EXPORT format version for export",
145         items=(('5', "Version 5", "vCoD, CoD:UO"),
146                ('6', "Version 6", "CoD2, CoD4, CoD5, CoD7")),
147         default='6',
148         )
149
150     use_selection = BoolProperty(
151         name="Selection only",
152         description="Export selected meshes only (object or weight paint mode)",
153         default=False
154         )
155
156     use_vertex_colors = BoolProperty(
157         name="Vertex colors",
158         description="Export vertex colors (if disabled, white color will be used)",
159         default=True
160         )
161
162     use_vertex_colors_alpha = BoolProperty(
163         name="As alpha",
164         description="Turn RGB vertex colors into grayscale (average value) and use it as alpha transparency. White is 1 (opaque), black 0 (invisible)",
165         default=False
166         )
167
168     use_apply_modifiers = BoolProperty(
169         name="Apply Modifiers",
170         description="Apply all mesh modifiers except Armature (preview resolution)",
171         default=True
172         )
173
174     use_armature = BoolProperty(
175         name="Armature",
176         description="Export bones (if disabled, only a 'tag_origin' bone will be written)",
177         default=True
178         )
179
180     use_vertex_cleanup = BoolProperty(
181         name="Clean up vertices",
182         description="Try this if you have problems converting to xmodel. Skips vertices which aren't used by any face and updates references.",
183         default=False
184         )
185
186     use_armature_pose = BoolProperty(
187         name="Pose animation to models",
188         description="Export meshes with Armature modifier applied as a series of XMODEL_EXPORT files",
189         default=False
190         )
191
192     use_frame_start = IntProperty(
193         name="Start",
194         description="First frame to export",
195         default=1,
196         min=0
197         )
198
199     use_frame_end = IntProperty(
200         name="End",
201         description="Last frame to export",
202         default=250,
203         min=0
204         )
205
206     use_weight_min = BoolProperty(
207         name="Minimum bone weight",
208         description="Try this if you get 'too small weight' errors when converting",
209         default=False,
210         )
211
212     use_weight_min_threshold = FloatProperty(
213         name="Threshold",
214         description="Smallest allowed weight (minimum value)",
215         default=0.010097,
216         min=0.0,
217         max=1.0,
218         precision=6
219         )
220
221     def execute(self, context):
222         from . import export_xmodel
223         start_time = time.clock()
224         result = export_xmodel.save(self, context, **self.as_keywords(ignore=("filter_glob", "check_existing")))
225
226         if not result:
227             self.report({'INFO'}, "Export finished in %.4f sec." % (time.clock() - start_time))
228             return {'FINISHED'}
229         else:
230             self.report({'ERROR'}, result)
231             return {'CANCELLED'}
232
233     # Extend ExportHelper invoke function to support dynamic default values
234     def invoke(self, context, event):
235
236         #self.use_frame_start = context.scene.frame_start
237         self.use_frame_start = context.scene.frame_current
238
239         #self.use_frame_end = context.scene.frame_end
240         self.use_frame_end = context.scene.frame_current
241
242         return super().invoke(context, event)
243
244     def draw(self, context):
245         layout = self.layout
246
247         row = layout.row(align=True)
248         row.prop(self, "use_version", expand=True)
249
250         # Calculate number of selected mesh objects
251         if context.mode in ('OBJECT', 'PAINT_WEIGHT'):
252             meshes_selected = len([m for m in bpy.data.objects if m.type == 'MESH' and m.select])
253         else:
254             meshes_selected = 0
255
256         col = layout.column(align=True)
257         col.prop(self, "use_selection", "Selection only (%i meshes)" % meshes_selected)
258         col.enabled = bool(meshes_selected)
259
260         col = layout.column(align=True)
261         col.prop(self, "use_apply_modifiers")
262
263         col = layout.column(align=True)
264         col.enabled = not self.use_armature_pose
265         if self.use_armature and self.use_armature_pose:
266             col.prop(self, "use_armature", "Armature  (disabled)")
267         else:
268             col.prop(self, "use_armature")
269
270         if self.use_version == '6':
271
272             row = layout.row(align=True)
273             row.prop(self, "use_vertex_colors")
274
275             sub = row.split()
276             sub.active = self.use_vertex_colors
277             sub.prop(self, "use_vertex_colors_alpha")
278
279         col = layout.column(align=True)
280         col.label("Advanced:")
281
282         col = layout.column(align=True)
283         col.prop(self, "use_vertex_cleanup")
284
285         box = layout.box()
286
287         col = box.column(align=True)
288         col.prop(self, "use_armature_pose")
289
290         sub = box.column()
291         sub.active = self.use_armature_pose
292         sub.label(text="Frame range: (%i frames)" % (abs(self.use_frame_end - self.use_frame_start) + 1))
293
294         row = sub.row(align=True)
295         row.prop(self, "use_frame_start")
296         row.prop(self, "use_frame_end")
297
298         box = layout.box()
299
300         col = box.column(align=True)
301         col.prop(self, "use_weight_min")
302
303         sub = box.column()
304         sub.enabled = self.use_weight_min
305         sub.prop(self, "use_weight_min_threshold")
306
307     @classmethod
308     def poll(self, context):
309         return (context.scene is not None)
310
311 class ExportXanim(bpy.types.Operator, ExportHelper):
312     """Save a XMODEL_XANIM File"""
313
314     bl_idname = "export_scene.xanim"
315     bl_label = 'Export XANIM_EXPORT'
316     bl_options = {'PRESET'}
317
318     filename_ext = ".XANIM_EXPORT"
319     filter_glob = StringProperty(default="*.XANIM_EXPORT", options={'HIDDEN'})
320
321     # List of operator properties, the attributes will be assigned
322     # to the class instance from the operator settings before calling.
323
324     use_selection = BoolProperty(
325         name="Selection only",
326         description="Export selected bones only (pose mode)",
327         default=False
328         )
329
330     use_framerate = IntProperty(
331         name="Framerate",
332         description="Set frames per second for export, 30 fps is commonly used.",
333         default=24,
334         min=1,
335         max=100
336         )
337
338     use_frame_start = IntProperty(
339         name="Start",
340         description="First frame to export",
341         default=1,
342         min=0
343         )
344
345     use_frame_end = IntProperty(
346         name="End",
347         description="Last frame to export",
348         default=250,
349         min=0
350         )
351
352     use_notetrack = BoolProperty(
353         name="Notetrack",
354         description="Export timeline markers as notetrack nodes",
355         default=True
356         )
357
358     use_notetrack_format = EnumProperty(
359         name="Notetrack format",
360         description="Notetrack format to use. Always set 'CoD 7' for Black Ops, even if not using notetrack!",
361         items=(('5', "CoD 5", "Separate NT_EXPORT notetrack file for 'World at War'"),
362                ('7', "CoD 7", "Separate NT_EXPORT notetrack file for 'Black Ops'"),
363                ('1', "all other", "Inline notetrack data for all CoD versions except WaW and BO")),
364         default='1',
365         )
366
367     def execute(self, context):
368         from . import export_xanim
369         start_time = time.clock()
370         result = export_xanim.save(self, context, **self.as_keywords(ignore=("filter_glob", "check_existing")))
371
372         if not result:
373             self.report({'INFO'}, "Export finished in %.4f sec." % (time.clock() - start_time))
374             return {'FINISHED'}
375         else:
376             self.report({'ERROR'}, result)
377             return {'CANCELLED'}
378
379     # Extend ExportHelper invoke function to support dynamic default values
380     def invoke(self, context, event):
381
382         self.use_frame_start = context.scene.frame_start
383         self.use_frame_end = context.scene.frame_end
384         self.use_framerate = round(context.scene.render.fps / context.scene.render.fps_base)
385
386         return super().invoke(context, event)
387
388     def draw(self, context):
389
390         layout = self.layout
391
392         bones_selected = 0
393         armature = None
394
395         # Take the first armature
396         for ob in bpy.data.objects:
397             if ob.type == 'ARMATURE' and len(ob.data.bones) > 0:
398                 armature = ob.data
399
400                 # Calculate number of selected bones if in pose-mode
401                 if context.mode == 'POSE':
402                     bones_selected = len([b for b in armature.bones if b.select])
403
404                 # Prepare info string
405                 armature_info = "%s (%i bones)" % (ob.name, len(armature.bones))
406                 break
407         else:
408             armature_info = "Not found!"
409
410         if armature:
411             icon = 'NONE'
412         else:
413             icon = 'ERROR'
414
415         col = layout.column(align=True)
416         col.label("Armature: %s" % armature_info, icon)
417
418         col = layout.column(align=True)
419         col.prop(self, "use_selection", "Selection only (%i bones)" % bones_selected)
420         col.enabled = bool(bones_selected)
421
422         layout.label(text="Frame range: (%i frames)" % (abs(self.use_frame_end - self.use_frame_start) + 1))
423
424         row = layout.row(align=True)
425         row.prop(self, "use_frame_start")
426         row.prop(self, "use_frame_end")
427
428         col = layout.column(align=True)
429         col.prop(self, "use_framerate")
430
431         # Calculate number of markers in export range
432         frame_min = min(self.use_frame_start, self.use_frame_end)
433         frame_max = max(self.use_frame_start, self.use_frame_end)
434         num_markers = len([m for m in context.scene.timeline_markers if frame_max >= m.frame >= frame_min])
435
436         col = layout.column(align=True)
437         col.prop(self, "use_notetrack", text="Notetrack (%i nodes)" % num_markers)
438
439         col = layout.column(align=True)
440         col.prop(self, "use_notetrack_format", expand=True)
441
442     @classmethod
443     def poll(self, context):
444         return (context.scene is not None)
445
446 def menu_func_xmodel_import(self, context):
447     self.layout.operator(ImportXmodel.bl_idname, text="CoD Xmodel (.XMODEL_EXPORT)")
448 """
449 def menu_func_xanim_import(self, context):
450     self.layout.operator(ImportXanim.bl_idname, text="CoD Xanim (.XANIM_EXPORT)")
451 """
452 def menu_func_xmodel_export(self, context):
453     self.layout.operator(ExportXmodel.bl_idname, text="CoD Xmodel (.XMODEL_EXPORT)")
454
455 def menu_func_xanim_export(self, context):
456     self.layout.operator(ExportXanim.bl_idname, text="CoD Xanim (.XANIM_EXPORT)")
457
458 def register():
459     bpy.utils.register_module(__name__)
460
461     bpy.types.INFO_MT_file_import.append(menu_func_xmodel_import)
462     #bpy.types.INFO_MT_file_import.append(menu_func_xanim_import)
463     bpy.types.INFO_MT_file_export.append(menu_func_xmodel_export)
464     bpy.types.INFO_MT_file_export.append(menu_func_xanim_export)
465
466 def unregister():
467     bpy.utils.unregister_module(__name__)
468
469     bpy.types.INFO_MT_file_import.remove(menu_func_xmodel_import)
470     #bpy.types.INFO_MT_file_import.remove(menu_func_xanim_import)
471     bpy.types.INFO_MT_file_export.remove(menu_func_xmodel_export)
472     bpy.types.INFO_MT_file_export.remove(menu_func_xanim_export)
473
474 if __name__ == "__main__":
475     register()