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

Custom root bone name setting #88

Merged
merged 3 commits into from
Oct 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
5 changes: 4 additions & 1 deletion src/addons/send2ue/core/io/fbx_b3.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,10 @@ def fbx_data_object_elements(root, ob_obj, scene_data):
obj_type = b"Camera"

if ob_obj.type == 'ARMATURE':
if bpy.context.scene.send2ue.export_object_name_as_root:
if bpy.context.scene.send2ue.export_custom_root_name:
# if the user has provided a custom name for a root bone, use this directly
ob_obj.name = bpy.context.scene.send2ue.export_custom_root_name
elif bpy.context.scene.send2ue.export_object_name_as_root:
# if the object is already named armature this forces the object name to root
if 'armature' == ob_obj.name.lower():
ob_obj.name = 'root'
Expand Down
5 changes: 4 additions & 1 deletion src/addons/send2ue/core/io/fbx_b4.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,10 @@ def fbx_data_object_elements(root, ob_obj, scene_data):
obj_type = b"Camera"

if ob_obj.type == 'ARMATURE':
if bpy.context.scene.send2ue.export_object_name_as_root:
if bpy.context.scene.send2ue.export_custom_root_name:
# if the user has provided a custom name for a root bone, use this directly
ob_obj.name = bpy.context.scene.send2ue.export_custom_root_name
elif bpy.context.scene.send2ue.export_object_name_as_root:
# if the object is already named armature this forces the object name to root
if 'armature' == ob_obj.name.lower():
ob_obj.name = 'root'
Expand Down
8 changes: 8 additions & 0 deletions src/addons/send2ue/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,14 @@ class Send2UeSceneProperties(property_class):
"the first bone in the armature hierarchy is used as the root bone in unreal."
)
)
export_custom_root_name: bpy.props.StringProperty(
name="Custom root bone name",
default="",
description=(
"If specified, this adds a root bone by this name in Unreal. This overrides the "
"\"Export object name as root bone\" setting."
)
)
export_custom_property_fcurves: bpy.props.BoolProperty(
name="Export custom property fcurves",
default=True,
Expand Down
1 change: 1 addition & 0 deletions src/addons/send2ue/ui/dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def draw_export_tab(self, layout):
properties = bpy.context.scene.send2ue
self.draw_property(properties, layout, 'use_object_origin')
self.draw_property(properties, layout, 'export_object_name_as_root')
self.draw_property(properties, layout, 'export_custom_root_name', enabled=not properties.export_object_name_as_root)

# animation settings box
self.draw_expanding_section(
Expand Down
4 changes: 4 additions & 0 deletions tests/test_send2ue_cubes.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ def test_auto_stash_active_action_option(self):
def test_export_object_name_as_root_option(self):
pass

@unittest.skip
def test_custom_root_bone_name(self):
pass

@unittest.skip
def test_export_custom_property_fcurves_option(self):
pass
Expand Down
13 changes: 13 additions & 0 deletions tests/test_send2ue_mannequins.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,19 @@ def test_export_object_name_as_root_option(self):
'frames': [2, 6, 11]
}})

def test_custom_root_bone_name(self):
"""
Tests custom root bone name option.
"""
self.run_custom_root_bone_name_option_tests({
'SK_Mannequin_Female': {
'rig': 'female_root',
'animations': ['third_person_walk_01', 'third_person_run_01'],
'bones': ['spine_02', 'calf_l', 'lowerarm_r'],
'frames': [2, 6, 11],
'custom_name': 'my_test_root_bone',
}})

def test_export_custom_property_fcurves_option(self):
"""
Tests export custom property fcurves option.
Expand Down
35 changes: 34 additions & 1 deletion tests/utils/base_test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,7 @@ def assert_curve(self, animation_name, curve_name, exists=True):
else:
self.assertFalse(result, f'Curve "{curve_name}" exists on animation "{animation_name}" when it should not!')

def assert_animation_hierarchy(self, rig_name, animation_name, bone_name, include_object=True):
def assert_animation_hierarchy(self, rig_name, animation_name, bone_name, include_object=True, custom_root_name=None):
self.log(
f'Checking the bone hierarchy of "{animation_name}" to see if "{bone_name}" has the same path '
f'to the root bone...'
Expand All @@ -702,6 +702,9 @@ def assert_animation_hierarchy(self, rig_name, animation_name, bone_name, includ
unreal_bone_path = self.unreal.get_bone_path_to_root(asset_path, bone_name)
blender_bone_path = self.blender.get_bone_path_to_root(rig_name, bone_name, include_object)

if custom_root_name:
blender_bone_path.append(custom_root_name)

self.assertEqual(
collections.Counter(blender_bone_path),
collections.Counter(unreal_bone_path),
Expand Down Expand Up @@ -1084,6 +1087,29 @@ def run_export_object_name_as_root_option_tests(self, objects_and_animations):
for frame in frames:
self.assert_animation_translation(rig_name, animation_name, bone_name, frame)

def run_custom_root_bone_name_option_tests(self, objects_and_animations):
self.blender.set_addon_property('scene', 'send2ue', 'export_all_actions', True)
self.blender.set_addon_property('scene', 'send2ue', 'import_animations', True)
# This option should be ignored as we're setting a custom name below.
self.blender.set_addon_property('scene', 'send2ue', 'export_object_name_as_root', True)

for object_name, data in objects_and_animations.items():
rig_name = data.get('rig')
animation_names = data.get('animations')
bone_names = data.get('bones')
frames = data.get('frames')
custom_root_bone_name = data.get('custom_name')
self.blender.set_addon_property('scene', 'send2ue', 'export_custom_root_name', custom_root_bone_name)
self.move_to_collection([object_name, rig_name], 'Export')
self.send2ue_operation()
self.assert_mesh_import(object_name)
# check that the animations are as expected
for animation_name in animation_names:
for bone_name in bone_names:
self.assert_animation_hierarchy(rig_name, animation_name, bone_name, include_object=False, custom_root_name=custom_root_bone_name)
for frame in frames:
self.assert_animation_translation(rig_name, animation_name, bone_name, frame)

def run_export_custom_property_fcurves_option_tests(self, objects_and_animations):
self.blender.set_addon_property('scene', 'send2ue', 'export_all_actions', True)
self.blender.set_addon_property('scene', 'send2ue', 'import_animations', True)
Expand Down Expand Up @@ -1168,6 +1194,9 @@ def test_export_object_name_as_root_option(self):
"""
raise NotImplementedError('This test case must be implemented or skipped')

def test_custom_root_bone_name(self):
raise NotImplementedError('This test case must be implemented or skipped')

def test_export_custom_property_fcurves_option(self):
"""
Tests export custom property fcurves option.
Expand Down Expand Up @@ -1235,6 +1264,10 @@ def test_auto_stash_active_action_option(self):
def test_export_object_name_as_root_option(self):
pass

@unittest.skip
def test_custom_root_bone_name(self):
pass

@unittest.skip
def test_use_object_origin_option(self):
pass
Expand Down
Loading