Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send2UE - Use Collections as Folders Animation & Groom Support #44

Merged
merged 9 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/addons/send2ue/dependencies/unreal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
119 changes: 104 additions & 15 deletions src/addons/send2ue/resources/extensions/use_collections_as_folders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand All @@ -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)
Expand All @@ -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.
Expand All @@ -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:
Expand Down Expand Up @@ -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')
8 changes: 5 additions & 3 deletions tests/test_send2ue_extension_use_collections_as_folders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
7 changes: 4 additions & 3 deletions tests/utils/base_test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -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...')
Expand Down Expand Up @@ -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)

Expand Down
Loading