Skip to content

Commit fb84769

Browse files
author
Roberto De Ioris
authored
Update FixingMixamoRootMotionWithPython.md
1 parent e5aa660 commit fb84769

File tree

1 file changed

+127
-1
lines changed

1 file changed

+127
-1
lines changed

tutorials/FixingMixamoRootMotionWithPython.md

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,134 @@ If all goes well, open the new skeleton and rotate again the left shoulder. Now
268268

269269
![fixed influences](https://github.com/20tab/UnrealEnginePython/raw/master/tutorials/FixingMixamoRootMotionWithPython_Assets/fixed_influences.png)
270270

271+
No more arms deformation :)
272+
271273
## Step 4: splitting 'Hips' track in animation
272274

273-
## Adding a context menu
275+
The final step is fixing animations. We basically need to add a new track for the root motion, and remove translation from the track of the 'Hips' bone (remember, each bone is mapped to a different track in the animation).
276+
277+
We will create a new empty animation for the 'rooted' skeleton, and we will copy (and eventually fix) each track from the selected animation.
278+
279+
This time we will ask the user to select a skeleton asset using a slate window, but we will not ask where to save the new copy, instead we will automatically generate a name (obviously feel free to add your asset save dialog).
280+
281+
```python
282+
import unreal_engine as ue
283+
from unreal_engine.classes import SkeletalMesh, Skeleton, AnimSequence, AnimSequenceFactory
284+
from unreal_engine import FTransform, FRawAnimSequenceTrack, FQuat
285+
from unreal_engine import SWindow, SObjectPropertyEntryBox
286+
287+
class RootMotionFixer:
288+
289+
...
290+
def set_skeleton(self, asset_data):
291+
"""
292+
This hook will be called after you selected a skeleton from the slate wizard
293+
"""
294+
self.choosen_skeleton = asset_data.get_asset()
295+
self.window.request_destroy()
296+
297+
def split_hips(self, animation, bone='Hips'):
298+
"""
299+
SObjectPropertyEntryBox is the asset selector widget (we limit it to the Skeleton class)
300+
"""
301+
# this will contain the user-selected skeleton (if-any)
302+
self.choosen_skeleton = None
303+
# first ask for which skeleton to use:
304+
self.window = SWindow(title='Choose your new Skeleton', modal=True, sizing_rule=1)(
305+
SObjectPropertyEntryBox(allowed_class=Skeleton, on_object_changed=self.set_skeleton)
306+
)
307+
# add_modal() will block the script until the user make some kind of input in the window
308+
self.window.add_modal()
309+
310+
# ensure a skeleton has been selected
311+
if not self.choosen_skeleton:
312+
raise DialogException('Please specify a Skeleton for retargeting')
313+
314+
# create a new empty animation from the skeleton
315+
factory = AnimSequenceFactory()
316+
factory.TargetSkeleton = self.choosen_skeleton
317+
318+
# automatically build its new path
319+
base_path = animation.get_path_name()
320+
package_name = ue.get_path(base_path)
321+
object_name = ue.get_base_filename(base_path)
322+
323+
new_anim = factory.factory_create_new(package_name + '/' + object_name + '_rooted')
324+
325+
# copy the number of frames and duration
326+
new_anim.NumFrames = animation.NumFrames
327+
new_anim.SequenceLength = animation.SequenceLength
328+
329+
# iterate each track to copy/fix
330+
for index, name in enumerate(animation.AnimationTrackNames):
331+
data = animation.get_raw_animation_track(index)
332+
if name == bone:
333+
# extract root motion
334+
root_motion = [position - data.pos_keys[0] for position in data.pos_keys]
335+
336+
# remove root motion from original track (but leave a single key for position, otherwise the track will break)
337+
data.pos_keys = [data.pos_keys[0]]
338+
new_anim.add_new_raw_track(name, data)
339+
340+
# create a new track (the root motion one)
341+
root_data = FRawAnimSequenceTrack()
342+
root_data.pos_keys = root_motion
343+
# ensure empty rotations !
344+
root_data.rot_keys = [FQuat()]
345+
346+
# add the track
347+
new_anim.add_new_raw_track('root', root_data)
348+
else:
349+
new_anim.add_new_raw_track(name, data)
350+
351+
new_anim.save_package()
352+
```
353+
354+
Now add support for AnimSequence in your final loop:
355+
356+
```python
357+
for uobject in ue.get_selected_assets():
358+
if uobject.is_a(SkeletalMesh):
359+
root_motion_fixer.add_root_to_skeleton(uobject)
360+
elif uobject.is_a(AnimSequence):
361+
root_motion_fixer.split_hips(uobject)
362+
else:
363+
raise DialogException('Only Skeletal Meshes and Skeletons are supported')
364+
```
365+
366+
Select an animation from the content browser and run the script.
367+
368+
You can downlod the full code here:
369+
370+
https://github.com/20tab/UnrealEnginePython/blob/master/tutorials/FixingMixamoRootMotionWithPython_Assets/mixamo.py
371+
372+
## Bonus step: adding a context menu
373+
374+
Running the script from the command line is not very handy (unless you are doing some kind of massive conversion).
375+
376+
It would be way cooler to execute it by right clicking the asset from the content browser. You can add a context menu extension:
377+
378+
```python
379+
class RootMotionFixer:
380+
381+
...
382+
def run_tasks(self, selected_assets):
383+
for asset_data in selected_assets:
384+
if asset_data.asset_class == 'SkeletalMesh':
385+
self.add_root_to_skeleton(asset_data.get_asset())
386+
elif asset_data.asset_class == 'AnimSequence':
387+
self.split_hips(asset_data.get_asset())
388+
else:
389+
raise DialogException('Only Skeletal Meshes are supported')
390+
391+
def __call__(self, menu, selected_assets):
392+
menu.begin_section('mixamo', 'mixamo')
393+
menu.add_menu_entry('fix root motion', 'fix root motion', self.run_tasks, selected_assets)
394+
menu.end_section()
395+
396+
# add a context menu
397+
ue.add_asset_view_context_menu_extension(RootMotionFixer())
398+
ue.log('Mixamo Root Motion Fixer registered')
399+
```
274400

275401
## Final Notes

0 commit comments

Comments
 (0)