-- WARNING: This script requires AE_Utilities.lua of version 1.11 or later! -- ************************************************** -- Provide Moho with the name of this script object -- ************************************************** ScriptName = "AE_KeyTools" -- ************************************************** -- General information about this script -- ************************************************** AE_KeyTools = {} function AE_KeyTools:Name() return self:Localize("Name") end function AE_KeyTools:Version() return "2.48" end function AE_KeyTools:Description() return self:Localize("Description") end function AE_KeyTools:Creator() return "A.Evseeva with a little help from Stan" end function AE_KeyTools:UILabel() return self:Localize("UILabel") end function AE_KeyTools:LoadPrefs(prefs) self.applyToChildLayers = prefs:GetBool("AE_KeyTools.applyToChildLayers", true) self.timelinevisibleOnly = prefs:GetBool("AE_KeyTools.timelinevisibleOnly", false) self.applyToRefs = prefs:GetBool("AE_KeyTools.applyToRefs", false) self.precalcCycles = prefs:GetBool("AE_KeyTools.precalcCycles", true) self.ignoreStringChannels = prefs:GetBool("AE_KeyTools.ignoreStringChannels", false) self.ignoreHiddenBones = prefs:GetBool("AE_KeyTools.ignoreHiddenBones", true) self.trackAnimOption = prefs:GetString("AE_KeyTools.trackAnimOption", "active") self.ifEqual = prefs:GetBool("AE_KeyTools.ifEqual", false) self.selectedBonesOnly = prefs:GetBool("AE_KeyTools.selectedBonesOnly", false) self.useStepIntervals = prefs:GetBool("AE_KeyTools.useStepIntervals", false) end function AE_KeyTools:SavePrefs(prefs) prefs:SetBool("AE_KeyTools.applyToChildLayers", self.applyToChildLayers) prefs:SetBool("AE_KeyTools.timelinevisibleOnly", self.timelinevisibleOnly) prefs:SetBool("AE_KeyTools.applyToRefs", self.applyToRefs) prefs:SetBool("AE_KeyTools.precalcCycles", self.precalcCycles) prefs:SetBool("AE_KeyTools.ignoreStringChannels", self.ignoreStringChannels) prefs:SetBool("AE_KeyTools.ignoreHiddenBones", self.ignoreHiddenBones) prefs:SetString("AE_KeyTools.trackAnimOption", self.trackAnimOption) prefs:SetBool("AE_KeyTools.ifEqual", self.ifEqual) prefs:SetBool("AE_KeyTools.selectedBonesOnly", self.selectedBonesOnly) prefs:SetBool("AE_KeyTools.useStepIntervals", self.useStepIntervals) end function AE_KeyTools:ResetPrefs() self.applyToChildLayers = true self.timelinevisibleOnly = false self.applyToRefs = false self.precalcCycles = true self.ignoreStringChannels = false self.ignoreHiddenBones = true self.trackAnimOption = "active" self.ifEqual = false self.selectedBonesOnly = false self.useStepIntervals = false end -- ************************************************** -- Is Relevant / Is Enabled -- ************************************************** function AE_KeyTools:IsRelevant(moho) return true end function AE_KeyTools:IsEnabled(moho) return true end function AE_KeyTools:OnMouseDown(moho, mouseEvent) end -- ************************************************** -- Tool level variables to bind UI -- ************************************************** AE_KeyTools.applyToChildLayers = true AE_KeyTools.timelinevisibleOnly = false AE_KeyTools.applyToRefs = false AE_KeyTools.precalcCycles = true AE_KeyTools.ignoreStringChannels = false AE_KeyTools.ignoreHiddenBones = true AE_KeyTools.fromFrame = 0 AE_KeyTools.toFrame = 0 AE_KeyTools.sourceFilePath = nil AE_KeyTools.sourceRootLayerUUID = nil AE_KeyTools.sourceRootLayerName = nil AE_KeyTools.targetFilePath = nil AE_KeyTools.storedValues = {} AE_KeyTools.keyInterp = nil AE_KeyTools.selectAllKeys = false AE_KeyTools.trackAnimOption = "active" AE_KeyTools.ifEqual = false AE_KeyTools.selectedBonesOnly = false AE_KeyTools.useStepIntervals = false AE_KeyTools.regtab = { ["("] = "%(", [")"] = "%)", ["."] = "%.", ["%"] = "%%", ["+"] = "%+", ["-"] = "%-", ["*"] = "%*", ["?"] = "%?", ["["] = "%[", ["]"] = "%]", ["^"] = "%^", ["$"] = "%$", } -- ************************************************** -- Tool options - create and respond to tool's UI -- ************************************************** AE_KeyTools.COPY = MOHO.MSG_BASE AE_KeyTools.PASTE = MOHO.MSG_BASE + 1 AE_KeyTools.ADD = MOHO.MSG_BASE + 2 AE_KeyTools.APPLY_TO_REFS = MOHO.MSG_BASE + 14 AE_KeyTools.APPLY_TO_CHILD_LAYERS = MOHO.MSG_BASE + 3 AE_KeyTools.TIMELINEVISIBLE_ONLY = MOHO.MSG_BASE + 27 AE_KeyTools.PRECALC_CYCLES = MOHO.MSG_BASE + 4 AE_KeyTools.IGNORE_STRING_CHANNELS = MOHO.MSG_BASE + 5 AE_KeyTools.IGNORE_HIDDEN_BONES = MOHO.MSG_BASE + 16 AE_KeyTools.CLEANUP = MOHO.MSG_BASE + 6 AE_KeyTools.SHOWANIM = MOHO.MSG_BASE + 7 AE_KeyTools.HIDEANIM = MOHO.MSG_BASE + 8 AE_KeyTools.SELECTKEY = MOHO.MSG_BASE + 9 AE_KeyTools.DESELECTKEYS = MOHO.MSG_BASE + 18 AE_KeyTools.PASTEKEYS = MOHO.MSG_BASE + 10 AE_KeyTools.COPYPASTEINTERP = MOHO.MSG_BASE + 15 AE_KeyTools.SHOWANIM1 = MOHO.MSG_BASE + 11 AE_KeyTools.NUDGERIGHT = MOHO.MSG_BASE + 12 AE_KeyTools.NUDGERIGHTx10 = MOHO.MSG_BASE + 25 AE_KeyTools.NUDGELEFT = MOHO.MSG_BASE + 13 AE_KeyTools.NUDGELEFTx10 = MOHO.MSG_BASE + 26 AE_KeyTools.ACTIVEANIM = MOHO.MSG_BASE + 19 AE_KeyTools.LONGANIM = MOHO.MSG_BASE + 20 AE_KeyTools.ANYANIM = MOHO.MSG_BASE + 21 AE_KeyTools.EQUALCHECK = MOHO.MSG_BASE + 22 AE_KeyTools.SELECTED_BONES_ONLY = MOHO.MSG_BASE + 23 AE_KeyTools.USE_STEP_INTERVALS = MOHO.MSG_BASE + 24 -- constants to make menu AE_KeyTools.PREVIOUS_KEY = MOHO.MSG_BASE + 100 AE_KeyTools.NEXT_KEY = MOHO.MSG_BASE + 101 AE_KeyTools.CURRENT_VALUES = MOHO.MSG_BASE + 102 AE_KeyTools.ZEROFRAME_VALUES = MOHO.MSG_BASE + 103 AE_KeyTools.PREVIOUSMARKER_VALUES = MOHO.MSG_BASE + 104 AE_KeyTools.getFromKey = AE_KeyTools.PREVIOUS_KEY --nudgearea menu AE_KeyTools.NUDGEAREA_TOTAL = MOHO.MSG_BASE + 30 AE_KeyTools.NUDGEAREA_VISIBLE = MOHO.MSG_BASE + 31 AE_KeyTools.NUDGEAREA_SELECTEDKEYS = MOHO.MSG_BASE + 32 AE_KeyTools.nudgeArea = AE_KeyTools.NUDGEAREA_TOTAL --selectkeys menu AE_KeyTools.SELECTKEYS_FRAME = MOHO.MSG_BASE + 40 AE_KeyTools.SELECTKEYS_AREA = MOHO.MSG_BASE + 41 AE_KeyTools.SELECTKEYS_TOTAL = MOHO.MSG_BASE + 42 AE_KeyTools.selectKeys = AE_KeyTools.SELECTKEYS_FRAME AE_KeyTools.SELECTKEYS_COLORED = MOHO.MSG_BASE + 17 function AE_KeyTools:DoLayout(moho, layout) self.copy = LM.GUI.Button(self:Localize("COPY"), self.COPY) layout:AddChild(self.copy) self.copy:SetToolTip(self:Localize("CopyTooltip")) layout:AddPadding(-15) self.paste = LM.GUI.Button(self:Localize("PASTE"), self.PASTE) layout:AddChild(self.paste) self.paste:SetToolTip(self:Localize("PasteTooltip")) self.fromFrameField = LM.GUI.DynamicText(self:Localize("From") .. "000") layout:AddChild(self.fromFrameField) self.copypasteInterpButton = LM.GUI.Button("C", self.COPYPASTEINTERP) layout:AddChild(self.copypasteInterpButton) self.copypasteInterpButton:SetToolTip(self:Localize("CopyPasteInterpTooltip")) self.pasteKeysButton = LM.GUI.ImageButton("ScriptResources/align_layers", self.Localize("PasteKeysTooltip"), false, self.PASTEKEYS, true) layout:AddChild(self.pasteKeysButton) self.pasteKeysButton:SetToolTip(self:Localize("PasteKeysTooltip")) self.setKey = LM.GUI.Button(self:Localize("ADD"), self.ADD) layout:AddChild(self.setKey) self.setKey:SetToolTip(self:Localize("AddTooltip")) self.getFromKeyMenu = LM.GUI.Menu("") self.getFromKeyMenu_popup = LM.GUI.PopupMenu(110, true) self.getFromKeyMenu_popup:SetMenu(self.getFromKeyMenu) self.getFromKeyMenu:AddItem(self:Localize("PreviousKey"), 0, self.PREVIOUS_KEY) self.getFromKeyMenu:AddItem(self:Localize("NextKey"), 0, self.NEXT_KEY) self.getFromKeyMenu:AddItem(self:Localize("CurrentValue"), 0, self.CURRENT_VALUES) self.getFromKeyMenu:AddItem(self:Localize("ZeroFrame"), 0, self.ZEROFRAME_VALUES) self.getFromKeyMenu:AddItem(self:Localize("PreviousMarker"), 0, self.PREVIOUSMARKER_VALUES) layout:AddChild(self.getFromKeyMenu_popup) self.getFromKeyMenu_popup:SetToolTip(self:Localize("MenuTooltip")) self.trackAnimMenu = LM.GUI.Menu("") self.trackAnimMenu:AddItem("Active animation only", 0, self.ACTIVEANIM) self.trackAnimMenu:AddItem("Any animation after frame 1", 0, self.LONGANIM) self.trackAnimMenu:AddItem("Any animation after frame 0", 0, self.ANYANIM) self.trackAnimPopup = LM.GUI.ImagePopupMenu("ScriptResources/ae_trackanimoptions", true, true) self.trackAnimPopup:SetMenu(self.trackAnimMenu) layout:AddChild(self.trackAnimPopup) self.equalCheck = LM.GUI.ImageButton("ScriptResources/ae_equal", "Set key even if value does not change", true, self.EQUALCHECK, true) layout:AddChild(self.equalCheck) self.includingsMenu = LM.GUI.Menu("Incl...") self.includingsMenu_popup = LM.GUI.PopupMenu(60, false) self.includingsMenu_popup:SetMenu(self.includingsMenu) self.includingsMenu:AddItem(self:Localize("ApplyToChildLayers"), 0, self.APPLY_TO_CHILD_LAYERS) self.includingsMenu:AddItem("Timeline-visible only", 0, self.TIMELINEVISIBLE_ONLY) self.includingsMenu:AddItem(self:Localize("ApplyToRefs"), 0, self.APPLY_TO_REFS) self.includingsMenu:AddItem(self:Localize("PrecalcCycles"), 0, self.PRECALC_CYCLES) self.includingsMenu:AddItem(self:Localize("IgnoreStringChannels"), 0, self.IGNORE_STRING_CHANNELS) self.includingsMenu:AddItem(self:Localize("IgnoreHiddenBones"), 0, self.IGNORE_HIDDEN_BONES) self.includingsMenu:AddItem("Selected bones only", 0, self.SELECTED_BONES_ONLY) self.includingsMenu:AddItem("Use step intervals", 0, self.USE_STEP_INTERVALS) layout:AddChild(self.includingsMenu_popup) self.includingsMenu_popup:SetToolTip(self:Localize("IncludingsMenuTooltip")) self.cleanButton = LM.GUI.Button(self:Localize("CLEANUP"), self.CLEANUP) layout:AddChild(self.cleanButton) self.cleanButton:SetToolTip(self:Localize("CleanupTooltip")) self.nudgeLeftButton = LM.GUI.ImageButton("curs_hresize_left", self:Localize("NudgeLeftTooltip"), false, self.NUDGELEFT, true) layout:AddChild(self.nudgeLeftButton) self.nudgeLeftButton:SetAlternateMessage(self.NUDGELEFTx10) self.nudgeLeftButton:SetToolTip(self:Localize("NudgeLeftTooltip")) layout:AddPadding(-15) self.nudgeRightButton = LM.GUI.ImageButton("curs_hresize_right", self:Localize("NudgeRightTooltip"), false, self.NUDGERIGHT, true) layout:AddChild(self.nudgeRightButton) self.nudgeRightButton:SetAlternateMessage(self.NUDGERIGHTx10) self.nudgeRightButton:SetToolTip(self:Localize("NudgeRightTooltip")) layout:AddPadding(-13) self.nudgeAreaMenu = LM.GUI.Menu("") self.nudgeAreaMenu_popup = LM.GUI.PopupMenu(70, true) self.nudgeAreaMenu_popup:SetMenu(self.nudgeAreaMenu) self.nudgeAreaMenu:AddItem(self:Localize("total"), 0, self.NUDGEAREA_TOTAL) self.nudgeAreaMenu:AddItem(self:Localize("visible"), 0, self.NUDGEAREA_VISIBLE) self.nudgeAreaMenu:AddItem(self:Localize("selected"), 0, self.NUDGEAREA_SELECTEDKEYS) layout:AddChild(self.nudgeAreaMenu_popup) self.nudgeAreaMenu_popup:SetToolTip("Filter keys to nudge") self.selectKeyButton = LM.GUI.Button("select", self.SELECTKEY) layout:AddChild(self.selectKeyButton) self.selectKeyButton:SetAlternateMessage(self.SELECTKEYS_COLORED) self.selectKeyButton:SetToolTip(self:Localize("selectKeyTooltip")) layout:AddPadding(-13) self.selectKeysMenu = LM.GUI.Menu("") self.selectKeysMenu_popup = LM.GUI.PopupMenu(60, true) self.selectKeysMenu_popup:SetMenu(self.selectKeysMenu) self.selectKeysMenu:AddItem(self:Localize("frame"), 0, self.SELECTKEYS_FRAME) self.selectKeysMenu:AddItem(self:Localize("area"), 0, self.SELECTKEYS_AREA) self.selectKeysMenu:AddItem(self:Localize("total"), 0, self.SELECTKEYS_TOTAL) layout:AddChild(self.selectKeysMenu_popup) self.selectKeysMenu_popup:SetToolTip("Area to select keys within") self.deselectKeysButton = LM.GUI.Button("deselect", self.DESELECTKEYS) --layout:AddChild(self.deselectKeysButton) self.showAnimButton = LM.GUI.Button("V", self.SHOWANIM) layout:AddChild(self.showAnimButton) self.showAnimButton:SetToolTip(self:Localize("ShowAnimTooltip")) self.showAnimButton1 = LM.GUI.Button("v", self.SHOWANIM1) layout:AddPadding(-15) layout:AddChild(self.showAnimButton1) self.showAnimButton1:SetToolTip(self:Localize("ShowAnim1Tooltip")) self.hideAnimButton = LM.GUI.Button("X", self.HIDEANIM) layout:AddPadding(-15) layout:AddChild(self.hideAnimButton) self.hideAnimButton:SetToolTip(self:Localize("HideAnimTooltip")) end function AE_KeyTools:UpdateWidgets(moho) self.fromFrameField:SetValue(self:Localize("From") .. self.fromFrame) self.toFrame = moho.frame self.getFromKeyMenu:SetChecked(self.getFromKey, true) self.nudgeAreaMenu:SetChecked(self.nudgeArea, true) self.selectKeysMenu:SetChecked(self.selectKeys, true) self.trackAnimMenu:UncheckAll() if self.trackAnimOption == "active" then self.trackAnimMenu:SetChecked(self.ACTIVEANIM, true) elseif self.trackAnimOption == "long" then self.trackAnimMenu:SetChecked(self.LONGANIM, true) elseif self.trackAnimOption == "any" then self.trackAnimMenu:SetChecked(self.ANYANIM, true) end self.equalCheck:SetValue(self.ifEqual) self.includingsMenu:SetChecked(self.APPLY_TO_CHILD_LAYERS, self.applyToChildLayers) self.includingsMenu:SetChecked(self.TIMELINEVISIBLE_ONLY, self.timelinevisibleOnly) self.includingsMenu:SetChecked(self.APPLY_TO_REFS, self.applyToRefs) self.includingsMenu:SetChecked(self.PRECALC_CYCLES, self.precalcCycles) self.includingsMenu:SetChecked(self.IGNORE_STRING_CHANNELS, self.ignoreStringChannels) self.includingsMenu:SetChecked(self.IGNORE_HIDDEN_BONES, self.ignoreHiddenBones) self.includingsMenu:SetChecked(self.SELECTED_BONES_ONLY, self.selectedBonesOnly) self.includingsMenu:SetChecked(self.USE_STEP_INTERVALS, self.useStepIntervals) if self.keyInterp then self.copypasteInterpButton:SetLabel("P", false) else self.copypasteInterpButton:SetLabel("C", false) end end function AE_KeyTools:HandleMessage(moho, view, msg) if msg == self.COPY then local currentFrame = moho.frame self.fromFrame = currentFrame self.fromFrameField:SetValue(self:Localize("From") .. self.fromFrame) self.sourceFilePath = moho.document:Path() self.sourceRootLayerUUID = moho.layer:UUID() self.sourceRootLayerName = moho.layer:Name() self.sourceRootLayerPath = self:GetLayerPath(moho.layer) -- if red marker store values with params if MOHO.MohoGlobals.PlayEnd == -1 then self:StoreValues(moho) else self:StoreValues(moho, true, MOHO.MohoGlobals.PlayEnd) end elseif msg == self.PREVIOUS_KEY or msg == self.NEXT_KEY or msg == self.CURRENT_VALUES or msg == self.ZEROFRAME_VALUES or msg == self.PREVIOUSMARKER_VALUES then self.getFromKey = self.getFromKeyMenu:FirstCheckedMsg() elseif msg == self.NUDGEAREA_TOTAL or msg == self.NUDGEAREA_VISIBLE or msg == self.NUDGEAREA_SELECTEDKEYS then self.nudgeArea = self.nudgeAreaMenu:FirstCheckedMsg() elseif msg == self.SELECTKEYS_FRAME or msg == self.SELECTKEYS_AREA or msg == self.SELECTKEYS_TOTAL then self.selectKeys = self.selectKeysMenu:FirstCheckedMsg() elseif msg == self.APPLY_TO_CHILD_LAYERS then self.includingsMenu:SetChecked(msg, not self.includingsMenu:IsChecked(msg)) self.applyToChildLayers = self.includingsMenu:IsChecked(self.APPLY_TO_CHILD_LAYERS) elseif msg == self.TIMELINEVISIBLE_ONLY then self.includingsMenu:SetChecked(msg, not self.includingsMenu:IsChecked(msg)) self.timelinevisibleOnly = self.includingsMenu:IsChecked(self.TIMELINEVISIBLE_ONLY) elseif msg == self.APPLY_TO_REFS then self.includingsMenu:SetChecked(msg, not self.includingsMenu:IsChecked(msg)) self.applyToRefs = self.includingsMenu:IsChecked(self.APPLY_TO_REFS) elseif msg == self.PRECALC_CYCLES then self.includingsMenu:SetChecked(msg, not self.includingsMenu:IsChecked(msg)) self.precalcCycles = self.includingsMenu:IsChecked(self.PRECALC_CYCLES) elseif msg == self.IGNORE_STRING_CHANNELS then self.includingsMenu:SetChecked(msg, not self.includingsMenu:IsChecked(msg)) self.ignoreStringChannels = self.includingsMenu:IsChecked(self.IGNORE_STRING_CHANNELS) elseif msg == self.IGNORE_HIDDEN_BONES then self.includingsMenu:SetChecked(msg, not self.includingsMenu:IsChecked(msg)) self.ignoreHiddenBones = self.includingsMenu:IsChecked(self.IGNORE_HIDDEN_BONES) elseif msg == self.SELECTED_BONES_ONLY then self.includingsMenu:SetChecked(msg, not self.includingsMenu:IsChecked(msg)) self.selectedBonesOnly = self.includingsMenu:IsChecked(self.SELECTED_BONES_ONLY) elseif msg == self.USE_STEP_INTERVALS then self.includingsMenu:SetChecked(msg, not self.includingsMenu:IsChecked(msg)) self.useStepIntervals = self.includingsMenu:IsChecked(self.USE_STEP_INTERVALS) elseif msg == self.ACTIVEANIM then self.trackAnimOption = "active" elseif msg == self.LONGANIM then self.trackAnimOption = "long" elseif msg == self.ANYANIM then self.trackAnimOption = "any" elseif msg == self.EQUALCHECK then self.ifEqual = self.equalCheck:Value() elseif msg == self.PASTE then moho.document:PrepMultiUndo() moho.document:SetDirty() if moho.document:Path() ~= self.sourceFilePath then self:RestoreValues(moho, self.sourceRootLayerPath, self:GetLayerPath(moho.layer)) elseif moho.layer:UUID() ~= self.sourceRootLayerUUID then self:RestoreValues(moho, self.sourceRootLayerPath, self:GetLayerPath(moho.layer)) else self:CopyPasteFrames(moho) end moho:UpdateUI() moho.layer:UpdateCurFrame() elseif msg == self.PASTEKEYS then moho.document:PrepMultiUndo() moho.document:SetDirty() self:PasteSelectedKeys(moho) moho:UpdateUI() elseif msg == self.ADD then moho.document:PrepMultiUndo() moho.document:SetDirty() self:SetKey(moho) moho:UpdateUI() moho.layer:UpdateCurFrame() elseif msg == self.CLEANUP then moho.document:PrepMultiUndo() moho.document:SetDirty() self:RemoveUnusedKeys(moho) moho:UpdateUI() moho.layer:UpdateCurFrame() elseif msg == self.SHOWANIM then moho.document:PrepMultiUndo() moho.document:SetDirty() self:ShowHideAnimLayers(moho,true,false) moho:UpdateUI() moho.layer:UpdateCurFrame() elseif msg == self.SHOWANIM1 then moho.document:PrepMultiUndo() moho.document:SetDirty() self:ShowHideAnimLayers(moho,true,true) moho:UpdateUI() moho.layer:UpdateCurFrame() elseif msg == self.HIDEANIM then moho.document:PrepMultiUndo() moho.document:SetDirty() self:ShowHideAnimLayers(moho,false) moho:UpdateUI() moho.layer:UpdateCurFrame() elseif msg == self.SELECTKEY then moho.document:PrepMultiUndo() moho.document:SetDirty() self:SelectKeys(moho) moho:UpdateUI() moho.layer:UpdateCurFrame() elseif msg == self.SELECTKEYS_COLORED then self:SelectKeys_colored(moho) elseif msg == self.DESELECTKEYS then moho.document:PrepMultiUndo() moho.document:SetDirty() self:DeselectKeys(moho) moho:UpdateUI() moho.layer:UpdateCurFrame() elseif msg == self.NUDGELEFT or msg == self.NUDGELEFTx10 or msg == self.NUDGERIGHT or msg == self.NUDGERIGHTx10 then moho.document:PrepMultiUndo() moho.document:SetDirty() if msg == self.NUDGELEFT then self:NudgeKeys(moho,false) end if msg == self.NUDGELEFTx10 then self:NudgeKeys(moho,false, 10) end if msg == self.NUDGERIGHT then self:NudgeKeys(moho,true) end if msg == self.NUDGERIGHTx10 then self:NudgeKeys(moho,true, 10) end moho:UpdateUI() moho.layer:UpdateCurFrame() elseif msg == self.COPYPASTEINTERP then moho.document:PrepMultiUndo() moho.document:SetDirty() self:CopyPasteInterp(moho) moho:UpdateUI() moho.layer:UpdateCurFrame() end end function AE_KeyTools:DrawMe(moho, view) end function AE_KeyTools:OnKeyDown(moho, keyEvent) --print("keyCode = ", keyEvent.keyCode) if keyEvent.altKey and keyEvent.shiftKey and keyEvent.keyCode == LM.GUI.KEY_DOWN then moho.document:PrepMultiUndo() moho.document:SetDirty() self:NudgeKeys(moho,true, 10) moho:UpdateUI() moho.layer:UpdateCurFrame() elseif keyEvent.altKey and keyEvent.shiftKey and keyEvent.keyCode == LM.GUI.KEY_UP then moho.document:PrepMultiUndo() moho.document:SetDirty() self:NudgeKeys(moho,false, 10) moho:UpdateUI() moho.layer:UpdateCurFrame() elseif keyEvent.ctrlKey and keyEvent.key == "d" then moho.document:PrepMultiUndo() moho.document:SetDirty() self:DeselectKeys(moho) moho:UpdateUI() moho.layer:UpdateCurFrame() end end -- ************************************************** -- The guts of this script -- ************************************************** function AE_KeyTools:CollectExcludeChannels(moho, layer) local exclude_channels = {} if moho:LayerAsBone(layer) then if self.ignoreHiddenBones or self.selectedBonesOnly then local skel = moho:LayerAsBone(layer):Skeleton() if skel:CountBones() > 0 then local maxChannel = layer:CountChannels()-1 local chInfo = MOHO.MohoLayerChannel:new_local() local absNumber = 0 for i=1, maxChannel do layer:GetChannelInfo(i, chInfo) local name = chInfo.name:Buffer() if string.sub(name, 1, 5) == "Bone " and chInfo.subChannelCount == skel:CountBones() then for j=0, skel:CountBones()-1 do if(self.ignoreHiddenBones and skel:Bone(j).fHidden) or (self.selectedBonesOnly and not skel:Bone(j).fSelected) then local hiddenChannel = layer:Channel(i,j,moho.document) table.insert(exclude_channels, tostring(hiddenChannel)) end end end absNumber = absNumber + chInfo.subChannelCount end end end end return exclude_channels end function AE_KeyTools:CheckChannelForAddKey(moho, channel, channelFrame) if channel:HasKey(channelFrame) then return false end if self.ignoreStringChannels and channel:ChannelType() == MOHO.CHANNEL_STRING then return false end if not self.precalcCycles and self:IsCycled(moho,channel,channelFrame) > 0 then return false end if self.precalcCycles and self:IsCycled(moho,channel,channelFrame) > 0 then return true end --check track anim option menu and return false if condition not met if self.trackAnimOption == "active" and channel:Duration() < channelFrame then return false end if self.trackAnimOption == "long" and channel:Duration() <= 1 then return false end if self.trackAnimOption == "any" and channel:Duration() < 1 then return false end if self.getFromKey == self.PREVIOUS_KEY and (not self.useStepIntervals) and channel:GetKeyInterpModeByID(channel:GetClosestKeyID(channelFrame)) == MOHO.INTERP_STEP then return false end return true end function AE_KeyTools:SetKey(moho) local layersCollection = self:GetSelectedLayers(moho) local previousMarkerTime = moho.frame if self.getFromKey == self.PREVIOUSMARKER_VALUES then markerChannel = moho.document.fTimelineMarkers previousMarkerTime = markerChannel:GetClosestKeyID(moho.frame - 1) previousMarkerTime = markerChannel:GetKeyWhen(previousMarkerTime) end for id, layer in pairs(layersCollection) do if (not self.selectedBonesOnly) or moho:LayerAsBone(layer) then local exclude_channels = self:CollectExcludeChannels(moho, layer) local curFrame = layer:TotalTimingOffset() + moho.frame if curFrame == 0 then return end for subId, id, channel, chInfo in AE_Utilities:IterateAllChannels(moho, layer) do if not(table.contains(exclude_channels, tostring(channel))) then if self:CheckChannelForAddKey(moho, channel, curFrame) and chInfo.channelID ~= CHANNEL_DOC_MARKERS and chInfo.channelID ~= CHANNEL_LAYER_MARKERS then local derivedChannel = AE_Utilities:GetDerivedChannel(moho, channel) local fromFrame = curFrame local keyID = channel:GetClosestKeyID(curFrame) if self.getFromKey == self.NEXT_KEY then keyID = keyID + 1 end if self.getFromKey == self.ZEROFRAME_VALUES then keyID = 0 end if self.getFromKey == self.PREVIOUSMARKER_VALUES then keyID = channel:GetClosestKeyID(layer:TotalTimingOffset() + previousMarkerTime) end if keyID < channel:CountKeys() then if self.getFromKey ~= self.CURRENT_VALUES then fromFrame = channel:GetKeyWhen(keyID) end if derivedChannel.AreDimensionsSplit and channel:AreDimensionsSplit() then for sss = 0, 2 do local subChannel = derivedChannel:DimensionChannel(sss) if subChannel then local val = subChannel:GetValue(fromFrame) local oldval = subChannel:GetValue(curFrame) if self.ifEqual or not AE_Utilities:IsEqualValues(subChannel, val, oldval) then subChannel:SetValue(curFrame, val) end end end else local val = derivedChannel:GetValue(fromFrame) local oldval = derivedChannel:GetValue(curFrame) if self.ifEqual or not AE_Utilities:IsEqualValues(derivedChannel, val, oldval) then derivedChannel:SetValue(curFrame, val) end end end end end end end end end function AE_KeyTools:CopyPasteFrames(moho) local layersCollection = self:GetSelectedLayers(moho) for id, layer in pairs(layersCollection) do local fromFrame = layer:TotalTimingOffset() + self.fromFrame local toFrame = layer:TotalTimingOffset() + self.toFrame for subId, channel in AE_Utilities:IterateLayerSubChannels(moho, layer) do if channel:Duration() > 0 and (not self.ignoreStringChannels or channel:ChannelType() ~= MOHO.CHANNEL_STRING) then local derivedChannel = AE_Utilities:GetDerivedChannel(moho, channel) local val = derivedChannel:GetValue(fromFrame) if self.precalcCycles and self:IsCycled(moho,channel,fromFrame)>0 then val = self:GetCycledValue(moho, derivedChannel, fromFrame) end derivedChannel:SetValue(toFrame, val) end end end end function AE_KeyTools:TreatAsCicleKeyframe(moho, channel, keyFrame, keysToDelete) if channel:GetKeyInterpMode(keyFrame) ~= MOHO.INTERP_CYCLE then return false end local interp = MOHO.InterpSetting:new_local() channel:GetKeyInterp(keyFrame, interp) local startCicle = interp.val2 if startCicle == -1 then startCicle = keyFrame - interp.val1 end for f = startCicle, keyFrame - 1 do if channel:HasKey(f) then local isRealKey = true for i = #keysToDelete, 1, -1 do if keysToDelete[i] < startCicle then break end if keysToDelete[i] == f then isRealKey = false break end end if isRealKey then return true end end end return false end function AE_KeyTools:RemoveUnusedKeys(moho) local layersCollection = self:GetSelectedLayers(moho) for id, layer in pairs(layersCollection) do for subID, ID, channel, chInfo in AE_Utilities:IterateAllChannels(moho, layer) do if channel:Duration() > 0 and (not self.ignoreStringChannels or channel:ChannelType() ~= MOHO.CHANNEL_STRING) then if chInfo.channelID ~= CHANNEL_LAYER_MARKERS and chInfo.channelID ~= CHANNEL_DOC_MARKERS then --print('processing ', chInfo.channelID) local keysToDelete = {} local derivedChannel = AE_Utilities:GetDerivedChannel(moho, channel) local val = derivedChannel:GetValue(0) local lastKeyFrame = 0 for i=1, channel:CountKeys()-1 do local keyTime = channel:GetKeyWhen(i) --print('time=', keyTime, ' key is ', i, ' lastKeyFrame=', lastKeyFrame) local nextVal = derivedChannel:GetValue(keyTime) if not AE_Utilities:IsEqualValues(channel, val, nextVal) or (lastKeyFrame > 0 and channel:GetKeyInterpMode(lastKeyFrame)) == MOHO.INTERP_CYCLE or channel:GetKeyInterpModeByID(i) == MOHO.INTERP_POSE or self:TreatAsCicleKeyframe(moho, channel, keyTime, keysToDelete) then -- if values are different --print('not equal') val = nextVal lastKeyFrame = keyTime else -- if values are the same --print('equal') if i< channel:CountKeys()-1 then local afterKey = i + 1 local afterTime = channel:GetKeyWhen(i + 1) local afterVal = derivedChannel:GetValue(afterTime) if AE_Utilities:IsEqualValues(channel, nextVal, afterVal) then table.insert(keysToDelete, keyTime) end end end end channel:ClearAfter(lastKeyFrame) for k, v in pairs(keysToDelete) do if v < lastKeyFrame then channel:DeleteKey(v) end end end end end end end function AE_KeyTools:ShowHideAnimLayers(moho,toShow, restrictFirsts) local minDuration = 0 if restrictFirsts then minDuration = 1 end local theLayers = self:GetSelectedLayers(moho, true, true) for i, layer in AE_Utilities:IterateAllLayers(moho) do if not toShow then if not layer:SecondarySelection() then layer:SetShownOnTimeline(false) end else if not moho.layer:IsGroupType() or layer:IsAncestorSelected() then local isAnimated = false local isFiltered = false for i, filteredLayer in pairs(theLayers) do if filteredLayer == layer then isFiltered = true break end end if isFiltered then for subId, channel in AE_Utilities:IterateLayerSubChannels(moho, layer) do if channel:Duration() > minDuration then isAnimated = true break end end end layer:SetShownOnTimeline(isAnimated) end end end end function AE_KeyTools:SelectKeys(moho) local filteredLayers = self:GetSelectedLayers(moho) local areaStart = MOHO.MohoGlobals.PlayStart if areaStart == -1 then areaStart = moho.document:StartFrame() end local areaEnd = MOHO.MohoGlobals.PlayEnd if areaEnd == -1 then areaEnd = moho.document:EndFrame() end for i, layer in pairs(filteredLayers) do local exclude_channels = self:CollectExcludeChannels(moho, layer) local layerFrame = moho.frame + layer:TotalTimingOffset() local layerAreaStart = areaStart + layer:TotalTimingOffset() local layerAreaEnd = areaEnd + layer:TotalTimingOffset() for subId, id, channel, chInfo in AE_Utilities:IterateAllChannels(moho, layer) do if (not self.ignoreStringChannels or channel:ChannelType() ~= MOHO.CHANNEL_STRING) and (not(table.contains(exclude_channels, tostring(channel)))) then if self.selectKeys == self.SELECTKEYS_TOTAL then if channel:Duration() > 0 then if subId == 1 then table.contains(exclude_channels, tostring(channel), true) end for k=1, channel:CountKeys()-1 do channel:SetKeySelectedByID(k, true) end end elseif self.selectKeys == self.SELECTKEYS_AREA then if channel:Duration() > 0 then if subId == 1 then table.contains(exclude_channels, tostring(channel), true) end for k=1, channel:CountKeys()-1 do local fr = channel:GetKeyWhen(k) if fr >= layerAreaStart and fr <= layerAreaEnd then channel:SetKeySelectedByID(k, true) end end end else if channel:HasKey(layerFrame) then channel:SetKeySelected(layerFrame, true) end end end end end end function AE_KeyTools:DeselectKeys(moho) for i, layer in AE_Utilities:IterateAllLayers(moho) do for subId, channel in AE_Utilities:IterateLayerSubChannels(moho, layer) do for k = 0, channel:CountKeys()-1 do channel:SetKeySelectedByID(k, false) end end end end function AE_KeyTools:InterFileCopyPasteFrames(moho) self.targetFilePath = moho.document:Path() self.toFrame = moho.frame moho:FileOpen(self.sourceFilePath) local valuesCollection = {} for subId, channel in AE_Utilities:IterateLayerSubChannels(moho, moho.layer) do local derivedChannel = AE_Utilities:GetDerivedChannel(moho, channel) if derivedChannel then local value = derivedChannel:GetValue(self.fromFrame + moho.layer:TotalTimingOffset()) valuesCollection[subId] = {["val"] = value} end end moho:FileOpen(self.targetFilePath) for subId, channel in AE_Utilities:IterateLayerSubChannels(moho, moho.layer) do local derivedChannel = AE_Utilities:GetDerivedChannel(moho, channel) if derivedChannel and valuesCollection[subId] then local frame = self.toFrame + moho.layer:TotalTimingOffset() local value = derivedChannel:GetValue(frame) if not AE_Utilities:IsEqualValues(derivedChannel, value, valuesCollection[subId].val) then derivedChannel:SetValue(frame, valuesCollection[subId].val) end end end end function AE_KeyTools:GetLayerPath(layer) local path = layer:Name() local nextLayer = layer while nextLayer:Parent() do nextLayer = nextLayer:Parent() path = nextLayer:Name() .. "\n" .. path end return path end function AE_KeyTools:Ordering2NameArray(moho, value, parentLayer, maptable) local valueArray = {} if value ~= "" then for token in string.gmatch(value, "[^|]+") do local name = nil if maptable then name = maptable[token] else local theLayer = AE_Utilities:GetLayerByUUID(moho, token) if theLayer then name = theLayer:Name() end end if name then table.insert(valueArray, name) end end else for i = 0, parentLayer:CountLayers()-1 do table.insert(valueArray, parentLayer:Layer(i):Name()) end end return valueArray end function AE_KeyTools:NameArray2ordering(moho, nameArray, parentLayer, maptable) local resultString = "" local zeroEqual = true if parentLayer then for i = 0, parentLayer:CountLayers() -1 do if parentLayer:Layer(i):Name() ~= nameArray[i + 1] then zeroEqual = false break end end end if zeroEqual then return "" end for i,v in pairs(nameArray) do local nextUUID = "" if maptable then nextUUID = maptable[v] elseif parentLayer then for j=0, parentLayer:CountLayers()-1 do local nextChild = parentLayer:Layer(j) if nextChild:Name() == v then nextUUID = nextChild:UUID() break end end end if nextUUID and #nextUUID > 0 then if #resultString > 0 then resultString = resultString .. "|" end resultString = resultString .. nextUUID end end return resultString end function AE_KeyTools:StoreValues(moho, saveAnimation, finishFrame) self.storedValues = {} local layersCollection = self:GetSelectedLayers(moho) for id, layer in pairs(layersCollection) do local path = self:GetLayerPath(layer) local layerInfo = {["name"] = layer:Name(), ["path"] = path, ["chans"] = {}} local skel = nil if layer:IsBoneType() then local boneLayer = moho:LayerAsBone(layer) if boneLayer then skel = boneLayer:Skeleton() end end for subId, id, channel, chInfo in AE_Utilities:IterateAllChannels(moho, layer) do if (not chInfo.selectionBased) and chInfo.channelID ~= CHANNEL_LAYER_ALL and chInfo.channelID < CHANNEL_DOC_MARKERS then if subId == 0 then layerInfo.chans[id] = {["vals"]={}, ["chantype"] = chInfo.channelID, ["numsubs"] = chInfo.subChannelCount} end local derivedChannel, channelType = AE_Utilities:GetDerivedChannel(moho, channel) if derivedChannel then local maptable = nil if chInfo.channelID == CHANNEL_LAYER_ORDER then maptable = {} local groupLayer = moho:LayerAsGroup(layer) if groupLayer then for nchild = 0, groupLayer:CountLayers()-1 do local nextChild = groupLayer:Layer(nchild) maptable[nextChild:UUID()] = nextChild:Name() end end end local value = derivedChannel:GetValue(self.fromFrame + moho.layer:TotalTimingOffset()) if maptable then value = self:Ordering2NameArray(moho, value, moho:LayerAsGroup(layer), maptable) end layerInfo.chans[id].vals[subId] = {["val"] = value, ["chantype"] = channelType} if skel and string.match(chInfo.name:Buffer(), ".*Bone.*") then if chInfo.subChannelCount == skel:CountBones() then layerInfo.chans[id].vals[subId].boneName = skel:Bone(subId):Name() end end if saveAnimation and finishFrame > self.fromFrame then local keys = {} for k = 0, derivedChannel:CountKeys()-1 do local globalTime = derivedChannel:GetKeyWhen(k) - moho.layer:TotalTimingOffset() if globalTime >= self.fromFrame and globalTime <= finishFrame then local keyValue = derivedChannel:GetValueByID(k) if maptable then keyValue = self:Ordering2NameArray(moho, keyValue, moho:LayerAsGroup(layer), maptable) end local interp = MOHO.InterpSetting:new_local() derivedChannel:GetKeyInterpByID(k, interp) local newKey = {["globalTime"]=globalTime, ["val"] = keyValue, ["interp"] = interp} table.insert(keys, newKey) end end layerInfo.chans[id].vals[subId].keys = keys end end end end table.insert(self.storedValues, layerInfo) end end function AE_KeyTools:RestoreValues(moho, oldRootPath, newRootPath) if #self.storedValues == 1 then return self:RestoreLayerValues(moho, moho.layer, self.storedValues[1].chans) end for i, obj in pairs(self.storedValues) do local sourcePath = obj.path if oldRootPath and newRootPath then sourcePath = obj.path:gsub(oldRootPath:gsub(".", self.regtab), newRootPath) end for id, layer in AE_Utilities:IterateAllLayers(moho) do local path = self:GetLayerPath(layer) if path == sourcePath then --print('ready to copy from '..string.gsub(sourcePath,'\n', ' ')..' to '..string.gsub(path,'\n', ' ')) self:RestoreLayerValues(moho, layer, obj.chans) end end end end function AE_KeyTools:RestoreLayerValues(moho, layer, storedLayerValues) local skel = nil if layer:IsBoneType() then local boneLayer = moho:LayerAsBone(layer) skel = boneLayer:Skeleton() end for subId, id, channel, chInfo in AE_Utilities:IterateAllChannels(moho, layer) do local derivedChannel, channelType = AE_Utilities:GetDerivedChannel(moho, channel) local storedChannel = storedLayerValues[id] if storedChannel then local storedSubChannel = nil local maptable = nil if chInfo.channelID == CHANNEL_LAYER_ORDER then maptable = {} local groupLayer = moho:LayerAsGroup(layer) if groupLayer then for nchild = 0, groupLayer:CountLayers()-1 do local nextChild = groupLayer:Layer(nchild) maptable[nextChild:Name()] = nextChild:UUID() end end end if storedChannel and storedChannel.chantype == chInfo.channelID then if storedChannel.numsubs > subId then storedSubChannel= storedChannel.vals[subId] end end if skel and string.match(chInfo.name:Buffer(), ".*Bone.*") and skel:Bone(subId) then local boneName = skel:Bone(subId):Name() if (not storedSubChannel) or storedSubChannel.boneName ~= boneName then storedSubChannel = nil for n,b in pairs(storedChannel.vals) do if b.boneName and b.boneName == boneName then storedSubChannel = b end end end end if derivedChannel and storedSubChannel and --storedChannel.numsubs <= chInfo.subChannelCount and storedSubChannel.chantype == channelType then local frame = self.toFrame + moho.layer:TotalTimingOffset() local value = derivedChannel:GetValue(frame) if storedSubChannel.keys then for k,v in pairs(storedSubChannel.keys) do frame = self.toFrame + moho.layer:TotalTimingOffset() + v.globalTime - self.fromFrame local val = v.val if maptable then val = self:NameArray2ordering(moho, val, moho:LayerAsGroup(layer), maptable) end if chInfo.channelID == CHANNEL_BONE_PARENT and val > -1 then local name = storedChannel.vals[val].boneName local newID = -1 if skel then for b = 0, skel:CountBones()-1 do if skel:Bone(b):Name() == name then newID = b break end end end val = newID end derivedChannel:SetValue(frame, val) derivedChannel:SetKeyInterp(frame, v.interp) end else local val = storedSubChannel.val if maptable then val = self:NameArray2ordering(moho, val, moho:LayerAsGroup(layer), maptable) end if chInfo.channelID == CHANNEL_BONE_PARENT and val > -1 then local name = storedChannel.vals[val].boneName local newID = -1 if skel then for b = 0, skel:CountBones()-1 do if skel:Bone(b):Name() == name then newID = b break end end end val = newID end if not AE_Utilities:IsEqualValues(derivedChannel, value, val ) then derivedChannel:SetValue(frame, val) end end end end --layer:UpdateCurFrame() end layer:UpdateCurFrame() end function AE_KeyTools:PasteSelectedKeys(moho) local firstKeyFrame = math.huge local keysCollection = {} local layersToUpdate = {} for i, layer in AE_Utilities:IterateAllLayers(moho) do if layer == moho.layer or layer:IsShownOnTimeline() or layer:SecondarySelection() then local foundKeys = false for subId, channel in AE_Utilities:IterateLayerSubChannels(moho, layer) do local derivedChannel = nil for k = 0, channel:CountKeys()-1 do if channel:IsKeySelectedByID(k) then if not derivedChannel then derivedChannel = AE_Utilities:GetDerivedChannel(moho, channel) end if derivedChannel then local keyTime = channel:GetKeyWhen(k) if (keyTime - layer:TotalTimingOffset()) < firstKeyFrame then firstKeyFrame = keyTime - layer:TotalTimingOffset() end local nextKey = {['channel'] = derivedChannel, ['time'] = keyTime, ['layer'] = layer} table.insert(keysCollection, nextKey) foundKeys = true end end end end if foundKeys then table.insert(layersToUpdate, layer) end end end if #keysCollection < 1 then return LM.GUI.Alert(LM.GUI.ALERT_WARNING, "No keys selected", "", "", "EXIT") end for k,v in pairs(keysCollection) do local val = v.channel:GetValue(v.time) local newTime = moho.frame + v.time - firstKeyFrame v.channel:SetValue(newTime, val) end moho.layer:UpdateCurFrame(true) for k,v in pairs(layersToUpdate) do v:UpdateCurFrame(true) end end function AE_KeyTools:DubbingChannel(moho, channelID) -- does not work as expected. Do not know, why if channelID > CHANNEL_BONE and channelID < CHANNEL_BONE_T then return true end if channelID > CHANNEL_BONE_T and channelID < CHANNEL_BONE_S then return true end if channelID > CHANNEL_BONE_S and channelID < CHANNEL_BONE_FLIPH then return true end return false end function AE_KeyTools:NudgeKeys(moho, toForward, step) if not step then step = 1 end local signedStep = step if not toForward then signedStep = -step end local layersCollection = self:GetSelectedLayers(moho) local channelsCollection = {} for id, layer in pairs(layersCollection) do if self.nudgeArea == self.NUDGEAREA_TOTAL or layer:IsShownOnTimeline() or layer == moho.layer then local curFrame = layer:TotalTimingOffset() + moho.frame for subId, channelId, channel, chInfo in AE_Utilities:IterateAllChannels(moho, layer) do if (not self.ignoreStringChannels) or channel:ChannelType() ~= MOHO.CHANNEL_STRING then if chInfo.channelID ~= CHANNEL_DOC_MARKERS or self.nudgeArea == self.NUDGEAREA_TOTAL then if chInfo.selectionBased == false then if (self.nudgeArea ~= self.NUDGEAREA_SELECTEDKEYS and not toForward) and channel:HasKey(curFrame-step) then return LM.GUI.Alert(LM.GUI.ALERT_WARNING, "Existing key in target frame", "", "", "EXIT") end if channel.Duration and channel:Duration() > curFrame-1 or self.nudgeArea == self.NUDGEAREA_SELECTEDKEYS then local nextChannel = {['channel'] = channel, ['curFrame'] = curFrame} local allwayCollected = false for i, item in pairs(channelsCollection) do if item.channel == channel then allwayCollected = true break end end if not allwayCollected then table.insert(channelsCollection, nextChannel) end end end end end end end end local deleteOld = false if self.nudgeArea == self.NUDGEAREA_SELECTEDKEYS then for k,v in pairs(channelsCollection) do for kID = 0, v.channel:CountKeys()-1 do if v.channel:IsKeySelectedByID(kID) then if v.channel:HasKey(v.channel:GetKeyWhen(kID) + signedStep) and not deleteOld then local userAnswer = LM.GUI.Alert(LM.GUI.ALERT_WARNING, "Existing key in target frame", "", "", "EXIT", "deselect problem keys and EXIT", "delete old") if userAnswer == 1 then for k1,v1 in pairs(channelsCollection) do for kID1 = 0, v1.channel:CountKeys()-1 do if v1.channel:IsKeySelectedByID(kID1) and v1.channel:HasKey(v1.channel:GetKeyWhen(kID1) + signedStep) then v1.channel:SetKeySelectedByID(kID1, false) end end end end if userAnswer == 2 then deleteOld = true else return end end end end end end for k,v in pairs(channelsCollection) do if self.nudgeArea == self.NUDGEAREA_SELECTEDKEYS then local startID = 0 local endID = v.channel:CountKeys() - 1 local iterator = 1 if toForward then startID, endID, iterator = endID, startID, -1 end for kID = startID, endID, iterator do if v.channel:IsKeySelectedByID(kID) then local f = v.channel:GetKeyWhen(kID) if deleteOld and v.channel:HasKey(f + signedStep) then v.channel:DeleteKey(f + signedStep) end v.channel:SetKeyWhen(kID, f + signedStep) end end else local lastKeyTime = v.channel:Duration() local curFrame = v.curFrame if toForward then for f = lastKeyTime, curFrame, -1 do if v.channel:HasKey(f) then local kID = v.channel:GetClosestKeyID(f) v.channel:SetKeyWhen(kID, f+step) end end else for f = curFrame, lastKeyTime do if v.channel:HasKey(f) then local kID = v.channel:GetClosestKeyID(f) v.channel:SetKeyWhen(kID, f-step) end end end end end if toForward then moho:SetCurFrame(moho.frame + step) else moho:SetCurFrame(moho.frame - step) end end function AE_KeyTools:SelectKeys_colored(moho) moho.document:SetDirty() moho.document:PrepUndo(nil) local interp = MOHO.InterpSetting:new_local() local colorToSelect = -1 local startFrame = MOHO.MohoGlobals.PlayStart if startFrame == -1 then startFrame = moho.document:StartFrame() end local endFrame = MOHO.MohoGlobals.PlayEnd if endFrame == -1 then endFrame = moho.document:EndFrame() end for i, layer in AE_Utilities:IterateAllLayers(moho) do if layer == moho.layer or layer:IsShownOnTimeline() then for j, i, channel, chInfo in AE_Utilities:IterateAllChannels(moho, layer) do if channel:Duration() > 0 then for f = startFrame, endFrame do if channel:HasKey(f) and channel:IsKeySelected(f) then channel:GetKeyInterp(f, interp) colorToSelect = interp.tags break end end end end end end if colorToSelect == -1 then return LM.GUI.Alert(LM.GUI.ALERT_WARNING, "First select any colored key to set the color") end for i, layer in AE_Utilities:IterateAllLayers(moho) do if layer == moho.layer or layer:IsShownOnTimeline() then for j, i, channel, chInfo in AE_Utilities:IterateAllChannels(moho, layer) do if channel:Duration() > 0 and chInfo.selectionBased == false then for k = 0, channel:CountKeys() -1 do local f = channel:GetKeyWhen(k) local globalF = f - layer:TotalTimingOffset() if (self.selectKeys == self.SELECTKEYS_AREA and (globalF < startFrame or globalF > endFrame)) or (self.selectKeys == self.SELECTKEYS_FRAME and globalF ~= moho.frame )then channel:SetKeySelected(f, false) else channel:GetKeyInterp(f, interp) if interp.tags == colorToSelect then channel:SetKeySelected(f, true) else channel:SetKeySelected(f, false) end end end end end end end moho:UpdateUI() moho.layer:UpdateCurFrame() end --------------------------------------------------------------------------------------------------------------------- function AE_KeyTools:GetSelectedLayers(moho, childrenAllwaysOn, forTimelineShow) local layersCollection = AE_Utilities:MohoListToTable(moho.document, moho.document.CountSelectedLayers, moho.document.GetSelectedLayer) if not self.applyToChildLayers and not childrenAllwaysOn then return layersCollection end table.sort(layersCollection, function(a,b) return (moho.document:LayerAbsoluteID(a)<moho.document:LayerAbsoluteID(b)) end) local childLayersCollection = {} for i, layer in AE_Utilities:IterateAllLayers(moho, moho.document:LayerAbsoluteID(layersCollection[1])) do if layer:SecondarySelection() then table.insert(childLayersCollection, layer) elseif not self.timelinevisibleOnly or layer:IsShownOnTimeline() or forTimelineShow then for j,parentLayer in pairs(layersCollection) do if AE_Utilities:IsAncestor(parentLayer, layer) then table.insert(childLayersCollection, layer) break end end end end if not self.applyToRefs then for i, layer in pairs(childLayersCollection) do if layer:IsReferencedLayer() and not layer:IsReferenceExternal() then table.remove(childLayersCollection, i) end end end return childLayersCollection end function AE_KeyTools:GetCycledValue(moho, channel, frame) local keyID = self:IsCycled(moho,channel, frame) if keyID <= 0 then return channel:GetValue(frame) end local interp = MOHO.InterpSetting:new_local() channel:GetKeyInterpByID(keyID, interp) local keyTime = channel:GetKeyWhen(keyID) local absCycle = interp.val2 > 0 and interp.val2 or keyTime - interp.val1 local timeDistance = frame - keyTime local referenceTime = timeDistance % (keyTime - absCycle + 1) + absCycle - 1 local referenceValue = channel:GetValue(referenceTime) if not interp:IsAdditiveCycle() then return referenceValue end local offset = channel:GetValue(keyTime) - channel:GetValue(absCycle - 1) local numOffsets = math.floor((timeDistance-1)/(keyTime - absCycle + 1))+1 return (offset * numOffsets + referenceValue) end function AE_KeyTools:IsCycled(moho,channel,frame) if channel:HasKey(frame) then return 0 end local keyID = channel:GetClosestKeyID(frame) if channel:GetKeyInterpModeByID(keyID) == MOHO.INTERP_CYCLE then return keyID end if channel:GetKeyInterpModeByID(keyID) == MOHO.INTERP_NOISY then return keyID end if channel:GetKeyInterpModeByID(keyID) == MOHO.INTERP_BOUNCE then return keyID end if channel:GetKeyInterpModeByID(keyID) == MOHO.INTERP_ELASTIC then return keyID end return 0 end function AE_KeyTools:CopyPasteInterp(moho) if self.keyInterp then for i, layer in AE_Utilities:IterateAllLayers(moho) do if layer == moho.layer or layer:IsShownOnTimeline() then for subId, channel in AE_Utilities:IterateLayerSubChannels(moho, layer) do for k = 0, channel:CountKeys()-1 do if channel:IsKeySelectedByID(k) then channel:SetKeyInterpByID(k,self.keyInterp) end end end end end local d = moho.document for i, channel in pairs({d.fCameraPanTilt, d.fCameraRoll, d.fCameraTrack, d.fCameraZoom}) do for k = 0, channel:CountKeys()-1 do if channel:IsKeySelectedByID(k) then channel:SetKeyInterpByID(k,self.keyInterp) end end end self.keyInterp = nil --self.copypasteInterpButton:SetImage("ScriptResources/smooth") self.copypasteInterpButton:SetLabel("C", false) else for i, layer in AE_Utilities:IterateAllLayers(moho) do if layer == moho.layer or layer:IsShownOnTimeline() then for subId, channel in AE_Utilities:IterateLayerSubChannels(moho, layer) do for k = 0, channel:CountKeys()-1 do if channel:IsKeySelectedByID(k) then if self.keyInterp then self.keyInterp = nil return LM.GUI.Alert(LM.GUI.ALERT_WARNING, "Please, select only one key for copy", "", "", "EXIT") end self.keyInterp = MOHO.InterpSetting:new_local() channel:GetKeyInterpByID(k,self.keyInterp) end end end end end local d = moho.document local cameraChannels = {d.fCameraPanTilt, d.fCameraRoll, d.fCameraZoom} if d.fCameraTrack:AreDimensionsSplit() then for dim=0, 2 do table.insert(cameraChannels, d.fCameraTrack:DimensionChannel(dim)) end else table.insert(cameraChannels, d.fCameraTrack) end for i, channel in pairs(cameraChannels) do for k = 0, channel:CountKeys()-1 do if channel:IsKeySelectedByID(k) then if self.keyInterp then self.keyInterp = nil return LM.GUI.Alert(LM.GUI.ALERT_WARNING, "Please, select only one key for copy", "", "", "EXIT") end self.keyInterp = MOHO.InterpSetting:new_local() channel:GetKeyInterpByID(k,self.keyInterp) end end end --self.copypasteInterpButton:SetImage("ScriptResources/show_handles") self.copypasteInterpButton:SetLabel("P", false) end end function table.contains(t, element) if not t then return false end for _, value in pairs(t) do if value == element then return true end end return false end -- ************************************************** -- Localization -- ************************************************** function AE_KeyTools:Localize(text) local fileWord = MOHO.Localize("/Menus/File/File=File") local phrase = {} phrase["Name"] = "Key Tool" phrase["Description"] = "Use Alt+Shift+UP/DOWN to nudge keys 10 frames at once" phrase["UILabel"] = "Key Tool" phrase["COPY"] = "COPY" phrase["PASTE"] = "PASTE" phrase["From"] = "From: " phrase["PasteKeysTooltip"] = "Paste selected keys" phrase["ADD"] = "ADD" phrase["NextKey"] = "Next key" phrase["PreviousKey"] = "Previous key" phrase["CurrentValue"] = "Current value" phrase["ZeroFrame"] = "Zero frame" phrase["PreviousMarker"] = "Previous Marker" phrase["ApplyToChildLayers"] = "Child layers" phrase["ApplyToRefs"] = "Referenced layers" phrase["PrecalcCycles"] = "Cycles" phrase["IgnoreStringChannels"] = "Ignore string channels" phrase["IgnoreHiddenBones"] = "Ignore hidden bones" phrase["NudgeRightTooltip"] = "Nudge keys right, alt-click for x10" phrase["NudgeLeftTooltip"] = "Nudge keys left, alt-click for x10" phrase["CLEANUP"] = "CLEAN" phrase["CopyTooltip"] = "Define the source frame to copy values from" phrase["PasteTooltip"] = "Copy values from the source frame into current frame" phrase["AddTooltip"] = "Set keys at the current frame, taking current values or values from previous/next key" phrase["MenuTooltip"] = "Where to take the values for the ADD command" phrase["IncludingMenuTooltip"] = "Which layers and channels to include for all operations" phrase["ChildLayersTooltip"] = "Include all sub-layers of the selected layers" phrase["RefsTooltip"] = "Include referenced layers" phrase["CyclesTooltip"] = "Calculate values in frames affected by cycles" phrase["IgnoreStringsTooltip"] = "Do not use the Switch and Layer Order channels" phrase["CleanupTooltip"] = "Remove duplicate keys at the end of each channel" phrase["ShowAnimTooltip"] = "Show animated layers on timeline" phrase["ShowAnim1Tooltip"] = "Show layers with animation after first frame" phrase["HideAnimTooltip"] = "Hide all non-selected layers from timeline" phrase["selectKeyTooltip"] = "Select all keys in current frame/area/total project; alt-click to select keys of same color" phrase["CopyPasteInterpTooltip"] = "Copy/paste key interpolation" if fileWord == "Файл" then phrase["Name"] = "Инструменты ключей" phrase["Description"] = "Инструменты ключей" phrase["UILabel"] = "Инструменты ключей" phrase["COPY"] = "КОПИРОВАТЬ" phrase["PASTE"] = "ВСТАВИТЬ" phrase["From"] = "Из: " phrase["PasteKeysTooltip"] = "Вставить выбранные ключи" phrase["ADD"] = "СОЗДАТЬ" phrase["NextKey"] = "След. ключ" phrase["PreviousKey"] = "Пред. ключ" phrase["CurrentValue"] = "Текущее значение" phrase["ZeroFrame"] = "Из нулевого кадра" phrase["PreviousMarker"] = "От предыдущего маркера" phrase["ApplyToChildLayers"] = "Дочерние слои" phrase["ApplyToRefs"] = "Референсы" phrase["PrecalcCycles"] = "Циклы" phrase["IgnoreStringChannels"] = "Игнорировать строчные каналы" phrase["IgnoreHiddenBones"] = "Игнорировать спрятанные кости" phrase["NudgeRightTooltip"] = "Сдвинуть ключи вправо" phrase["NudgeLeftTooltip"] = "Сдвинуть ключи влево" phrase["CLEANUP"] = "ОЧИСТИТЬ" phrase["CopyTooltip"] = "Сохранить номер исходного кадра" phrase["PasteTooltip"] = "Вставить ключи из исходного кадра" phrase["AddTooltip"] = "Создать ключи в текущем кадре, базируясь на предыдущем/сдледующем/текущем значении" phrase["MenuTooltip"] = "Откуда брать значения для SET" phrase["IncludingMenuTooltip"] = "Над какими слоями и каналами производить операции" phrase["ChildLayersTooltip"] = "Обрабатывать всю иерархию дочерних слоев каждого выбранного слоя" phrase["RefsTooltip"] = "Обрабатывать референсные слои" phrase["CyclesTooltip"] = "Вычислять значения в кадрах-источниках, на которые влияют циклы" phrase["IgnoreStringsTooltip"] = "Не учитывать каналы переключетелей и порядка слоев" phrase["CleanupTooltip"] = "Убрать одинаковые ключи в конце каждого канала" phrase["ShowAnimTooltip"] = "Показать на таймлайне слои с анимацией" phrase["ShowAnim1Tooltip"] = "Показать на таймлайне слои с анимацией после первого кадра" phrase["HideAnimTooltip"] = "Убрать с таймлайна все невыбранные слои" phrase["selectKeyTooltip"] = "Выбрать все ключи в текущем кадре" end return phrase[text] or text; end
AE Key Tools
Listed
Author: A.Evseeva
View Script
Script type: Tool
Uploaded: Jul 14 2020, 08:28
Last modified: Jan 05 2023, 08:15
Multiple buttons combined to a tool for manipulating keys in character animation.
Setting keys, moving keys, copy and paste poses and so on, for any number of sublayers in skeleton hierarchy.
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: 1399

AE Key Tools
Listed
Author: A.Evseeva
View Script
Script type: Tool
Uploaded: Jul 14 2020, 08:28
Last modified: Jan 05 2023, 08:15
Multiple buttons combined to a tool for manipulating keys in character animation.
Setting keys, moving keys, copy and paste poses and so on, for any number of sublayers in skeleton hierarchy.
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: 1399