Update
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 59 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.3 KiB |
BIN
2023/icons/offsetkeys.png
Normal file
BIN
2023/icons/offsetkeys.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
178
2023/scripts/animation_tools/offsetkeys.py
Normal file
178
2023/scripts/animation_tools/offsetkeys.py
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Key Offset Tool
|
||||||
|
Made by Martin Chang, 2021
|
||||||
|
Modified for multi-version Maya compatibility, 2025
|
||||||
|
"""
|
||||||
|
|
||||||
|
import maya.cmds as cmds
|
||||||
|
|
||||||
|
# Global variables
|
||||||
|
bakeLayer = True
|
||||||
|
keyOffsetField = None
|
||||||
|
startOffsetField = None
|
||||||
|
endOffsetField = None
|
||||||
|
|
||||||
|
def offsetKeys(forward, *args):
|
||||||
|
"""Offset keys for selected objects"""
|
||||||
|
global keyOffsetField
|
||||||
|
|
||||||
|
objects = cmds.ls(orderedSelection=True, long=False)
|
||||||
|
|
||||||
|
if not keyOffsetField or not cmds.floatField(keyOffsetField, exists=True):
|
||||||
|
cmds.warning("Key Offset Tool window not found. Please open the tool first.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
keyOffset = cmds.floatField(keyOffsetField, query=True, value=True)
|
||||||
|
except RuntimeError:
|
||||||
|
cmds.warning("Failed to get keyframe offset value.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not objects:
|
||||||
|
cmds.confirmDialog(title='Error', message='Select one or more objects to convert to offset keys')
|
||||||
|
return
|
||||||
|
|
||||||
|
if forward:
|
||||||
|
for i in range(len(objects)):
|
||||||
|
try:
|
||||||
|
cmds.keyframe(objects[i], edit=True, relative=True, timeChange=keyOffset*(i+1))
|
||||||
|
except RuntimeError as e:
|
||||||
|
cmds.warning("Failed to offset keys for {}: {}".format(objects[i], str(e)))
|
||||||
|
else:
|
||||||
|
for i in range(len(objects)):
|
||||||
|
try:
|
||||||
|
startTime = cmds.playbackOptions(q=True, minTime=True)
|
||||||
|
firstKey = cmds.findKeyframe(objects[i], which='first')
|
||||||
|
cmds.keyframe(objects[i], edit=True, relative=True, timeChange=-(firstKey-startTime))
|
||||||
|
except RuntimeError as e:
|
||||||
|
cmds.warning("Failed to revert offset for {}: {}".format(objects[i], str(e)))
|
||||||
|
|
||||||
|
def scaleToStart(*args):
|
||||||
|
"""Scale keys to start of time range"""
|
||||||
|
global startOffsetField
|
||||||
|
|
||||||
|
objects = cmds.ls(orderedSelection=True, long=False)
|
||||||
|
|
||||||
|
if not startOffsetField or not cmds.floatField(startOffsetField, exists=True):
|
||||||
|
cmds.warning("Key Offset Tool window not found. Please open the tool first.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
startOffset = cmds.floatField(startOffsetField, query=True, value=True)
|
||||||
|
startTime = cmds.playbackOptions(q=True, minTime=True)
|
||||||
|
except RuntimeError:
|
||||||
|
cmds.warning("Failed to get offset or time range values.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not objects:
|
||||||
|
cmds.confirmDialog(title='Error', message='Select one or more objects to scale keys to start')
|
||||||
|
return
|
||||||
|
|
||||||
|
for i in range(len(objects)):
|
||||||
|
try:
|
||||||
|
firstKey = cmds.findKeyframe(objects[i], which='first')
|
||||||
|
if startOffset == 0:
|
||||||
|
cmds.warning("Offset from Start cannot be zero for {}".format(objects[i]))
|
||||||
|
continue
|
||||||
|
cmds.selectKey(objects[i], time=(firstKey, firstKey+startOffset))
|
||||||
|
cmds.scaleKey(timePivot=firstKey+startOffset, timeScale=(firstKey-startTime)/startOffset + 1)
|
||||||
|
if firstKey+startOffset <= startTime:
|
||||||
|
cmds.warning('The desired keyframe to scale from is in front of the beginning of the time range, which will cause scaling issues')
|
||||||
|
except (RuntimeError, ZeroDivisionError) as e:
|
||||||
|
cmds.warning("Failed to scale keys for {}: {}".format(objects[i], str(e)))
|
||||||
|
|
||||||
|
def scaleToEnd(*args):
|
||||||
|
"""Scale keys to end of time range"""
|
||||||
|
global endOffsetField
|
||||||
|
|
||||||
|
objects = cmds.ls(orderedSelection=True, long=False)
|
||||||
|
|
||||||
|
if not endOffsetField or not cmds.floatField(endOffsetField, exists=True):
|
||||||
|
cmds.warning("Key Offset Tool window not found. Please open the tool first.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
endOffset = cmds.floatField(endOffsetField, query=True, value=True)
|
||||||
|
endTime = cmds.playbackOptions(q=True, maxTime=True)
|
||||||
|
except RuntimeError:
|
||||||
|
cmds.warning("Failed to get offset or time range values.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not objects:
|
||||||
|
cmds.confirmDialog(title='Error', message='Select one or more objects to scale keys to end')
|
||||||
|
return
|
||||||
|
|
||||||
|
for i in range(len(objects)):
|
||||||
|
try:
|
||||||
|
lastKey = cmds.findKeyframe(objects[i], which='last')
|
||||||
|
if endOffset == 0:
|
||||||
|
cmds.warning("Offset from End cannot be zero for {}".format(objects[i]))
|
||||||
|
continue
|
||||||
|
cmds.selectKey(objects[i], time=(lastKey, lastKey-endOffset))
|
||||||
|
cmds.scaleKey(timePivot=lastKey-endOffset, timeScale=1-((lastKey-endTime)/endOffset))
|
||||||
|
if lastKey-endOffset >= endTime:
|
||||||
|
cmds.warning('The desired keyframe to scale from is behind the end of the time range, which will cause scaling issues')
|
||||||
|
except (RuntimeError, ZeroDivisionError) as e:
|
||||||
|
cmds.warning("Failed to scale keys for {}: {}".format(objects[i], str(e)))
|
||||||
|
|
||||||
|
|
||||||
|
def show():
|
||||||
|
"""Show the Key Offset Tool window"""
|
||||||
|
global keyOffsetField, startOffsetField, endOffsetField
|
||||||
|
|
||||||
|
# Delete existing window if it exists
|
||||||
|
if cmds.window("keyWin", exists=True):
|
||||||
|
cmds.deleteUI("keyWin")
|
||||||
|
|
||||||
|
# Create window
|
||||||
|
physWindow = cmds.window(
|
||||||
|
"keyWin",
|
||||||
|
title="Key Offset Tool",
|
||||||
|
resizeToFitChildren=True,
|
||||||
|
sizeable=False,
|
||||||
|
minimizeButton=False,
|
||||||
|
maximizeButton=False,
|
||||||
|
menuBar=False
|
||||||
|
)
|
||||||
|
cmds.window(physWindow, edit=True, h=100, w=100)
|
||||||
|
|
||||||
|
cmds.columnLayout(columnAttach=('both', 0), columnWidth=190)
|
||||||
|
|
||||||
|
cmds.text(label='Offset Keys:', height=30)
|
||||||
|
|
||||||
|
cmds.setParent('..')
|
||||||
|
cmds.rowLayout(numberOfColumns=2)
|
||||||
|
cmds.text(label='Keyframe Offset:', width=120)
|
||||||
|
keyOffsetField = cmds.floatField(value=0.5, precision=2)
|
||||||
|
|
||||||
|
cmds.setParent('..')
|
||||||
|
cmds.rowLayout(numberOfColumns=2, height=30)
|
||||||
|
cmds.button(label='Offset', width=75, command=lambda *args: offsetKeys(1))
|
||||||
|
cmds.button(label='RevertOffset', width=75, command=lambda *args: offsetKeys(0))
|
||||||
|
|
||||||
|
cmds.setParent('..')
|
||||||
|
cmds.text(label='Scale Keys to Time Range:', height=30)
|
||||||
|
|
||||||
|
cmds.setParent('..')
|
||||||
|
cmds.rowLayout(numberOfColumns=2)
|
||||||
|
cmds.text(label='Offset from Start:', width=120)
|
||||||
|
startOffsetField = cmds.floatField(value=10, precision=1)
|
||||||
|
|
||||||
|
cmds.setParent('..')
|
||||||
|
cmds.button(label='Scale To Start', width=75, command=scaleToStart)
|
||||||
|
|
||||||
|
cmds.setParent('..')
|
||||||
|
cmds.rowLayout(numberOfColumns=2)
|
||||||
|
cmds.text(label='Offset from End:', width=120)
|
||||||
|
endOffsetField = cmds.floatField(value=10, precision=1)
|
||||||
|
|
||||||
|
cmds.setParent('..')
|
||||||
|
cmds.button(label='Scale To End', width=75, command=scaleToEnd)
|
||||||
|
|
||||||
|
cmds.showWindow(physWindow)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
show()
|
||||||
@@ -214,5 +214,39 @@ global proc shelf_Nexus_Animation () {
|
|||||||
-commandRepeatable 1
|
-commandRepeatable 1
|
||||||
-flat 1
|
-flat 1
|
||||||
;
|
;
|
||||||
|
shelfButton
|
||||||
|
-enableCommandRepeat 1
|
||||||
|
-flexibleWidthType 3
|
||||||
|
-flexibleWidthValue 32
|
||||||
|
-enable 1
|
||||||
|
-width 35
|
||||||
|
-height 34
|
||||||
|
-manage 1
|
||||||
|
-visible 1
|
||||||
|
-preventOverride 0
|
||||||
|
-annotation "Key Offset Tool - Offset and scale keyframes for multiple objects"
|
||||||
|
-enableBackground 0
|
||||||
|
-backgroundColor 0 0 0
|
||||||
|
-highlightColor 0.321569 0.521569 0.65098
|
||||||
|
-align "center"
|
||||||
|
-label "Offset Keys"
|
||||||
|
-labelOffset 0
|
||||||
|
-rotation 0
|
||||||
|
-flipX 0
|
||||||
|
-flipY 0
|
||||||
|
-useAlpha 1
|
||||||
|
-font "plainLabelFont"
|
||||||
|
-overlayLabelColor 0.8 0.8 0.8
|
||||||
|
-overlayLabelBackColor 0 0 0 0.5
|
||||||
|
-image "offsetkeys.png"
|
||||||
|
-image1 "offsetkeys.png"
|
||||||
|
-style "iconOnly"
|
||||||
|
-marginWidth 0
|
||||||
|
-marginHeight 1
|
||||||
|
-command "import animation_tools.offsetkeys\nanimation_tools.offsetkeys.show()"
|
||||||
|
-sourceType "python"
|
||||||
|
-commandRepeatable 1
|
||||||
|
-flat 1
|
||||||
|
;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user