Image
-- TODO:
-- * Sync actions knopje, zodat je alle actions hebt.
-- * Export actions knopje. Misschien in sync inbouwen met checkboxes per actie die niet in de source bestaat

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

ScriptName = "LK_InsertActions"

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

LK_InsertActions = {}

function LK_InsertActions:Name()
	return 'Insert Actions'
end

function LK_InsertActions:Version()
	return '1.0'
end

function LK_InsertActions:UILabel()
	return 'Insert Actions'
end

function LK_InsertActions:Creator()
	return 'Lukas Krepel, Frame Order'
end

function LK_InsertActions:Description()
	return 'Insert Poses and Animation saved in Actions into the Mainline via toolbar dropdown menus.'
end

function LK_InsertActions:ColorizeIcon()
	return true
end

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

function LK_InsertActions:IsRelevant(moho)
	if MohoMode ~= nil then
		return MohoMode.keys
	else
		return true
	end
end

function LK_InsertActions:IsEnabled(moho)
	return true
end

-- **************************************************
-- Draw everything in the viewport
-- **************************************************
function LK_InsertActions:DrawMe(moho, view)
	FO_Utilities:DrawMeMod(moho, view, -1)
end

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

function LK_InsertActions:OnMouseDown(moho, mouseEvent)
	if LK_SelectBone ~= nil then
		LK_SelectBone:OnMouseDown(moho, mouseEvent)
	end
end

function LK_InsertActions:OnMouseMoved(moho, mouseEvent)
	if LK_SelectBone ~= nil then
		LK_SelectBone:OnMouseMoved(moho, mouseEvent)
	end
end

function LK_InsertActions:OnMouseUp(moho, mouseEvent)
	if LK_SelectBone ~= nil then
		LK_SelectBone:OnMouseUp(moho, mouseEvent)
	end
end

function LK_InsertActions:OnKeyDown(moho, keyEvent)
	if LK_Powertool ~= nil then
		LK_Powertool:OnKeyDown(moho, keyEvent)
	end
end

LK_InsertActions.TOGGLE_KEY_PREV_FRAME = 				MOHO.MSG_BASE
LK_InsertActions.TOGGLE_RELATIVE_POS = 					MOHO.MSG_BASE + 1

LK_InsertActions.SAVE_NEW_POSE =						MOHO.MSG_BASE + 17
LK_InsertActions.SAVE_NEW_ANIMATION =					MOHO.MSG_BASE + 18

LK_InsertActions.MARK_ROOT_BONE =						MOHO.MSG_BASE + 19

LK_InsertActions.TOGGLE_CHANNELS =						MOHO.MSG_BASE + 50 -- * 50 t/m 65

LK_InsertActions.INSERT_POSE =							MOHO.MSG_BASE + 100 -- 100 t/m 199 gereserveerd
LK_InsertActions.INSERT_ANIMATION =						MOHO.MSG_BASE + 200 -- 200 t/m 299 gereserveerd

LK_InsertActions.keyPrevFrame = false
LK_InsertActions.relativePos = true

-- **************************************************
-- Load / Saves Preferences
-- **************************************************
function LK_InsertActions:LoadPrefs(prefs)
	-- * Channels:
	FO_Channels:LoadPrefs(prefs)
	-- * Options:
	self.keyPrevFrame = prefs:GetBool("LK_InsertActions.keyPrevFrame", false)
	self.relativePos = prefs:GetBool("LK_InsertActions.relativePos", true)
end

function LK_InsertActions:SavePrefs(prefs)
	-- * Channels:
	FO_Channels:SavePrefs(prefs)
	-- * Options:
	prefs:SetBool("LK_InsertActions.keyPrevFrame", self.keyPrevFrame)
	prefs:SetBool("LK_InsertActions.relativePos", self.relativePos)
end

function LK_InsertActions:ResetPrefs()
	-- * Channels:
	FO_Channels:ResetPrefs()
	-- * Options:
	LK_InsertActions.keyPrevFrame = false
	LK_InsertActions.relativePos = true
	LK_InsertActions.stepInterpolation = true
end

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

function LK_InsertActions:DoLayout(moho, layout)
	FO_Utilities.tinyUI = false
	-- ************************	
	-- *** Create Actions:  ***
	-- ************************
	FO_Utilities:Divider(layout, "Create Actions", true)
	layout:PushH(LM.GUI.ALIGN_CENTER, 2)	
	-- * Pose:
	self.newPoseButton = LM.GUI.ImageButton("ScriptResources/FO_icons/add_pose", "Save New Pose", false, self.SAVE_NEW_POSE, true)
	layout:AddChild(self.newPoseButton)
	-- * Animation:
	self.newAnimationButton = LM.GUI.ImageButton("ScriptResources/FO_icons/add_animation", "Save New Animations", false, self.SAVE_NEW_ANIMATION, true)
	layout:AddChild(self.newAnimationButton)
	layout:Pop()
	-- *****************	
	-- *** Actions:  ***
	-- *****************
	FO_Utilities:Divider(layout, "Insert Actions", false)
	layout:PushH(LM.GUI.ALIGN_CENTER, 2)
	-- * Poses:
	self.PosesDropdown = LM.GUI.Menu('Pose')
	local posesDropdownSize = 75
	local animationsDropdownSize = 75
	if not FO_Utilities.tinyUI then
		-- posesDropdownSize = 150
		-- animationsDropdownSize = 150
	end	
	self.PosesDropdown_popup = LM.GUI.PopupMenu(posesDropdownSize, false)
	self.PosesDropdown_popup:SetMenu(self.PosesDropdown)
	self.PosesDropdown_popup:SetToolTip("Insert a Pose into the Mainline")
	-- * Animations:
	layout:AddChild(self.PosesDropdown_popup, LM.GUI.ALIGN_LEFT, 0)
	self.AnimationsDropdown = LM.GUI.Menu('Animation')
	self.AnimationsDropdown_popup = LM.GUI.PopupMenu(animationsDropdownSize, false)
	self.AnimationsDropdown_popup:SetMenu(self.AnimationsDropdown)
	self.AnimationsDropdown_popup:SetToolTip("Insert Animation into the Mainline")
	layout:AddChild(self.AnimationsDropdown_popup, LM.GUI.ALIGN_LEFT, 0)
	layout:Pop()
	-- **************************	
	-- *** Layers & Channels: ***
	-- **************************
	FO_Channels:DoLayout(moho, layout, self.TOGGLE_CHANNELS)
	-- *****************	
	-- *** Options:  ***
	-- *****************
	FO_Utilities:Divider(layout, "Options", false)
	-- * Key previous frame checkbox:
	self.KeyPoseOnPreviousFrameCheckbox = LM.GUI.CheckBox("Key previous frame", self.TOGGLE_KEY_PREV_FRAME)
	layout:AddChild(self.KeyPoseOnPreviousFrameCheckbox)
	-- * Relative position checkbox:
	self.relativePosCheckbox = LM.GUI.CheckBox("Relative placement", self.TOGGLE_RELATIVE_POS)
	layout:AddChild(self.relativePosCheckbox)
	-- * Mark Root Bone Button:
	self.markAsRootBoneButton = LM.GUI.ImageButton("ScriptResources/FO_icons/mark_root_bone", "Mark selected bone as root bone", false, self.MARK_ROOT_BONE, true)
	layout:AddChild(self.markAsRootBoneButton)
	layout:AddPadding(-14)
	self.rootBoneText = LM.GUI.DynamicText("Rootbone: Room for a long bone name")
	layout:AddChild(self.rootBoneText)
end

function LK_InsertActions:UpdateWidgets(moho)
	FO_Channels:UpdateWidgets(moho)
	local layer = moho.layer
	self.poses = {}
	self.animations = {}
	for a=0, layer:CountActions()-1 do
		actionName = layer:ActionName(a)
		if not layer:IsSmartBoneAction(actionName) then
			local duration = layer:ActionDuration(actionName)
			if duration == 1 then
				table.insert(self.poses, actionName)
			elseif duration > 1 then
				table.insert(self.animations, actionName)
			end
		end
	end
	-- * Sort alphabetically:
	table.sort(self.poses, function(a,b) return a < b end)
	table.sort(self.animations, function(a,b) return a < b end)
	-- * Clear dropdown items (so UpdateUI() doesn't create duplicates)
	self.PosesDropdown:RemoveAllItems()
	self.AnimationsDropdown:RemoveAllItems()
	-- * Do stuff:
	for i = 1, #self.poses do
		self.PosesDropdown:AddItem(self.poses[i], nil, self.INSERT_POSE + i)
		self.PosesDropdown:SetEnabled(self.INSERT_POSE + i, moho.frame ~= 0)
	end

	for i = 1, #self.animations do
		self.AnimationsDropdown:AddItem(self.animations[i], nil, self.INSERT_ANIMATION + i)
		self.AnimationsDropdown:SetEnabled(self.INSERT_ANIMATION + i, moho.frame ~= 0)
	end
	-- *** Checkboxes:
	-- * Options:
	self.KeyPoseOnPreviousFrameCheckbox:SetValue(self.keyPrevFrame)
	self.relativePosCheckbox:SetValue(self.relativePos)
	-- * Root bone:
	local skel = moho:Skeleton()
	local rootBoneButtonEnable = false
	if skel ~= nil then
		local selectedBoneID = skel:SelectedBoneID()
		if selectedBoneID ~= -1 then
			rootBoneButtonEnable = (skel:Bone(selectedBoneID).fParent == -1)
		end
	end
	self.markAsRootBoneButton:Enable(rootBoneButtonEnable)
	local ScriptInfo = moho.layer:ScriptData()
	self.rootBoneName = ScriptInfo:GetString("Rootbone")
	if self.rootBoneName ~= "" then
		self.rootBoneText:SetValue("Rootbone: "..self.rootBoneName)
	else
		self.rootBoneText:SetValue("Rootbone: -")
	end
end

function LK_InsertActions:HandleMessage(moho, view, msg)
	FO_Channels:HandleMessage(moho, view, msg, self.TOGGLE_LAYER_CHANNELS)
	if msg == self.MARK_ROOT_BONE then
		moho.document:PrepUndo("", moho.layer, false)
		moho.document:SetDirty()
		local skel = moho:Skeleton()
		local selectedBoneID = skel:SelectedBoneID()
		self.rootBoneName = skel:Bone(selectedBoneID):Name()
		local ScriptInfo = moho.layer:ScriptData()
		ScriptInfo:Set("Rootbone", self.rootBoneName)
	elseif msg == self.SAVE_NEW_POSE or msg == self.SAVE_NEW_ANIMATION then
		-- * Inser mainline into action:
		local first = moho.frame
		local last = moho.frame
		if (msg == self.SAVE_NEW_ANIMATION) then
			first = MOHO.MohoGlobals.PlayStart
			last = MOHO.MohoGlobals.PlayEnd
			if first == -1 or last == -1 then
				local channels = {}
				FO_Channels:AddLayerChannels(moho, moho.layer, channels)
				for i = 1, #channels do
					local channel = channels[i]
					local endKey = channel:Duration()
					local keyCount = channel:CountKeys() - 1
					local keysFound = 0
					local frameNum = endKey
					while keysFound < keyCount do
						thisKey = channel:GetClosestKeyID(frameNum)
						local keyFrameNum = channel:GetKeyWhen(thisKey)
						keysFound = 1 + keysFound
						if channel:IsKeySelected(keyFrameNum) then
							if keyFrameNum < first or first == -1 then
								first = keyFrameNum
							end
							if keyFrameNum > last then
								last = keyFrameNum
							end
						end
						frameNum = keyFrameNum - 1
					end
				end
			end
			if first == -1 or last == -1 then
				FO_Utilities:Alert("Either select a range of keys or set Play-Start and Play-End to use as in/out-points!")
				return
			end
		end
		local actionName = ""
		local n = 0
		if (msg == self.SAVE_NEW_ANIMATION) then
			while moho.layer:HasAction(actionName) or actionName == "" do
				n = n + 1
				actionName = "Animation "..#self.animations+n
			end
		else
			while moho.layer:HasAction(actionName) or actionName == "" do
				n = n + 1
				actionName = "Pose "..#self.poses+n
			end
		end
		local dlog = FO_Utilities:InputTextDialog(moho, "Pose", "Name:", actionName):DoModal()
		if (dlog == LM.GUI.MSG_CANCEL) then
			return
		end
		actionName = FO_Utilities.input
		if moho.layer:HasAction(actionName) then
			FO_Utilities:Alert("Action '"..actionName.."' already exists!")
			return
		end
		-- * Prep Undo:
		moho.document:PrepUndo("", moho.layer, false)
		moho.document:SetDirty()
		-- * FREEZE POSE:
		-- LK_Powertool:HandleMessage(moho, view, LK_Powertool.ADD_KEYSET_CURRENT)
		local fromAction = moho.layer:CurrentAction()
		local tempAction = "Temporary_Mainline_Copy"
		if fromAction == "" then
			-- * Trying to get from mainline, which is not possible.
			-- * Creating temporary action and copy mainline into it, we'll delete it after we're done.
			moho.layer:ActivateAction(tempAction)
			fromAction = tempAction
			moho.layer:InsertAction("", 1)
			-- *** Freeze first and last frame in temporary action:
			-- * Get layers:
			local layers = {}
			FO_Channels:AddLayerWithChildren(moho, moho.layer, layers)
			-- * Get channels for layers:
			local channels = {}
			for i = 1, #layers do
				local layer = layers[i]
				FO_Channels:AddLayerChannels(moho, layer, channels)
			end
			channels = FO_Channels:ChannelsAsTypes(moho, channels)
			for i = 1, #channels do
				local channel = channels[i]
				channel:SetValue(first, channel:GetValue(first))
				channel:SetValue(last, channel:GetValue(last))
			end
			-- *** End Freeze.
		end
		-- * Get frame:
		local frame = moho.frame
		-- * Create action:
		moho.layer:ActivateAction(actionName)
		self:InsertAction(moho, fromAction, 1, first, last)
		-- * Clear markers:
		moho.layer.fTimelineMarkers:Clear()
		-- * Clean channels:
		Syn_Clean_Keys_Severe:Run(moho, view)
		-- * Go back to Mainline:
		moho.layer:DeleteAction(tempAction)
		moho.layer:ActivateAction("") -- * Return to mainline (temp disabled for debugging)
		moho.layer:DeleteAction("")
		-- * Notify:
		if (moho.layer:ActionDuration(actionName) == 0) then
			moho.layer:DeleteAction(actionName)
			-- * Update:
			moho.layer:UpdateCurFrame(true)
			moho:UpdateUI()
			MOHO:Redraw()
			FO_Utilities:Alert("Created action '"..actionName.."' was empty and has been deleted!")
			return
		end
		-- * Update:
		moho.layer:UpdateCurFrame(true)
		moho:UpdateUI()
		MOHO:Redraw()
		FO_Utilities:Alert("Created new Action: '"..actionName.."'.")
	elseif msg == self.TOGGLE_KEY_PREV_FRAME then
		self.keyPrevFrame = not self.keyPrevFrame
	elseif msg == self.TOGGLE_RELATIVE_POS then
		self.relativePos = not self.relativePos
	elseif msg > self.INSERT_POSE and msg < (self.INSERT_POSE + 99) then
		-- * Prep Undo:
		moho.document:PrepUndo("", moho.layer, false)
		moho.document:SetDirty()
		local option = (msg - self.INSERT_POSE)
		local actionName = self.poses[option]
		self:InsertAction(moho, actionName, moho.frame)
	elseif msg > self.INSERT_ANIMATION and msg < (self.INSERT_ANIMATION + 99) then
		-- * Prep Undo:
		moho.document:PrepUndo("", moho.layer, false)
		moho.document:SetDirty()
		local option = (msg - self.INSERT_ANIMATION)
		local actionName = self.animations[option]
		self:InsertAction(moho, actionName, moho.frame)
	end
	FO_Utilities:PaintKeys(moho)
	moho.layer:UpdateCurFrame(true)
	moho:UpdateUI()
	MOHO:Redraw()
end

function LK_InsertActions:InsertAction(moho, actionName, when, first, last)
	local length = 0
	when = when or moho.frame
	first = first or 1
	last = last or 999999999
	if when < 1 then
		return
	end
	-- * Get layers:
	local layers = {}
	FO_Channels:AddLayerWithChildren(moho, moho.layer, layers)
	-- * Get channels for layers:
	local channels = {}
	for i = 1, #layers do
		local layer = layers[i]
		FO_Channels:AddLayerChannels(moho, layer, channels)
	end
	channels = FO_Channels:ChannelsAsTypes(moho, channels)
	-- **** Relative:
	-- * Layer Translation:
	local translationChannel = moho:ChannelAsAnimVec3(moho.layer.fTranslation)
	local layerTranslationOffsetValue = LM.Vector3:new_local()
	layerTranslationOffsetValue = translationChannel.value - translationChannel:GetValue(0)
	local translationActionChannel = translationChannel:ActionByName(actionName)
	if translationActionChannel ~= nil then
		translationActionChannel = FO_Channels:ChannelAsType(moho, translationActionChannel)
		layerTranslationOffsetValue = layerTranslationOffsetValue - translationActionChannel:GetValue(1) + translationActionChannel:GetValue(0)
	end
	-- * Layer Scale:
	local scaleChannel = moho:ChannelAsAnimVec3(moho.layer.fScale)
	local layerScaleOffsetValue = LM.Vector3:new_local()
	layerScaleOffsetValue = scaleChannel.value
	-- * Bone Translation:
	local rootBonePosChannel = nil
	local rootBoneOffsetValue = LM.Vector2:new_local()
	local rootBoneTranslationChannels = {}
	if moho.layer:IsBoneType() then
		local boneLayer = moho:LayerAsBone(moho.layer)
		local skel = boneLayer:Skeleton()
		local rootBone = nil
		-- * Look for selected rootbone and all other rootbones:
		for i = 0, skel:CountBones() - 1 do
			local bone = skel:Bone(i)
			if bone:Name() == self.rootBoneName then
				rootBone = bone
			end
			-- * Also check for all rootbones without parents:
			if bone.fParent == -1 then
				table.insert(rootBoneTranslationChannels, bone.fAnimPos)
			end
		end
		-- * If no bone with a Hip tag is found, find the first root bone without a parent:
		if rootBone == nil then
			for i = 0, skel:CountBones() - 1 do
				local bone = skel:Bone(i)
				if bone.fParent == -1 and not bone.fHidden then
					rootBone = bone
					break
				end
			end
		end
		-- *
		if rootBone ~= nil then
			rootBonePosChannel = rootBone.fAnimPos
			rootBonePosChannel = FO_Channels:ChannelAsType(moho, rootBonePosChannel)
			rootBoneOffsetValue = rootBonePosChannel.value - rootBonePosChannel:GetValue(0)			
			local rootBonePosActionChannel = rootBonePosChannel:ActionByName(actionName)
			if rootBonePosActionChannel ~= nil then
				rootBonePosActionChannel = FO_Channels:ChannelAsType(moho, rootBonePosActionChannel)
				rootBoneOffsetValue = rootBoneOffsetValue - rootBonePosActionChannel:GetValue(1) + rootBonePosActionChannel:GetValue(0)
			end
		end
	end
	-- *
	for c = 1, #channels do
		local channel = channels[c]
		local actionChannel = channel:ActionByName(actionName)
		local keyedChannel = false
		if actionChannel ~= nil then
			actionChannel = FO_Channels:ChannelAsType(moho, actionChannel)
			local endKey = actionChannel:Duration()
			if endKey > last then
				endKey = last
			end
			local thisKey = 0
			local keyCount = actionChannel:CountKeys() - 1
			local keysFound = 0
			local frameNum = endKey
			if self.keyPrevFrame and keyCount > 0 and when ~= 1 then
				channel:SetValue(when-1, channel:GetValue(when-1))
			end
			while keysFound < keyCount do
				thisKey = actionChannel:GetClosestKeyID(frameNum)
				local keyFrameNum = actionChannel:GetKeyWhen(thisKey)
				if keyFrameNum > length then
					length = keyFrameNum
				end
				keysFound = keysFound + 1
				local interp = MOHO.InterpSetting:new_local()
				actionChannel:GetKeyInterp(keyFrameNum, interp)
				-- *
				local actionKeyValue = actionChannel:GetValue(keyFrameNum)
				local frame = when + keyFrameNum - first
				-- *
				if self.relativePos then
					-- * Relative layer position:
					if channel == translationChannel then
						-- * TODO...Adjust translation to scale? (actionKeyValue:Set(actionKeyValue.x * layerScaleOffsetValue.x, actionKeyValue.y * layerScaleOffsetValue.y, actionKeyValue.z) -- * NOT MULTIPLYING Z-AXIS!
						actionKeyValue = actionKeyValue + layerTranslationOffsetValue
					end
					-- * Relative layer scale:
					if channel == scaleChannel then
						-- * Assuming Scale=1 is the scale used in Actions. Note that it can be different from frame 0.
						actionKeyValue:Set(actionKeyValue.x * layerScaleOffsetValue.x, actionKeyValue.y * layerScaleOffsetValue.y, actionKeyValue.z * layerScaleOffsetValue.z)
					end
					-- * Relative root bone position:
					if table.contains(rootBoneTranslationChannels, channel) then
						actionKeyValue = actionKeyValue + rootBoneOffsetValue
					end
				end
				--
				if keyFrameNum >= first and keyFrameNum <= last then
					-- * Set key!
					channel:SetValue(frame, actionKeyValue)
					-- * Adjust interpolation (for poses only):
					if (moho.layer:ActionDuration(actionName) == 1) or (first == last) then
						if MOHO.MohoGlobals.DefaultInterp ~= -1 then -- * -1 = COPY PREVIOUS KEY
							interp.interpMode = MOHO.MohoGlobals.DefaultInterp
						else
							prevKey = channel:GetClosestKeyID(when-1)
							prevKeyFrameNum = channel:GetKeyWhen(prevKey)
							local prevInterp = MOHO.InterpSetting:new_local()
							channel:GetKeyInterp(prevKeyFrameNum, prevInterp)
							if prevInterp.interpMode ~= 5 then -- * 5 = INTERP_CYCLE
								interp.interpMode = prevInterp.interpMode
							else
								interp.interpMode = 0 -- * 1 = INTERP_SMOOTH
							end
						end
					end
					channel:SetKeyInterp(frame, interp)
					keyedChannel = true
				end
				frameNum = keyFrameNum - 1
			end
		end
		-- * Key channels that are not keyed in the action:
		if not keyedChannel then
			-- * Only on channels that already have a key:
			if channel:CountKeys() > 1 then
				if channel.value ~= channel:GetValue(0) then
					if self.keyPrevFrame then
						channel:SetValue(when-1, channel:GetValue(when-1))
					end
					-- * Relative layer position:
					if self.relativePos and channel == translationChannel then
						local newVal = channel.value
						channel:SetValue(when, newVal)
					elseif self.relativePos and channel == scaleChannel then
						local newVal = channel.value
						channel:SetValue(when, newVal)
					-- * Relative root bone position:
					elseif table.contains(rootBoneTranslationChannels, channel) then
						local newVal = channel.value
						actionKeyValue = newVal + rootBoneOffsetValue
						channel:SetValue(when, newVal)
					else
						channel:Reset(when)
					end
				end
			end
		end
	end
	self.markActions = true
	if self.markActions then
		local markerChannel = moho.layer.fTimelineMarkers
		markerChannel:SetValue(when, actionName)
		local markerInterp = MOHO.InterpSetting:new_local()
		markerInterp.hold = length - 1
		markerInterp.tags = 2 -- * 2 = Orange
		markerChannel:SetKeyInterp(when, markerInterp)
	end
	-- * Update UI:
	FO_Utilities:PaintKeys(moho)
	moho.layer:UpdateCurFrame(true)
	moho:UpdateUI()
	MOHO:Redraw()
end

Icon
Insert Actions
Unlisted

Author: Lukas View Script
Script type: Tool

Uploaded: May 15 2023, 07:37

Last modified: Sep 05 2023, 12:57

Insert actions
Tool to create and insert actions with lots of options.
Image
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: 574