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

ScriptName = "SZ_SelectBonesByName"

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

SZ_SelectBonesByName = {}

-- **************************************************
-- Settings
-- **************************************************

SZ_SelectBonesByName.numberOfSearchBoxes = 8

SZ_SelectBonesByName.delimiter = '+'
SZ_SelectBonesByName.caseSensitive = false

SZ_SelectBonesByName.searchFieldWidth = 80
SZ_SelectBonesByName.dividerSpace = -4
SZ_SelectBonesByName.distanceFromTextToButton = -16

SZ_SelectBonesByName.saveSeparator = "=" -- separates keys from values in saved presets
SZ_SelectBonesByName.scriptDataPrefix = "SZ_SelectBonesByName_"

-- **************************************************
-- General methods
-- **************************************************

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

function SZ_SelectBonesByName:Version()
	return "1.2"
end

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

function SZ_SelectBonesByName:Creator()
	return "Stan from 2danimator.ru"
end

function SZ_SelectBonesByName:UILabel()
	return self:Localize("Label")
end

function SZ_SelectBonesByName:SavePrefs(prefs)
	for i = 1, self.numberOfSearchBoxes do
		prefs:SetString("SZ_SelectBonesByName.searchText"..i, self.searchTexts[i])
	end
end

function SZ_SelectBonesByName:LoadPrefs(prefs)
	for i = 1, self.numberOfSearchBoxes do
		self.searchTexts[i] = prefs:GetString("SZ_SelectBonesByName.searchText"..i, "")
	end
end

function SZ_SelectBonesByName:ResetPrefs()
	for i = 1, self.numberOfSearchBoxes do
		self.searchTexts[i] = ""
	end
end

-- **************************************************
-- Recurring values
-- **************************************************

SZ_SelectBonesByName.searchTexts = {}
for i = 1, SZ_SelectBonesByName.numberOfSearchBoxes do
	table.insert(SZ_SelectBonesByName.searchTexts, "")
end

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

function SZ_SelectBonesByName:IsEnabled(moho)
	local skel = moho:Skeleton()
	if skel then 
		return true
	end
	return false
end

function SZ_SelectBonesByName:IsRelevant(moho)
	local skel = moho:Skeleton()
	if skel then 
		return true
	end
	return false
end

-- function SZ_SelectBonesByName:OnMouseUp(moho, mouseEvent)
	-- local skel = moho:Skeleton()
	-- skel:SelectNone()
-- end

function SZ_SelectBonesByName:OnMouseDown(moho, mouseEvent)
	-- Do nothing (this is for Moho to recognize the tool script) 
end


-- **************************************************
-- Tool options - create and respond to tool's UI
-- **************************************************

SZ_SelectBonesByName.CLEAR_ALL = MOHO.MSG_BASE
SZ_SelectBonesByName.SAVE = MOHO.MSG_BASE + 1
SZ_SelectBonesByName.LOAD = MOHO.MSG_BASE + 2
SZ_SelectBonesByName.BUTTON_MSG_BASE = MOHO.MSG_BASE + 3
SZ_SelectBonesByName.ALT_BUTTON_MSG_BASE = MOHO.MSG_BASE + 103
SZ_SelectBonesByName.TEXT_MSG_BASE = MOHO.MSG_BASE + 203

SZ_SelectBonesByName.textBoxes = {}

function SZ_SelectBonesByName:DoLayout(moho, layout)
	for i=1, self.numberOfSearchBoxes do
		self.textBoxes[i] = LM.GUI.TextControl(self.searchFieldWidth, "", self.TEXT_MSG_BASE + i)
		layout:AddChild(self.textBoxes[i])
		layout:AddPadding(self.distanceFromTextToButton)
		local searchButton = LM.GUI.Button(self:Localize("Select"), self.BUTTON_MSG_BASE + i)
		searchButton:SetAlternateMessage(self.ALT_BUTTON_MSG_BASE + i)
		layout:AddChild(searchButton)
		layout:AddPadding(self.dividerSpace)
		layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL, 0)
		layout:AddPadding(self.dividerSpace)
	end
	layout:AddChild(LM.GUI.Button(self:Localize("ClearAll"), self.CLEAR_ALL))
	layout:AddPadding(self.dividerSpace)
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL, 0)
	layout:AddPadding(self.dividerSpace)
	layout:AddChild(LM.GUI.Button(self:Localize("Save"), self.SAVE))
	layout:AddChild(LM.GUI.Button(self:Localize("Load"), self.LOAD))
end

function SZ_SelectBonesByName:UpdateWidgets(moho)
	local scriptData = moho.layer:ScriptData()
	for i=1, self.numberOfSearchBoxes do
		if scriptData:HasKey(self.scriptDataPrefix .. i) then
			self.textBoxes[i]:SetValue(scriptData:GetString(self.scriptDataPrefix .. i))
		else
			self.textBoxes[i]:SetValue(self.searchTexts[i])
		end
	end
end

function SZ_SelectBonesByName:HandleMessage(moho, view, msg)
	local scriptData = moho.layer:ScriptData()
	
	if msg == self.CLEAR_ALL then
		for i=1, self.numberOfSearchBoxes do
			self.searchTexts[i] = ""
			scriptData:Remove(self.scriptDataPrefix .. i)
		end
		self:UpdateWidgets(moho)
	elseif msg == self.SAVE then
		local fileName = LM.GUI.SaveFile(self:Localize("Name") .. " - " .. self:Localize("SaveCaption"))		
		if fileName ~= "" then
			local str = ""
			for i=1, self.numberOfSearchBoxes do
				local val = self.searchTexts[i]
				str = str .. i .. self.saveSeparator .. val .. "\n"
			end
			local f = io.open(fileName, "w")
			if f == nil then
				LM.GUI.Alert(LM.GUI.ALERT_WARNING, self:Localize("FileSaveError1"), self:Localize("FileSaveError2"), self:Localize("FileSaveError3"), self:Localize("OK"))
			else		
				f:write(str)
				f:close()
			end
		end		
	elseif msg == self.LOAD then
		local fileName = LM.GUI.OpenFile(self:Localize("Name") .. " - " .. self:Localize("LoadCaption"))
		if fileName ~= "" then
			local fileLines = io.lines(fileName)
			if fileLines then
				for line in fileLines do
					local i, val = string.match(line, "(.+)"..self.saveSeparator.."(.*)")
					self.searchTexts[tonumber(i)] = val
					scriptData:Set(self.scriptDataPrefix .. i, val)
				end
			end
			self:UpdateWidgets(moho)
		end
	else	
		for i=1, self.numberOfSearchBoxes do
			if msg == self.BUTTON_MSG_BASE + i then
				self:SearchBones(moho, self.textBoxes[i]:Value())
			elseif msg == self.ALT_BUTTON_MSG_BASE + i then
				local val = self:GetSelectedBonesSearchString(moho)
				self.searchTexts[i] = val
				scriptData:Set(self.scriptDataPrefix .. i, val)
				self:UpdateWidgets(moho)
			elseif msg == self.TEXT_MSG_BASE + i then
				local val = self.textBoxes[i]:Value()
				self.searchTexts[i] = val
				scriptData:Set(self.scriptDataPrefix .. i, val)
			end
		end
	end
end

-- **************************************************
-- The search itself
-- **************************************************

function SZ_SelectBonesByName:SearchBones(moho, str)
	moho.document:PrepUndo(moho.layer)
	moho.document:SetDirty()
	local searchStrings = self:Split(str, self.delimiter)
	local skel = moho:Skeleton()
	-- deselect all bones:
	for i=0, skel:CountBones()-1 do
		local bone = skel:Bone(i)
		bone.fSelected = false
	end
	-- do the search:
	for i=0, skel:CountBones()-1 do
		local bone = skel:Bone(i)
		local name = bone:Name()
		for _, searchString in ipairs(searchStrings) do
			if not self.caseSensitive then
				searchString = string.lower(searchString)
				name = string.lower(name)
			end
			if string.match(name, searchString) then
				bone.fSelected = true
			end
		end
	end
end

-- **************************************************
-- Utility methods
-- **************************************************

function SZ_SelectBonesByName:Split(s, delimiter)
	-- https://stackoverflow.com/questions/1426954/split-string-in-lua
	result = {}
	for match in (s..delimiter):gmatch("(.-)"..delimiter) do
		table.insert(result, match)
	end
	return result
end

function SZ_SelectBonesByName:GetSelectedBonesSearchString(moho)
	local result = ""
	local skel = moho:Skeleton()
	for i=0, skel:CountBones()-1 do
		local bone = skel:Bone(i)
		if bone.fSelected then
			result = result .. bone:Name() .. '$' .. self.delimiter
		end
	end
	result = result:gsub(self.delimiter .. '$', '')
	return result
end

-- **************************************************
-- Localization
-- **************************************************

function SZ_SelectBonesByName:Localize(text)
	local fileWord = MOHO.Localize("/Menus/File/File=File")
	
	local phrase = {}
	
	phrase["Name"] = "Select Bones by Name"
	phrase["Description"] = "Selects bones whose names match the search string. Alt+click the Select button to remember current selection."
	phrase["Label"] = "Select Bones by Name"
	phrase["ClearAll"] = "Clear all"		
	phrase["Select"] = "Select"		
	phrase["Save"] = "Save"		
	phrase["SaveCaption"] = "Save preset"
	phrase["Load"] = "Load"
	phrase["LoadCaption"] = "Load preset"
	phrase["FileSaveError1"] = "Error creating file!"
	phrase["FileSaveError2"] = "Perhaps the path contains non-English characters."
	phrase["FileSaveError3"] = "Please try saving in another location, with English characters only."
	phrase["OK"] = "OK"
	
	if fileWord == "Файл" then
		phrase["Name"] = "Выделить кости по имени"
		phrase["Description"] = "Выделяет кости у которых имя соответствует строке поиска. Alt+клик на кнопку запоминает текущее выделение."
		phrase["Label"] = "Выделить кости по имени"
		phrase["ClearAll"] = "Очистить все"
		phrase["Select"] = "Выделить"
		phrase["Save"] = "Сохранить"
		phrase["SaveCaption"] = "Сохранить настройки"
		phrase["Load"] = "Загрузить"
		phrase["LoadCaption"] = "Загрузить настройки"
		phrase["FileSaveError1"] = "Не удалось сохранить файл!"
		phrase["FileSaveError2"] = "Скорее всего, из-за неанглийских букв в пути."
		phrase["FileSaveError3"] = "Попробуйте сохранить в папку, названную английскими буквами."
		phrase["OK"] = "OK"
	end
	
	return phrase[text];
end

Icon
Select Bones by Name
Listed

Author: Stan View Script
Script type: Tool

Uploaded: Feb 20 2021, 09:29

Select bones whose names match the search string
Image

This tool was the earlier version of the Bone Selection Buttons script. In some cases it may be more convenient to select bones by "googling" their names.
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: 16