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

ScriptName = "AE_Lipsync"

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

AE_Lipsync = {}

function AE_Lipsync:Name()
	return 'Lipsync tool'
end

function AE_Lipsync:Version()
	return '1.16'
end

function AE_Lipsync:UILabel()
	return 'Lipsync tool'
end

function AE_Lipsync:Creator()
	return 'Alexandra Evseeva'
end

function AE_Lipsync:Description()
	return 'Apply lipsync prepared in "lipsync" action with Names set by markers and filters set by red and orange labled keys.'
end

function AE_Lipsync:OnMouseDown(moho, mouseEvent)
    -- need for tool instead of button
end

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

function AE_Lipsync:IsRelevant(moho)
	return true
end

function AE_Lipsync:IsEnabled(moho)
	return true
end

function AE_Lipsync:LoadPrefs(prefs)
	self.accuracy = prefs:GetFloat("AE_Lipsync.accuracy", 0.1)
	self.numButtons = prefs:GetInt("AE_Lipsync.numButtons", 6)
	self.buttonSize = prefs:GetInt("AE_Lipsync.buttonSize", 3)
end

function AE_Lipsync:SavePrefs(prefs)
	prefs:SetFloat("AE_Lipsync.accuracy", self.accuracy)
	prefs:SetInt("AE_Lipsync.numButtons", self.numButtons)
	prefs:SetInt("AE_Lipsync.buttonSize", self.buttonSize)
end

function AE_Lipsync:ResetPrefs()
	AE_Lipsync.currentAction = AE_Lipsync.defaultAction
	AE_Lipsync.numButtons = 6
	AE_Lipsync.buttonSize = 3
	AE_Lipsync.accuracy = 0.1
end

-- **************************************************
-- Keyboard/Mouse Control
-- **************************************************

-- **************************************************
-- Tool Panel Layout
-- **************************************************

AE_Lipsync.defaultAction = "lipsync"
AE_Lipsync.currentAction = AE_Lipsync.defaultAction
AE_Lipsync.numButtons = 6
AE_Lipsync.buttonSize = 3
AE_Lipsync.accuracy = 0.1
--AE_Lipsync.lastLayer = nil

AE_Lipsync.DEFAULT_BUTTON = MOHO.MSG_BASE + 100
AE_Lipsync.ALTER_BUTTON = MOHO.MSG_BASE + 150
AE_Lipsync.DEFAULT_MENU = MOHO.MSG_BASE + 50
AE_Lipsync.ADD2CURRENT = MOHO.MSG_BASE + 1
AE_Lipsync.ADD2NEW = MOHO.MSG_BASE + 2
AE_Lipsync.REMOVE_BUTTON = MOHO.MSG_BASE + 3
AE_Lipsync.AUTOMAP_BUTTON = MOHO.MSG_BASE + 4
AE_Lipsync.CURRENT_BUTTON = MOHO.MSG_BASE + 5

local settingsDialog = {}
local newActionDialog = {}

function AE_Lipsync:DoLayout(moho, layout)

	
	self.dlog = settingsDialog:new()
	self.settingsPopup = LM.GUI.PopupDialog('settings...', false, 0)
	self.settingsPopup:SetDialog(self.dlog)
	layout:AddChild(self.settingsPopup, LM.GUI.ALIGN_LEFT, 0)
	
	--TODO: uncomment for enabling add new label to current or new action
	--[[
	self.addButton = LM.GUI.Button('add to:', self.ADD2CURRENT)
	layout:AddChild(self.addButton, LM.GUI.ALIGN_LEFT, 0)
	self.addButton:SetAlternateMessage(self.ADD2NEW)
	--]]	
	
	self.actionMenu = LM.GUI.Menu('action for apply')
	self.actionMenu_popup = LM.GUI.PopupMenu(120, true)
	self.actionMenu_popup:SetMenu(self.actionMenu)
	layout:AddChild(self.actionMenu_popup, LM.GUI.ALIGN_LEFT, 0)
	

	self.defaultButton = LM.GUI.Button('default', self.DEFAULT_BUTTON)
	layout:AddChild(self.defaultButton, LM.GUI.ALIGN_LEFT, 0)
	self.currentButton = LM.GUI.Button('current', self.CURRENT_BUTTON)
	layout:AddChild(self.currentButton, LM.GUI.ALIGN_LEFT, 0)	
	
	self.buttons = {}
	local defaultText = ""
	for i=1, self.buttonSize do defaultText = defaultText .. "." end
	for i=1, self.numButtons do
		local nextButton = LM.GUI.Button(defaultText, self.DEFAULT_BUTTON + i)
		layout:AddChild(nextButton, LM.GUI.ALIGN_LEFT, 0)
		table.insert(self.buttons, {["button"] = nextButton, ["text"] = "..."})
		nextButton:SetAlternateMessage(self.ALTER_BUTTON + i)
	end
	
	self.removeButton = LM.GUI.Button('remove', self.REMOVE_BUTTON)
	layout:AddChild(self.removeButton, LM.GUI.ALIGN_LEFT, 0)	
	self.removeButton:SetToolTip("Remove current set keys from current frame")
	
	self.automapButton = LM.GUI.Button('auto map', self.AUTOMAP_BUTTON)
	layout:AddChild(self.automapButton, LM.GUI.ALIGN_LEFT, 0)	
	self.automapButton:SetToolTip("Set keys for selected labels interval")	

end


function AE_Lipsync:UpdateWidgets(moho)
	--if moho.layer ~= self.lastLayer then
		self:PopulateActionList(moho)
		self:PrepareAction(moho, self.currentAction)
		--self.lastLayer = moho.layer
	--end
end

function AE_Lipsync:PopulateActionList(moho)
	-- find all the actions for active layer, its children or parents, which have marker channel inside it
	local actionNames = {}
	for i, layer in AE_Utilities:IterateAllLayers(moho) do
		if layer == moho.layer or AE_Utilities:IsAncestor(layer, moho.layer) or AE_Utilities:IsAncestor(moho.layer, layer) then
			local markerChannel = layer.fTimelineMarkers
			for a=0, markerChannel:CountActions()-1 do
				local nextName = markerChannel:ActionName(a)
				local allways_added = false
				for k,v in pairs(actionNames) do
					if v == nextName then
						allways_added = true
						break
					end
				end
				if not allways_added then
					table.insert(actionNames, nextName)
				end
			end
		end
	end
	self.actionMenu:RemoveAllItems()
	for k, v in pairs(actionNames) do
		local m = self.DEFAULT_MENU + k
		self.actionMenu:AddItem(v, 0, m)
		if v == self.currentAction then self.actionMenu:SetChecked(m, true) end
	end
	if self.actionMenu:FirstChecked() < 0 and self.actionMenu:CountItems() > 0 then
		self.actionMenu:SetChecked(self.DEFAULT_MENU + 1, true)
		self.currentAction = actionNames[1]
	end
end


function AE_Lipsync:HandleMessage(moho, view, msg)
	if msg > self.DEFAULT_MENU and msg < self.DEFAULT_BUTTON then
		self.currentAction = self.actionMenu:FirstCheckedLabel()
		self:PrepareAction(moho, self.currentAction)
	elseif msg == self.DEFAULT_BUTTON then
		moho.document:SetDirty()
		moho.document:PrepUndo()
		self:ApplyAction(moho, self.currentAction)
	elseif msg == self.CURRENT_BUTTON then
		moho.document:SetDirty()
		moho.document:PrepUndo()
		self:ApplyAction(moho, self.currentAction, nil, true)		
	elseif msg == self.REMOVE_BUTTON then
		moho.document:SetDirty()
		moho.document:PrepUndo()
		self:RemoveActions(moho, self.currentAction)		
	elseif msg == self.AUTOMAP_BUTTON then
		moho.document:SetDirty()
		moho.document:PrepUndo()
		self:AutoMapActions(moho, self.currentAction)	
	elseif msg >= self.DEFAULT_BUTTON and msg < self.ALTER_BUTTON then
		moho.document:SetDirty()
		moho.document:PrepUndo()	
		local theButton = self.buttons[msg - self.DEFAULT_BUTTON]
		if theButton.text == '...' then
			local newLabel = self:AddAction(moho, self.currentAction)
			if newLabel then 
				moho:UpdateUI()
			end
		else
			self:ApplyAction(moho, self.currentAction, theButton.text)
		end
	elseif msg >= self.ALTER_BUTTON then
		moho.document:SetDirty()
		moho.document:PrepUndo()
		local theButton = self.buttons[msg - self.ALTER_BUTTON]
		if theButton.text == '...' then 
			local newLabel = self:AddAction(moho, self.currentAction)
			if newLabel then 
				moho:UpdateUI()
			end
		else
			self:FixAction(moho, self.currentAction, theButton.text)
		end
	end
end

function AE_Lipsync:FindActionMarkersChannel(moho, actionName)
	for i, layer in AE_Utilities:IterateAllLayers(moho) do
		if layer == moho.layer or AE_Utilities:IsAncestor(layer, moho.layer) or AE_Utilities:IsAncestor(moho.layer, layer) then
			local actionChannel = layer.fTimelineMarkers:ActionByName(actionName)
			if actionChannel and actionChannel:Duration() > 0 then
				return moho:ChannelAsAnimString(actionChannel)
			end
		end
	end
end

function AE_Lipsync:PrepareAction(moho, actionName)
	local markerChannel = self:FindActionMarkersChannel(moho, actionName)
	local buttonCounter = 1
	if markerChannel then
		for k=1, markerChannel:CountKeys()-1 do
			local nextText = markerChannel:GetValueByID(k)
			local alsoFound = false
			for i=1, buttonCounter-1 do
				if self.buttons[i].text == nextText then
					alsoFound = true
					break
				end
			end
			if nextText == "default" then alsoFound = true end
			if not alsoFound then
				self.buttons[buttonCounter].text = nextText
				self.buttons[buttonCounter].button:SetLabel(nextText, false)
				self.buttons[buttonCounter].button:Enable(true)
				buttonCounter = buttonCounter + 1
				if buttonCounter > self.numButtons then break end
			end
		end
	end
	if buttonCounter > self.numButtons then return end
	self.buttons[buttonCounter].button:SetLabel("...", false)
	self.buttons[buttonCounter].text = "..."
	for i = buttonCounter + 1, self.numButtons do
		self.buttons[i].button:SetLabel(".", false)
		self.buttons[i].button:Enable(false)	
	end
end

function AE_Lipsync:GetFilteringChannels(moho, actionName)
	local redChannels = {}
	local orangeChannels = {}
	local interp = MOHO.InterpSetting:new_local()
	for i, layer in AE_Utilities:IterateAllLayers(moho) do
		if layer == moho.layer or AE_Utilities:IsAncestor(layer, moho.layer) or AE_Utilities:IsAncestor(moho.layer, layer) then
			for j, i, channel, chInfo in AE_Utilities:IterateAllChannels(moho, layer) do
				local actionChannel = channel:ActionByName(actionName)
				if actionChannel then
					for k=1, actionChannel:CountKeys()-1 do
						actionChannel:GetKeyInterpByID(k, interp)
						if interp.tags == 1 then 
							table.insert(redChannels, {["channel"] = channel, ["layer"]=layer})
							--print(i, " ", j, " ", chInfo.name:Buffer())
							break
						elseif interp.tags == 2 then
							table.insert(orangeChannels, {["channel"] = channel, ["layer"]=layer})
							break
						end
					end
				end
			end
		end
	end
	return redChannels, orangeChannels
end

function AE_Lipsync:FindLabeledFrame(moho, actionName, label, filterFrame, redChannels, orangeChannels)
	if not redChannels and not orangeChannels then 
		redChannels, orangeChannels = self:GetFilteringChannels(moho, actionName)
	end
	--print("Found ", #redChannels, " red and ", #orangeChannels, " orange channels")
	--for i,c in pairs(redChannels) do print(tostring(c.channel)) end
	--for i,c in pairs(orangeChannels) do print(tostring(c.channel)) end
	local markerChannel = self:FindActionMarkersChannel(moho, actionName)	
	actionFrame = nil
	local isFliped = false
	for k=1, markerChannel:CountKeys()-1 do
		if markerChannel:GetValueByID(k) == label then
			local t = markerChannel:GetKeyWhen(k)
			--print("checking key ", k, " at frame ", t)			
			local filtered = true
			for i,v in pairs(orangeChannels) do
				local channel = AE_Utilities:GetDerivedChannel(moho, v.channel)
				local curValue = channel:GetValue(filterFrame + v.layer:TotalTimingOffset())
				local actChannel = AE_Utilities:GetDerivedChannel(moho, v.channel:ActionByName(actionName))
				local actValue = actChannel:GetValue(t)
				--print("orange ", 180*curValue/math.pi, " ", 180*actValue/math.pi)
				if not AE_Utilities:IsEqualValues(channel, curValue, actValue, self.accuracy) then
					filtered = false
					break
				else
					--print("not equal")
				end
			end
			if filtered then 
				for i,v in pairs(redChannels) do
					local channel = AE_Utilities:GetDerivedChannel(moho, v.channel)
					local zeroValue = channel:GetValue(0) 
					local curValue = channel:GetValue(filterFrame + v.layer:TotalTimingOffset()) 
					--print("first: ", curValue/math.pi*180, " (frame ", filterFrame + v.layer:TotalTimingOffset(), " )")
					curValue = curValue + AE_Utilities:SumActionInfluences(moho, filterFrame + v.layer:TotalTimingOffset(), channel, v.layer)
					--print("second: ", curValue/math.pi*180)
					local actChannel = AE_Utilities:GetDerivedChannel(moho, v.channel:ActionByName(actionName))
					local actValue = actChannel:GetValue(t)
					--print("red ", 180*curValue/math.pi, " ", 180*actValue/math.pi)
					if math.abs(math.abs(curValue-zeroValue) - math.abs(actValue-zeroValue)) > self.accuracy then						
						filtered = false
						break
					else
						if math.abs((curValue-zeroValue) - (actValue-zeroValue))  > self.accuracy then
							isFliped = true
						end
					end
				end					
			end
			if filtered then 
				return t, isFliped
			end
		end
	end	
	return actionFrame
end

function AE_Lipsync:SearchForOpposite(moho, layer)
	local currentLayer = layer
	local parents = {}
	while not (string.sub(currentLayer:Name(),-1) == "R" or string.sub(currentLayer:Name(),-1) == "L") do
		if not currentLayer:Parent() then return nil end
		table.insert(parents, currentLayer:Name()) 		
		currentLayer = currentLayer:Parent()

	end
	
	local sourceName = currentLayer:Name()
	local targetName = string.sub(sourceName, 1, -2)
	if string.sub(sourceName,-1) == "R" then targetName = targetName .. "L"
	elseif string.sub(sourceName,-1) == "L" then targetName = targetName .. "R"
	else return nil end
		
	local layersHolder = moho.document
	if currentLayer:Parent() then
		layersHolder = currentLayer:Parent()
	end
	local oppositeLayer = nil
	for i=0, layersHolder:CountLayers()-1 do
		local nextLayer = layersHolder:Layer(i)
		if nextLayer:Name() == targetName then 
			oppositeLayer = nextLayer
			break
		end
	end
	if not oppositeLayer then return nil end
	
	local nextParent = oppositeLayer
	local nextChild = nil
	if #parents == 0 then return oppositeLayer end
	for i = #parents, 1, -1 do
		nextParent = moho:LayerAsGroup(nextParent)
		nextChild = nil
		if not nextParent then return nil end
		for j=0, nextParent:CountLayers()-1 do
			if nextParent:Layer(j):Name() == parents[i] then
				nextChild = nextParent:Layer(j)
				break
			end
		end
		if not nextChild then return nil end
		nextParent = nextChild
	end
	if nextChild:Name() == layer:Name() then return nextChild end
	return nil
end

function AE_Lipsync:IterateActionValueChannels(moho, actionName, redChannels, orangeChannels, isFliped)
	if not redChannels and not orangeChannels then 
		redChannels, orangeChannels = self:GetFilteringChannels(moho, actionName)
	end
	local channels2iterate = {}
	for i, layer in AE_Utilities:IterateAllLayers(moho) do
		if layer == moho.layer or AE_Utilities:IsAncestor(moho.layer, layer) then
			local oppositeLayer = nil
			if isFliped then 
				oppositeLayer = self:SearchForOpposite(moho, layer) 
			end
			for subID, ID, channel, chInfo in AE_Utilities:IterateAllChannels(moho, layer) do			
				local isFiltering = false
				for j,v in pairs(redChannels) do
					if v.channel == channel then
						isFiltering = true
						break
					end
				end
				if not isFiltering then
					for j,v in pairs(orangeChannels) do
						if v.channel == channel then
							isFiltering = true
							break
						end
					end				
				end
				if not isFiltering then
					local actChannel = channel:ActionByName(actionName)			
					if oppositeLayer then
						local oppInfo = MOHO.MohoLayerChannel:new_local()
						oppositeLayer:GetChannelInfo(ID, oppInfo)
						if oppInfo.subChannelCount >= chInfo.subChannelCount then
							local oppositeChannel = oppositeLayer:Channel(ID, subID, moho.document)
							actChannel = oppositeChannel and oppositeChannel:ActionByName(actionName)
						else 
							actChannel = nil
						end
					end
					if actChannel and actChannel:Duration() > 0 then 
						local curChannel = AE_Utilities:GetDerivedChannel(moho, channel)
						actChannel = AE_Utilities:GetDerivedChannel(moho, actChannel)
						local item2insert = {["channel"] = curChannel, ["actChannel"] = actChannel, ["layer"] = layer, ["chInfo"]=chInfo}
						table.insert(channels2iterate, item2insert)					
					end					
				end
			end
		end
	end
	return pairs(channels2iterate)
end

function AE_Lipsync:ApplyAction(moho, actionName, label, setCurrent)

	if setCurrent then
		for i, layer in AE_Utilities:IterateAllLayers(moho) do
			for subID, ID, channel, chInfo in AE_Utilities:IterateAllChannels(moho, layer) do
				for k = 0, channel:CountKeys() - 1 do
					channel:SetKeySelectedByID(k, false)
				end
			end
			layer:SetShownOnTimeline(false)
		end
	end

	-- find filtering channels storing them into two arrays
	local actionFrame = 0
	local isFilped = false

	local redChannels, orangeChannels = self:GetFilteringChannels(moho, actionName)
	-- if label then find action frame corresponding to filters
	if label then
		actionFrame, isFliped = self:FindLabeledFrame(moho, actionName, label, moho.frame, redChannels, orangeChannels)
		--print("found at ", actionFrame, " flip is ", tostring(isFilped))
		if not actionFrame then return LM.GUI.Alert(LM.GUI.ALERT_WARNING, "No label " .. label .. " meet current filters in " .. actionName) end
	end
	-- iterate action channels wich is not filtering ones and apply values from actionFrame
	for k,v in self:IterateActionValueChannels(moho, actionName, redChannels, orangeChannels, isFliped) do
		local layerFrame = moho.frame + v.layer:TotalTimingOffset()
		if setCurrent then 
			v.channel:SetValue(layerFrame, v.channel:GetValue(layerFrame))
			v.layer:SetShownOnTimeline(true)
			v.channel:SetKeySelected(layerFrame, true)
		else 
			v.channel:SetValue(layerFrame, v.actChannel:GetValue(actionFrame))
		end
		-- if lipsync closest key is STEP then set step for this and previous keys
		if v.actChannel:GetKeyInterpModeByID(v.actChannel:GetClosestKeyID(actionFrame)) == MOHO.INTERP_STEP then
			local interp = MOHO.InterpSetting:new_local()
			v.channel:GetKeyInterp(layerFrame, interp)
			interp.interpMode = MOHO.INTERP_STEP
			v.channel:SetKeyInterp(layerFrame, interp)
			local prevKey = v.channel:GetClosestKeyID(layerFrame) - 1
			if prevKey > 0 then 
				v.channel:GetKeyInterpByID(prevKey, interp)
				interp.interpMode = MOHO.INTERP_STEP
				v.channel:SetKeyInterpByID(prevKey, interp)
			end
		end
		v.layer:UpdateCurFrame()
	end
	
	moho:UpdateSelectedChannels()
	moho:UpdateUI()
end

function AE_Lipsync:RemoveActions(moho, actionName)
	-- iterate current action channels but not filtering ones
	-- and remove current frame key if exist
	for k,v in self:IterateActionValueChannels(moho, actionName) do
		v.channel:DeleteKey(moho.frame)
		v.layer:UpdateCurFrame()
	end
	moho:UpdateSelectedChannels()
	moho:UpdateUI()	
end

function AE_Lipsync:AutoMapActions(moho, actionName)
	-- collect selected markers and get min and max frames
	local mainMarkerChannel = nil
	local minFrame = 10000
	local maxFrame = -10000
	for i, layer in AE_Utilities:IterateAllLayers(moho) do
		if layer == moho.layer or AE_Utilities:IsAncestor(layer, moho.layer) or AE_Utilities:IsAncestor(moho.layer, layer) then
			local nextMarkerChannel = layer.fTimelineMarkers
			for k=1, nextMarkerChannel:CountKeys()-1 do
				if nextMarkerChannel:IsKeySelectedByID(k) then 
					local fr = nextMarkerChannel:GetKeyWhen(k)
					if minFrame > fr then minFrame = fr end
					if maxFrame < fr then maxFrame = fr end
				end
			end
			if maxFrame > -10000 then
				mainMarkerChannel = nextMarkerChannel
				if minFrame >= 10000 then minFrame = 1 end
				break
			end
		end
	end
	-- from min to max frame 
	-- iterate channels 
	local redChannels, orangeChannels = self:GetFilteringChannels(moho, actionName)
	for f=minFrame, maxFrame do
		local actionFrame = nil
		if mainMarkerChannel:HasKey(f) then
			local label = mainMarkerChannel:GetValue(f)
			if label == "" then actionFrame = 0
			else
				actionFrame, isFliped = self:FindLabeledFrame(moho, actionName, label, f, redChannels, orangeChannels)
			end
		end
		for k,v in self:IterateActionValueChannels(moho, actionName, redChannels, orangeChannels, isFliped) do		
			-- set value if marker exists (finding filtered value)
			if actionFrame then 
				v.channel:SetValue(f, v.actChannel:GetValue(actionFrame))
			else
				--remove keys if no marker 
				v.channel:DeleteKey(f)
			end
		end
	end
	for k,v in self:IterateActionValueChannels(moho, actionName, redChannels, orangeChannels) do
		v.layer:UpdateCurFrame()
	end
	moho:UpdateSelectedChannels()
	moho:UpdateUI()		
end

-- **************************************************
-- settingsDialog
-- **************************************************



settingsDialog.MSG_BASE = MOHO.MSG_BASE + 500
settingsDialog.ACCURACY = settingsDialog.MSG_BASE
settingsDialog.BUTTONS_NUMBER = settingsDialog.MSG_BASE + 1
settingsDialog.BUTTON_SIZE = settingsDialog.MSG_BASE + 2
settingsDialog.FORCE_KEYS = settingsDialog.MSG_BASE + 3

function settingsDialog:new()
	local d = LM.GUI.SimpleDialog('', settingsDialog)
	local l = d:GetLayout()
	
	d.forceKeysCheck = LM.GUI.CheckBox('Use keyed channels not included in action', d.FORCE_KEYS)
	l:AddChild(d.forceKeysCheck, LM.GUI.ALIGN_LEFT, 0)

	d.textControl1Input = LM.GUI.TextControl(0, '5.000', d.ACCURACY, LM.GUI.FIELD_UFLOAT, 'Filtering accuracy:')
	l:AddChild(d.textControl1Input, LM.GUI.ALIGN_LEFT, 0)
	d.textControl1Input:SetToolTip('Set 22.5 degrees for all frames in 45 degrees interval')
	--d.textControl1Input:SetWheelInc(0.0001)

	d.buttonsNumberInput = LM.GUI.TextControl(0, '6', d.BUTTONS_NUMBER, LM.GUI.FIELD_INT, 'Buttons number:')
	l:AddChild(d.buttonsNumberInput, LM.GUI.ALIGN_LEFT, 0)

	d.buttonSizeInput = LM.GUI.TextControl(0, '3', d.BUTTON_SIZE, LM.GUI.FIELD_INT, 'Button size:')
	l:AddChild(d.buttonSizeInput, LM.GUI.ALIGN_LEFT, 0)

	d.pressAltCtrlShiftLToUpdateToolText = LM.GUI.DynamicText('Press Alt Ctrl Shift L to update tool', 0)
	l:AddChild(d.pressAltCtrlShiftLToUpdateToolText, LM.GUI.ALIGN_LEFT, 0)
	return d
end

function settingsDialog:UpdateWidgets(moho)
	self.textControl1Input:SetValue(180 * AE_Lipsync.accuracy/math.pi)
	self.buttonsNumberInput:SetValue(AE_Lipsync.numButtons)
	self.buttonSizeInput:SetValue(AE_Lipsync.buttonSize)
	self.forceKeysCheck:SetValue(AE_Lipsync.forceKeys)
end

function settingsDialog:OnOK(moho)
	AE_Lipsync.accuracy = math.pi * self.textControl1Input:FloatValue()/180
	AE_Lipsync.numButtons = self.buttonsNumberInput:IntValue()
	AE_Lipsync.buttonSize = self.buttonSizeInput:IntValue()
	AE_Lipsync.forceKeys = self.forceKeysCheck:Value()
end

function settingsDialog:HandleMessage(msg)
	--[[
	if msg == self.ACCURACY then
		print('Message ACCURACY received')
	elseif msg == self.BUTTONS_NUMBER then
		print('Message BUTTONS_NUMBER received')
	elseif msg == self.BUTTON_SIZE then
		print('Message BUTTON_SIZE received')
	else
		
	end
	--]]
end

-- **************************************************
-- newActionDialog
-- **************************************************

newActionDialog.MSG_BASE = MOHO.MSG_BASE + 600
newActionDialog.ACTION = newActionDialog.MSG_BASE + 1
newActionDialog.LABEL = newActionDialog.MSG_BASE + 2
newActionDialog.newAction = false
AE_Lipsync.newActionName = ""
AE_Lipsync.newLabelName = ""
newActionDialog.BONELIST = newActionDialog.MSG_BASE + 10

function newActionDialog:new(moho)
	local d = LM.GUI.SimpleDialog('Create new pose from current keys', newActionDialog)
	local l = d:GetLayout()

	--[[
	d.newActionNameControl = LM.GUI.TextControl(200, '', d.ACTION, LM.GUI.FIELD_TEXT, 'New action name:')
	l:AddChild(d.newActionNameControl, LM.GUI.ALIGN_LEFT, 0)
	--]] 
	
	d.newLabelControl = LM.GUI.TextControl(100, '', d.LABEL, LM.GUI.FIELD_TEXT, 'New pose label:')
	l:AddChild(d.newLabelControl, LM.GUI.ALIGN_LEFT, 0)	

	--[[
	self.bonelistMenu = LM.GUI.Menu('Filtering smartbones')
	self.bonelistMenu_popup = LM.GUI.PopupMenu(120, false)
	self.bonelistMenu_popup:SetMenu(self.actionMenu)
	layout:AddChild(self.bonelistMenu_popup, LM.GUI.ALIGN_LEFT, 0)
	--]]

	return d
end

function newActionDialog:UpdateWidgets(moho)
	--self.newActionNameControl:Enable(self.newAction)
	--self.bonelistMenu_popup:Enable(self.newAction)
	--TODO: populate self.bonelistMenu with active layer or its skeleton parent layer's smartbones, 
	--TODO: checking thouse having keys in current frame
end

function newActionDialog:OnOK(moho)
	--AE_Lipsync.newActionName = self.newActionNameControl:Value()
	AE_Lipsync.newLabelName = self.newLabelControl:Value()	
	--TODO: and something about selected smartbones
end

function AE_Lipsync:AddFilterValue(moho, actionName, actionFrame, channel, layer, colorTag)
	local layerFrame = moho.frame + layer:TotalTimingOffset()
	channel = AE_Utilities:GetDerivedChannel(moho, channel)
	local actionChannel = channel:ActionByName(actionName)
	actionChannel = AE_Utilities:GetDerivedChannel(moho, actionChannel)
	actionChannel:SetValue(actionFrame, channel:GetValue(layerFrame))
	local interp = MOHO.InterpSetting:new_local()
	actionChannel:GetKeyInterp(actionFrame, interp)
	interp.tags = colorTag
	actionChannel:SetKeyInterp(actionFrame, interp)
end

function AE_Lipsync:AddAction(moho, actionName)

	local dlog = newActionDialog:new(moho)
	--TODO: set newActionDialog.newAction
	if (dlog:DoModal() == LM.GUI.MSG_CANCEL) then
		return nil
	end
	
	moho.document:SetDirty()
	moho.document:PrepUndo(nil)
	
	--TODO: if newAction - create it with filters and all current frame keys for layer and its children
	--TODO: if not -- add labeled key to currently active action (what to do with filters?..)
	
	local newName = self.newLabelName
	local nextFrame = moho.layer:ActionDuration(actionName) + 1
	local markerChannel = self:FindActionMarkersChannel(moho, actionName)
	markerChannel:SetValue(nextFrame, newName)
	local redChannels, orangeChannels = self:GetFilteringChannels(moho, actionName)
	for i, v in pairs(redChannels) do
		self:AddFilterValue(moho, actionName, nextFrame, v.channel, v.layer, 1)
	end
	for i, v in pairs(orangeChannels) do
		self:AddFilterValue(moho, actionName, nextFrame, v.channel, v.layer, 2)
	end	
	self:FixAction(moho, actionName, newName)
	markerChannel:SetValue(nextFrame, newName)
	return newName
	
end



function AE_Lipsync:FixAction(moho, actionName, label)
	--get action frame (using filtering)
	local redChannels, orangeChannels = self:GetFilteringChannels(moho, actionName)
	-- if label then find action frame corresponding to filters
	if label then
		actionFrame, isFliped = self:FindLabeledFrame(moho, actionName, label, moho.frame, redChannels, orangeChannels)
		--print("found at ", actionFrame, " flip is ", tostring(isFilped))
		if not actionFrame then return LM.GUI.Alert(LM.GUI.ALERT_WARNING, "No label " .. label .. " meet current filters in " .. actionName) end
	end
	local markerChannel = self:FindActionMarkersChannel(moho, actionName)
	--iterate existing action channels and fix their values
	for k,v in self:IterateActionValueChannels(moho, actionName, redChannels, orangeChannels, isFliped) do
		if v.actChannel ~= markerChannel then
			local layerFrame = moho.frame + v.layer:TotalTimingOffset()
			v.actChannel:SetValue(actionFrame, v.channel:GetValue(layerFrame))
		end
	end
	
	local newChannels = {}
	if moho.layer:LayerType() == MOHO.LT_VECTOR then		
		local mesh = moho:Mesh()
		for i = 0, mesh:CountPoints() - 1 do
			if mesh:Point(i).fSelected then
				local point = mesh:Point(i)
				table.insert(newChannels, {channel=point.fAnimPos, layer=moho.layer})
				for c = 0, point:CountCurves() - 1 do
					local where = -1
					local curve, where = point:Curve (id, where)
					local curvatureChannel = AE_Utilities:GetCurvatureChannel(moho, moho.layer, curve, where)
					if curvatureChannel then table.insert(newChannels, {channel=curvatureChannel, layer=moho.layer}) end
					for _, channelType in pairs({'GetWeightChannel', 'GetOffsetChannel'}) do
						for _, handleType in pairs({true, false}) do
							local nextChannel = AE_Utilities[channelType](AE_Utilities, moho, moho.layer, curve, where, handleType)
							if nextChannel then table.insert(newChannels, {channel=nextChannel, layer=moho.layer}) end
						end
					end
				end
			end
		end
	end

	if self.forceKeys then
		for i, layer in AE_Utilities:IterateAllLayers(moho) do
			if layer:IsAncestorSelected() or layer == moho.layer then
				local layerFrame = moho.frame + layer:TotalTimingOffset()
				for k, j, channel, chInfo in AE_Utilities:IterateAllChannels(moho, moho.layer) do
					if channel:HasKey(layerFrame) then table.insert(newChannels, {channel=channel, layer=layer}) end
				end
			end
		end
	end
	
	for i, entry in pairs(newChannels) do
		local channel = entry.channel
		if not channel:ActionByName(actionName) then		
			channel = AE_Utilities:GetDerivedChannel(moho, channel)
			local layerFrame = moho.frame + entry.layer:TotalTimingOffset()
			local curValue = channel:GetValue(layerFrame)
			channel:ActivateAction(actionName)
			actChannel = channel:ActionByName(actionName)
			actChannel = AE_Utilities:GetDerivedChannel(moho, actChannel)
			actChannel:SetValue(actionFrame, curValue)
			if actionFrame > 1 then actChannel:SetValue(actionFrame - 1, actChannel:GetValue(0)) end
			--if actionFrame < moho.layer:ActionDuration(actionName) then 
			actChannel:SetValue(actionFrame + 1, actChannel:GetValue(0)) --end
			channel:ActivateAction("")
		end
	end

	
	moho:UpdateSelectedChannels()
	moho:UpdateUI()
	
end

Icon
Morph-Lipsync
Listed

Script type: Tool

Uploaded: Jun 26 2022, 14:08

Last modified: Jul 19 2022, 03:54

Apply marked frames of actions to main timeline with buttons
Mainly used for lipsync. Draft presentation in video:

Final presentation is coming... not soon.
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: 104