I am new 3D Animations and was wondering when you are done animating. Which "Outliner in Maya" do choose to export as fbx? I am using your "Skyrim_Animation_Rig_for_Maya" File
Hi there! Welcome to the world of 3D animations. I'm glad you're using this rig for your projects. When you're done animating and ready to export your work as an FBX file from Maya, you'll need to select a specific bone in the Outliner. For the Skyrim mod, the bone you should look for is named "NPC_s_Root_s__ob_Root_cb_". This is the top bone in the hierarchy for a human character in Skyrim. Here's a step-by-step guide to help you:
1. Open the Outliner in Maya. You can usually find this in the Windows menu. 2. In the Outliner, navigate through the list and find the bone named "NPC_s_Root_s__ob_Root_cb_". 3. Select this bone. Make sure it's the only thing selected. 4. Now, go to File > Export Selection. This opens the export window. 5. In the export window, choose FBX as your file format. 6. Hit export, and you're done!
If you have any more questions or need further assistance, feel free to ask. Happy animating!
I have only tested and verified the process using Maya 2018. While the basic principles of exporting FBX files remain consistent across versions, I cannot guarantee that the process will be exactly the same. I mean, I guess maya2024 can do this too.
It's now 2 AM here in Japan, so I'm going to call it a night. Feel free to reach out if you have more questions, and I'll get back to you as soon as I can. Goodnight!
You'd need a way to translate the root motion in the rig to the Anim Motion annotations that the AMR mod uses. There's a script from MikeNike (in Python) that would need to be edited for Maya as opposed to blender, but yeah.
Spoiler:
Show
import bpy import os import re from bpy_extras.io_utils import ExportHelper from bpy_extras.io_utils import ImportHelper from bpy.types import Operator from bpy.props import StringProperty from bpy.utils import register_class from math import log class hkanno_OT_output(Operator, ExportHelper): #----> invoke ExportHelper operator """for use with hkanno64, recommend you place your files inside working directory in your mod folder""" bl_idname = 'hkanno.txt_output' #-----------------> operator name bl_label = 'Export Annotations' #-----------------> button label for file browser bl_options = {'PRESET', 'UNDO'}
filename_ext = '' #-------------------------------> filename extension (leave empty, I write the extensions after)
filter_glob: StringProperty( default='*.txt', #----------------------------> filter files in browser to ONLY those with these extensions options={'HIDDEN'} )
bonecount = 1 #------------------------------> subtract bonecount by number of junk bones (bones in your skeleton that don't contribute to the hierarchy and are not meant to be processed in HCT) ex; NPC and skeleton.nif itself for bone in skeleton.bones: bonecount += 1 #-------------------------> for each iteration, add 1 to the bonecount
for marker in bpy.context.scene.timeline_markers: if (marker.frame >= firstframe) and (marker.frame <= lastframe): #--------> check if marker is within render timeline range count = count + 1 #------------------> for each iteration, add 1 to count
if export_root_motion == 1: count = count + (lastframe - firstframe) # cheap way to add animmotion to NumAnnotations count without doing the loop we do later twice (since root motion is sampled every frame) else: print('skipping root motion...')
f = open(self.filepath + '.txt', 'w') #------> open textfile at the chosen directory (self.filepath) and in write mode ('w') f.write('# numOriginalFrames:' + (str(lastframe - firstframe)) + '\n' + # number of frames (found simply by subtracting the first frame from the last frame to find the difference) '# duration:' + (str((lastframe - firstframe)/framerate)) + '\n' # duration of animation (found just by dividing the first formula by the scene framerate) '# numAnnotationTracks: ' + (str(bonecount)) + '\n' # number of bones in skyrim skeleton '# numAnnotations: '+ (str(count)) + '\n\n') #number of annotations
bpy.ops.screen.animation_cancel(restore_frame=False)# stop animation playback bpy.ops.screen.frame_jump() #----------------> Jump to first frame on render timeline startpos = bpy.data.objects[rig].pose.bones[referencetrack].location.copy() startposx = (round(startpos[0], 4)) startposy = (round(startpos[1], 4)) startposz = (round(startpos[2], 4))
for currentframe in range(firstframe, lastframe): # Iterate over frames in render timeline bpy.ops.screen.frame_offset(delta=+1) #--> For each iteration, jump one frame ahead time = (round((currentframe - firstframe + 1) / framerate, 4)) # Calculate time value irrespective of render timeline offsets
# Here you can change the axis of orientation for the root motion calculation (In skyrim X is side to side, Y is forwards and backwards, Z is vertical) locx = (round(bpy.data.objects[rig].pose.bones[referencetrack].location[0], 4)) locy = (round(bpy.data.objects[rig].pose.bones[referencetrack].location[1], 4)) locz = (round(bpy.data.objects[rig].pose.bones[referencetrack].location[2], 4)) normx = (locx-startposx) normy = (locy-startposy) normz = (locz-startposz)
bpy.ops.screen.animation_cancel(restore_frame=True)# stop animation playback bpy.ops.screen.frame_jump() #----------------> Jump to first frame on render timeline startrot = bpy.data.objects[rig].pose.bones[referencetrack].rotation_euler.copy() startrotz = (round(startrot[2], 4))
for currentframe in range(firstframe, lastframe): # Iterate over frames in render timeline bpy.ops.screen.frame_offset(delta=+1) #--> For each iteration, jump one frame ahead time = (round((currentframe - firstframe + 1) / framerate, 4)) # Calculate time value irrespective of render timeline offsets
f1 = open(self.filepath + '.txt', 'a') #-----> append animevents to our text file (to keep things visually organized) for marker in bpy.context.scene.timeline_markers: frame = marker.frame #-------------------> Basically the same as blah_blah.scene...timeline_markers.frame (gets the frame of a marker) if (marker.frame >= firstframe) and (marker.frame <= lastframe): # check if marker is within render timeline range anno = (str(marker)).split('"')[1] f.write((str((frame - firstframe)/framerate)) + ' ' + (str(anno)) + '\n') print(((frame - firstframe)/ framerate), anno) f.close() f1.close()
class hkanno_OT_input(Operator, ImportHelper): #-----> invoke ExportHelper operator """import markers from text file""" bl_idname = 'hkanno.txt_input' #-----------------> operator name bl_label = 'Import Annotations' #----------------> button label for file browser bl_options = {'PRESET', 'UNDO'}
filename_ext = '' #------------------------------> filename extension (leave empty, I write the extensions after)
filter_glob: StringProperty( default='*.txt', #---------------------------> filter files in browser to ONLY those with these extensions options={'HIDDEN'} )
print('file chosen: ' + (self.filepath)) f = open(self.filepath, 'r')
textfile = f.readlines() for line in textfile: if 'animmotion' in line: line = line.replace('.' , '') elif 'numAnnotationTracks' in line: line = line.replace('.', '') elif 'numAnnotations' in line: line = line.replace('.', '') elif 'duration' in line: line = line.replace('.', '') elif 'numOriginalFrames' in line: line = line.replace('.', '') else: marker = line.split(' ', 1) markername = (marker[-1]) markername2 = markername.replace('\n', '') time = re.findall('\d.\d+', line) floatconversion = [float(F) for F in time] for i in floatconversion: Frame = (int((i*framerate)+firstframe)) bpy.context.scene.timeline_markers.new((str(markername2)), frame = Frame) print(markername)
return {'FINISHED'}
class hkanno_OT_clear(bpy.types.Operator): """Clear markers from scene""" bl_idname = 'hkanno.markers_clear' bl_label = 'Clear Markers'
This rig would works for SL animations because they are not made from paired rig, they are just a 2 single animations as I mentioned in the description page.
It’s difficult to export paired rig from Skyrim. because there is no paired skeleton in the game data(archive). I want you to read a article below especially “The Theory Behind All of This” section. You may understand why I can’t export it.https://www.nexusmods.com/skyrim/mods/49311/
Yes, totally, i understand, thanks. I just have a question : If i want to replace, for exemple a SL anim, do I have to touch the behaviors file or not ?
I made anims for FO4, just start with skyrim and in Fallout, there are a not the behaviors things.
Apologies if I missed something as I am pretty new to Maya animation. However, I got a couple of errors.
1. Does this require ngSkinTools or a similar plugin? I got the following in the Script Editor when I opened the file - "Unrecognized node type 'ngst2SkinLayerData'; preserving node information during this session."
2. The Control Picker File seems to reference some images such as: "image.path": "D:/warehouse/refs/refs/Window27.png". However, these don't seem to have been included?
1. Yes I used ngskintools, Please ignore that error since I tweaked body mesh's weight (which was not necessary at all) then forgot to delete that. 2. That’s my mistakes too. I re-uploaded them as Miscellaneous files. please check it out.
22 comments
I am using your "Skyrim_Animation_Rig_for_Maya" File
When you're done animating and ready to export your work as an FBX file from Maya, you'll need to select a specific bone in the Outliner. For the Skyrim mod, the bone you should look for is named "NPC_s_Root_s__ob_Root_cb_". This is the top bone in the hierarchy for a human character in Skyrim.
Here's a step-by-step guide to help you:
1. Open the Outliner in Maya. You can usually find this in the Windows menu.
2. In the Outliner, navigate through the list and find the bone named "NPC_s_Root_s__ob_Root_cb_".
3. Select this bone. Make sure it's the only thing selected.
4. Now, go to File > Export Selection. This opens the export window.
5. In the export window, choose FBX as your file format.
6. Hit export, and you're done!
If you have any more questions or need further assistance, feel free to ask. Happy animating!
While the basic principles of exporting FBX files remain consistent across versions, I cannot guarantee that the process will be exactly the same.
I mean, I guess maya2024 can do this too.
It's now 2 AM here in Japan, so I'm going to call it a night. Feel free to reach out if you have more questions, and I'll get back to you as soon as I can. Goodnight!
I've left a comment on your YouTube video. Please check it out when you can.
That is what I'm curious about too. I've never tried MCO things, would you please test this rig out??
import os
import re
from bpy_extras.io_utils import ExportHelper
from bpy_extras.io_utils import ImportHelper
from bpy.types import Operator
from bpy.props import StringProperty
from bpy.utils import register_class
from math import log
class hkanno_OT_output(Operator, ExportHelper): #----> invoke ExportHelper operator
"""for use with hkanno64, recommend you place your files inside working directory in your mod folder"""
bl_idname = 'hkanno.txt_output' #-----------------> operator name
bl_label = 'Export Annotations' #-----------------> button label for file browser
bl_options = {'PRESET', 'UNDO'}
filename_ext = '' #-------------------------------> filename extension (leave empty, I write the extensions after)
filter_glob: StringProperty(
default='*.txt', #----------------------------> filter files in browser to ONLY those with these extensions
options={'HIDDEN'}
)
def execute(self, context):
rig = 'control rig'
referencetrack = 'root'
skyrim_rig = 'skeleton.nif'
scaleunits = 10
hkannoversion = 'hkanno64.exe'
framerate = bpy.context.scene.render.fps
currentframe = bpy.context.scene.frame_current
firstframe = bpy.context.scene.frame_start
lastframe = bpy.context.scene.frame_end
skeleton = bpy.data.armatures[skyrim_rig]
export_root_motion = bpy.data.window_managers["WinMan"].export_root_motion
export_root_rotation = bpy.data.window_managers["WinMan"].export_root_rotation
bonecount = 1 #------------------------------> subtract bonecount by number of junk bones (bones in your skeleton that don't contribute to the hierarchy and are not meant to be processed in HCT) ex; NPC and skeleton.nif itself
for bone in skeleton.bones:
bonecount += 1 #-------------------------> for each iteration, add 1 to the bonecount
print('\n\n' + 'exported file:', self.filepath, '\n')
count = 0
for marker in bpy.context.scene.timeline_markers:
if (marker.frame >= firstframe) and (marker.frame <= lastframe): #--------> check if marker is within render timeline range
count = count + 1 #------------------> for each iteration, add 1 to count
if export_root_motion == 1:
count = count + (lastframe - firstframe) # cheap way to add animmotion to NumAnnotations count without doing the loop we do later twice (since root motion is sampled every frame)
else:
print('skipping root motion...')
if export_root_rotation == 1:
count = count + (lastframe - firstframe)
else:
print('skipping root rotation... \n')
duration = (round((lastframe - firstframe)/framerate, 2))
f = open(self.filepath + '.txt', 'w') #------> open textfile at the chosen directory (self.filepath) and in write mode ('w')
f.write('# numOriginalFrames:' + (str(lastframe - firstframe)) + '\n' + # number of frames (found simply by subtracting the first frame from the last frame to find the difference)
'# duration:' + (str((lastframe - firstframe)/framerate)) + '\n' # duration of animation (found just by dividing the first formula by the scene framerate)
'# numAnnotationTracks: ' + (str(bonecount)) + '\n' # number of bones in skyrim skeleton
'# numAnnotations: '+ (str(count)) + '\n\n') #number of annotations
bpy.ops.screen.animation_cancel(restore_frame=False)# stop animation playback
bpy.ops.screen.frame_jump() #----------------> Jump to first frame on render timeline
startpos = bpy.data.objects[rig].pose.bones[referencetrack].location.copy()
startposx = (round(startpos[0], 4))
startposy = (round(startpos[1], 4))
startposz = (round(startpos[2], 4))
for currentframe in range(firstframe, lastframe): # Iterate over frames in render timeline
bpy.ops.screen.frame_offset(delta=+1) #--> For each iteration, jump one frame ahead
time = (round((currentframe - firstframe + 1) / framerate, 4)) # Calculate time value irrespective of render timeline offsets
# Here you can change the axis of orientation for the root motion calculation (In skyrim X is side to side, Y is forwards and backwards, Z is vertical)
locx = (round(bpy.data.objects[rig].pose.bones[referencetrack].location[0], 4))
locy = (round(bpy.data.objects[rig].pose.bones[referencetrack].location[1], 4))
locz = (round(bpy.data.objects[rig].pose.bones[referencetrack].location[2], 4))
normx = (locx-startposx)
normy = (locy-startposy)
normz = (locz-startposz)
if export_root_motion == 1:
f.write((str(time)) + ' ' + 'animmotion' + ' ' + (str(normx*scaleunits)) + ' ' + (str(normy*scaleunits)) + ' ' + (str(normz*scaleunits)) + '\n')
print(time, 'animmotion', (normx*scaleunits), (normy*scaleunits), (normz*scaleunits))
f.write('\n\n')
bpy.ops.screen.animation_cancel(restore_frame=True)# stop animation playback
bpy.ops.screen.frame_jump() #----------------> Jump to first frame on render timeline
startrot = bpy.data.objects[rig].pose.bones[referencetrack].rotation_euler.copy()
startrotz = (round(startrot[2], 4))
for currentframe in range(firstframe, lastframe): # Iterate over frames in render timeline
bpy.ops.screen.frame_offset(delta=+1) #--> For each iteration, jump one frame ahead
time = (round((currentframe - firstframe + 1) / framerate, 4)) # Calculate time value irrespective of render timeline offsets
rotz = (round(bpy.data.objects[rig].pose.bones[referencetrack].rotation_euler[2], 4))
normzr = (rotz-startrotz)
if export_root_rotation == 1:
f.write((str(time)) + ' ' + 'animrotation' + ' ' + (str(normzr*57.27)) + '\n')
print(time, 'animrotation', (normzr))
f.write('\n\n')
f1 = open(self.filepath + '.txt', 'a') #-----> append animevents to our text file (to keep things visually organized)
for marker in bpy.context.scene.timeline_markers:
frame = marker.frame #-------------------> Basically the same as blah_blah.scene...timeline_markers.frame (gets the frame of a marker)
if (marker.frame >= firstframe) and (marker.frame <= lastframe): # check if marker is within render timeline range
anno = (str(marker)).split('"')[1]
f.write((str((frame - firstframe)/framerate)) + ' ' + (str(anno)) + '\n')
print(((frame - firstframe)/ framerate), anno)
f.close()
f1.close()
f2 = open(self.filepath + '.bat', 'w')
f2.write(hkannoversion + ' ' + 'update' + ' ' + '-i' + ' ' + '"' + (os.path.basename(self.filepath)) + '.txt' + '"' + ' ' + '"%~1"' + ' ' + '"' + (os.path.basename(self.filepath)) + '.hkx' + '"')
f2.close()
print('bones in rig: ', bonecount)
print('frames: ', (str(lastframe - firstframe)))
print('duration: ', (str((lastframe - firstframe)/framerate)))
print('framerate: ', framerate)
return {'FINISHED'}
class hkanno_OT_input(Operator, ImportHelper): #-----> invoke ExportHelper operator
"""import markers from text file"""
bl_idname = 'hkanno.txt_input' #-----------------> operator name
bl_label = 'Import Annotations' #----------------> button label for file browser
bl_options = {'PRESET', 'UNDO'}
filename_ext = '' #------------------------------> filename extension (leave empty, I write the extensions after)
filter_glob: StringProperty(
default='*.txt', #---------------------------> filter files in browser to ONLY those with these extensions
options={'HIDDEN'}
)
def execute(self, context):
bpy.context.scene.timeline_markers.clear()
firstframe = bpy.context.scene.frame_start
framerate = bpy.context.scene.render.fps
print('file chosen: ' + (self.filepath))
f = open(self.filepath, 'r')
textfile = f.readlines()
for line in textfile:
if 'animmotion' in line:
line = line.replace('.' , '')
elif 'numAnnotationTracks' in line:
line = line.replace('.', '')
elif 'numAnnotations' in line:
line = line.replace('.', '')
elif 'duration' in line:
line = line.replace('.', '')
elif 'numOriginalFrames' in line:
line = line.replace('.', '')
else:
marker = line.split(' ', 1)
markername = (marker[-1])
markername2 = markername.replace('\n', '')
time = re.findall('\d.\d+', line)
floatconversion = [float(F) for F in time]
for i in floatconversion:
Frame = (int((i*framerate)+firstframe))
bpy.context.scene.timeline_markers.new((str(markername2)), frame = Frame)
print(markername)
return {'FINISHED'}
class hkanno_OT_clear(bpy.types.Operator):
"""Clear markers from scene"""
bl_idname = 'hkanno.markers_clear'
bl_label = 'Clear Markers'
def execute(Self, Context):
bpy.context.scene.timeline_markers.clear()
return {'FINISHED'}
def register():
bpy.utils.register_class(hkanno_OT_output)
bpy.utils.register_class(hkanno_OT_input)
bpy.utils.register_class(hkanno_OT_clear)
def unregister():
bpy.utils.unregister_class(hkanno_OT_output)
bpy.utils.unregister_class(hkanno_OT_input)
bpy.utils.unregister_class(hkanno_OT_clear)
if __name__ == '__main__':
register()
#bpy.ops.hkanno.txt_output('INVOKE_DEFAULT')
I'm so GLAD to see a rig in Maya haha ! Thank you so much for this !
May I ask if it's possible to export paired rigs, for killmoves ? Or could it work for sexlab also ?
This rig would works for SL animations because they are not made from paired rig, they are just a 2 single animations as I mentioned in the description page.
It’s difficult to export paired rig from Skyrim. because there is no paired skeleton in the game data(archive).
I want you to read a article below especially “The Theory Behind All of This” section. You may understand why I can’t export it.https://www.nexusmods.com/skyrim/mods/49311/
Does my answer make sense??
Yes, totally, i understand, thanks. I just have a question : If i want to replace, for exemple a SL anim, do I have to touch the behaviors file or not ?
I made anims for FO4, just start with skyrim and in Fallout, there are a not the behaviors things.
Apologies if I missed something as I am pretty new to Maya animation. However, I got a couple of errors.
1. Does this require ngSkinTools or a similar plugin? I got the following in the Script Editor when I opened the file - "Unrecognized node type 'ngst2SkinLayerData'; preserving node information during this session."
2. The Control Picker File seems to reference some images such as: "image.path": "D:/warehouse/refs/refs/Window27.png". However, these don't seem to have been included?
Thanks,
1. Yes I used ngskintools, Please ignore that error since I tweaked body mesh's weight (which was not necessary at all) then forgot to delete that.
2. That’s my mistakes too. I re-uploaded them as Miscellaneous files. please check it out.
Thank you for telling me.
Happy new year.
Thanks and Happy New Year!