diff --git a/src/addons/send2ue/dependencies/unreal.py b/src/addons/send2ue/dependencies/unreal.py index f41b0505..f944f774 100644 --- a/src/addons/send2ue/dependencies/unreal.py +++ b/src/addons/send2ue/dependencies/unreal.py @@ -456,8 +456,8 @@ def create_binding_asset(groom_asset_path, mesh_asset_path): ) # source groom asset and target skeletal mesh for the binding asset - groom_binding_asset.set_editor_property('groom', groom_asset) groom_binding_asset.set_editor_property('target_skeletal_mesh', mesh_asset) + groom_binding_asset.set_editor_property('groom', groom_asset) # if a previous version of the binding asset exists, consolidate all references with new asset if existing_binding_asset: diff --git a/src/addons/send2ue/resources/extensions/use_collections_as_folders.py b/src/addons/send2ue/resources/extensions/use_collections_as_folders.py index 31fd8efb..288a6bc6 100644 --- a/src/addons/send2ue/resources/extensions/use_collections_as_folders.py +++ b/src/addons/send2ue/resources/extensions/use_collections_as_folders.py @@ -17,6 +17,43 @@ class UseCollectionsAsFoldersExtension(ExtensionBase): "the specified mesh folder in your unreal project" ) ) + + def pre_animation_export(self, asset_data, properties): + """ + Defines the pre animation export logic that uses blender collections as unreal folders + + :param dict asset_data: A mutable dictionary of asset data for the current asset. + :param Send2UeSceneProperties properties: The scene property group that contains all the addon properties. + """ + if self.use_collections_as_folders: + asset_type = asset_data.get('_asset_type') + if asset_type and asset_type in [UnrealTypes.ANIM_SEQUENCE]: + action_name = asset_data.get('_action_name') + if action_name: + action = bpy.data.actions.get(action_name) + + if not action: + print(f"Couldnt find action '{action_name}'.") + return + + # Grab armature object to then grab the collection from, as we can't grab it from the action itself + armature_object_name = asset_data['_armature_object_name'] + armature_object = bpy.data.objects.get(armature_object_name) + if not armature_object: + print(f"Couldnt find armature '{armature_object_name}'.") + return + + asset_name = utilities.get_asset_name(action_name, properties) + + _, file_extension = os.path.splitext(asset_data.get('file_path')) + export_path = self.get_full_export_path(properties, UnrealTypes.ANIM_SEQUENCE, armature_object) + file_name_with_extension = f'{asset_name}{file_extension}' + + file_path = os.path.join(export_path, file_name_with_extension) + + self.update_asset_data({ + 'file_path': file_path + }) def pre_mesh_export(self, asset_data, properties): """ @@ -27,9 +64,7 @@ def pre_mesh_export(self, asset_data, properties): """ if self.use_collections_as_folders: asset_type = asset_data.get('_asset_type') - if asset_type and asset_type in [UnrealTypes.ANIM_SEQUENCE, UnrealTypes.GROOM]: - print('Unsupported at this time') - elif asset_type and asset_type in [UnrealTypes.STATIC_MESH, UnrealTypes.SKELETAL_MESH]: + if asset_type and asset_type in [UnrealTypes.STATIC_MESH, UnrealTypes.SKELETAL_MESH]: object_name = asset_data.get('_mesh_object_name') if object_name: scene_object = bpy.data.objects.get(object_name) @@ -49,6 +84,36 @@ def pre_mesh_export(self, asset_data, properties): 'file_path': os.path.join(export_path, file_name_with_extension) }) + def pre_groom_export(self, asset_data, properties): + """ + Defines the pre groom export logic that uses blender collections as unreal folders + + :param dict asset_data: A mutable dictionary of asset data for the current asset. + :param Send2UeSceneProperties properties: The scene property group that contains all the addon properties. + """ + + if self.use_collections_as_folders: + asset_type = asset_data.get('_asset_type') + if asset_type and asset_type in [UnrealTypes.GROOM]: + object_name = asset_data.get('_object_name') + if object_name: + scene_object = bpy.data.objects.get(object_name) + particle_object_name = asset_data.get('_particle_object_name') + if particle_object_name: + scene_object = utilities.get_mesh_object_for_groom_name(object_name) + + asset_name = utilities.get_asset_name(object_name, properties) + + _, file_extension = os.path.splitext(asset_data.get('file_path')) + export_path = self.get_full_export_path(properties, asset_type, scene_object) + file_name_with_extension = f'{asset_name}{file_extension}' + file_path = os.path.join(export_path, file_name_with_extension) + self.update_asset_data({ + 'file_path': file_path + }) + + + def get_full_export_path(self, properties, asset_type, scene_object): """ Gets the unreal export path when use_collections_as_folders extension is active. @@ -73,17 +138,41 @@ def pre_import(self, asset_data, properties): if self.use_collections_as_folders: asset_type = asset_data.get('_asset_type') if asset_type and asset_type == UnrealTypes.ANIM_SEQUENCE: - object_name = asset_data['_armature_object_name'] - scene_object = bpy.data.objects.get(object_name) - # update skeletal asset path now that it is under new collections path - self.update_asset_data({ - 'skeleton_asset_path': utilities.get_skeleton_asset_path( - scene_object, - properties, - self.get_full_import_path, - scene_object, - ) - }) + action_name = asset_data.get('_action_name') + if action_name: + asset_name = utilities.get_asset_name(action_name, properties) + + armature_object_name = asset_data['_armature_object_name'] + armature_object = bpy.data.objects.get(armature_object_name) + + import_path = self.get_full_import_path(properties, UnrealTypes.ANIM_SEQUENCE, armature_object) + + self.update_asset_data({ + # update skeletal asset path now that it is under new collections path + 'skeleton_asset_path': utilities.get_skeleton_asset_path( + armature_object, + properties, + self.get_full_import_path, + armature_object, + ), + # update asset folder and path to the new path based on the collections + 'asset_folder': import_path, + 'asset_path': f'{import_path}{asset_name}' + }) + elif asset_type and asset_type == UnrealTypes.GROOM: + object_name = asset_data.get('_object_name') + if object_name: + scene_object = bpy.data.objects.get(object_name) + particle_object_name = asset_data.get('_particle_object_name') + if particle_object_name: + scene_object = utilities.get_mesh_object_for_groom_name(object_name) + + asset_name = utilities.get_asset_name(object_name, properties) + import_path = self.get_full_import_path(properties, asset_type, scene_object) + self.update_asset_data({ + 'asset_folder': import_path, + 'asset_path': f'{import_path}{asset_name}' + }) elif asset_type: object_name = asset_data.get('_mesh_object_name') if object_name: @@ -161,4 +250,4 @@ def draw_paths(self, dialog, layout, properties): :param bpy.types.UILayout layout: The extension layout area. :param Send2UeSceneProperties properties: The scene property group that contains all the addon properties. """ - dialog.draw_property(self, layout, 'use_collections_as_folders') + dialog.draw_property(self, layout, 'use_collections_as_folders') \ No newline at end of file diff --git a/tests/test_send2ue_extension_use_collections_as_folders.py b/tests/test_send2ue_extension_use_collections_as_folders.py index 38933f53..d83d6d5c 100644 --- a/tests/test_send2ue_extension_use_collections_as_folders.py +++ b/tests/test_send2ue_extension_use_collections_as_folders.py @@ -40,12 +40,14 @@ def run_use_collections_as_folders_option_tests(self, objects_and_collections): # check that the mesh name is the parent collection mesh_folder_path = self.blender.get_addon_property('scene', 'send2ue', 'unreal_mesh_folder_path') collection_hierarchy_path = '/'.join(collection_hierarchy[1:]) - folder_path = f'{mesh_folder_path}{collection_hierarchy_path}/' - self.assert_asset_exists(object_name, folder_path, True) + final_mesh_folder_path = f'{mesh_folder_path}{collection_hierarchy_path}/' + self.assert_asset_exists(object_name, final_mesh_folder_path, True) # check that the groom assets have the correct binding target mesh for groom in groom_systems: - self.assert_binding_asset(groom, object_name, folder_path) + groom_folder_path = self.blender.get_addon_property('scene', 'send2ue', 'unreal_groom_folder_path') + final_groom_folder_path = f'{groom_folder_path}{collection_hierarchy_path}/' + self.assert_binding_asset(groom, object_name, final_mesh_folder_path, final_groom_folder_path) class TestSend2UeExtensionCollectionsAsFoldersCubes( diff --git a/tests/utils/base_test_case.py b/tests/utils/base_test_case.py index 912de1bd..b07f5d04 100644 --- a/tests/utils/base_test_case.py +++ b/tests/utils/base_test_case.py @@ -400,7 +400,7 @@ def assert_asset_exists(self, asset_name, folder_path, exists=True): self.log(f'Ensuring that "{asset_name}" exists...') self.assertTrue( self.unreal.asset_exists(f'{folder_path}{asset_name}'), - f'The "{asset_name}" does not exist in unreal!' + f'The "{asset_name}" does not exist in unreal! Loc: {folder_path}' ) else: self.log(f'Ensuring that "{asset_name}" does not exist...') @@ -433,14 +433,15 @@ def assert_groom_import(self, asset_name, exists=True): folder_path = self.blender.get_addon_property('scene', 'send2ue', 'unreal_groom_folder_path') self.assert_asset_exists(asset_name, folder_path, exists) - def assert_binding_asset(self, groom_asset_name, target_mesh_name, mesh_folder_path=None): + def assert_binding_asset(self, groom_asset_name, target_mesh_name, mesh_folder_path=None, groom_folder_path=None): self.log(f'Checking that binding asset is created correctly for "{groom_asset_name}"...') binding_asset_name = f'{groom_asset_name}_{target_mesh_name}_Binding' if not mesh_folder_path: mesh_folder_path = self.blender.get_addon_property('scene', 'send2ue', 'unreal_mesh_folder_path') - groom_folder_path = self.blender.get_addon_property('scene', 'send2ue', 'unreal_groom_folder_path') + if not groom_folder_path: + groom_folder_path = self.blender.get_addon_property('scene', 'send2ue', 'unreal_groom_folder_path') self.assert_asset_exists(binding_asset_name, groom_folder_path, True)