Page 1 of 1

Mirror/Reverse Keyframes SCRIPT

Posted: Tue Aug 05, 2025 2:11 pm
by Shizukuishi
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:

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


Re: Mirror Keyframes Script

Posted: Fri Aug 08, 2025 4:31 pm
by banegame
Hello there and thank you so much for this script. I was looking for the same thing within the last week and I ended up working on my own. However, after running your menu script and loving it, I've taken the time to modify it with some of what it's missing (and what you asked for). Here's a link to the new "tool" (no longer a menu script) and icon.

Changes:
1. Converted to tool
2. Added Icon
3. Added dialogue box with an option for reversing keyframe "values" and an option for reversing keyframes in place (automatically deletes old keyframes)


When the dialogue box opens, if you simply select "ok" it will just reverse and paste the keyframes at the playhead, same as before. But if you decide you need to reverse values, you can do that too by selecting the option. If you want to reverse, reverse value in the place of the selected keyframes, you can select reverse in place and the original keyframes will be deleted and replaced with the new keyframes (based on you selection).

It took way too long to get the code working correctly so if you find anything that is wonky or more improvements that are needed, maybe we can refine it some more.


UPDATE: It looks like the script isn't applying to point curve keyframes. working on a fix...

Re: Mirror Keyframes Script

Posted: Sat Aug 09, 2025 12:19 am
by Shizukuishi
Hello and thanks for taking the time to work on it! Looks it is going to be awsome, iI hope you will find a fix soon so that i can test it!

PS. I still think it is a Crashcore script, i really can't call it mine... :roll:

Re: Mirror Keyframes Script

Posted: Sat Aug 09, 2025 10:53 am
by banegame
Oh indeed. It's a CC script. Also, my head hurts. I spent like 6 hours yesterday and today working on it and I cannot get it to function on anything other than the "point motion" channel. it's really frustrating

Re: Mirror Keyframes Script

Posted: Sat Aug 09, 2025 1:56 pm
by hayasidist
Whilst not of direct use (i.e. you're not trying curvature <-> Bezier) this might help explain the curvature sub-channels. https://mohoscripting.com/snippets/moho ... conversion.

if you upload what you had done, someone else may be able to help... (no link in your first post!)

oh BTW, which Lua version are you using?

Re: Mirror Keyframes Script

Posted: Sun Aug 10, 2025 8:52 am
by Shizukuishi
hayasidist wrote: Sat Aug 09, 2025 1:56 pm Whilst not of direct use (i.e. you're not trying curvature <-> Bezier) this might help explain the curvature sub-channels. https://mohoscripting.com/snippets/moho ... conversion.

if you upload what you had done, someone else may be able to help... (no link in your first post!)

oh BTW, which Lua version are you using?
Thanks Hayasidist! While waiting for updates, with this information I was able to add curvature channels to the current script!

Re: Mirror Keyframes Script

Posted: Tue Aug 12, 2025 9:05 am
by banegame
Here is the script. The "reverse values" function is not currently working, and the way "reverse in place (and delete)" works is by moving the playhead to frame 0. I've been working on a solution for both issues, but feel free to look into them.

This is a overhauled keyframe reversal script with a dialogue box and checkbox options that work independently and in conjunction with each other. I haven't fully tested with bones/switch layers and I'm certain it won't work with bitmaps. Also feel free to test these things.

https://drive.google.com/file/d/1XQuZAx ... drive_link
https://drive.google.com/file/d/1JFueCK ... drive_link

I'm also using Notepad++ v.8.8.3

Re: Mirror Keyframes Script

Posted: Tue Aug 12, 2025 11:52 am
by Shizukuishi
Hi Banegane, and thanks for this version. I hope your headache has gone away in the meantime! I tried the script, but as you predicted, it doesn't work on bone or switch layers at the moment. I can also confirm the playhead behavior. Honestly, I didn't understand what reverse value should do, even if it isn't working.
I'll try to dig around a bit.

Re: Mirror Keyframes Script

Posted: Tue Aug 12, 2025 12:53 pm
by lucasfranca
Interesting script! Following here for updates and testing the current one. Thanks!

Re: Mirror/Reverse Keyframes Script

Posted: Tue Aug 12, 2025 3:36 pm
by banegame
I'm going to work on it a bit tonight. Basically, reversing the keyframes simply reverses the selected keyframe, in time, at the playhead. For example, frames 10 and 20 are selected and the playhead is moved to frame 30. At frame 30, frame 20 is pasted and frame 10 is pasted at frame 40. But you know this much already. For values, you are reversing Vector2 (2D) Values like like layer position, control point coordinates, etc. as well as Vector3 (3D) Values like 3D layer position, rotation, or bone transformations.

Right now the reverse in place and delete requires the playhead to return to 0 to negate some weird offset caused by the position curvature coding. It was a pain in the butt to figure that part out and maybe the method you're using in your script works better? Maybe you could modify the FMP script to use your curvature code and it'll resolve the playhead reset? I don't know. At any rate, you all have the most current build and I will update it once there's a new stable version.

Re: Mirror/Reverse Keyframes Script

Posted: Tue Aug 12, 2025 4:13 pm
by Shizukuishi
I had time to work on it and your version unlocked what I was initially searching for. Thanks to your improvement I've been able to make my own version that totally suits my needs.

I always like to keep things as simple as possible. In this new version I removed the dialog and i didn't consider the reverse value option (thanks for the explanation, I thought about it but wasn't sure), instead it auto detects the action to perform. If playhead coincide with first keyframe selected it reverses in place (playhead no longers moves to frame 0), if not mirrors keyframe at playhead position. Now works on all channels (I hope) and all layer types. It works if multiple layer are selected or timeline visibility is checked.

If you'd like to add more to it and explore new function would be great!

This is the link for the new version with credits for all us. I also changed the icon to fit more the Moho UI style:

https://mohoscripts.com/script/mp_ReverseKeyframes

Re: Mirror/Reverse Keyframes Script

Posted: Tue Aug 12, 2025 7:50 pm
by banegame
See, that's what I'm talking about, community! You have indeed taken it from a cumbersome boobittybop, to a sleek one-click!

Re: Mirror/Reverse Keyframes Script

Posted: Fri Aug 15, 2025 7:22 am
by lucasfranca
I tested both and are very good. Now I'm in doubt with which one I will stay with! haha

Thanks to all of you.