Image
-- WARNING: This script requires AE_Utilities.lua of version 1.11 or later!

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

ScriptName = "DV_TweenMachine"

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

DV_TweenMachine = {}

function DV_TweenMachine:Name()
	return self:Localize("Name")
end

function DV_TweenMachine:Version()
	return "1.0"
end

function DV_TweenMachine:Description()
	return self:Localize("Description")
end

function DV_TweenMachine:Creator()
	return "davood tabatabei davoodice,thanks to A.Evseeva, Stan, Lukas Krepel"
end

function DV_TweenMachine:UILabel()
	return self:Localize("UILabel")
end

function DV_TweenMachine:ColorizeIcon()
	return true
end

function DV_TweenMachine:LoadPrefs(prefs)
	self.applyToChildLayers = prefs:GetBool("DV_TweenMachine.applyToChildLayers", true)
	self.timelinevisibleOnly = prefs:GetBool("DV_TweenMachine.timelinevisibleOnly", false)
	self.applyToRefs = prefs:GetBool("DV_TweenMachine.applyToRefs", false)
	self.precalcCycles = prefs:GetBool("DV_TweenMachine.precalcCycles", true)
	self.ignoreStringChannels = prefs:GetBool("DV_TweenMachine.ignoreStringChannels", false)
	self.ignoreHiddenBones = prefs:GetBool("DV_TweenMachine.ignoreHiddenBones", true)
	self.trackAnimOption = prefs:GetString("DV_TweenMachine.trackAnimOption", "active")
	self.ifEqual = prefs:GetBool("DV_TweenMachine.ifEqual", false)
	self.selectedBonesOnly = prefs:GetBool("DV_TweenMachine.selectedBonesOnly", false)	
	self.useStepIntervals = prefs:GetBool("DV_TweenMachine.useStepIntervals", false)
	self.previewSlider = prefs:GetBool("DV_TweenMachine.previewSlider", true)

end

function DV_TweenMachine:SavePrefs(prefs)
	prefs:SetBool("DV_TweenMachine.applyToChildLayers", self.applyToChildLayers)
	prefs:SetBool("DV_TweenMachine.timelinevisibleOnly", self.timelinevisibleOnly)
	prefs:SetBool("DV_TweenMachine.applyToRefs", self.applyToRefs)	
	prefs:SetBool("DV_TweenMachine.precalcCycles", self.precalcCycles)
	prefs:SetBool("DV_TweenMachine.ignoreStringChannels", self.ignoreStringChannels)	
	prefs:SetBool("DV_TweenMachine.ignoreHiddenBones", self.ignoreHiddenBones)	
	prefs:SetString("DV_TweenMachine.trackAnimOption", self.trackAnimOption)	
	prefs:SetBool("DV_TweenMachine.ifEqual", self.ifEqual)
	prefs:SetBool("DV_TweenMachine.selectedBonesOnly", self.selectedBonesOnly)	
	prefs:SetBool("DV_TweenMachine.useStepIntervals", self.useStepIntervals)
	prefs:SetBool("DV_TweenMachine.previewSlider", self.previewSlider)

end

function DV_TweenMachine:ResetPrefs()
	self.applyToChildLayers = false
	self.timelinevisibleOnly = false
	self.applyToRefs = false
	self.precalcCycles = false
	self.ignoreStringChannels = true
	self.ignoreHiddenBones = true
	self.ifEqual = true
	self.selectedBonesOnly = true
	self.useStepIntervals = false
	self.previewSlider = false

end

-- **************************************************
-- Is Relevant / Is Enabled
-- **************************************************

function DV_TweenMachine:IsRelevant(moho)
	if MohoMode ~= nil then
		return not MohoMode.vanilla
	end
	return true
end

function DV_TweenMachine:IsEnabled(moho)
	return true
end

-- **************************************************
-- Include Bone Select tool mouse functions
-- **************************************************

function DV_TweenMachine:OnMouseDown(moho, mouseEvent)
	LM_SelectBone:OnMouseDown(moho, mouseEvent)
end
function DV_TweenMachine:OnMouseUp(moho, mouseEvent)
	LM_SelectBone:OnMouseUp(moho, mouseEvent)
end
function DV_TweenMachine:OnMouseMoved(moho, mouseEvent)
	LM_SelectBone:OnMouseMoved(moho, mouseEvent)
end

-- **************************************************
-- Tool level variables to bind UI
-- **************************************************

-- * Settings:
DV_TweenMachine.NumberOFButtons = 13
DV_TweenMachine.presetButton={0,5,10,15,50,80,85,90,95,100,105,110,115}

DV_TweenMachine.applyToChildLayers = false
DV_TweenMachine.timelinevisibleOnly = false
DV_TweenMachine.applyToRefs = false
DV_TweenMachine.precalcCycles = false
DV_TweenMachine.ignoreStringChannels = true
DV_TweenMachine.ignoreHiddenBones = true
DV_TweenMachine.ifEqual = true
DV_TweenMachine.selectedBonesOnly = true
DV_TweenMachine.useStepIntervals = false
DV_TweenMachine.previewSlider = false

-- * Slider:
DV_TweenMachine.sliderVal = 0
DV_TweenMachine.slidertextVal = 0
DV_TweenMachine.tweenButton = {}
-- DV_TweenMachine.currentPercentage = 0

-- **************************************************
-- Tool options - create and respond to tool's UI
-- **************************************************
-- * Settings:
DV_TweenMachine.APPLY_TO_CHILD_LAYERS			= MOHO.MSG_BASE + 1
DV_TweenMachine.TIMELINEVISIBLE_ONLY			= MOHO.MSG_BASE + 2
DV_TweenMachine.APPLY_TO_REFS					= MOHO.MSG_BASE + 3
DV_TweenMachine.PRECALC_CYCLES	 				= MOHO.MSG_BASE + 4
DV_TweenMachine.IGNORE_STRING_CHANNELS			= MOHO.MSG_BASE + 5
DV_TweenMachine.IGNORE_HIDDEN_BONES				= MOHO.MSG_BASE + 6
DV_TweenMachine.SELECTED_BONES_ONLY				= MOHO.MSG_BASE + 7
DV_TweenMachine.USE_STEP_INTERVALS				= MOHO.MSG_BASE + 8
DV_TweenMachine.EQUALCHECK						= MOHO.MSG_BASE + 9
DV_TweenMachine.PREVIEWSLIDERCHECK				= MOHO.MSG_BASE + 10
DV_TweenMachine.TOGGLE_ACCURATE_SLIDER_PREVIEW	= MOHO.MSG_BASE + 11
-- * Slider:
DV_TweenMachine.SLIDER_DRAG						= MOHO.MSG_BASE + 12
DV_TweenMachine.SLIDER_DRAG_TEXT_INPUT			= MOHO.MSG_BASE + 13
DV_TweenMachine.BUTTON_MSG 						= MOHO.MSG_BASE + 100
DV_TweenMachine.ALT_BUTTON_MSG					= MOHO.MSG_BASE + 200
DV_TweenMachine.TWEEK_PLUS 						= MOHO.MSG_BASE + 14
DV_TweenMachine.TWEEK_PLUS_ALT					= MOHO.MSG_BASE + 15
DV_TweenMachine.TWEEK_MINUS 					= MOHO.MSG_BASE + 16
DV_TweenMachine.TWEEK_MINUS_ALT					= MOHO.MSG_BASE + 17
DV_TweenMachine.SLIDER_OK		 				= MOHO.MSG_BASE + 18

function DV_TweenMachine:DoLayout(moho, layout)
	-- * Tween machine:
	-- self.twname = LM.GUI.StaticText(self:Localize("Tween Machine:"))
	-- layout:AddChild(self.twname)
	-- * Settings dropdown:
	self.settingsMenu = LM.GUI.Menu(self:Localize("Settings"))
	self.settingsMenu_popup = LM.GUI.PopupMenu(100, false)
	self.settingsMenu_popup:SetMenu(self.settingsMenu)
	self.settingsMenu:AddItem(self:Localize("ApplyToChildLayers"), 0, self.APPLY_TO_CHILD_LAYERS)	
	-- self.settingsMenu:AddItem(self:Localize("Timeline-visible Child Layers Only"), 0, self.TIMELINEVISIBLE_ONLY)
	-- self.settingsMenu:AddItem(self:Localize("ApplyToRefs"), 0, self.APPLY_TO_REFS)	
	-- self.settingsMenu:AddItem(self:Localize("PrecalcCycles"), 0, self.PRECALC_CYCLES)	
	-- self.settingsMenu:AddItem(self:Localize("IgnoreStringChannels"), 0, self.IGNORE_STRING_CHANNELS)	
	self.settingsMenu:AddItem(self:Localize("IgnoreHiddenBones"), 0, self.IGNORE_HIDDEN_BONES)
	self.settingsMenu:AddItem(self:Localize("SelectedBonesOnly"), 0, self.SELECTED_BONES_ONLY)
	-- self.settingsMenu:AddItem(self:Localize("Use step intervals"), 0, self.USE_STEP_INTERVALS)
	-- self.settingsMenu:AddItem(self:Localize("Set key even if value does not change"), 0, self.EQUALCHECK)
	-- self.settingsMenu:AddItem(self:Localize("Preview toolbar (disable if scrubbing/playback is slow)"), 0, self.PREVIEWSLIDERCHECK)
	-- self.settingsMenu:AddItem(self:Localize("Less accurate toolbar preview (uses current layer's keys only, faster)"), 0, self.TOGGLE_ACCURATE_SLIDER_PREVIEW)
	layout:AddChild(self.settingsMenu_popup)
	self.settingsMenu_popup:SetToolTip(self:Localize("settingsMenuTooltip"))

	-- * buttons
	for i=1 , self.NumberOFButtons do
		local btnName = LM.GUI.Button("", self.BUTTON_MSG + i)
		self.tweenButton[i]=btnName
		self.tweenButton[i]:SetLabel(self.presetButton[i])
		layout:AddChild(self.tweenButton[i])
		btnName:SetAlternateMessage(self.ALT_BUTTON_MSG + i)
	end

	self.tweekMinus = LM.GUI.Button("", self.TWEEK_MINUS)
	self.tweekMinus:SetLabel("<<")
	layout:AddChild(self.tweekMinus)
	self.tweekMinus:SetAlternateMessage(self.TWEEK_MINUS_ALT)
	
	self.tweekPlus = LM.GUI.Button("", self.TWEEK_PLUS)
	self.tweekPlus:SetLabel(">>")
	layout:AddChild(self.tweekPlus)
	self.tweekPlus:SetAlternateMessage(self.TWEEK_PLUS_ALT)

	-- * Slider:
	local sliderSize = 200
	self.slider = LM.GUI.Slider(sliderSize,false,false, self.SLIDER_DRAG)
	layout:AddChild(self.slider)
	self.slider:SetRange(-15,115)
	self.slider:SetNumTicks(3)
	self.slider:SetShowTicks(true)
	self.slider:SetSnapToTicks(false)
	-- * Slider text input:
	self.slidertext = LM.GUI.TextControl(30, "0", self.SLIDER_DRAG_TEXT_INPUT, LM.GUI.FIELD_INT, MOHO.Localize(""))
	self.slidertext:SetWheelInc(1)
	layout:AddChild(self.slidertext)
	layout:AddPadding(-15)
	self.perc = LM.GUI.StaticText(self:Localize("%"))
	layout:AddChild(self.perc)	
	self.sliderOK = LM.GUI.Button(self:Localize("OK"), self.SLIDER_OK)
	layout:AddChild(self.sliderOK)
end

-- **************************************************
-- Find key
-- **************************************************
function DV_TweenMachine:FindKey(channels, start_frame, end_frame, moho)
	local found_key = false
	local layer = moho.layer
	local timing_offset
	local increment
	if end_frame > start_frame then
		increment = 1
	else
		increment = -1
	end
	if layer.TotalTimingOffset ~= nil then
		timing_offset = layer:TotalTimingOffset()
	else
	timing_offset = 0
	end
	-- * Go through next/previous frames and stop when a keyframe is found
	for frame = start_frame, end_frame, increment do
		for i, channel in ipairs(channels) do
		   if channel.HasKey ~= nil and channel:HasKey(frame) then
			   found_key = true
			   break
		   end
		end
		if found_key then
			return frame-timing_offset
		end
	end
	return 0
end



function DV_TweenMachine:UpdateWidgets(moho)

	self.slider:SetValue(self.sliderVal)
	self.slidertext:SetValue(self.slidertextVal)
	-- * Update slider:
	--local preview = true
	-- if( true) then
		-- self.currentPercentage = 0
		-- if(not self.slider:IsMouseDragging()) then
			-- local layer = moho.layer  
			-- local current_frame 
			-- if moho.layerFrame ~= nil then
				-- current_frame = moho.layerFrame
			-- else
				-- current_frame = moho.frame
			-- end
			-- -- * Get channels:
			-- local channels = {}
			-- channels = self:GetChannels(moho)

			-- -- * Check whether there are any keys to the left of the cursor
			-- local prevKey = self:FindKey(channels, current_frame-1, 0, moho)
			-- local nextKey = self:FindKey(channels, current_frame, moho.document:AnimDuration(), moho)
			-- if current_frame > moho.document:AnimDuration() then
				-- nextKey = current_frame
			-- end
			-- if nextKey ~= 0 then
				-- -- *
				-- local range = math.abs(nextKey-prevKey)
				-- currentPercentage = (current_frame-prevKey)/range*100
				-- self.sliderVal = currentPercentage
				-- self.slidertextVal = currentPercentage
			-- else
				-- if prevKey ~= nextKey then
					-- currentPercentage = 100
				-- end
			-- end
		-- end
		-- self.slidertextVal = currentPercentage
		-- self.sliderVal = currentPercentage

		-- -- *
		-- self.slider:SetValue(self.sliderVal)	
		-- self.slidertext:SetValue(self.slidertextVal)
	
	-- end
	-- * Update menu:

	self.settingsMenu:SetEnabled(self.TOGGLE_ACCURATE_SLIDER_PREVIEW, self.previewSlider)
	-- *
	self.settingsMenu:SetChecked(self.APPLY_TO_CHILD_LAYERS, self.applyToChildLayers)
	self.settingsMenu:SetChecked(self.TIMELINEVISIBLE_ONLY, self.timelinevisibleOnly)	
	self.settingsMenu:SetChecked(self.APPLY_TO_REFS, self.applyToRefs)
	self.settingsMenu:SetChecked(self.PRECALC_CYCLES, self.precalcCycles)
	self.settingsMenu:SetChecked(self.IGNORE_STRING_CHANNELS, self.ignoreStringChannels)	
	self.settingsMenu:SetChecked(self.IGNORE_HIDDEN_BONES, self.ignoreHiddenBones)	
	self.settingsMenu:SetChecked(self.SELECTED_BONES_ONLY, self.selectedBonesOnly)
	self.settingsMenu:SetChecked(self.USE_STEP_INTERVALS, self.useStepIntervals)
	self.settingsMenu:SetChecked(self.EQUALCHECK, self.ifEqual)
	self.settingsMenu:SetChecked(self.PREVIEWSLIDERCHECK, self.previewSlider)

end

function DV_TweenMachine:HandleMessage(moho, view, msg)
	if msg == self.SLIDER_DRAG then
		if not self.undoPrepped then
			moho.document:PrepMultiUndo()
			moho.document:SetDirty()
			self.undoPrepped = true
		end
		self.slidertext:SetValue(self.slider:Value())
		self.slidertextVal = self.slider:Value()
		if(not self.slider:IsMouseDragging()) then	

			self.sliderVal = self.slider:Value()
			self:SetKeyTween(moho, self.slider:Value())
			moho.layer:UpdateCurFrame()		
			self.undoPrepped = false
			moho:UpdateUI()
		end
	elseif msg == self.SLIDER_DRAG_TEXT_INPUT then
		self.slidertextVal = self.slidertext:Value()
		self.sliderVal = self.slidertext:Value()
		moho.document:PrepMultiUndo()
		moho.document:SetDirty()
		self:SetKeyTween(moho, self.slidertext:Value())
		moho:UpdateUI()
		moho.layer:UpdateCurFrame()
		self.sliderVal = self.slider:Value()
	elseif msg == self.APPLY_TO_CHILD_LAYERS then
		self.settingsMenu:SetChecked(msg, not self.settingsMenu:IsChecked(msg))
		self.applyToChildLayers = self.settingsMenu:IsChecked(self.APPLY_TO_CHILD_LAYERS)
	elseif msg == self.TIMELINEVISIBLE_ONLY then
		self.settingsMenu:SetChecked(msg, not self.settingsMenu:IsChecked(msg))
		self.timelinevisibleOnly = self.settingsMenu:IsChecked(self.TIMELINEVISIBLE_ONLY)		
	elseif msg == self.APPLY_TO_REFS then
		self.settingsMenu:SetChecked(msg, not self.settingsMenu:IsChecked(msg))
		self.applyToRefs = self.settingsMenu:IsChecked(self.APPLY_TO_REFS)		
	elseif msg == self.PRECALC_CYCLES then
		self.settingsMenu:SetChecked(msg, not self.settingsMenu:IsChecked(msg))
		self.precalcCycles = self.settingsMenu:IsChecked(self.PRECALC_CYCLES)
	elseif msg == self.IGNORE_STRING_CHANNELS then
		self.settingsMenu:SetChecked(msg, not self.settingsMenu:IsChecked(msg))
		self.ignoreStringChannels = self.settingsMenu:IsChecked(self.IGNORE_STRING_CHANNELS)
	elseif msg == self.IGNORE_HIDDEN_BONES then
		self.settingsMenu:SetChecked(msg, not self.settingsMenu:IsChecked(msg))
		self.ignoreHiddenBones = self.settingsMenu:IsChecked(self.IGNORE_HIDDEN_BONES)
	elseif msg == self.SELECTED_BONES_ONLY then
		self.settingsMenu:SetChecked(msg, not self.settingsMenu:IsChecked(msg))
		self.selectedBonesOnly = self.settingsMenu:IsChecked(self.SELECTED_BONES_ONLY)
	elseif msg == self.USE_STEP_INTERVALS then
		self.settingsMenu:SetChecked(msg, not self.settingsMenu:IsChecked(msg))
		self.useStepIntervals = self.settingsMenu:IsChecked(self.USE_STEP_INTERVALS)	
	elseif msg == self.EQUALCHECK then
		self.settingsMenu:SetChecked(msg, not self.settingsMenu:IsChecked(msg))
		self.ifEqual = self.settingsMenu:IsChecked(msg)

		
	elseif msg >= self.BUTTON_MSG and msg <= self.BUTTON_MSG+50 then
		
		local val = self.presetButton[msg-self.BUTTON_MSG]
		self:SetKeyTween(moho, val)
		self.slidertext:SetValue(val)
		self.slidertextVal = val
		self.sliderVal = val
		moho:UpdateUI()
		moho.layer:UpdateCurFrame()
		
	elseif msg >= self.ALT_BUTTON_MSG and msg <= self.ALT_BUTTON_MSG+50 then
		
		local val = -self.presetButton[msg-self.ALT_BUTTON_MSG]
		self:SetKeyTween(moho, val)
		self.slidertext:SetValue(val)
		self.slidertextVal = val
		self.sliderVal = val
		moho:UpdateUI()
		moho.layer:UpdateCurFrame()

	elseif msg == self.TWEEK_PLUS then		
		local val = self.slidertextVal+1
		self.slidertext:SetValue(val)
		self.slidertextVal = val
		self.sliderVal = val
		self:SetKeyTween(moho, val)

		moho:UpdateUI()
		moho.layer:UpdateCurFrame()
		
	elseif msg == self.TWEEK_PLUS_ALT then		
		local val = self.slidertextVal+2
		self.slidertext:SetValue(val)
		self.slidertextVal = val
		self.sliderVal = val
		self:SetKeyTween(moho, val)

		moho:UpdateUI()
		moho.layer:UpdateCurFrame()
		
	elseif msg == self.TWEEK_MINUS then		
		local val = self.slidertextVal-1
		self.slidertext:SetValue(val)
		self.slidertextVal = val
		self.sliderVal = val
		self:SetKeyTween(moho, val)

		moho:UpdateUI()
		moho.layer:UpdateCurFrame()
		
	elseif msg == self.TWEEK_MINUS_ALT then		
		local val = self.slidertextVal-2
		self.slidertext:SetValue(val)
		self.slidertextVal = val
		self.sliderVal = val
		self:SetKeyTween(moho, val)

		moho:UpdateUI()
		moho.layer:UpdateCurFrame()
		
	elseif msg == self.SLIDER_OK then
		self.sliderVal = self.slidertext:Value()
		moho.document:PrepMultiUndo()
		moho.document:SetDirty()
		self:SetKeyTween(moho,self.slidertext:Value())
		moho:UpdateUI()
		moho.layer:UpdateCurFrame()
		self.sliderVal = self.slider:Value()
	end
end

function DV_TweenMachine:DrawMe(moho, view)
	LM_TransformLayer:DrawMe(moho, view)
end


-- **************************************************
-- Get channels that are supposed to be excluded
-- **************************************************
function DV_TweenMachine: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

-- **************************************************
-- ??? Don't quite understand this AE_ function. What is track animation?
-- **************************************************
function DV_TweenMachine:CheckChannelForAddKeyTween(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 DV_TweenMachine:CheckChannelForAddKey(moho, channel, channelFrame)
--davoodice	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

-- **************************************************
-- Get channels used for tween
-- **************************************************
function DV_TweenMachine:GetChannels(moho)
	local channels = {}
	local layersCollection = self:GetSelectedLayers(moho)
	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:CheckChannelForAddKeyTween(moho, channel, curFrame) then
						local derivedChannel = AE_Utilities:GetDerivedChannel(moho, channel)
						table.insert(channels, derivedChannel)
					end
				end
			end
		end
	end
	return channels
end

-- **************************************************
-- Get layer channels only
-- **************************************************
function DV_TweenMachine:LayerChannels(moho)
	local layerChannels = {}
	local chInfo = MOHO.MohoLayerChannel:new_local()
	for i = 0, moho.layer:CountChannels()-1 do
	    moho.layer:GetChannelInfo(i, chInfo)
	    if not chInfo.selectionBased then
	        for j=0, chInfo.subChannelCount-1 do
	            local subChannel = moho.layer:Channel(i, j, moho.document)
	            local channel = nil 
	            if subChannel:ChannelType() == MOHO.CHANNEL_VAL then channel = moho:ChannelAsAnimVal(subChannel)
	            elseif subChannel:ChannelType() == MOHO.CHANNEL_VEC2  then channel = moho:ChannelAsAnimVec2(subChannel)
	            elseif subChannel:ChannelType() == MOHO.CHANNEL_VEC3  then channel = moho:ChannelAsAnimVec3(subChannel)
	            elseif subChannel:ChannelType() == MOHO.CHANNEL_BOOL  then channel = moho:ChannelAsAnimBool(subChannel)
	            elseif subChannel:ChannelType() == MOHO.CHANNEL_COLOR  then channel = moho:ChannelAsAnimColor(subChannel)
	            elseif subChannel:ChannelType() == MOHO.CHANNEL_STRING  then channel = moho:ChannelAsAnimString(subChannel)
	            end
	            -- do something with it
	            if channel then
	            	table.insert(layerChannels, channel)
	            end
	        end
	    end
	end
	return layerChannels
end

-- **************************************************
-- Set a key at a certain percentage
-- **************************************************
function DV_TweenMachine:SetKeyTween(moho, percentage,tweek)
	tweek =  false
	if(tweek) then
		self:SetKey(moho)
	end
	
	percentage =percentage or 100
	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:CheckChannelForAddKeyTween(moho, channel, curFrame) then
						local derivedChannel = AE_Utilities:GetDerivedChannel(moho, channel)
						--local fromFrame = curFrame
						local fromFrameP = curFrame
						local fromFrameN = curFrame
						local keyID = channel:GetClosestKeyID(curFrame)
						local pID = keyID
						local nID = keyID + 1
						
						if(channel:HasKey(curFrame) and  not tweek) then
							pID = keyID - 1
							nID = keyID + 1
						end				
					--	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
						fromFrameP = channel:GetKeyWhen(pID)
						-- * Fix
						if nID == channel:CountKeys() then
							return
						end
						fromFrameN = channel:GetKeyWhen(nID)
						--print(fromFrameP.."   "..fromFrameN)
						local valP = derivedChannel:GetValue(fromFrameP)
						local valN = derivedChannel:GetValue(fromFrameN)				
								if derivedChannel.AreDimensionsSplit and channel:AreDimensionsSplit() then
									for sss = 0, 2 do
										local subChannel = derivedChannel:DimensionChannel(sss)
										if subChannel then
											local valPs = subChannel:GetValue(fromFrameP)
											local valNs = subChannel:GetValue(fromFrameN)
											
											if(valPs~= nil and valNs ~= nil) then
												local vals = (valNs - valPs) * percentage/100 + valPs
											--	local oldval = derivedChannel:GetValue(curFrame)
												subChannel:SetValue(curFrame, vals)
										    end
											
											
											
										--	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)
									if(valP~= nil and valN ~= nil and type(valP)~= "boolean" and type(valN)~= "boolean") then
										local val = (valN - valP) * percentage/100 + valP
										--local oldval = derivedChannel:GetValue(curFrame)
										derivedChannel:SetValue(curFrame, val)
									end
								end
	
						end
					end
				end
			end
		end
	end
end


function DV_TweenMachine: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)
						--davoodice modify for accpet key if exist key in current frame
						if(channel:HasKey(curFrame)) then
							keyID = keyID - 1
						end	
						if (self.getFromKey == self.NEXT_KEY and channel:HasKey(curFrame)) then keyID = keyID + 2 	
							elseif 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(self.getFromKey == self.PREVIOUS_KEY ) then keyID = keyID - 1 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
---------------------------------------------------------------------------------------------------------------------

-- **************************************************
-- Get all selected layers
-- **************************************************
function DV_TweenMachine:GetSelectedLayers(moho)
	local layersCollection = AE_Utilities:MohoListToTable(moho.document, moho.document.CountSelectedLayers, moho.document.GetSelectedLayer)
	if not self.applyToChildLayers 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 not self.timelinevisibleOnly or layer:IsShownOnTimeline() 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() then table.remove(childLayersCollection, i) end
		end
	end
	return childLayersCollection
end

-- **************************************************
-- Get a channel's frame value if it's cycled
-- **************************************************
function DV_TweenMachine: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

-- **************************************************
-- Check if a frame on a channel is cycled
-- **************************************************
function DV_TweenMachine: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

-- **************************************************
-- Check if a table contains an element
-- **************************************************
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 DV_TweenMachine:Localize(text)
	local fileWord = MOHO.Localize("/Menus/File/File=File")
	
	local phrase = {}
	
	phrase["Name"] = "DV_Tween Machine"
	phrase["Description"] = "Tween Machine"
	phrase["UILabel"] = "DV Tween Machine"

	phrase["Settings"] = "Settings"
	phrase["SettingsTooltip"] = "Which layers and channels to include for all operations"
	
	phrase["ApplyToChildLayers"] = "Include Child layers"	
	phrase["ApplyToRefs"] = "Include Referenced layers"
	phrase["PrecalcCycles"] = "Include Cycles"
	phrase["SelectedBonesOnly"] = "Apply to Selected bones only"
	phrase["IgnoreStringChannels"] = "Ignore string channels"	
	phrase["IgnoreHiddenBones"] = "Ignore hidden bones"
		
	if fileWord == "Файл" then
		phrase["Name"] = "Инструменты ключей"
		phrase["Description"] = "Инструменты ключей"
		phrase["UILabel"] = "Инструменты ключей"

		phrase["Settings"] = "Settings"
		phrase["SettingsTooltip"] = "Which layers and channels to include for all operations"
		
		phrase["ApplyToChildLayers"] = "Дочерние слои"
		phrase["ApplyToRefs"] = "Референсы"
		phrase["PrecalcCycles"] = "Циклы"
		phrase["IgnoreStringChannels"] = "Игнорировать строчные каналы"
		phrase["IgnoreHiddenBones"] = "Игнорировать спрятанные кости"
		phrase["ChildLayersTooltip"] = "Обрабатывать всю иерархию дочерних слоев каждого выбранного слоя"
	end
	return phrase[text] or text;
end




Icon
DV Tween Machine
Listed

Script type: Tool

Uploaded: Mar 13 2022, 01:21

help to create break down keyframes for bone or layer transform
for mor information go to this link
you need AE_utility to use this script.
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: 224