|
| 1 | +bl_info = { |
| 2 | + "name": "FBX Action Exporter (Full Settings)", |
| 3 | + "author": "You", |
| 4 | + "version": (2, 2), |
| 5 | + "blender": (4, 0, 0), |
| 6 | + "location": "View3D > Sidebar > Armature Tools", |
| 7 | + "description": "FBX exporter for all actions with full export settings and collapsible layout", |
| 8 | + "category": "Import-Export", |
| 9 | +} |
| 10 | + |
| 11 | +import bpy |
| 12 | +import os |
| 13 | +from bpy.types import Operator, Panel, PropertyGroup |
| 14 | +from bpy.props import * |
| 15 | + |
| 16 | +class FBXExportSettings(PropertyGroup): |
| 17 | + export_path: StringProperty(name="Export Path", subtype="DIR_PATH") |
| 18 | + |
| 19 | + # Transform |
| 20 | + global_scale: FloatProperty(name="Scale", default=1.0, min=0.001, max=1000.0) |
| 21 | + apply_scale_options: EnumProperty( |
| 22 | + name="Apply Scalings", |
| 23 | + items=[ |
| 24 | + ('FBX_SCALE_NONE', "All Local", ""), |
| 25 | + ('FBX_SCALE_UNITS', "FBX Units Scale", ""), |
| 26 | + ('FBX_SCALE_CUSTOM', "FBX Custom Scale", ""), |
| 27 | + ('FBX_SCALE_ALL', "FBX All", "") |
| 28 | + ], |
| 29 | + default='FBX_SCALE_NONE' |
| 30 | + ) |
| 31 | + axis_forward: EnumProperty( |
| 32 | + name="Forward", |
| 33 | + items=[(x, x, "") for x in ['X', 'Y', 'Z', '-X', '-Y', '-Z']], |
| 34 | + default='-Z' |
| 35 | + ) |
| 36 | + axis_up: EnumProperty( |
| 37 | + name="Up", |
| 38 | + items=[(x, x, "") for x in ['X', 'Y', 'Z', '-X', '-Y', '-Z']], |
| 39 | + default='Y' |
| 40 | + ) |
| 41 | + apply_unit_scale: BoolProperty(name="Apply Unit") |
| 42 | + use_space_transform: BoolProperty(name="Use Space Transform", default=True) |
| 43 | + bake_space_transform: BoolProperty(name="Apply Transform") |
| 44 | + |
| 45 | + # Geometry |
| 46 | + mesh_smooth_type: EnumProperty( |
| 47 | + name="Smoothing", |
| 48 | + items=[('OFF', "Normals Only", ""), ('FACE', "Face", ""), ('EDGE', "Edge", "")], |
| 49 | + default='OFF' |
| 50 | + ) |
| 51 | + use_subsurf: BoolProperty(name="Export Subdivision Surface") |
| 52 | + use_mesh_modifiers: BoolProperty(name="Apply Modifiers", default=True) |
| 53 | + use_mesh_edges: BoolProperty(name="Loose Edges") |
| 54 | + use_triangles: BoolProperty(name="Triangulate Faces") |
| 55 | + use_tspace: BoolProperty(name="Tangent Space") |
| 56 | + colors_type: EnumProperty( |
| 57 | + name="Vertex Colors", |
| 58 | + items=[('NONE', "None", ""), ('SRGB', "sRGB", ""), ('LINEAR', "Linear", "")], |
| 59 | + default='SRGB' |
| 60 | + ) |
| 61 | + prioritize_active_color: BoolProperty(name="Prioritize Active Color") |
| 62 | + |
| 63 | + # Armature |
| 64 | + use_armature_deform_only: BoolProperty(name="Only Deform Bones") |
| 65 | + add_leaf_bones: BoolProperty(name="Add Leaf Bones", default=True) |
| 66 | + armature_nodetype: EnumProperty( |
| 67 | + name="Armature FBXNode Type", |
| 68 | + items=[('NULL', "Null", ""), ('ROOT', "Root", ""), ('LIMBNODE', "LimbNode", "")], |
| 69 | + default='NULL' |
| 70 | + ) |
| 71 | + primary_bone_axis: EnumProperty( |
| 72 | + name="Primary Bone Axis", |
| 73 | + items=[(x, x, "") for x in ['X', 'Y', 'Z', '-X', '-Y', '-Z']], |
| 74 | + default='Y' |
| 75 | + ) |
| 76 | + secondary_bone_axis: EnumProperty( |
| 77 | + name="Secondary Bone Axis", |
| 78 | + items=[(x, x, "") for x in ['X', 'Y', 'Z', '-X', '-Y', '-Z']], |
| 79 | + default='X' |
| 80 | + ) |
| 81 | + |
| 82 | +class FBX_PT_export_main(Panel): |
| 83 | + bl_label = "FBX Action Export" |
| 84 | + bl_idname = "FBX_PT_export_main" |
| 85 | + bl_space_type = 'VIEW_3D' |
| 86 | + bl_region_type = 'UI' |
| 87 | + bl_category = "Armature Tools" |
| 88 | + |
| 89 | + def draw(self, context): |
| 90 | + pass |
| 91 | + |
| 92 | +class FBX_PT_path(Panel): |
| 93 | + bl_label = "Export Path" |
| 94 | + bl_parent_id = "FBX_PT_export_main" |
| 95 | + bl_space_type = 'VIEW_3D' |
| 96 | + bl_region_type = 'UI' |
| 97 | + bl_category = "Armature Tools" |
| 98 | + bl_options = {'DEFAULT_CLOSED'} |
| 99 | + |
| 100 | + def draw(self, context): |
| 101 | + layout = self.layout |
| 102 | + layout.prop(context.scene.fbx_export, "export_path") |
| 103 | + |
| 104 | +class FBX_PT_transform(Panel): |
| 105 | + bl_label = "Transform" |
| 106 | + bl_parent_id = "FBX_PT_export_main" |
| 107 | + bl_space_type = 'VIEW_3D' |
| 108 | + bl_region_type = 'UI' |
| 109 | + bl_category = "Armature Tools" |
| 110 | + bl_options = {'DEFAULT_CLOSED'} |
| 111 | + |
| 112 | + def draw(self, context): |
| 113 | + p = context.scene.fbx_export |
| 114 | + layout = self.layout |
| 115 | + layout.prop(p, "global_scale") |
| 116 | + layout.prop(p, "apply_scale_options") |
| 117 | + layout.prop(p, "axis_forward") |
| 118 | + layout.prop(p, "axis_up") |
| 119 | + layout.prop(p, "apply_unit_scale") |
| 120 | + layout.prop(p, "use_space_transform") |
| 121 | + layout.prop(p, "bake_space_transform") |
| 122 | + |
| 123 | +class FBX_PT_geometry(Panel): |
| 124 | + bl_label = "Geometry" |
| 125 | + bl_parent_id = "FBX_PT_export_main" |
| 126 | + bl_space_type = 'VIEW_3D' |
| 127 | + bl_region_type = 'UI' |
| 128 | + bl_category = "Armature Tools" |
| 129 | + bl_options = {'DEFAULT_CLOSED'} |
| 130 | + |
| 131 | + def draw(self, context): |
| 132 | + p = context.scene.fbx_export |
| 133 | + layout = self.layout |
| 134 | + layout.prop(p, "mesh_smooth_type") |
| 135 | + layout.prop(p, "use_subsurf") |
| 136 | + layout.prop(p, "use_mesh_modifiers") |
| 137 | + layout.prop(p, "use_mesh_edges") |
| 138 | + layout.prop(p, "use_triangles") |
| 139 | + layout.prop(p, "use_tspace") |
| 140 | + layout.prop(p, "colors_type") |
| 141 | + layout.prop(p, "prioritize_active_color") |
| 142 | + |
| 143 | +class FBX_PT_armature(Panel): |
| 144 | + bl_label = "Armature" |
| 145 | + bl_parent_id = "FBX_PT_export_main" |
| 146 | + bl_space_type = 'VIEW_3D' |
| 147 | + bl_region_type = 'UI' |
| 148 | + bl_category = "Armature Tools" |
| 149 | + bl_options = {'DEFAULT_CLOSED'} |
| 150 | + |
| 151 | + def draw(self, context): |
| 152 | + p = context.scene.fbx_export |
| 153 | + layout = self.layout |
| 154 | + layout.prop(p, "primary_bone_axis") |
| 155 | + layout.prop(p, "secondary_bone_axis") |
| 156 | + layout.prop(p, "armature_nodetype") |
| 157 | + layout.prop(p, "use_armature_deform_only") |
| 158 | + layout.prop(p, "add_leaf_bones") |
| 159 | + |
| 160 | +class FBX_PT_export_button(Panel): |
| 161 | + bl_label = "Export" |
| 162 | + bl_parent_id = "FBX_PT_export_main" |
| 163 | + bl_space_type = 'VIEW_3D' |
| 164 | + bl_region_type = 'UI' |
| 165 | + bl_category = "Armature Tools" |
| 166 | + bl_options = {'DEFAULT_CLOSED'} |
| 167 | + |
| 168 | + def draw(self, context): |
| 169 | + layout = self.layout |
| 170 | + layout.operator("export_fbx.actions", text="Export All Actions") |
| 171 | + |
| 172 | +class ExportAllActionsOperator(Operator): |
| 173 | + bl_idname = "export_fbx.actions" |
| 174 | + bl_label = "Export All Actions" |
| 175 | + |
| 176 | + def execute(self, context): |
| 177 | + obj = context.object |
| 178 | + p = context.scene.fbx_export |
| 179 | + |
| 180 | + if not obj or obj.type != 'ARMATURE': |
| 181 | + self.report({'ERROR'}, "Select an Armature") |
| 182 | + return {'CANCELLED'} |
| 183 | + |
| 184 | + if not p.export_path: |
| 185 | + self.report({'ERROR'}, "Set an export directory") |
| 186 | + return {'CANCELLED'} |
| 187 | + |
| 188 | + if not obj.animation_data: |
| 189 | + obj.animation_data_create() |
| 190 | + |
| 191 | + scene = context.scene |
| 192 | + old_start = scene.frame_start |
| 193 | + old_end = scene.frame_end |
| 194 | + exported = 0 |
| 195 | + |
| 196 | + for action in bpy.data.actions: |
| 197 | + if not action.use_fake_user: |
| 198 | + continue |
| 199 | + |
| 200 | + start = int(action.frame_range[0]) |
| 201 | + end = int(action.frame_range[1]) |
| 202 | + track = obj.animation_data.nla_tracks.new() |
| 203 | + track.name = f"TEMP_{action.name}" |
| 204 | + strip = track.strips.new(action.name, start, action) |
| 205 | + strip.action_frame_start = start |
| 206 | + strip.action_frame_end = end |
| 207 | + |
| 208 | + scene.frame_start = start |
| 209 | + scene.frame_end = end |
| 210 | + filepath = os.path.join(p.export_path, f"{action.name}.fbx") |
| 211 | + |
| 212 | + try: |
| 213 | + bpy.ops.export_scene.fbx( |
| 214 | + filepath=filepath, |
| 215 | + global_scale=p.global_scale, |
| 216 | + apply_scale_options=p.apply_scale_options, |
| 217 | + axis_forward=p.axis_forward, |
| 218 | + axis_up=p.axis_up, |
| 219 | + apply_unit_scale=p.apply_unit_scale, |
| 220 | + use_space_transform=p.use_space_transform, |
| 221 | + bake_space_transform=p.bake_space_transform, |
| 222 | + mesh_smooth_type=p.mesh_smooth_type, |
| 223 | + use_subsurf=p.use_subsurf, |
| 224 | + use_mesh_modifiers=p.use_mesh_modifiers, |
| 225 | + use_mesh_edges=p.use_mesh_edges, |
| 226 | + use_triangles=p.use_triangles, |
| 227 | + use_tspace=p.use_tspace, |
| 228 | + colors_type=p.colors_type, |
| 229 | + prioritize_active_color=p.prioritize_active_color, |
| 230 | + object_types={'ARMATURE'}, |
| 231 | + use_armature_deform_only=p.use_armature_deform_only, |
| 232 | + add_leaf_bones=p.add_leaf_bones, |
| 233 | + armature_nodetype=p.armature_nodetype, |
| 234 | + primary_bone_axis=p.primary_bone_axis, |
| 235 | + secondary_bone_axis=p.secondary_bone_axis |
| 236 | + ) |
| 237 | + exported += 1 |
| 238 | + except Exception as e: |
| 239 | + self.report({'WARNING'}, f"Export failed: {action.name}: {e}") |
| 240 | + |
| 241 | + obj.animation_data.nla_tracks.remove(track) |
| 242 | + |
| 243 | + scene.frame_start = old_start |
| 244 | + scene.frame_end = old_end |
| 245 | + self.report({'INFO'}, f"Exported {exported} actions") |
| 246 | + return {'FINISHED'} |
| 247 | + |
| 248 | +classes = [ |
| 249 | + FBXExportSettings, |
| 250 | + FBX_PT_export_main, |
| 251 | + FBX_PT_path, |
| 252 | + FBX_PT_transform, |
| 253 | + FBX_PT_geometry, |
| 254 | + FBX_PT_armature, |
| 255 | + FBX_PT_export_button, |
| 256 | + ExportAllActionsOperator |
| 257 | +] |
| 258 | + |
| 259 | +def register(): |
| 260 | + for cls in classes: |
| 261 | + bpy.utils.register_class(cls) |
| 262 | + bpy.types.Scene.fbx_export = PointerProperty(type=FBXExportSettings) |
| 263 | + |
| 264 | +def unregister(): |
| 265 | + for cls in reversed(classes): |
| 266 | + bpy.utils.unregister_class(cls) |
| 267 | + del bpy.types.Scene.fbx_export |
| 268 | + |
| 269 | +if __name__ == "__main__": |
| 270 | + register() |
0 commit comments