Skip to content

Instantly share code, notes, and snippets.

@tamask
Last active February 6, 2019 06:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tamask/a6b1649a2d3e952bfe53229f93d06eca to your computer and use it in GitHub Desktop.
Save tamask/a6b1649a2d3e952bfe53229f93d06eca to your computer and use it in GitHub Desktop.
Autoexport to an FBX target file (Blender 2.80)
import os
import bpy
import bpy_extras
import mathutils as mu
from io_scene_fbx import ExportFBX
bl_info = {
'name': 'Autoexport FBX Target (.fbx)',
'author': 'Tamas Kemenczy',
'blender': (2, 80, 0),
'version': (0, 10),
'description': 'Autoexport an FBX file to a specified target path',
'category': 'Import-Export',
}
PRESETS = {
'UNITY': {
'global_scale': 0.01,
'axis_up': 'Y',
'axis_forward': '-Z',
'use_tspace': False,
'use_armature_deform_only': True,
'bake_anim_use_all_bones': False,
'bake_anim_use_nla_strips': False,
'bake_anim_use_all_actions': False,
'bake_anim_force_startend_keying': False,
}
}
# awful but necessary workaround because writing to ID objects in
# certain contexts is 'not allowed'
__LAYOUT__ = None
class PseudoOperator:
# 2.80 fbx save_single function expects an operator to allow reporting
def report(self, *args, **kwargs):
pass
def filepath_update_fn(self, context):
if self.filepath and not self.filepath.endswith('.fbx'):
self.filepath += '.fbx'
if self.filepath:
install_autoexport_handler()
else:
remove_autoexport_handler();
def autoexport_update_fn(self, context):
install_autoexport_handler()
def preset_update_fn(self, context):
bpy.ops.export_scene.fbx_target_preset()
class FbxTargetSettings(bpy.types.PropertyGroup):
filepath : bpy.props.StringProperty(
name='Filepath',
subtype='FILE_PATH',
update=filepath_update_fn)
autoexport : bpy.props.BoolProperty(
name='Autoexport',
default=True,
update=autoexport_update_fn)
preset: bpy.props.EnumProperty(
items=(
('NONE', 'Blender', 'Use Blender\'s defaults'),
('UNITY', 'Unity', 'Common settings for Unity 3D'),
),
update=preset_update_fn,
name='Preset')
show_export_options : bpy.props.BoolProperty(name='Show Export Options')
@property
def layout(self):
global __LAYOUT__
return __LAYOUT__
def as_keywords(self, *args, **kwargs):
keys = set(self.__annotations__.keys())
keys.difference_update(set(kwargs.get('ignore', tuple())))
keywords = {}
for key in list(keys):
keywords[key] = getattr(self, key)
return keywords
def get_defaults(self, *args, **kwargs):
keys = set(self.__annotations__.keys())
keys.difference_update(set(kwargs.get('ignore', tuple())))
keywords = {}
for key in list(keys):
props = self.__annotations__[key][1]
if 'default' in props:
keywords[key] = props['default']
return keywords
FbxTargetSettings.__annotations__.update(ExportFBX.__annotations__)
class FbxTargetObject(bpy.types.PropertyGroup):
export : bpy.props.BoolProperty(
name='Export', default=True,
description='Include object in FBX target')
PROP_FBX_TARGET_SETTINGS = bpy.props.PointerProperty(type=FbxTargetSettings)
PROP_FBX_TARGET_OBJECT = bpy.props.PointerProperty(type=FbxTargetObject)
class RENDER_PT_fbx_target(bpy.types.Panel):
bl_label = 'FBX Target'
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = 'output'
def draw(self, context):
fbx = context.scene.fbx_target
layout = self.layout
layout.use_property_split = False
layout.use_property_decorate = False # No animation.
layout.prop(fbx, 'filepath', text='')
have_path = len(fbx.filepath) > 0
layout.use_property_split = True
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
col = flow.column()
col.enabled = have_path
col.prop(fbx, 'autoexport', text='Autoexport on save')
row = layout.row(align=True)
row.enabled = have_path
row.prop(fbx, 'preset', text='')
row = layout.row()
row.enabled = have_path
row.operator('export_scene.fbx_target', text='Export Now')
if have_path:
filepath = get_abs_path(fbx.filepath)
dirpath = os.path.dirname(filepath)
if not os.path.exists(dirpath):
box = layout.box()
box.label(text='Path does not exist, it will be created', icon='ERROR')
layout.use_property_split = False
layout.prop(fbx, 'show_export_options', toggle=True)
if fbx.show_export_options:
global __LAYOUT__
__LAYOUT__ = self.layout
ExportFBX.draw(fbx, context)
__LAYOUT__ = None
class OBJECT_PT_fbx_target(bpy.types.Panel):
bl_label = 'FBX Target'
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = 'object'
def draw(self, context):
fbx = context.active_object.fbx_target
layout = self.layout
layout.use_property_split = False
layout.use_property_decorate = False # No animation.
layout.use_property_split = True
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
col = flow.column()
col.prop(fbx, 'export', text='Include')
def get_abs_path(filepath):
if filepath.startswith('//'):
filepath = os.path.join(os.path.dirname(bpy.data.filepath), filepath[2:])
return filepath
def save_fbx_target(operator, context, scene):
fbx = scene.fbx_target
context_objects = [o for o in scene.objects if o.fbx_target.export]
if not context_objects:
operator.report({'INFO'}, 'Nothing to export')
return {'CANCELLED'}
global_matrix = bpy_extras.io_utils.axis_conversion(
to_forward=fbx.axis_forward,
to_up=fbx.axis_up,
).to_4x4()
keywords = fbx.as_keywords(ignore=(
'filepath', 'autoexport', 'show_export_options', 'preset',
'batch_mode', 'check_existing', 'filter_glob', 'ui_tab',))
keywords.update({
'global_matrix': global_matrix,
'context_objects': context_objects,
})
filepath = get_abs_path(fbx.filepath)
dirpath = os.path.dirname(filepath)
if not os.path.exists(dirpath):
os.makedirs(dirpath)
from io_scene_fbx import export_fbx_bin
return export_fbx_bin.save_single(operator, scene, context.depsgraph, filepath=filepath, **keywords)
class EXPORT_SCENE_OT_fbx_target(bpy.types.Operator):
'''Export an FBX file of the scene a target path.'''
bl_idname = 'export_scene.fbx_target'
bl_label = 'Autoexport FBX Target'
def execute(self, context):
try:
ret = save_fbx_target(self, context, context.scene)
filepath = get_abs_path(context.scene.fbx_target.filepath)
basename = os.path.basename(filepath)
self.report({'INFO'}, 'Saved %s' % basename)
return ret
except FileNotFoundError as e:
self.report({'ERROR'}, e.strerror)
return {'CANCELLED'}
class EXPORT_SCENE_OT_fbx_target_preset(bpy.types.Operator):
'''Apply an export preset to the FBX target.'''
bl_idname = 'export_scene.fbx_target_preset'
bl_label = 'Apply FBX Target Preset'
def execute(self, context):
fbx = context.scene.fbx_target
if fbx.preset == 'NONE':
preset = fbx.get_defaults(ignore=(
'filepath', 'autoexport', 'show_export_options', 'preset',
'batch_mode', 'check_existing', 'filter_glob', 'ui_tab',))
else:
preset = PRESETS[fbx.preset]
for key, value in preset.items():
setattr(fbx, key, value)
return {'FINISHED'}
_handler_installed = False
def on_save_post(scene):
for scene in bpy.data.scenes:
if scene.fbx_target.filepath and scene.fbx_target.autoexport:
save_fbx_target(PseudoOperator(), bpy.context, scene)
def install_autoexport_handler():
global _handler_installed
if not _handler_installed:
add_handler = False
for scene in bpy.data.scenes:
if scene.fbx_target.filepath and scene.fbx_target.autoexport:
add_handler = True
break
if add_handler:
bpy.app.handlers.save_post.append(on_save_post)
_handler_installed = True
def remove_autoexport_handler():
global _handler_installed
if _handler_installed:
bpy.app.handlers.save_post.remove(on_save_post)
_handler_installed = False
@bpy.app.handlers.persistent
def on_load_post(scene):
global _handler_installed
_handler_installed = False
install_autoexport_handler()
def register():
for obj in __REGISTER__:
if hasattr(obj, 'count'):
cls, prop, value = obj
setattr(cls, prop, value)
else:
bpy.utils.register_class(obj)
bpy.app.handlers.load_post.append(on_load_post)
def unregister():
for obj in __REGISTER__:
if hasattr(obj, 'count'):
cls, prop, value = obj
if hasattr(cls, prop):
delattr(cls, prop)
else:
bpy.utils.unregister_class(obj)
bpy.app.handlers.load_post.remove(on_load_post)
__REGISTER__ = (
FbxTargetSettings,
FbxTargetObject,
(bpy.types.Scene, 'fbx_target', PROP_FBX_TARGET_SETTINGS),
(bpy.types.Object, 'fbx_target', PROP_FBX_TARGET_OBJECT),
RENDER_PT_fbx_target,
OBJECT_PT_fbx_target,
EXPORT_SCENE_OT_fbx_target,
EXPORT_SCENE_OT_fbx_target_preset,
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment