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