Rename and sort FBF childlayers by timeline appearance

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

Moderators: Víctor Paredes, Belgarath, slowtiger

Post Reply
User avatar
Lukas
Posts: 1336
Joined: Fri Apr 09, 2010 9:00 am
Location: Netherlands
Contact:

Rename and sort FBF childlayers by timeline appearance

Post by Lukas »

I've been experimenting with some tweaks in an attempt to try and make FBF more useable. One of them is this script that renames each child of a FBF layer to its first appearance, for example if Layer 1 is has a switchkey on frame 5, 6 and 20, it will be renamed "Frame_00005". Unused frames will be named "Unused". After that it sorts the frames by name. Which kind of works, but when you try and select an old frame to re-use via the switch-dropdown, the order is all wrong, because it's actually the 'animated' layer order of the 'layer order channel'... So it's ordered correctly in the layer panel, but not in the right click switch menu (or ctrl+alt+righ click menu).

Not sure it's possible to re-order the layers in another way. If anyone can figure out a different trick that'd be nice. Without the menu being alphabetically ordered the script isn't as useful as I hoped it would be.

Edit: Thanks for the help Wes, it works. This code below is the updated script.
Edit: Re-used frames now get their own color so you can distinguish them. (red = blank frame) (plain = regular drawing used once) (other color = re-used drawings)

Here it is:

Code: Select all

-- ************************************************************************************
-- Rename and sort sublayers of a FBF switchlayer to "Frame 1" etc. to match their first timeline appearance
-- ************************************************************************************

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

ScriptName = "LK_RenameFBFLayers"

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

LK_RenameFBFLayers = {}

function LK_RenameFBFLayers:Name()
	return "Rename FBF-frames"
end

function LK_RenameFBFLayers:Version()
	return "0"
end

function LK_RenameFBFLayers:Description()
	return "Rename and sort sublayers of a FBF switchlayer to 'Frame 1' etc. to match their time."
end

function LK_RenameFBFLayers:Creator()
	return "Lukas Krepel, Frame Order"
end

function LK_RenameFBFLayers:UILabel()
	return "Rename FBF-frames"
end

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

function LK_RenameFBFLayers:IsEnabled(moho)
	if moho.layer:LayerType() ~= MOHO.LT_SWITCH then
		return false
	else
		local switchLayer = moho:LayerAsSwitch(moho.layer)
		if not switchLayer:IsFBFLayer() then
			return false
		end
	end
	return true
end

function LK_RenameFBFLayers:Run(moho)
	moho.document:PrepUndo(moho.layer)
	moho.document:SetDirty()
	LK_RenameFBFLayers:Rename(moho)
end

function LK_RenameFBFLayers:Rename(moho)
	local switchLayer = moho:LayerAsSwitch(moho.layer)
	local channel = switchLayer:SwitchValues() -- 10106 = CHANNEL_SWITCH
	local endKey = channel:Duration()
	local thisKey = 0
	local keyCount = channel:CountKeys()
	local keysFound = 0
	local frameNum = endKey
	local layers = {}
	for i = switchLayer:CountLayers()-1, 0, -1 do
		layer = switchLayer:Layer(i)
		table.insert(layers, layer)
	end
	for i = 1, #layers do
		local layer = layers[i]
		layer:SetName("tempname "..i)
	end
	local frameLayers = {}
	local alreadyColored = {}
	local reuseColors = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }
		local col = 1
	while keysFound < keyCount do
		keysFound = 1 + keysFound
		thisKey = channel:GetClosestKeyID(frameNum)
		keyFrameNum = channel:GetKeyWhen(thisKey)
		local leadingZeros = string.format("%05d", keyFrameNum)
		local newName = "Frame_"..leadingZeros
		local keyValue = switchLayer:GetValue(keyFrameNum)
		frameNum = keyFrameNum - 1
		
		for i = 1, #layers do
			local layer = layers[i]
			local layerName = layer:Name()
			if layerName == keyValue then
				
				if table.contains(frameLayers, layer) then
					if not table.contains(alreadyColored, layer) then
						local color = reuseColors[col]
						col = col + 1
						layer:SetLabelColor(color) -- * From table For re-used frames.
						table.insert(alreadyColored, layer)
						layer:SetName(newName.." *")
					end
				else
					layer:SetName(newName)
					layer:SetLabelColor(0) -- * 0 = Plain -- For regular frames.
				end
                local mesh = moho:LayerAsVector(layer):Mesh()
                if mesh:CountPoints() == 0 then
					layer:SetLabelColor(1) -- * 2 = orange -- For empty frames.
					if table.contains(frameLayers, layer) then
						--layer:SetLabelColor(1) -- * 1 = red -- For re-used empty frame.
					end
				end
				table.insert(frameLayers, layer)
			end
		end
	end
	local unusedCount = 0
	local blankReusedCount = 1
	for i = 1, #layers do
		local layer = layers[i]
		if layer:LabelColor() == 1 then
			if blankReusedCount == 1 then
				layer:SetName("Blank")
			else
				layer:SetName("Blank "..blankReusedCount)
			end
			blankReusedCount = blankReusedCount + 1
		end
		if not table.contains(frameLayers, layer) then
			unusedCount = unusedCount + 1
			layer:SetName("Unused_"..unusedCount)
			layer:SetLabelColor(1) -- * 1 = red
		end
	end
	self:SortByName(moho)
end

-- **************************************************
-- Sort layers by name
-- **************************************************
function LK_RenameFBFLayers:SortByName(moho)
	-- * Create table
	local thisLayer = moho:LayerAsGroup(moho.layer)
	thisLayer:EnableLayerOrdering(false)
	local order, layer, layerName = {}
	for i = thisLayer:CountLayers()-1, 0, -1 do
		layer = thisLayer:Layer(i)
		layerName = layer:Name()
		table.insert(order, {layer, layerName})
	end
	-- * Sort table alphabetically:
	table.sort(order,
		function(a,b)
			return (a[2]<b[2])
		end)
	-- * Sort layer order:
	if #order > 1 then
		for i = 1, #order-1 do
			local moveLayer = order[i+1][1]
			local behindLayer = order[i][1]
			moho:PlaceLayerBehindAnother(moveLayer, behindLayer)
		end
	end
	-- *
	moho.layer:UpdateCurFrame()
	moho.view:DrawMe()
end
-- **************************************************
-- Check whether a table contains an element
-- **************************************************
function table.contains(table, element)
	if table ~= nil then
		for _, value in pairs(table) do
			if value == element then
	    		return true
	    	end
	  	end
	end
  	return false
end
I wanted to add the script after a custom "new fbf frame" shortcut so it would run every time you create a new frame:

Code: Select all

moho.document:PrepUndo(moho.layer)
moho.document:SetDirty()
local switchLayer = moho:LayerAsSwitch(moho.layer)
local currentLayerName = switchLayer:GetValue(moho.frame)
local layers = {}
for i = switchLayer:CountLayers()-1, 0, -1 do
	layer = switchLayer:Layer(i)
	table.insert(layers, layer)
end
local encounter = false
for i = 1, #layers do
	local layer = layers[i]
	local layerName = layer:Name()
	--print (layerName.." == "..currentLayerName)
	if layerName == currentLayerName then
		encounter = true
		moho:SetSelLayer(layer, false, false)
		break
	end
end
if not encounter then
	print ("error... "..currentLayerName)
	return
end
moho:CreateNewLayer(LT_VECTOR, false)
local channel = switchLayer:SwitchValues() -- 10106 = CHANNEL_SWITCH
channel.value:Set(moho.layer:Name())
channel:StoreValue()
moho:SetSelLayer(switchLayer, false, false)
LK_RenameFBFLayers:Rename(moho)
moho:UpdateUI()
Last edited by Lukas on Sat Apr 10, 2021 10:59 am, edited 3 times in total.
User avatar
synthsin75
Posts: 10267
Joined: Mon Jan 14, 2008 11:20 pm
Location: Oklahoma
Contact:

Re: Rename (works) and sort (...help!) FBF childlayers by timeline appearance

Post by synthsin75 »

It doesn't look like you've defined the function table.contains.

You probably shouldn't be using the layer order channel to sort the layers unless you actually need animated layer sorting.
Instead, you probably want ScriptInterface:PlaceLayerBehindAnother(moveLayer, behindThis)


I don't see your script sorting the layers either. I moved them manually to test and it doesn't correct that.
EDIT: Oh, that's because the table.contains error doesn't allow the script to finish.
User avatar
Lukas
Posts: 1336
Joined: Fri Apr 09, 2010 9:00 am
Location: Netherlands
Contact:

Re: Rename (works) and sort (...help!) FBF childlayers by timeline appearance

Post by Lukas »

synthsin75 wrote: Thu Apr 08, 2021 11:44 pm It doesn't look like you've defined the function table.contains.
Agh, I forgot that one needed to be included.

Code: Select all

-- **************************************************
-- Check whether a table contains an element
-- **************************************************
function table.contains(table, element)
	if table ~= nil then
		for _, value in pairs(table) do
			if value == element then
	    		return true
	    	end
	  	end
	end
  	return false
end
synthsin75 wrote: Thu Apr 08, 2021 11:44 pmYou probably shouldn't be using the layer order channel to sort the layers unless you actually need animated layer sorting.
Instead, you probably want ScriptInterface:PlaceLayerBehindAnother(moveLayer, behindThis)
Thanks, I'll look into it!
User avatar
Lukas
Posts: 1336
Joined: Fri Apr 09, 2010 9:00 am
Location: Netherlands
Contact:

Re: Rename (works) and sort (...help!) FBF childlayers by timeline appearance

Post by Lukas »

synthsin75 wrote: Thu Apr 08, 2021 11:44 pmYou probably shouldn't be using the layer order channel to sort the layers unless you actually need animated layer sorting.
Instead, you probably want ScriptInterface:PlaceLayerBehindAnother(moveLayer, behindThis)
That works! Thanks Wes!

I updated the script in the original post if anyone wants to use it. Just save it as a textfile named "LK_RenameFBFLayers.lua" in the menuscripts folder. It also labels unused layers red and layers that occur more than once purple.
User avatar
synthsin75
Posts: 10267
Joined: Mon Jan 14, 2008 11:20 pm
Location: Oklahoma
Contact:

Re: Rename and sort FBF childlayers by timeline appearance

Post by synthsin75 »

Glad I could help. :wink:
User avatar
Lukas
Posts: 1336
Joined: Fri Apr 09, 2010 9:00 am
Location: Netherlands
Contact:

Re: Rename and sort FBF childlayers by timeline appearance

Post by Lukas »

Updated the script:
Re-used frames now get their own color so you can distinguish them. (red = blank frame) (plain = regular drawing used once) (other color = re-used drawings)

I let it auto-run on freehand onmouseup and when creating new fbf-frames etc, it's very useful imo 🤓
Post Reply