Files
vrchat-project-template/Assets/avatar-example/mesh/blend~/fbx_export.py
2025-01-28 06:37:44 +05:00

260 lines
7.8 KiB
Python

import os
from datetime import datetime
import bpy
_FUTURE = bpy.app.version >= (2, 80, 0)
from bpy.props import EnumProperty, StringProperty, BoolProperty, IntProperty
from bpy.types import UIList, Operator, Panel, Context, Scene
if _FUTURE:
from bpy.types import Collection as Group
else:
from bpy.types import Group as Group
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(text=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())
def exporter_ascii(context: Context, filepath: str):
bpy.ops.export_scene.fbx(
context,
version='ASCII6100',
use_selection=True,
global_scale=1.0,
axis_forward='-Z',
axis_up='Y',
object_types=context.scene.fbx_object_types,
use_mesh_modifiers=True,
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=True,
use_default_take=False,
path_mode='RELATIVE',
batch_mode='OFF',
use_batch_own_dir=False,
filepath=filepath,
)
def exporter_binary(context: Context, filepath: str):
bpy.ops.export_scene.fbx(
context,
# Common
version = 'BIN7400',
axis_forward = '-Z',
axis_up = 'Y',
apply_scale_options = 'FBX_SCALE_ALL',
apply_unit_scale = True,
bake_space_transform = True,
global_scale = 1.0,
object_types = context.scene.fbx_object_types,
use_custom_props = False,
use_selection = True,
# Mesh
mesh_smooth_type = 'FACE',
use_mesh_modifiers = False,
use_mesh_edges = False,
use_tspace = False,
# Armature
add_leaf_bones = False,
primary_bone_axis = 'Y',
secondary_bone_axis = 'X',
use_armature_deform_only = False,
armature_nodetype = 'NULL',
# Animation
bake_anim = False,
bake_anim_use_all_bones = True,
bake_anim_use_all_actions = True,
bake_anim_use_nla_strips = True,
bake_anim_force_startend_keying = True,
bake_anim_step = 1.0,
bake_anim_simplify_factor = 1.0,
# File
filepath=filepath,
path_mode = 'RELATIVE',
embed_textures = False,
batch_mode = 'OFF',
)
exporter = exporter_binary if _FUTURE else exporter_ascii
rootpath = os.path.normpath(bpy.path.abspath(context.scene.fbx_export_dir))
groups = bpy.data.collections if _FUTURE else bpy.data.groups
override = context.copy()
for group in [g for g in groups if g.fbx_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.update(
{
"selected_objects": group.objects
}
)
exporter(override, filepath)
print("\n[%s] Batch FBX export end." % datetime.now())
class FBX_OT_ManualExport(Operator):
bl_idname = "fbx.manual_export"
bl_label = "Manual Export"
bl_description = "Export FBX manualy"
def execute(self, context):
try:
fbx_export(context)
except Exception as e:
self.report({'ERROR'}, str(e))
return {'CANCELLED'}
return {'FINISHED'}
class FBX_UL_ExportItems(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
row = layout.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='GROUP')
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()
split = col.split()
sub = split.column()
sub.label(text="Filter groups:")
sub.template_list("FBX_UL_ExportItems", "", bpy.data, "collections" if _FUTURE else "groups", scene, "fbx_group_index", rows=5)
sub = split.column()
sub.label(text="Filter object types:")
sub.prop(scene, "fbx_object_types")
col.separator()
sub = col.row()
sub.alignment = 'CENTER'
sub.prop(scene, "fbx_on_save")
sub.prop(scene, "fbx_force_binary")
col.prop(scene, "fbx_export_dir")
col.operator(FBX_OT_ManualExport.bl_idname)
def on_save(*args, **kwargs) -> None:
"""
Runs when Blender file is being saved
"""
if not bpy.context.scene.fbx_on_save:
return
try:
fbx_export(bpy.context)
except Exception as e:
message_box(str(e), "Export error", 'ERROR')
classes = [
FBX_OT_ManualExport,
FBX_UL_ExportItems,
SCENE_PT_fbx_autoexport,
]
def register() -> None:
for cls in classes:
bpy.utils.register_class(cls)
Scene.fbx_export_dir = StringProperty(
name="FBX Export Dir",
description="FBX Export Directory",
subtype="DIR_PATH",
default="//../fbx/",
)
Scene.fbx_on_save = BoolProperty(
name="Export on save",
description="Export FBX on Blender file save",
default=False
)
Scene.fbx_object_types = EnumProperty(
name="Object Types",
options={'ENUM_FLAG'},
items=(
('EMPTY', "Empty", ""),
('CAMERA', "Camera", ""),
('LAMP', "Lamp", ""),
('ARMATURE', "Armature", "WARNING: not supported in dupli/group instances"),
('MESH', "Mesh", ""),
('OTHER', "Other", "Other geometry types, like curve, metaball, etc. (converted to meshes)"),
),
description="Which kind of object to export",
default={'ARMATURE', 'EMPTY', 'MESH', 'OTHER'},
)
Scene.fbx_group_index = IntProperty(default=0)
Scene.fbx_force_binary = BoolProperty(
name="Binary format",
description="Force export FBX file as binary",
default=False
)
Group.fbx_exportable = BoolProperty(
name="Export Group",
description="Mark group as FBX exportable",
default=False
)
bpy.app.handlers.save_pre.append(on_save)
def unregister() -> None:
bpy.app.handlers.save_pre.remove(on_save)
del Group.fbx_exportable
del Scene.fbx_group_index
del Scene.fbx_object_types
del Scene.fbx_on_save
del Scene.fbx_export_dir
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
register()