From 3fd6371608220e9bb78642877e82b0f40d499e41 Mon Sep 17 00:00:00 2001 From: raccoon Date: Thu, 28 Nov 2024 03:44:52 +0500 Subject: [PATCH] Brand new export script with filtering --- .../mesh/blend~/avatar-example_2.79.blend | 4 +- .../avatar-example/mesh/blend~/fbx_export.py | 201 ++++++++++++++++++ 2 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 Assets/avatar-example/mesh/blend~/fbx_export.py diff --git a/Assets/avatar-example/mesh/blend~/avatar-example_2.79.blend b/Assets/avatar-example/mesh/blend~/avatar-example_2.79.blend index 10db6f7..a7ef3f8 100644 --- a/Assets/avatar-example/mesh/blend~/avatar-example_2.79.blend +++ b/Assets/avatar-example/mesh/blend~/avatar-example_2.79.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:551bcd459068ce18a1f437b83f8d3c0f7043ed52bc9679cbcca9b1ec1a4fd34a -size 648908 +oid sha256:ee7309c6b5ecce6ecf699855aaee82689e07bdf85212ba280246af6f52bc7c92 +size 674272 diff --git a/Assets/avatar-example/mesh/blend~/fbx_export.py b/Assets/avatar-example/mesh/blend~/fbx_export.py new file mode 100644 index 0000000..4a69ff6 --- /dev/null +++ b/Assets/avatar-example/mesh/blend~/fbx_export.py @@ -0,0 +1,201 @@ +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)