Mirror/Reverse Keyframes SCRIPT
Posted: Tue Aug 05, 2025 2:11 pm
Update: Version 1.0
https://mohoscripts.com/script/mp_ReverseKeyframes
Functions:
Mirrors selected keyframes at playhead position. Reverse Keyframes if playhead coincides with first frame selected. Should work on all layer types and on all channels. Works with multiple layers selected or timeline visibility.
Bugs: Does not keep interpolation method
***
Hi everyone! I haven't been active on the forum for many years, mainly because I've had to use After Effects a lot for work. Recently, I was assigned a long project with animated characters, so I had to quickly upgrade (from 12 to 14) to Moho. I'm very happy with the progress that has been made, and the new features are incredible. I also immediately tested several new scripts (the Mult Rush ones are exceptional).
However, I am used to a very fast workflow and therefore I am always very demanding with the timeline (if you remember the timeline navigator that Stan made for me) to the point of trying to arrange some scripts with ChatGPT. One feature I have always missed is the ability to reverse keyframes, and I haven't found anything new that could facilitate this task. So, after carefully combing through everything, I found a very old Crashcore script with a similar function: reverse layer keyframes. I fed it to ChatGPT and made it work for Moho 14, but I found it extremely cumbersome. So I extracted a menu script that mirrors the selected keyframes to the current cursor position. It... works, but obviously I don't know how to write scripts, so its function is approximate. For example, there are channels on which it doesn't work; it only works on the selected layer and is unable to reverse the keyframes to their original position.
It would be great if someone could improve it in some way. Even in its current state, it's a huge help to me, I chose a menu script to make it as automatic as possible and I actually use it as a toolscript to which I assign a shortcut. This is the code:
https://mohoscripts.com/script/mp_ReverseKeyframes
Functions:
Mirrors selected keyframes at playhead position. Reverse Keyframes if playhead coincides with first frame selected. Should work on all layer types and on all channels. Works with multiple layers selected or timeline visibility.
Bugs: Does not keep interpolation method
***
Hi everyone! I haven't been active on the forum for many years, mainly because I've had to use After Effects a lot for work. Recently, I was assigned a long project with animated characters, so I had to quickly upgrade (from 12 to 14) to Moho. I'm very happy with the progress that has been made, and the new features are incredible. I also immediately tested several new scripts (the Mult Rush ones are exceptional).
However, I am used to a very fast workflow and therefore I am always very demanding with the timeline (if you remember the timeline navigator that Stan made for me) to the point of trying to arrange some scripts with ChatGPT. One feature I have always missed is the ability to reverse keyframes, and I haven't found anything new that could facilitate this task. So, after carefully combing through everything, I found a very old Crashcore script with a similar function: reverse layer keyframes. I fed it to ChatGPT and made it work for Moho 14, but I found it extremely cumbersome. So I extracted a menu script that mirrors the selected keyframes to the current cursor position. It... works, but obviously I don't know how to write scripts, so its function is approximate. For example, there are channels on which it doesn't work; it only works on the selected layer and is unable to reverse the keyframes to their original position.
It would be great if someone could improve it in some way. Even in its current state, it's a huge help to me, I chose a menu script to make it as automatic as possible and I actually use it as a toolscript to which I assign a shortcut. This is the code:
Code: Select all
-- cc_reverse_layer_keyframes.lua
-- Adds Point Curvature channel support (curvature/weight/offset)
ScriptName = "CC_REVERSE_SELECTED_KEYFRAMES_FULL"
CC_REVERSE_SELECTED_KEYFRAMES_FULL = {}
function CC_REVERSE_SELECTED_KEYFRAMES_FULL:Name()
return "CC: Reverse Selected Keyframes (Full Auto)"
end
function CC_REVERSE_SELECTED_KEYFRAMES_FULL:Version()
return "1.3"
end
function CC_REVERSE_SELECTED_KEYFRAMES_FULL:Description()
return "Reverse selected keyframes (transform, points, bones, switch, curvature) automatically at current cursor position."
end
function CC_REVERSE_SELECTED_KEYFRAMES_FULL:Creator()
return "Based on Crashcore | Expanded by OpenAI"
end
function CC_REVERSE_SELECTED_KEYFRAMES_FULL:UILabel()
return "Reverse Selected Keyframes"
end
function CC_REVERSE_SELECTED_KEYFRAMES_FULL:IsEnabled(moho)
return moho.layer ~= nil
end
local function AddIfValid(vecList, vec)
if vec ~= nil then
table.insert(vecList, vec)
end
end
-- Collect all relevant anim channels (including curvature)
local function GetAllAnimVecs(moho, layer)
local vecs = {}
-- Layer standard transforms / props
AddIfValid(vecs, layer.fTranslation)
AddIfValid(vecs, layer.fRotationX)
AddIfValid(vecs, layer.fRotationY)
AddIfValid(vecs, layer.fRotationZ)
AddIfValid(vecs, layer.fScale)
AddIfValid(vecs, layer.fShear)
AddIfValid(vecs, layer.fFlipH)
AddIfValid(vecs, layer.fFlipV)
AddIfValid(vecs, layer.fVisibility)
AddIfValid(vecs, layer.fAlpha)
AddIfValid(vecs, layer.fBlur)
-- Vector layer: points & curvature
local mesh = moho:Mesh()
if mesh then
-- Point positions
for i = 0, mesh:CountPoints() - 1 do
local pt = mesh:Point(i)
AddIfValid(vecs, pt.fAnimPos)
end
-- Add the composite Point Curvature channel (covers curvature/weight/offset subchannels)
if layer.Channel then
local curveChan = layer:Channel(MOHO.CHANNEL_CURVE) -- selection-based multi-subchannel
AddIfValid(vecs, curveChan)
end
for c = 0, mesh:CountCurves() - 1 do
local curve = mesh:Curve(c)
if curve and curve.CountPoints then
for pid = 0, curve:CountPoints() - 1 do
-- M_Curve:Curvature(ptID) -> AnimVal
AddIfValid(vecs, curve:Curvature(pid))
end
end
end
end
-- Bone layer
if layer:IsBoneType() then
local skel = moho:Skeleton()
if skel then
for i = 0, skel:CountBones() - 1 do
local bone = skel:Bone(i)
AddIfValid(vecs, bone.fAnimPos)
AddIfValid(vecs, bone.fAnimAngle)
AddIfValid(vecs, bone.fAnimScale)
AddIfValid(vecs, bone.fIKLock)
AddIfValid(vecs, bone.fIKParentTarget)
AddIfValid(vecs, bone.fIKGlobalAngle)
end
end
end
-- Switch layer
if layer:LayerType() == MOHO.LT_SWITCH then
local switch = moho:LayerAsSwitch(layer)
AddIfValid(vecs, switch:SwitchValues())
end
return vecs
end
-- Find the range of selected keyframes across all channels
local function GetSelectedKeyframeRange(vecs, moho)
local first, last
local doc = moho.document
local s, e = doc:StartFrame(), doc:EndFrame()
for _, vec in ipairs(vecs) do
for f = s, e do
if vec:HasKey(f) and vec:IsKeySelected(f) then
if not first or f < first then first = f end
if not last or f > last then last = f end
end
end
end
return first, last
end
-- Mirror the selected keys into a reversed copy starting at pasteStart
local function ReverseSelectedKeyframes(vecs, first, last, pasteStart)
local duration = last - first + 1
for _, vec in ipairs(vecs) do
for f = first, last do
if vec:HasKey(f) and vec:IsKeySelected(f) then
local offset = f - first
local destFrame = pasteStart + (duration - offset - 1)
vec:AddKey(destFrame)
vec:SetValue(destFrame, vec:GetValue(f))
end
end
end
end
function CC_REVERSE_SELECTED_KEYFRAMES_FULL:Run(moho)
local layer = moho.layer
if not layer then return end
local vecs = GetAllAnimVecs(moho, layer)
local firstKey, lastKey = GetSelectedKeyframeRange(vecs, moho)
if not firstKey or not lastKey or firstKey == lastKey then return end
local pasteStart = moho.frame
moho.document:PrepUndo(layer)
ReverseSelectedKeyframes(vecs, firstKey, lastKey, pasteStart)
moho.document:SetDirty()
end