Skip to content

Instantly share code, notes, and snippets.

@hartmannr76
Created May 3, 2024 18:19
Show Gist options
  • Save hartmannr76/c8e677c79754f48b67e37e3262a7f31d to your computer and use it in GitHub Desktop.
Save hartmannr76/c8e677c79754f48b67e37e3262a7f31d to your computer and use it in GitHub Desktop.
import unreal
def register():
menu_owner = "AnimSequenceBoneFixer_AddControlsForSelected"
tool_menus = unreal.ToolMenus.get()
menu = tool_menus.extend_menu('ContentBrowser.AssetContextMenu.AnimSequence')
if (menu):
entry = add_controls_for_selected()
entry.init_entry(menu_owner,
"ContentBrowser.AssetContextMenu.AnimSequence", "GetAssetActions", "FixAnimBones", "Fix Anim Bones")
menu.add_menu_entry_object(entry)
@unreal.uclass()
class add_controls_for_selected_options(unreal.Object):
'''Custom class for AnimSequence creation settings'''
child_parent_mappings = unreal.uproperty(unreal.Map(str, str), meta={"DisplayName": "Child->Parent Mappings"})
def _post_init(self):
'''Set default class values after initialization'''
self.child_parent_mappings = unreal.Map(str, str)
@unreal.uclass()
class add_controls_for_selected(unreal.ToolMenuEntryScript):
'''
Custom Tool Menu Entry for AnimSequence Bone Fix
This is mostly just an automated version of https://www.youtube.com/watch?v=2ZFoEi9XRms
Takes in a list of child->parent bone mappings, creates a LevelSequence,
bakes the selected AnimSequence into the LevelSequence, snaps the selected
mappings, saves the AnimSequence back out, and deletes the LevelSequence
so you are left with a clean repo.
'''
user_data = add_controls_for_selected_options()
@unreal.ufunction(override=True)
def get_label(self, context):
'''
Override function for label of menu entry item
'''
return "Fix animations"
@unreal.ufunction(override=True)
def get_tool_tip(self, context):
'''Override function for tool tip hover text'''
return "Fix Controls from selected Bones"
@unreal.ufunction(override=True)
def can_execute(self, context):
'''Override function for if the menu entry can be executed'''
anim_seq_context = context.find_by_class(
unreal.ContentBrowserAssetContextMenuContext)
return len(anim_seq_context.selected_assets) > 0
@unreal.ufunction(override=True)
def execute(self, context):
'''Override function for the menu entry execution'''
anim_seq_context = unreal.ContentBrowserAssetContextMenuContext.cast(context.find_by_class(
unreal.ContentBrowserAssetContextMenuContext))
object_details_view_options = unreal.EditorDialogLibraryObjectDetailsViewOptions()
object_details_view_options.show_object_name = False
object_details_view_options.allow_search = False
if not unreal.EditorDialog.show_object_details_view(
"Anim Fixer",
self.user_data,
object_details_view_options
):
return
for anim_seq in anim_seq_context.selected_assets:
seq_path = f'{anim_seq.package_path}/FixDrivers'
driver_asset_name = f'FixDriver_{anim_seq.asset_name}'
asset = unreal.AnimSequence.cast(anim_seq.get_asset())
sequencer = unreal.LevelSequence.cast(unreal.AssetToolsHelpers.get_asset_tools().create_asset(driver_asset_name, seq_path, unreal.LevelSequence, unreal.LevelSequenceFactoryNew()))
sequencer.set_display_rate(asset.data_model_interface.get_frame_rate())
sequencer.set_playback_end(asset.data_model_interface.get_number_of_frames())
sequencer.set_work_range_end(asset.get_play_length())
anim_seq_export_options = unreal.AnimSeqExportOption()
anim_seq_export_options.export_transforms = True
anim_seq_export_options.export_attribute_curves = True
actor_binding = sequencer.add_spawnable_from_instance(asset)
unreal.SequencerTools.link_anim_sequence(sequencer, asset, anim_seq_export_options, actor_binding)
baked = unreal.ControlRigSequencerLibrary.bake_to_control_rig(
unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world(),
sequencer,
unreal.FKControlRig,
anim_seq_export_options,
False,
0.001,
actor_binding
)
unreal.LevelSequenceEditorBlueprintLibrary.open_level_sequence(sequencer)
if baked:
rig_proxy = unreal.ControlRigSequencerBindingProxy.cast(unreal.ControlRigSequencerLibrary.get_control_rigs(sequencer)[0])
snap_settings = unreal.ControlRigSnapSettings()
snap_settings.keep_offset = False
snap_settings.snap_position = True
snap_settings.snap_rotation = True
snap_settings.snap_scale = False
# ('ik_foot_r_control', 'foot_r_control'), ('ik_foot_l_control', 'foot_l_control')
for child, parent in self.user_data.child_parent_mappings.items():
unreal.ControlRigSequencerLibrary.snap_control_rig(
sequencer,
unreal.FrameNumber(0),
unreal.FrameNumber(sequencer.get_playback_end()),
unreal.ControlRigSnapperSelection(
control_rigs=[
unreal.ControlRigForWorldTransforms(
control_rig=rig_proxy.control_rig,
control_names=[child]
),
]
),
unreal.ControlRigSnapperSelection(
control_rigs=[
unreal.ControlRigForWorldTransforms(
control_rig=rig_proxy.control_rig,
control_names=[parent]
),
]
),
snap_settings
)
unreal.SequencerTools.export_anim_sequence(
unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world(),
sequencer,
asset,
anim_seq_export_options,
actor_binding,
False
)
del rig_proxy
del actor_binding
else:
print('no good')
unreal.LevelSequenceEditorBlueprintLibrary.close_level_sequence()
unreal.SequencerTools.clear_linked_anim_sequences(sequencer)
unreal.EditorAssetLibrary.save_directory('/Game/', True, True)
unreal.EditorAssetLibrary.delete_loaded_asset(sequencer)
del sequencer
register()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment