import os from datetime import datetime import bpy from bpy.props import StringProperty, BoolProperty, PointerProperty, IntProperty from bpy.types import UIList, Operator, Panel, PropertyGroup, Context from io_scene_fbx import ExportFBX def message_box(message: str, title: str = "Message", icon: str = 'INFO') -> None: """Shows modal window with a message :param message: Message text :param title: Window title :param icon: Window icon """ def draw(self, context): col = self.layout.column(align=True) for line in message.strip().split("\n"): col.label(line) bpy.context.window_manager.popup_menu(draw, title=title, icon=icon) def name_as_path(name: str) -> str: """Converts and sanitizes an arbitrary string to a canonical path with OS-specific separator :param name: Some name :returns: Canonical path """ return os.path.join(*[bpy.path.clean_name(n) for n in name.replace("\\", "/").split("/") if n]) + ".fbx" def fbx_export(context: Context) -> None: """ Export all objects Groups to ASCII FBX """ print("[%s] Batch FBX export begin" % datetime.now()) rootpath = os.path.normpath(bpy.path.abspath(context.scene.fbx_export.export_dir)) for group in [g for g in bpy.data.groups if g.fbx_export.exportable]: filepath = os.path.join(rootpath, name_as_path(group.name)) basepath = os.path.dirname(filepath) if not os.path.exists(basepath): os.makedirs(basepath) override = context.copy() override.update( { "selected_objects": [o for o in group.objects if o.fbx_exportable] } ) bpy.ops.export_scene.fbx( override, version='ASCII6100', use_selection=True, global_scale=1.0, axis_forward='-Z', axis_up='Y', object_types=context.scene.fbx_export.object_types, use_mesh_modifiers=context.scene.fbx_export.use_mesh_modifiers, mesh_smooth_type='OFF', use_mesh_edges=False, use_tspace=False, use_armature_deform_only=False, add_leaf_bones=False, primary_bone_axis='Y', secondary_bone_axis='X', armature_nodetype='NULL', use_anim=False, path_mode='RELATIVE', batch_mode='OFF', use_batch_own_dir=False, filepath=filepath, ) print("[%s] Batch FBX export end." % datetime.now()) def on_save(*args, **kwargs) -> None: """ Runs when Blender file is being saved """ if not bpy.context.scene.fbx_export.on_save: return try: fbx_export(bpy.context) except Exception as e: message_box(str(e), "Export error", 'ERROR') class FBX_OT_ForceExport(Operator): bl_idname = "fbx.force_export" bl_label = "Force Export" bl_description = "Force export FBX" def execute(self, context): try: fbx_export(context) except Exception as e: self.report({'ERROR'}, str(e)) return {'CANCELLED'} return {'FINISHED'} class AutoexportProps(PropertyGroup): object_types = ExportFBX.object_types use_mesh_modifiers = ExportFBX.use_mesh_modifiers export_dir = StringProperty( name="FBX Export Dir", description="FBX Export Directory", subtype="DIR_PATH", default="//../fbx/", ) on_save = BoolProperty( name="Export on save", description="Export FBX on Blender file save", default=True ) group_index = IntProperty(default=0) class GroupExportProps(PropertyGroup): exportable = BoolProperty( name="Export Group", description="Mark group as FBX exportable", default=False ) obj_index = IntProperty(default=0) show_obj_list = BoolProperty(default=False) class FBX_UL_ExportObjects(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): col = layout.column() row = col.row(align=True) row.prop(item, "fbx_exportable", icon='CHECKBOX_HLT' if item.fbx_exportable else 'CHECKBOX_DEHLT', text="", emboss=False) row.prop(item, "name", text="", emboss=False, icon='OBJECT_DATA' if item.dupli_type == "NONE" else 'OUTLINER_OB_GROUP_INSTANCE') class FBX_UL_ExportItems(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): col = layout.box().column() row = col.split() sub = row.row(align=True) # sub.alignment = 'LEFT' sub.prop(item.fbx_export, "exportable", icon='CHECKBOX_HLT' if item.fbx_export.exportable else 'CHECKBOX_DEHLT', text="", emboss=False) sub.prop(item, "name", text="", emboss=False, icon='GROUP') sub = row.row() sub.alignment = 'RIGHT' sub.label(text=str(len(item.objects)), icon='OBJECT_DATA') sub.prop(item.fbx_export, "show_obj_list", icon='TRIA_DOWN' if item.fbx_export.show_obj_list else 'TRIA_LEFT', text="", emboss=False) if item.fbx_export.show_obj_list: col.template_list("FBX_UL_ExportObjects", str(index), item, "objects", item.fbx_export, "obj_index", rows=2) class SCENE_PT_fbx_autoexport(Panel): """Creates a Panel in the scene context of the properties editor""" bl_label = "FBX Auto-Export" bl_idname = "SCENE_PT_fbx_autoexport" bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "scene" def draw(self, context): layout = self.layout scene = context.scene col = layout.column() col.prop(scene.fbx_export, "use_mesh_modifiers") sub = col.row() sub.prop(scene.fbx_export, "object_types") col.template_list("FBX_UL_ExportItems", "", bpy.data, "groups", scene.fbx_export, "group_index", rows=3) col.separator() col.prop(scene.fbx_export, "export_dir") row = col.row(align=True) row.prop(scene.fbx_export, "on_save", icon='LOAD_FACTORY') row.operator(FBX_OT_ForceExport.bl_idname, icon="PASTEDOWN") bpy.types.Object.fbx_exportable = BoolProperty( name="Export Object", description="Mark object as FBX exportable", default=True ) bpy.utils.register_class(FBX_OT_ForceExport) bpy.utils.register_class(FBX_UL_ExportObjects) bpy.utils.register_class(FBX_UL_ExportItems) bpy.utils.register_class(AutoexportProps) bpy.utils.register_class(GroupExportProps) bpy.types.Scene.fbx_export = PointerProperty(type=AutoexportProps) bpy.types.Group.fbx_export = PointerProperty(type=GroupExportProps) bpy.utils.register_class(SCENE_PT_fbx_autoexport) bpy.app.handlers.save_pre.append(on_save)