Last active
March 30, 2024 03:38
-
-
Save jagt/27ccba6e1837606c0e2e2675bccd0bf6 to your computer and use it in GitHub Desktop.
Standalone script for exporting camera animation w/ custom attributes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
export camera standalone | |
1. put in `C:/Users/<user>/Documents/maya/scripts` | |
2. open maya and select the camera in outliner | |
3. menu 'custom->Export Selected Camera' | |
''' | |
from maya import cmds | |
from maya import mel | |
def make_kwargs(overrides, **kwargs): | |
kwargs.update(overrides) | |
return kwargs | |
def get_first_shape(transform): | |
'''return its first shape''' | |
shapes = cmds.listRelatives(transform, c=True, ni=True, shapes=True) | |
return shapes[0] if shapes else None | |
def bake_objects_results(objects, **kwargs): | |
startTime = cmds.playbackOptions(min=True, q=True) | |
endTime = cmds.playbackOptions(max=True, q=True) | |
cmds.bakeResults(objects, **make_kwargs( | |
kwargs, | |
simulation=True, | |
# 'hierarchy' here means expand this bake to the hierarchy above/below/both or none | |
hierarchy="below", | |
shape=True, | |
sampleBy=1.0, | |
disableImplicitControl=True, | |
time=(startTime, endTime) | |
)) | |
def constraints_rst(source, target, mo=False): | |
'''constraints translate/rotation/scale from source to target, returns all constraints''' | |
constraints = [] | |
# parent constraint is basically translate+rotation | |
if mo: | |
constraints.extend( cmds.pointConstraint(source, target, mo=True, weight=1) ) | |
constraints.extend( cmds.orientConstraint(source, target, mo=True, weight=1) ) | |
constraints.extend( cmds.scaleConstraint(source, target, mo=True, weight=1) ) | |
else: | |
constraints.extend( cmds.pointConstraint(source, target, offset=[0,0,0], mo=False, weight=1) ) | |
constraints.extend( cmds.orientConstraint(source, target, offset=[0,0,0], mo=False, weight=1) ) | |
constraints.extend( cmds.scaleConstraint(source, target, offset=[1,1,1], mo=False, weight=1) ) | |
# not sure why this doesn't work | |
return constraints | |
def _setup_camer_fov_expr(group, cameraShape): | |
# setup attr and expressions | |
# turns out expression is one of the things that mel can do but python can't | |
cmds.addAttr( longName='Export_CameraNearClip', attributeType='float', readable=True, keyable=True) | |
cmds.addAttr( longName='Export_CameraFarClip', attributeType='float', readable=True, keyable=True) | |
cmds.addAttr( longName='Export_CameraVerticalFOV', attributeType='float', readable=True, keyable=True) | |
cmds.addAttr( longName='Export_CameraHorizontalFOV', attributeType='float', readable=True, keyable=True) | |
cmds.expression(s = u'%s.Export_CameraNearClip = %s.nearClipPlane' % (group, cameraShape)) | |
cmds.expression(s = u'%s.Export_CameraFarClip = %s.farClipPlane' % (group, cameraShape)) | |
FOV_EXPR = u''' | |
{0}.{3} = 2.0 * atan((0.5 * {1}.{2}) / ({1}.focalLength * 0.03937)) * 57.29578 | |
''' | |
cmds.expression(s =FOV_EXPR.format(group, cameraShape, 'verticalFilmAperture', 'Export_CameraVerticalFOV')) | |
cmds.expression(s =FOV_EXPR.format(group, cameraShape, 'horizontalFilmAperture', 'Export_CameraHorizontalFOV')) | |
def _setup_camera_physics_expr(group, cameraShape): | |
# setup attr and expressions | |
# turns out expression is one of the things that mel can do but python can't | |
cmds.addAttr( longName='Export_HorizontalFilmAperture', attributeType='float', readable=True, keyable=True ) | |
cmds.addAttr( longName='Export_VerticalFilmAperture', attributeType='float', readable=True, keyable=True ) | |
cmds.addAttr( longName='Export_FocalLength', attributeType='float', readable=True, keyable=True ) | |
cmds.expression(s = u'%s.Export_HorizontalFilmAperture = %s.horizontalFilmAperture' % (group, cameraShape)) | |
cmds.expression(s = u'%s.Export_VerticalFilmAperture = %s.verticalFilmAperture' % (group, cameraShape)) | |
cmds.expression(s = u'%s.Export_FocalLength = %s.focalLength' % (group, cameraShape)) | |
def bake_animation_to(source, target, hierarchy='none', simulation=False): | |
''' | |
# https://www.highend3d.com/maya/script/make-anim-global-for-maya | |
copy all animation to, note that this don't do simulation and should really be | |
used against already baked animations | |
''' | |
constraints = constraints_rst(source, target) | |
bake_objects_results([target], hierarchy=hierarchy, simulation=simulation) | |
cmds.delete(constraints) | |
def bake_camera_animation_to_global_physics(camera, name): | |
group = cmds.group(name=name, empty=True) | |
cameraShape = get_first_shape(camera) | |
_setup_camer_fov_expr(group, cameraShape) | |
_setup_camera_physics_expr(group, cameraShape) | |
bake_animation_to(camera, group, simulation=True) | |
def export_selected_static_mesh_animation(file_path): | |
# UE4 can't use static mesh animation anyway, export as animation only | |
# and import it in engine later | |
_export_animation_fbx(file_path, animation_only=True) | |
# clear when done | |
cmds.select(cl=True) | |
def _export_commons(): | |
# defaults | |
# https://docs.unrealengine.com/en-us/Engine/Content/FBX/BestPractices | |
mel.eval("FBXProperty Export|IncludeGrp|Geometry|SmoothingGroups -v true;") | |
mel.eval("FBXProperty Export|IncludeGrp|Geometry|expHardEdges -v false;") | |
mel.eval("FBXProperty Export|IncludeGrp|Geometry|TangentsandBinormals -v false;") | |
mel.eval("FBXProperty Export|IncludeGrp|Geometry|SmoothMesh -v false;") | |
mel.eval("FBXProperty Export|IncludeGrp|Geometry|SelectionSet -v false;") | |
mel.eval("FBXProperty Export|IncludeGrp|Geometry|BlindData -v false;") | |
mel.eval('FBXProperty Export|IncludeGrp|Geometry|GeometryNurbsSurfaceAs -v "Interactive Display Mesh";') | |
mel.eval("FBXProperty Export|IncludeGrp|Geometry|Instances -v false;") | |
mel.eval("FBXProperty Export|IncludeGrp|Geometry|ContainerObjects -v true;") | |
mel.eval("FBXProperty Export|IncludeGrp|Geometry|Triangulate -v false;") | |
# unused things | |
# actually we don't really have referenced constaants | |
mel.eval("FBXExportReferencedAssetsContent -v false") | |
mel.eval("FBXExportConstraints -v false") | |
mel.eval("FBXExportCameras -v false") | |
mel.eval("FBXExportLights -v false") | |
mel.eval("FBXExportEmbeddedTextures -v false") | |
mel.eval("FBXExportInputConnections -v false") | |
mel.eval("FBXExportSmoothingGroups -v false") | |
mel.eval("FBXExportSmoothMesh -v false") | |
def _execute_export_selected(file_path): | |
file_path = file_path.replace('\\', '/') | |
mel.eval('FBXExport -f "{0}" -s;'.format(file_path)) | |
def _export_animation_fbx(file_path, animation_only=False): | |
_export_commons() | |
# start = str(cmds.playbackOptions(ast=True, q=True)) | |
# end = str(cmds.playbackOptions(aet=True, q=True)) | |
if animation_only: | |
mel.eval("FBXExportAnimationOnly -v true;") | |
else: | |
mel.eval("FBXExportAnimationOnly -v false;") | |
# should've been baked already | |
mel.eval("FBXExportBakeComplexAnimation -v false;") | |
_execute_export_selected(file_path) | |
def export_camera_animation(root, file_path): | |
assert not cmds.listRelatives(root, p=True), '%s should be global object' % root | |
cmds.select(cl=True) | |
cmds.select(root) | |
# additionally select all keyable attributes for exporting custom attribute | |
# ! note that this exported fbx won't have the curve animated when imported | |
# in maya, as the connection is lost in current settings. but the curve is actually | |
# exported | |
channel_box = mel.eval('global string $gChannelBoxName; $temp=$gChannelBoxName;') | |
attrs = [u'%s.%s' % (root, attr) for attr in cmds.listAttr(root, keyable=True)] | |
cmds.channelBox(channel_box, e=1, select=attrs) | |
_export_animation_fbx(file_path, animation_only=True) | |
def _main(): | |
'''main that called from maya''' | |
EXPORT_NAME = 'ExportCamera' | |
camera = cmds.ls(sl=1) | |
if not camera: | |
cmds.confirmDialog(title='Error', message=u'nothing is selected, select a camera to export') | |
return | |
cameraShape = get_first_shape(camera) | |
if not cameraShape or not cmds.objectType(cameraShape, isType=u'camera'): | |
cmds.confirmDialog(title='Error', message=u'%s is not a camera, select a camera to export.' % camera) | |
return | |
bake_camera_animation_to_global_physics(cmds.ls(sl=1), EXPORT_NAME) | |
ret = cmds.fileDialog2(ds=2, fm=0, ff="FBX (*.fbx);;", caption='Export Camera FBX Path') | |
if not ret: return | |
file_path = ret[0] | |
export_camera_animation(EXPORT_NAME, file_path) | |
def setup(): | |
# add a new menu | |
cmds.setParent('MayaWindow') | |
cmds.menu(label='custom') | |
cmds.menuItem(label='Export Selected Camera', command='import export_camera_standalone; export_camera_standalone._main()') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
python("import export_camera_standalone; export_camera_standalone.setup()"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment