PrepMultiUndo()

Moho allows users to write new tools and plugins. Discuss scripting ideas and problems here.

Moderators: Víctor Paredes, Belgarath, slowtiger

Post Reply
lehtiniemi
Posts: 107
Joined: Mon Jan 14, 2013 3:18 pm

PrepMultiUndo()

Post by lehtiniemi »

How do I use PrepMultiUndo()? I'm performing things that affect multiple layers, does this help undo those in one go?

I have a situation where I don't know in forehand which layers will be touched. Instead of force-adding PrepUndo() to each layer even if they're not touched, I was wishing this could maybe be a solution?
User avatar
synthsin75
Posts: 9968
Joined: Mon Jan 14, 2008 11:20 pm
Location: Oklahoma
Contact:

Re: PrepMultiUndo()

Post by synthsin75 »

PrepMultiUndo is a document class function, so just call it before making the changes you want to be undoable.
lehtiniemi
Posts: 107
Joined: Mon Jan 14, 2013 3:18 pm

Re: PrepMultiUndo()

Post by lehtiniemi »

synthsin75 wrote:PrepMultiUndo is a document class function, so just call it before making the changes you want to be undoable.
I tried to use it like that, but for some reason this happened:
-When using only PrepMultiUndo, only one undo-step is added and it doesn't undo all the changes to different layers, only like for one layer
-If I try to use PrepUndo(layer) multiple times withing the document, then for some reason a right amount of undo steps is added, but they aren't undoed properly
-If I use PrepMultiUndo and add PrepUndo(layer) on each layer, then the amount of PrepUndo-steps is added and they are undoed properly.

I don't know what's happening, if I'm doing something wrong or is PrepMultiUndo supposed to be called if you use PrepUndo many times?

Or can I somehow use PrepMultiUndo to add only one undostep that undoes all the changes on separate layers? From your message I understand it's supposed to work like this but for some reason it doesn't undo changes on all layers.
User avatar
synthsin75
Posts: 9968
Joined: Mon Jan 14, 2008 11:20 pm
Location: Oklahoma
Contact:

Re: PrepMultiUndo()

Post by synthsin75 »

Maybe you need to use it with the argument. PrepMultiUndo(true)
lehtiniemi
Posts: 107
Joined: Mon Jan 14, 2013 3:18 pm

Re: PrepMultiUndo()

Post by lehtiniemi »

synthsin75 wrote:Maybe you need to use it with the argument. PrepMultiUndo(true)
Doesn't seem to work. :/ Tried with both true and false and all the combinations of PrepMultiUndo() and PrepUndo() mentioned in my original post.
User avatar
synthsin75
Posts: 9968
Joined: Mon Jan 14, 2008 11:20 pm
Location: Oklahoma
Contact:

Re: PrepMultiUndo()

Post by synthsin75 »

Then you may need to provide a working code snippet to test what you are actually looking to do.
lehtiniemi
Posts: 107
Joined: Mon Jan 14, 2013 3:18 pm

Re: PrepMultiUndo()

Post by lehtiniemi »

synthsin75 wrote:Then you may need to provide a working code snippet to test what you are actually looking to do.
I can post my whole script, but this is how I'm trying to make it work:
PrepMultiUndo(true)
... code to move keyframes around in multiple layers

I would like this to produce only one undo step that undos all the keyframe changes. For some reason, this only undos changes for one layer.

If you want to have a look, here's the whole tool script. Currently I'm adding a PrepUndo(layer) for every layer I go through because I couldn't get the multiundo to work. Of course, this is pain when there are tens of layers. It doesn't even check if they are changed, currently. Work in progress. It's currently using my own function to find keyframes, I'll switch to your technique but haven't done it yet.

This script will make it really easy to insert and cut time in ASPRO, undo is now the only thing that's not working...

Code: Select all

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

ScriptName = "JL_time_tool"

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

JL_time_tool = {}


function JL_time_tool:Name()
	return "Time tool"
end

function JL_time_tool:Version()
	return "1.0"
end


function JL_time_tool:Description()
	return "Insert or delete time by dragging in the viewport. Insert by dragging right, delete by dragging left."
end


function JL_time_tool:Creator()
	return "Juhana Lehtiniemi - www.juhanalehtiniemi.com"
end

function JL_time_tool:UILabel()
	return "Time tool"
end



-- **************************************************
-- Recurring values
-- **************************************************
JL_time_tool.startFrame=0
JL_time_tool.endFrame=0
JL_time_tool.originaLayerPos=nil
JL_time_tool.originaBonePos=nil
JL_time_tool.selectedBone=nil
JL_time_tool.selectedLayer=nil

JL_MSG_not_a_bone_layer = "Current layer is not a bone layer."
JL_MSG_select_one_bone_only = "Please select 1 bone only. You can select a bone by ALT-clicking it."


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

function JL_time_tool:FindKey(moho, selectedVec, startFrame, direction)
	local keyframe = 0
	local frameCount = selectedVec:Duration()


	if (direction < 0) -- backwards
	then
		for i = startFrame, 0, -1 do
		-- Start from one frame before current frame
			if (selectedVec:HasKey(i)) then
				-- bone.fAnimPos:HasKey(curFrame - i)==true
				keyframe = i
				break
			end
		end
	else -- forward

		for i = startFrame, frameCount do
		-- Start from one frame before current frame
			if (selectedVec:HasKey(i)) then
				-- bone.fAnimPos:HasKey(curFrame - i)==true
				keyframe = i
				break
			end
		end

	end

	return(keyframe)
end

function JL_time_tool:InsertTimeToChannel(moho, channel, startFrame, endFrame)
	-- Add AMOUNT to animation length or not?

	local nextKeyframe, nextKeyframeID
	local modified = false

	if channel:CountKeys() > 1 then
		-- Chronological start and end frames
		local newStartFrame, newEndFrame

		-- Calculate loop direction
		local direction, offset
		
		-- If user selection is from left to right, then we're starting from the end to insert time
		if (startFrame < endFrame) then
			direction = -1

			newStartFrame = startFrame
			newEndFrame = endFrame

			offset = endFrame-startFrame

	 		-- Search next keyframe backwards from the end of the channel
			local foundKeyframe = self:FindKey(moho, channel, channel:Duration(), direction)

			-- Repeat until given frame reached
			while (foundKeyframe >= newStartFrame) do
				nextKeyframeID = channel:GetClosestKeyID(foundKeyframe)
				channel:SetKeyWhen(nextKeyframeID, foundKeyframe+offset)

				-- Search backwards for next keyframe after the last found keyframe
				foundKeyframe = self:FindKey(moho, channel, foundKeyframe-1, direction)
				-- if (foundKeyframe == 0) then break end
				-- Changes to channels done
				modified = true
			end

		else
			-- If user selected the range backwards, then we have to start from left to right to delete time
			direction = 1

			newStartFrame = endFrame
			newEndFrame = startFrame

			offset = endFrame-startFrame

	 		-- Search next keyframe backwards from the end of the channel
			local foundKeyframe = self:FindKey(moho, channel, newStartFrame, direction)

			-- Repeat until given frame reached
			while (foundKeyframe <= channel:Duration() and foundKeyframe ~= 0) do
				nextKeyframeID = channel:GetClosestKeyID(foundKeyframe)
				channel:SetKeyWhen(nextKeyframeID, foundKeyframe+offset)

				-- Search for next keyframe after the last found keyframe
				foundKeyframe = self:FindKey(moho, channel, foundKeyframe+1, direction)

				-- Changes to channels done
				modified = true
			end


		end

	end

	return modified
end

function JL_time_tool:InsertTimeToLayer(moho, layer, startFrame, endFrame)
	local numCh = layer:CountChannels()
	local modified = false

	-- Currently prepping undo for every channel because I can't check if they will be changed
	moho.document:PrepUndo(layer)

	for i = 0, numCh - 2 do
		local chInfo = MOHO.MohoLayerChannel:new_local()
		layer:GetChannelInfo(i, chInfo)

		if (chInfo.subChannelCount == 1) then
			local ch = layer:Channel(i, 0, moho.document)
			-- print(MOHO.Localize("/Scripts/Menu/ListChannels/Channel=Channel ") .. i .. ": " .. chInfo.name:Buffer() .. MOHO.Localize("/Scripts/Menu/ListChannels/Keyframes= Keyframes: ") .. ch:CountKeys())

			if (self:InsertTimeToChannel(moho, ch, startFrame, endFrame)) then
				modified = true
			end
		else
			-- print(MOHO.Localize("/Scripts/Menu/ListChannels/Channel=Channel ") .. i .. ": " .. chInfo.name:Buffer())
			for subID = 0, chInfo.subChannelCount - 1 do
				local ch = layer:Channel(i, subID, moho.document)
				if (self:InsertTimeToChannel(moho, ch, startFrame, endFrame)) then
					modified = true
				end
				-- print(MOHO.Localize("/Scripts/Menu/ListChannels/SubChannel=    Sub-channel ") .. subID .. MOHO.Localize("/Scripts/Menu/ListChannels/Keyframes= Keyframes: ") .. ch:CountKeys())
			end
		end
	end

	-- if (modified) then
		-- moho.document:PrepUndo(layer)
	-- end

	return modified
end

function JL_time_tool:InsertTimeToGroupLayer(moho, groupLayer, startFrame, endFrame)
	local modified = false

	for i = 0, groupLayer:CountLayers()-1 do
		local currentLayer = groupLayer:Layer(i)

		if (self:InsertTimeToLayer(moho, currentLayer, startFrame, endFrame)) then
			modified = true
		end

		if (currentLayer:IsGroupType()) then
			-- Go through the group layer as well
			if (self:InsertTimeToGroupLayer(moho, moho:LayerAsGroup(currentLayer), startFrame, endFrame)) then
				modified = true
			end
		end
	end

	return modified
end


function JL_time_tool:OnMouseDown(moho, mouseEvent)
	local curFrame = moho.layer:CurFrame()

	self.startFrame = curFrame

	if (self.startFrame == 0) then
		LM.GUI.Alert(LM.GUI.ALERT_INFO, "Start frame number has to be other than 0.", nil, nil, "OK", nil, nil)
		return false
	end

	return true
end


function JL_time_tool:OnMouseMoved(moho, mouseEvent)
	if (self.startFrame == 0) then return false end

	local mouseGenValue=mouseEvent.pt.x-mouseEvent.startPt.x
	
	if (mouseEvent.shiftKey) then
		self.endFrame = self.startFrame+math.floor(mouseGenValue/50)
	else
		self.endFrame = self.startFrame+math.floor(mouseGenValue/25)
	end

	if (self.endFrame < 0) then
		self.endFrame = 0
	end

	local range = self.endFrame-self.startFrame

	if (range > 0) then
		self.selectionRangeText:SetValue(self.InsertText.." "..math.abs(range).." frames")
	elseif (range < 0) then
		self.selectionRangeText:SetValue(self.DeleteText.." "..math.abs(range).." frames")
	else
		self.selectionRangeText:SetValue(self.DefaultText)
	end


	moho:SetCurFrame(self.endFrame)
	
	mouseEvent.view:DrawMe()
	return true
end


function JL_time_tool:OnMouseUp(moho, mouseEvent)
	if (self.startFrame == 0) then return false end
	-- Go through the range		
	local newStartFrame
	local newEndFrame

	-- if scrubbing backwards, let's start looping from the endframe instead and go forwards
	if (self.startFrame > self.endFrame) then
		newStartFrame = self.endFrame
		newEndFrame = self.startFrame
	else
		newStartFrame = self.startFrame
		newEndFrame = self.endFrame
	end

	-- go through each frame in the range and compensate the layer movement by the bone movement

	local frameAmount = newEndFrame-newStartFrame
	-- print ("moving frames by ", frameAmount)

	local modified = false

	-- print ("top levels layers found: ", moho.document:CountLayers())
	moho.document:PrepMultiUndo(false)

	local layerList
	-- If going through only selected layers
	if not (self.editAllLayers) then
		for i = 0, moho.document:CountSelectedLayers()-1 do
			-- All contained group layers will be included automatically and selection doesn't affect there
			if (self:InsertTimeToLayer(moho, moho.document:GetSelectedLayer(i), self.startFrame, self.endFrame)) then
				modified = true
			end
		end
	else
		if (self:InsertTimeToGroupLayer(moho, moho.document, self.startFrame, self.endFrame)) then
			modified = true
		end
	end


	if (modified) then moho.document:SetDirty() end


	self.selectionRangeText:SetValue(self.DefaultText)
	-- self.selectionRange:SetValue(0)
	moho:SetCurFrame(self.startFrame)
	-- moho:NewKeyframe(CHANNEL_LAYER_T)
	-- moho:UpdateSelectedChannels()
	-- moho.layer:UpdateCurFrame()
	moho:UpdateUI()
	return true
end

JL_time_tool.RANGE_CHANGED = MOHO.MSG_BASE
JL_time_tool.EDIT_ALL_LAYERS = MOHO.MSG_BASE+1

JL_time_tool.DefaultText = "Select time to insert/delete."
JL_time_tool.InsertText = "Inserting time"
JL_time_tool.DeleteText = "Deleting time"

-- First init before LoadPrefs starts to work
JL_time_tool.editAllLayers = true



function JL_time_tool:DoLayout(moho, layout)

	self.selectionRangeText = LM.GUI.DynamicText(self.DefaultText, 0)
	layout:AddChild(self.selectionRangeText)
	layout:AddPadding()
	layout:AddPadding()


	
	self.editAllLayersCheckbox = LM.GUI.CheckBox("Edit all layers", self.EDIT_ALL_LAYERS)
	self.editAllLayersCheckbox:SetValue(self.editAllLayers)
	layout:AddChild(self.editAllLayersCheckbox)
	
end



function JL_time_tool:HandleMessage(moho, view, msg)
	if (msg == self.EDIT_ALL_LAYERS) then
		self.editAllLayers = self.editAllLayersCheckbox:Value()
	end

end




function JL_time_tool:SavePrefs(prefs)
	prefs:SetBool("JL_time_tool.editAllLayers", self.editAllLayers)
end


function JL_time_tool:LoadPrefs(prefs)
	self.editAllLayers = prefs:GetBool("JL_time_tool.editAllLayers", JL_time_tool.editAllLayers)
end

function JL_time_tool:ResetPrefs(prefs)
	JL_time_tool.editAllLayers = true
end


User avatar
synthsin75
Posts: 9968
Joined: Mon Jan 14, 2008 11:20 pm
Location: Oklahoma
Contact:

Re: PrepMultiUndo()

Post by synthsin75 »

Try removing ALL undos, both multi and single, and put moho.document:PrepMultiUndo() as the first line in your OnMouseDown function.
lehtiniemi
Posts: 107
Joined: Mon Jan 14, 2013 3:18 pm

Re: PrepMultiUndo()

Post by lehtiniemi »

synthsin75 wrote:Try removing ALL undos, both multi and single, and put moho.document:PrepMultiUndo() as the first line in your OnMouseDown function.
I have:
-Group layer
---- Vector layer
-Bone layer
-Vector layer

PrepMultiUndo() (both with true and false as parameter) produces only one undo step which undoes the keyframe changes only on the first group layer (but not layers in it).
User avatar
synthsin75
Posts: 9968
Joined: Mon Jan 14, 2008 11:20 pm
Location: Oklahoma
Contact:

Re: PrepMultiUndo()

Post by synthsin75 »

I don't have the time to delve too deeply into troubleshooting an entire script, but here's my suggestion. Select each layer before making changes to it. See if that helps.
lehtiniemi
Posts: 107
Joined: Mon Jan 14, 2013 3:18 pm

Re: PrepMultiUndo()

Post by lehtiniemi »

I understand. Thanks for your help anyway.

The craziest thing is that when I finally gave up on it and decided to only do just one

Code: Select all

PrepUndo(nil)
at MouseDown, it works but of course it shouldn't. As a result of this, I get one undo step that undos every change on every layer I touch. (at least it looks that way). But PrepUndo(nil) is supposed to tell AS that no undo should be possible. Something's going south there, but it works now for some reason.

I have now posted the script for testing. It includes many of problems you guys helped me to solve. :) Thank you so much!
User avatar
synthsin75
Posts: 9968
Joined: Mon Jan 14, 2008 11:20 pm
Location: Oklahoma
Contact:

Re: PrepMultiUndo()

Post by synthsin75 »

I found that I have used PrepUndo(nil) on some older scripts of mine. I've never seen PrepUndo tell AS something is not undoable. So I think you found the right way to do it.
Post Reply