Image
-- **************************************************
-- Provide Moho with the name of this script object
-- **************************************************

ScriptName= "SS_CycleKeys"

-- **************************************************
-- Create interpolation cycle using selected keyframe/s
-- version:	01.00 MH12+ #520924
-- by Sam Cogheil (SimplSam)
-- **************************************************

--[[ ***** Licence & Warranty *****

    __NONE__  -- Use and Abuse freely

    Attribution appreciated

]]

--[[
    ***** SPECIAL THANKS to:
	*    Stan (and team) @ MOHO Scripting -- https://mohoscripting.com
	*    The friendly faces @ Lost Marble / Moho Forum -- https://www.lostmarble.com/forum
]]

-- **************************************************
-- General information about this script
-- **************************************************

SS_CycleKeys= {}

function SS_CycleKeys:Name()
	return "Cycle Keyframes"
end

function SS_CycleKeys:Version()
	return "1.00 #5209"
end

function SS_CycleKeys:Description()
	return "Cycle Keyframes"
end

function SS_CycleKeys:Creator()
	return "Sam Cogheil (SimplSam)"
end

function SS_CycleKeys:UILabel()
	return "Cycle Keyframes"
end

function SS_CycleKeys:IsEnabled(moho)
	return true
end

function SS_CycleKeys:IsRelevant(moho)
    return true
end

function SS_CycleKeys:ColorizeIcon()
    return true
end

SS_CycleKeys.isDebug = false -- true | false

-- **************************************************
-- The guts of this script
-- **************************************************

function SS_CycleKeys:Run(moho)

    local mohodoc= moho.document
    local thisFrame= moho.drawingLayerFrame
    local interp= MOHO.InterpSetting:new_local()
    local timelineLayers= {}
    local cycleTo, cycleFrom

    --= Gather Timeline visible layers
	function GetTimelineLayers(_layer)
        if (_layer:IsGroupType()) then
            local _groupLayer= moho:LayerAsGroup(_layer)
            if (_layer:SecondarySelection() or _layer:IsShownOnTimeline()) then
                table.insert(timelineLayers, _layer)
            end
            for iLL= 0, _groupLayer:CountLayers() -1 do -- Local Layers
                GetTimelineLayers(_groupLayer:Layer(iLL))
            end
        else
            if (_layer:SecondarySelection() or _layer:IsShownOnTimeline()) then
                table.insert(timelineLayers, _layer)
            end
        end
	end

    local chanInfo= MOHO.MohoLayerChannel:new_local()
    local channels= {}
    local chanCount, allSelectedCount = 0, 0

    for iGL= 0, mohodoc:CountLayers() -1 do  -- GlobalLayers
        GetTimelineLayers(mohodoc:Layer(iGL))
    end

    for _, layer in ipairs(timelineLayers) do
        for i= 0, layer:CountChannels() -1 do
            layer:GetChannelInfo(i, chanInfo)
            if (chanInfo.subChannelCount > 0) and (chanInfo.channelID ~= CHANNEL_LAYER_ALL) and (not chanInfo.selectionBased) then
                for j= 0, chanInfo.subChannelCount -1 do
                    local subchan= layer:Channel(i, j, mohodoc)
                    local animChannel, animDimension, canSplitDims
                    local numDims= 1
                    -- chanInfo.separableDimensions ?
                    if subchan:ChannelType()     == MOHO.CHANNEL_VAL    then animChannel= moho:ChannelAsAnimVal(subchan)
                    elseif subchan:ChannelType() == MOHO.CHANNEL_VEC2   then animChannel= moho:ChannelAsAnimVec2(subchan)   canSplitDims= true   numDims= 2
                    elseif subchan:ChannelType() == MOHO.CHANNEL_VEC3   then animChannel= moho:ChannelAsAnimVec3(subchan)   canSplitDims= true   numDims= 3
                    elseif subchan:ChannelType() == MOHO.CHANNEL_BOOL   then animChannel= moho:ChannelAsAnimBool(subchan)
                    elseif subchan:ChannelType() == MOHO.CHANNEL_COLOR  then animChannel= moho:ChannelAsAnimColor(subchan)
                    elseif subchan:ChannelType() == MOHO.CHANNEL_STRING then animChannel= moho:ChannelAsAnimString(subchan)
                    end

                    local whenFirst, whenLast = -1, -1
                    local selectedCount= 0
                    if animChannel then
                        if (canSplitDims and animChannel:AreDimensionsSplit()) then
                            for dim= 0, numDims -1 do
                                animDimension= animChannel:DimensionChannel(dim)
                                selectedCount= 0
                                whenFirst, whenLast = -1, -1
                                for iKey= 0, animDimension:CountKeys() -1 do
                                    if animDimension:IsKeySelectedByID(iKey) then
                                        if (whenFirst == -1) then
                                            whenFirst= animDimension:GetKeyWhen(iKey)
                                        else
                                            whenLast= animDimension:GetKeyWhen(iKey)
                                        end
                                        selectedCount = selectedCount +1
                                    end
                                end
                                if (selectedCount > 0) then
                                    chanCount = chanCount +1
                                    table.insert(channels, {layer= layer, channel= animDimension, chanIdx= i, whenFirst= whenFirst, whenLast= whenLast, selectedCount= selectedCount})
                                    allSelectedCount= allSelectedCount + selectedCount
                                end
                            end
                        elseif (animChannel:CountKeys() > 1) then
                            for iKey= 0, animChannel:CountKeys() -1 do
                                if animChannel:IsKeySelectedByID(iKey) then
                                    if (whenFirst == -1) then
                                        whenFirst= animChannel:GetKeyWhen(iKey)
                                    else
                                        whenLast= animChannel:GetKeyWhen(iKey)
                                    end
                                    selectedCount = selectedCount +1
                                end
                            end
                            if (selectedCount > 0) then
                                chanCount = chanCount +1
                                table.insert(channels, {layer= layer, channel= animChannel, chanIdx= i, whenFirst= whenFirst, whenLast= whenLast, selectedCount= selectedCount})
                                allSelectedCount = allSelectedCount + selectedCount
                            end
                        end
                    end
                end
            end
        end
    end

    if (self.isDebug) then print(("\ndBug:: CycleKeys #Selected: %s / Chans: %s"):format(allSelectedCount, chanCount)) end

    if (chanCount > 0) then
        mohodoc:PrepUndo()
        mohodoc:SetDirty()
    end

    for _, keyset in ipairs(channels) do
        local kfTags
        local animChannel= keyset.channel

        if (keyset.whenLast ~= -1) then  -- 2+ kfs
            cycleTo= keyset.whenFirst
            cycleFrom= keyset.whenLast
        else -- 1 kf
            cycleFrom= keyset.whenFirst
            if (thisFrame > cycleFrom +1) then
                cycleTo= cycleFrom
                cycleFrom= thisFrame
            elseif (thisFrame < cycleFrom) then
                cycleTo= thisFrame -1
            else
                break -- skip (kfs too close)
            end
        end

        if (cycleTo < cycleFrom -1) then
            local layer= keyset.layer
            layer:GetChannelInfo(keyset.chanIdx, chanInfo)
            if (self.isDebug) then print(("dBug:: Chan [%s/%d] - Cycle from: %d to: %d  #%s"):format(chanInfo.name:Buffer(), chanInfo.channelID, cycleFrom, cycleTo, animChannel:ChannelType())) end

            --= add To key - if missing at or before To
            if (not animChannel:HasKey(cycleTo)) and (not animChannel:HasKey(cycleTo +1)) then
                animChannel:AddKey(cycleTo)
            end

            --= add From key (copy To key) - if missing
            if (not animChannel:HasKey(cycleFrom)) then
                animChannel:AddKey(cycleFrom)
                animChannel:SetValue(cycleFrom, animChannel:GetValue(cycleTo))
                layer:UpdateCurFrame()
                animChannel:GetKeyInterp(cycleTo, interp)
                kfTags = interp.tags
            end

            animChannel:GetKeyInterp(cycleFrom, interp)
            interp.interpMode= MOHO.INTERP_CYCLE
            interp.val1= cycleFrom -cycleTo -1 --rel
            interp.val2= -1 --abs
            interp.tags = kfTags and kfTags or interp.tags
            animChannel:SetKeyInterp(cycleFrom, interp)
        end
    end

    moho:UpdateUI()
end

Icon
SS - Cycle Keys
Listed

Script type: Button/Menu

Uploaded: Oct 01 2022, 00:33

Script Version: 1.00 #5209

Quickly create cycled keyframes in the timeline - using existing keyframes and optionally the current frame position
A convenience tool which allows you to quickly create cycled keyframes in the timeline. It is similar to the existing ‘change interpolation to Cycle’ function, but allows for quicker creation when cycling between 2 existing keyframes - or cycling to/from a single keyframe.

The tool is run from a simple click of the tool button and has 3 modes of operation as follows:

1. Cycle Around

Creates a cycle between 2 selected keyframes - when 2 or more keyframes have been selected on a single timeline channel. If more than 2 keyframes are selected - the first and last selected keyframes will be used to create the cycle.

    Image

2. Cycle To

Creates a cycle between the current frame position and a single selected keyframe - when the current frame position is greater than the keyframe position. A new keyframe will be created at the current frame position with the same value of the selected keyframe - unless a keyframe already exists at the current frame position.

    Image

3. Cycle From

Creates a cycle between a single selected keyframe and the current frame position - when the current frame position is less than the keyframe position. A new keyframe will be created to the left (-1) of the current frame position - unless one already exists there (-1) or at the current frame position.

    Image


Video Demo

  

So … Why do you need the Cycle Keys tool?

Speed and convenience. The addon can enhance your cycle creation workflow. Particularly handy when creating cycles that don’t begin at the start of the timeline.

Features:
- Works across all visible timeline keyframes, and multiple channel cycles can be created in a single click
- When multiple channel keyframes are selected - each channel is processed individually (i.e. Cycle Around/To/From)
- Will overwrite existing cycle-from (origin) keyframe cycle-back distance setting
- Preserves cycle-to (destination) keyframes/values
- Cycles are created Relative

To use:
- Select one or more keyframes, (optionally adjust current frame), and Run the tool from the Tools palette

Additional Notes:
- version: 01.00 #520930 MH12+
- release: 1.00
- Compatible with MH12+

Special Thanks to:
- Stan (and the team): MOHO Scripting – https://mohoscripting.com
- The friendly faces @ Lost Marble Moho forum – https://www.lostmarble.com/forum/

Installation Options:

This script, and all other scripts on this site are distributed as free software under the GNU General Public License 3.0 or later.
Downloads count: 1234