Last active
April 25, 2021 00:02
-
-
Save Potentii/c414ca8eb9811f94598407b408b121b7 to your computer and use it in GitHub Desktop.
Blender script to export a collection as FBX files (with correct pivot)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -------------------------------------- | |
| # Copyright 2021 Guilherme Reginaldo Ruella<potentii@gmail.com> | |
| # | |
| # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | |
| # | |
| # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |
| # | |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| # -------------------------------------- | |
| # -------------------------------------- | |
| # INSTRUCTIONS: | |
| # | |
| # USE CASE: | |
| # - This scripts exports FBXs resources for a Low/High poly mesh workflow. | |
| # - Fixes the FBX pivot. This is useful when the exported objects are not in the center of the scene. | |
| # - Fixes the FBX rotation issues in Unity (using the bake_space_transform option, so armatures are not supported) | |
| # | |
| # USAGE: | |
| # Consider a car model example. | |
| # Sample project structure: | |
| # - Car_001 (collection -> selected) | |
| # - Car_001_LP (collection) | |
| # - Car_001_Door_LP (object) | |
| # - Car_001_Window_LP (object) | |
| # - Car_001_Body_LP (object -> active) | |
| # - Car_001_Wheels_LP (object) | |
| # - Car_001_HP (collection) | |
| # - Car_001_Door_HP (object) | |
| # - Car_001_Window_HP (object) | |
| # - Car_001_Body_HP (object) | |
| # - Car_001_Wheels_HP (object) | |
| # - Car_001_Hidden (collection) | |
| # - Car_001_Deform_001 (object) | |
| # - Car_001_SpiralBezier_001 (object) | |
| # | |
| # You can use your own naming convention, but take notice: | |
| # - The root collection (i.e. the selected one) will influence the directory name of the export | |
| # - The children collections will be used to name the exported resources | |
| # - The children collections ending with 'Hidden' will not be exported. So you can use them to store bezier paths, unfinished objects, etc. | |
| # | |
| # The script needs the following to work as intended: | |
| # - A selected collection that has children collections in it. | |
| # - An active object. This object's pivot will be used as the FBX pivot, that way you can customize the pivot based on your needs, without needing to reset the objects to the center of the scene (the script actually does that, and then resets the object back to their original position). | |
| # | |
| # Each children collection will generate an FBX file (except the one ending with 'Hidden'). | |
| # So, in the example above, the script will generate: | |
| # - ./Car_001/Car_001_LP.fbx | |
| # - ./Car_001/Car_001_HP.fbx | |
| # | |
| # | |
| # -------------------------------------- | |
| import os | |
| import bpy | |
| def has_correct_scale(obj): | |
| return obj.scale[0]==1 and obj.scale[1]==1 and obj.scale[2]==1 | |
| def select_only(objs): | |
| # De-selecting all objects in scene: | |
| bpy.ops.object.select_all(action='DESELECT') | |
| # Selecting only the given objects: | |
| for obj in objs: | |
| obj.select_set(True) | |
| def translate_relative(rel_obj, objs, to_pos): | |
| rel_locations = [None] * len(objs) | |
| for i, obj in enumerate(objs): | |
| if obj.name_full != rel_obj.name_full: | |
| rel_locations[i] = obj.location - rel_obj.location | |
| rel_obj.location = to_pos; | |
| for i, obj in enumerate(objs): | |
| if obj.name_full != rel_obj.name_full: | |
| obj.location = rel_obj.location + rel_locations[i] | |
| def export_fbx(file_name, bake_transform = False): | |
| file_path = os.path.join('', bpy.path.abspath('//' + file_name)) | |
| file_dir = os.path.dirname(file_path) | |
| if not os.path.exists(file_dir): | |
| os.makedirs(file_dir) | |
| bpy.ops.export_scene.fbx( | |
| # General | |
| check_existing=True, | |
| filepath=file_path, | |
| filter_glob='*.fbx', | |
| path_mode='AUTO', | |
| embed_textures=False, | |
| batch_mode='OFF', | |
| use_batch_own_dir=True, | |
| use_metadata=True, | |
| # Include | |
| use_selection=True, | |
| use_active_collection=False, | |
| object_types={'EMPTY', 'CAMERA', 'LIGHT', 'ARMATURE', 'MESH', 'OTHER'}, | |
| use_custom_props=False, | |
| # Transform | |
| global_scale=1.0, | |
| apply_scale_options='FBX_SCALE_NONE', | |
| axis_forward='-Z', | |
| axis_up='Y', | |
| apply_unit_scale=True, | |
| use_space_transform=True, | |
| bake_space_transform=bake_transform, | |
| # Geometry | |
| mesh_smooth_type='OFF', | |
| use_subsurf=False, | |
| use_mesh_modifiers=True, | |
| use_mesh_modifiers_render=False, # Disabled | |
| use_mesh_edges=False, | |
| use_tspace=False, | |
| # Armature | |
| primary_bone_axis='Y', | |
| secondary_bone_axis='X', | |
| armature_nodetype='NULL', | |
| use_armature_deform_only=False, | |
| add_leaf_bones=True, | |
| # Animation | |
| bake_anim=True, | |
| bake_anim_use_all_bones=True, | |
| bake_anim_use_nla_strips=True, | |
| bake_anim_use_all_actions=True, | |
| bake_anim_force_startend_keying=True, | |
| bake_anim_step=1.0, | |
| bake_anim_simplify_factor=1.0) | |
| def parent_coll(search_coll, child_colls, parent): | |
| for child_coll in child_colls: | |
| if(child_coll == search_coll): | |
| return parent | |
| return parent_coll(search_coll, child_coll.children, child_coll) | |
| return parent | |
| # -------------------------------------- | |
| # EXECUTION: | |
| # -------------------------------------- | |
| HIDDEN_COLL_SUFIX = '_Hidden' | |
| ROOT_SAVE_FILE_PATH = './' | |
| SINGLE_COLL = False | |
| previous_selected = bpy.context.selected_objects | |
| root_coll = bpy.context.collection | |
| sub_colls = root_coll.children | |
| #if len(sub_colls) == 0: | |
| if SINGLE_COLL: | |
| sub_colls = [root_coll] | |
| # Validating objects: | |
| for obj in root_coll.all_objects: | |
| # Validate objects' scale: | |
| if not has_correct_scale(obj): | |
| raise Exception(obj.full_name + ' does not have been scaled correctly: ' + obj.scale) | |
| # The active object will determine the pivot of the imports: | |
| active_obj = bpy.context.active_object | |
| active_obj_original_location = active_obj.location | |
| root_folder_name = root_coll.name | |
| if SINGLE_COLL: | |
| parent = parent_coll(coll, bpy.data.collections, None) | |
| if parent == None: | |
| raise Exception('Cannot determine collection parent') | |
| root_folder_name = parent.name | |
| for coll in sub_colls: | |
| # Ignoring collections with the 'hidden' suffix: | |
| if coll.name.endswith(HIDDEN_COLL_SUFIX): | |
| continue | |
| # Getting the all the collection's objects: | |
| coll_objs = coll.all_objects | |
| # Selecting only the objects in this collection: | |
| select_only(coll_objs) | |
| # Zeroing the position of the objects: | |
| translate_relative(active_obj, coll_objs, [0,0,0]) | |
| try: | |
| # Exporting the FBX: | |
| export_fbx(root_folder_name + '/' + coll.name + '.fbx', True) | |
| finally: | |
| # Reseting the objects position to initial state: | |
| translate_relative(active_obj, coll_objs, active_obj_original_location) | |
| # Reseting selection: | |
| select_only(previous_selected) | |
| # TODO log finished | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment