FO_Utilities = {} -- ********** -- LAYER TAGS -- ********** FO_Utilities.rigTag = "rig" FO_Utilities.animTag = "anim" FO_Utilities.outfitTag = "outfit" FO_Utilities.embedTag = "embed" FO_Utilities.keysTag = "keys" -- ********* -- BONE TAGS -- ********* -- * Switchbone boneTag FO_Utilities.switchTag = ".switch" -- * Switchbone control tags: FO_Utilities.controlTag = ".control" -- * Switchbone position tags: FO_Utilities.posTag = ".pos" FO_Utilities.guiTag = "GUI" FO_Utilities.originTag = "origin" -- *** List of all tags (LK_SelectBone needs this) -- FO_Utilities.boneTags = { FO_Utilities.constrainTranslationTag, FO_Utilities.constrainXTranslationTag, FO_Utilities.constrainYTranslationTag, FO_Utilities.constrainRotationTag, FO_Utilities.constrainScaleTag, FO_Utilities.forceStepTag, FO_Utilities.hipTag, FO_Utilities.footTag, FO_Utilities.controlTag, FO_Utilities.switchTag.."%d?%d?%d?", FO_Utilities.posTag.."%d?%d?%d?" } FO_Utilities.boneTags = { "!t", "!x", "!y", "!r", "!s", "_step", "!hip", "!foot", FO_Utilities.controlTag, FO_Utilities.switchTag.."%d?%d?%d?", FO_Utilities.posTag.."%d?%d?%d?" } -- * Settings: FO_Utilities.tinyUI = false FO_Utilities.tinyUITreshold = 1500 FO_Utilities.reverseBoneColorButtons = false FO_Utilities.forceBigUI = false -- * Color names: FO_Utilities.colorNames = {} FO_Utilities.colorNames[0] = "Plain" FO_Utilities.colorNames[1] = "Red" FO_Utilities.colorNames[2] = "Orange" FO_Utilities.colorNames[3] = "Yellow" FO_Utilities.colorNames[4] = "Green" FO_Utilities.colorNames[5] = "Blue" FO_Utilities.colorNames[6] = "Purple" FO_Utilities.colorNames[7] = "Tan" FO_Utilities.colorNames[8] = "Pink" FO_Utilities.colorNames[9] = "Turquoise" FO_Utilities.colorNames[10] = "CadetBlue" FO_Utilities.colorNames[11] = "Coral" -- * Color RGB values: FO_Utilities.colorsR = {} FO_Utilities.colorsG = {} FO_Utilities.colorsB = {} FO_Utilities.colorsR[0], FO_Utilities.colorsG[0], FO_Utilities.colorsB[0] = MOHO.MohoGlobals.ElemCol.r, MOHO.MohoGlobals.ElemCol.g, MOHO.MohoGlobals.ElemCol.b -- No color FO_Utilities.colorsR[1], FO_Utilities.colorsG[1], FO_Utilities.colorsB[1] = 220, 64, 51 -- Red FO_Utilities.colorsR[2], FO_Utilities.colorsG[2], FO_Utilities.colorsB[2] = 240, 160, 0 -- Orange FO_Utilities.colorsR[3], FO_Utilities.colorsG[3], FO_Utilities.colorsB[3] = 240, 240, 0 -- Yellow FO_Utilities.colorsR[4], FO_Utilities.colorsG[4], FO_Utilities.colorsB[4] = 64, 220, 51 -- Green FO_Utilities.colorsR[5], FO_Utilities.colorsG[5], FO_Utilities.colorsB[5] = 89, 126, 183 -- Blue FO_Utilities.colorsR[6], FO_Utilities.colorsG[6], FO_Utilities.colorsB[6] = 192, 96, 191 -- Purple FO_Utilities.colorsR[7], FO_Utilities.colorsG[7], FO_Utilities.colorsB[7] = 225, 185, 107 -- Tan FO_Utilities.colorsR[8], FO_Utilities.colorsG[8], FO_Utilities.colorsB[8] = 248, 162, 159 -- Pink FO_Utilities.colorsR[9], FO_Utilities.colorsG[9], FO_Utilities.colorsB[9] = 109, 223, 210 -- Turquoise FO_Utilities.colorsR[10], FO_Utilities.colorsG[10], FO_Utilities.colorsB[10] = 129, 200, 187 -- Cadet Blue FO_Utilities.colorsR[11], FO_Utilities.colorsG[11], FO_Utilities.colorsB[11] = 248, 146, 96 -- Coral FO_Utilities.colorsR[12], FO_Utilities.colorsG[12], FO_Utilities.colorsB[12] = MOHO.MohoGlobals.InacCol.r, MOHO.MohoGlobals.InacCol.g, MOHO.MohoGlobals.InacCol.b -- Color for hidden bones FO_Utilities.colorsR[13], FO_Utilities.colorsG[13], FO_Utilities.colorsB[13] = MOHO.MohoGlobals.SelCol.r, MOHO.MohoGlobals.SelCol.g, MOHO.MohoGlobals.SelCol.b -- Color for selected bones -- ************************************************** -- Adjust toolbar display to screen width -- ************************************************** function FO_Utilities:DrawMeTinyUI(moho) -- * Full screen on MacOS Cintiq 21UX low res mode = 1498 local UIbool = false if not FO_Utilities.forceBigUI then UIbool = moho.view:Graphics():Width() < FO_Utilities.tinyUITreshold end if FO_Utilities.tinyUI ~= UIbool then FO_Utilities.tinyUI = UIbool FO_Utilities:ReloadTools(moho) end end -- ************************************************************ -- Draw custom UI things in the viewport if FO_Draw is present -- ************************************************************ function FO_Utilities:DrawMeMod(moho, view, mousePickedID) if FO_Draw ~= nil then FO_Draw:DrawMeMod(moho, view, mousePickedID) end end -- ************************************************** -- Reload tools by hopping in and out of frame 0 -- ************************************************** function FO_Utilities:ReloadTools(moho) local returnFrame = moho.frame local tempFrame = 0 local rememberDisabledDrawingToolsNonZero = MOHO.MohoGlobals.DisableDrawingToolsNonZero MOHO.MohoGlobals.DisableDrawingToolsNonZero = true if returnFrame == tempFrame then tempFrame = 1 end moho:SetCurFrame(tempFrame) moho:SetCurFrame(returnFrame) MOHO.MohoGlobals.DisableDrawingToolsNonZero = rememberDisabledDrawingToolsNonZero end -- ************************************************** -- Getting the skeleton -- ************************************************** function FO_Utilities:GetSkel(moho) local skel = moho:Skeleton() if (skel == nil) then if (not moho.layer:IsBoneType()) then skel = moho:ParentSkeleton() end end return skel end function FO_Utilities:Divider(layout, title, first) if not first then layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL, 0) -- * Divider end if FO_Utilities.tinyUI == false and title ~= nil then layout:AddChild(LM.GUI.StaticText(title..":")) end end function FO_Utilities:DialogDivider(layout, title, first) if not first then layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL, 0) -- * Divider end if title ~= nil then local longEmptyString = " " layout:AddChild(LM.GUI.StaticText(title..":"..longEmptyString)) end end -- ************************************************** -- Get Layer-matrix without Camera-matrix -- ************************************************** function FO_Utilities:LayerMatrix(moho, layer, frame) -- actions not processed yet local prevMatrix = LM.Matrix:new_local() prevMatrix:Identity() repeat local prevLayer = layer local matrix = LM.Matrix:new_local() layer:GetLayerTransform(frame, matrix, moho.document) matrix:Multiply(prevMatrix) prevMatrix:Set(matrix) if layer:Parent() then layer = layer:Parent() end until layer == prevLayer -- * Subtract camera matrix: local cameraMatrix = LM.Matrix:new_local() moho.document:GetCameraMatrix(frame, cameraMatrix) cameraMatrix:Invert() cameraMatrix:Multiply(prevMatrix) local layerMatrixWithoutCamera = cameraMatrix return layerMatrixWithoutCamera end -- ************************************************** -- Get Layer-matrix WITH Camera-matrix -- ************************************************** function FO_Utilities:LayerMatrixWithCamera(moho, layer, frame) -- actions not processed yet local prevMatrix = LM.Matrix:new_local() prevMatrix:Identity() repeat local prevLayer = layer local matrix = LM.Matrix:new_local() layer:GetLayerTransform(frame, matrix, moho.document) matrix:Multiply(prevMatrix) prevMatrix:Set(matrix) if layer:Parent() then layer = layer:Parent() end until layer == prevLayer return prevMatrix --[[ -- * Subtract camera matrix: local cameraMatrix = LM.Matrix:new_local() moho.document:GetCameraMatrix(frame, cameraMatrix) cameraMatrix:Invert() cameraMatrix:Multiply(prevMatrix) local layerMatrixWithoutCamera = cameraMatrix return layerMatrixWithoutCamera --]] end -- ************************************************** -- Embed layerscript on layer -- ************************************************** function FO_Utilities:EmbedLayerScript(moho, layer, script) local userpath = string.gsub(moho:UserAppDir(), '\\', '/') local layerscriptsfolder = userpath .. "/Shared Resources/Layerscripts/" if not string.match(script, ".lua") then script = script..".lua" end local scriptfile = script local script = layerscriptsfolder..scriptfile layer:SetLayerScript(script) FO_Utilities:AddTag("embed", layer) end -- ************************************************** -- Python Script Command String -- ************************************************** function FO_Utilities:Python(moho, pythonScript) -- if not string.match(pythonScript, ".py") then -- pythonScript = pythonScript..".py" -- end local pythonVersion = "" local pythonScriptDirectory = moho:UserAppDir().."/python/" pythonScriptPath = pythonScriptDirectory..pythonScript -- * Fix slashes per OS: if(FO_Utilities:getOS() == "win") then pythonVersion = "python" pythonScriptPath = string.gsub(pythonScriptPath, "/", "\\") -- * Reverse and double slashes for Windows -- * Add quotes around paths: pythonScriptPath = "\""..pythonScriptPath.."\"" else pythonVersion = "/Library/Frameworks/Python.framework/Versions/3.9/bin/python3" Debug:Log("pythonVersion = "..pythonVersion) pythonScriptPath = string.gsub(pythonScriptPath, " ", "\\ ") -- * Add backslash to space end local command = pythonVersion.." "..pythonScriptPath return command end -- ************************************************** -- Run Powershell Command??? -- ************************************************** function FO_Utilities:Powershell(programWithParams) print ("FO_Utilities:Powershell(programWithParams = "..programWithParams..")") local psCmd = '"powershell.exe -Command "Start-Process cmd " -ArgumentList "/c", "\'' .. programWithParams .. '\'" -Verb RunAs -Wait"' --print (psCmd) os.execute(psCmd) end -- ***************************************************** -- Return a string based on any variables. By hayasidist -- ***************************************************** function FO_Utilities:ToString(...) return HS_formatUserData(...) end -- ************************************************** -- Execute command line -- ************************************************** function FO_Utilities:Execute(command, ask, feedback, moho) Debug:Log(" - Command:") Debug:Log(command) Debug:Log("ask = "..tostring(ask)) Debug:Log("feedback = "..tostring(feedback)) if ask then local messages = string.split(command, " %-") table.insert(messages, "Are you sure?") local dlog = FO_Utilities:AreYouSureDialog(moho, "Execute command?", messages) if (dlog:DoModal() == LM.GUI.MSG_CANCEL) then return "cancelled" end end Debug:Log("Executing command!") local output if self:getOS() == ("unix") then if self:MohoVersion(moho) < 14 then output = io.popen(command) else output = os.execute(command.." &") -- * The & makes it run as a background process. end else -- os.execute(' "start "any title" "C:\\Program Files\\Lost Marble\\Moho 13.5\\Moho.exe" -r "w:\\zombies.moho" -f PNG -o "R:\\\\_out\\\\zombies\\\\" " ') output = os.execute(command) end Debug:Log("output = "..tostring(output)) return output -- * returns true if an error is thrown, otherwise nil. end -- ************************************************** -- Reveal directory in Finder/Explorer -- ************************************************** function FO_Utilities:RevealDirectory(path) local command local output if self:getOS() == "win" then path = string.gsub(path, "/", "\\") command = "explorer \""..path.."\"" output = os.execute(command) Debug:Log("Windows: output = "..tostring(output)) else command = "open "..string.gsub(path, " ", "\\ ") if FO_Utilities:MohoVersion() >= 14 then -- * Without moho argument this returns 0 or 14+ output = os.execute(command) else output = io.popen(command) -- * Doesn't work in Moho 14 anymore. end if output == nil then FO_Utilities:Alert("Directory doesn't exist (yet), or you are not connected to the server!", path) end end end -- ************************************************** -- Getting the rig layer: -- ************************************************** function FO_Utilities:RigLayer(moho) local skel = FO_Utilities:GetSkel(moho) local layer = moho.layer local tags = layer:UserTags() if string.match(tags, FO_Utilities.rigTag) then return layer end -- while (layer ~= nil and not string.match(tags, FO_Utilities.rigTag)) do layer = layer:Parent() if (layer ~= nil) then tags = layer:UserTags() if string.match(tags, FO_Utilities.rigTag) then return layer end end end -- -- * FAILED TO FIND A RIG-TAGGED BONE LAYER!!!") if (moho.layer:LayerType() == 4 or moho.layer:LayerType() == 5 ) then -- 4 = LT_BONE -- 5 = LT_SWITCH return moho.layer end -- *** Trying again, but this time, we're not going to look for a tag, but any bone layer. layer = moho.layer while layer ~= nil and (not (layer:LayerType() == 4) or not (layer:LayerType() == 5)) do layer = layer:Parent() if layer ~= nil then if layer:LayerType() == 4 or layer:LayerType() == 5 then return layer end end end -- * ...ALSO FAILED TO FIND AN UN-TAGGED BONE LAYER!!!") return nil end -- ******************************************************* -- Gets color for bone according to comment, returns color -- ******************************************************* function FO_Utilities:LegacyBoneCommentColor(moho, boneID) -- print ("bonecommentcolor being called") local color = 0 local rigLayer = FO_Utilities:RigLayer(moho)--fix? if rigLayer == nil then -- * help is dit verstandig? return color end local comment = rigLayer:UserComments() if comment == nil then local skel = FO_Utilities:GetSkel(moho) color = skel:Bone(boneID):Tags() return color end local boneNumber = boneID+1 if string.len(comment) < boneNumber * 3 then return 0 end -- *** WAARSCHUWING: comment:sub crashed keihard als hij niks kan vinden *** color = comment:sub(boneNumber*2-1+boneNumber-1, boneNumber*2-1+boneNumber) -- variant om "01.02.03." te lezen if string.match(color, "%d%d") then color = tonumber(color) else local skel = FO_Utilities:GetSkel(moho) local bone = skel:Bone(boneID) if bone ~= nil then color = bone:Tags() end end return color end -- **************************************************************************** -- Returns duration as a formatted string, for example: "6 Minutes, 30 Seconds" -- **************************************************************************** function FO_Utilities:DisplayDuration(time) local days = math.floor(time/86400) local hours = math.floor(math.fmod(time, 86400)/3600) local minutes = math.floor(math.fmod(time,3600)/60) local seconds = math.floor(math.fmod(time,60)) local msg = "" if days > 0 then msg = string.format("%d Days, ", days) end if hours > 0 then msg = msg..string.format("%d Hours, ", hours) end if minutes > 0 then msg = msg..string.format("%d Minutes, ", minutes) end msg = msg..string.format("%d Seconds", seconds) return msg end -- ****************************************************** -- Return filename of the embedded layerscript of a layer -- ****************************************************** function FO_Utilities:LayerScript(layer) local embeddedscript = layer:LayerScript() if embeddedscript ~= "" then embeddedscript = self:FileName(embeddedscript) else embeddedscript = nil end return embeddedscript end -- ************************************************************ -- Finds all layercomps in current document and returns 'comps' -- ************************************************************ function FO_Utilities:AllComps(moho) local doc = moho.document local comps = {} for i = 0, doc:CountLayerComps() do local comp = doc:GetLayerComp(i) table.insert(comps, comp) end return comps end -- ********************************************************* -- Finds all layers in current document and returns 'layers' -- ********************************************************* function FO_Utilities:AllLayers(moho, doc) doc = doc or moho.document -- print ("FO_Utilities:AllLayers(moho)") local layers = {} local stack = {} local sp = 0 for i=0, doc:CountLayers()-1 do local layer = doc:Layer(i) table.insert(layers, layer) local group = nil local layerID = 0 while true do if (layer:IsGroupType()) then table.insert(stack, {group, layerID -1}) sp = sp+1 group = moho:LayerAsGroup(layer) layerID = group:CountLayers() end if (layerID > 0) then layerID = layerID -1 layer = group:Layer--[[ByDepth]](layerID) table.insert(layers, layer) else layerID = -1 while (sp > 0) do group, layerID = stack[sp][1], stack[sp][2] table.remove(stack) sp = sp -1 if (layerID >= 0) then layer = group:Layer--[[ByDepth]](layerID) table.insert(layers, layer) break end end end if (layerID < 0) then break end end end return layers end -- ********************************************************* -- Like AllLayers() but skipping non visible and STB layers -- ********************************************************* function FO_Utilities:ShotLayers(moho, doc) doc = doc or moho.document -- print ("FO_Utilities:ShotLayers(moho, doc)") local layers = {} local stack = {} local sp = 0 for i=0, doc:CountLayers()-1 do local layer = doc:Layer(i) -- -- if (string.match(string.lower(layer:Name()), string.lower("STB"))) then -- break elseif not layer:IsVisible() then -- break else -- -- table.insert(layers, layer) local group = nil local layerID = 0 while true do if (layer:IsGroupType()) then table.insert(stack, {group, layerID -1}) sp = sp+1 group = moho:LayerAsGroup(layer) layerID = group:CountLayers() end if (layerID > 0) then layerID = layerID -1 layer = group:Layer--[[ByDepth]](layerID) table.insert(layers, layer) else layerID = -1 while (sp > 0) do group, layerID = stack[sp][1], stack[sp][2] table.remove(stack) sp = sp -1 if (layerID >= 0) then layer = group:Layer--[[ByDepth]](layerID) table.insert(layers, layer) break end end end if (layerID < 0) then break end -- end end end return layers end -- ********************************************************* -- Make sure target_doc imports all styles form source_doc -- ********************************************************* function FO_Utilities:MatchStyles(source_doc, target_doc) for i = 0, source_doc:CountStyles() - 1 do local sourceStyle = source_doc:StyleByID(i) local alreadyExists = false for j = 0, target_doc:CountStyles() - 1 do local targetStyle = target_doc:StyleByID(j) if sourceStyle:ArePropertiesEqual(targetStyle) then alreadyExists = true break end end if not alreadyExists then target_doc:AddStyle(sourceStyle) end end end -- ********************************************************* -- Finds all layers in current document and returns 'layers' -- ********************************************************* function FO_Utilities:LayerInclChildLayers(moho, layer) local layers = {} local stack = {} local sp = 0 table.insert(layers, layer) if (layer:IsGroupType()) then moho:LayerAsGroup(layer) for i=0, layer:CountLayers()-1 do local layer = layer:Layer(i) table.insert(layers, layer) local group = nil local layerID = 0 while true do if (layer:IsGroupType()) then table.insert(stack, {group, layerID -1}) sp = sp+1 group = moho:LayerAsGroup(layer) layerID = group:CountLayers() end if (layerID > 0) then layerID = layerID -1 layer = group:Layer--[[ByDepth]](layerID) table.insert(layers, layer) else layerID = -1 while (sp > 0) do group, layerID = stack[sp][1], stack[sp][2] table.remove(stack) sp = sp -1 if (layerID >= 0) then layer = group:Layer--[[ByDepth]](layerID) table.insert(layers, layer) break end end end if (layerID < 0) then break end end end end return layers end -- ********************************************************** -- Returns full layerstructure of specified layer as a string -- For example: "~/Forest/CrazyGuy/Arm/HandSwitch/Open" -- ********************************************************** function FO_Utilities:LayerStructure(layer) structure = layer:Name() while layer:Parent() ~= nil do layer = layer:Parent() structure = layer:Name().."/"..structure end structure = "~/"..structure return structure end -- ************************************************** -- List files in specific directory, returns 'files' -- ************************************************** function FO_Utilities:ListFiles(directory, moho) local files = {} moho:BeginFileListing(directory, true) local file = moho:GetNextFile() while file ~= nil do if file ~= ".DS_Store" then table.insert(files, file) end file = moho:GetNextFile() end table.sort(files, function(a, b) return a:upper() < b:upper() end) -- * Sorts table alphabetically return files end -- ************************************************** -- Check if a certain file exists -- ************************************************** function FO_Utilities:FileExists(moho, filePath) -- * Check if file exists: filePath = string.gsub(filePath, "\\", "/") local lastslashpos = (filePath:reverse()):find("%/") -- find last slash local fileName = (filePath:sub(-lastslashpos+1)) -- filename only local fileDir = string.gsub(filePath, string.gsub(fileName, "%-", "%%-"), "") -- * Catch dash characters moho:BeginFileListing(fileDir, true) local fileFound = false local file = moho:GetNextFile() while file ~= nil and not fileFound do if file == fileName then fileFound = true else file = moho:GetNextFile() end end return fileFound end -- ************************************************** -- Get file directory -- ************************************************** function FO_Utilities:FileDirectory(filePath) filePath = string.gsub(filePath, "\\", "/") local lastslashpos = (filePath:reverse()):find("%/") -- find last slash local fileDir = string.sub(filePath, 0, -lastslashpos-1) return fileDir end -- ************************************************** -- Get Project directory -- ************************************************** function FO_Utilities:GetProjectDirectory(path) -- * Find Project directory: local projectDirectory = FO_Utilities:FileDirectory(path) local foundProjectDirectory = false local lookForDirectories = { "/Shots/", "/Rigs/", "/Actions/" } -- * This are always located in the root of the Project directory. for i = 1, #lookForDirectories do if not foundProjectDirectory then local lookFor = lookForDirectories[i] local firstDirPos = (string.lower(path)):find(string.lower(lookFor)) if firstDirPos ~= nil then projectDirectory = string.sub(path, 0, firstDirPos-1) foundProjectDirectory = true else projectDirectory = FO_Utilities:FileDirectory(path) end end end return projectDirectory end -- **************************************************** -- List layers with a specific tag, returns 'taglayers' -- **************************************************** function FO_Utilities:ListLayersOfType(type, moho) local typeLayers = {} local layers = FO_Utilities:AllLayers(moho) local i for i = 1, #layers do layer = layers[i] if layer:LayerType() == type then table.insert(typeLayers, layer) end end return typeLayers end -- **************************************************** -- List layers with a specific tag, returns 'taglayers' -- **************************************************** function FO_Utilities:ListLayersWithTag(tag, moho) local taglayers = {} local layers = FO_Utilities:AllLayers(moho) local i for i = 1, #layers do layer = layers[i] local tags = layer:UserTags() --local tag = "rig" if string.match(tags, tag) then table.insert(taglayers, layer) end end return taglayers end -- **************************************************** -- -- **************************************************** function FO_Utilities:LayerHasTag(layer, tag) local tags = layer:UserTags() return (string.match(string.lower(tags), string.lower(tag))) end -- ************************************************** -- Find a layer by name -- ************************************************** function FO_Utilities:AnyLayerByName(moho, name, types) local layers = FO_Utilities:AllLayers(moho) for i = 1, #layers do local layer = layers[i] if types ~= nil then local layerType = layer:LayerType() for j = 1, #types do local type = types[j] if layerType ~= type then if layer:Name() == name then return layer end end end else if layer:Name() == name then return layer end end end return nil end -- ************************************************** -- LAYER TYPES -- ************************************************** -- 0 LT_UNKNOWN Unkown layer type -- 1 LT_VECTOR Vector layer type -- 2 LT_IMAGE Image layer type -- 3 LT_GROUP Group layer type -- 4 LT_BONE Bone layer type -- 5 LT_SWITCH Switch layer type -- 6 LT_PARTICLE Particle layer -- 7 LT_NOTE Note layer type -- 8 LT_3D 3D layer type -- 9 LT_AUDIO Audio layer type -- 10 LT_PATCH Patch layer type -- 11 LT_TEXT Text layer type -- ************************************************** -- ************************************************** -- Toggles a tag on selected layers -- ************************************************** function FO_Utilities:ToggleTagSelectedLayers(tag, moho) local selCount = moho.document:CountSelectedLayers() local layer = moho.layer local tagMatch = false local tagNotfound = false local doAddTag = false local tags --Round 1 - Check if any of the selected layers already has the tag but other don't. Because in that case we should add the tag to all layers which don't have it yet. for i = 0, selCount - 1 do local layer = moho.document:GetSelectedLayer(i) tags = layer:UserTags() -- * Is ["..tag.."] in ["..tags.."]?") if string.match(tags, tag) then tagMatch = true else tagNotfound = true end end --Decide if we need to add or remove the tag from all layers if tagNotfound == true then-- and tagMatch == false then doAddTag = true end -- * doAddTag: "..tostring(doAddTag)) --Round 2 - Actually do it per layer for i = 0, selCount - 1 do local layer = moho.document:GetSelectedLayer(i) local tags = layer:UserTags() if doAddTag == false then -- Remove tag from layer self:RemoveTag(tag, layer, moho) elseif doAddTag == true then -- Add tag to each layer self:AddTag(tag, layer, moho) end end end -- ***************************************************************************************** -- Check if layer should never be considered an animation layer (particles, references, etc) -- ***************************************************************************************** function FO_Utilities:NonKeysTagLayer(layer, moho) local skipLayer = false -- * Skip Particles: local parentLayer = layer:Parent() -- * todo, check entire parent hierarchy if parentLayer ~= nil then if moho:LayerAsParticle(parentLayer) ~= nil then skipLayer = true end end return skipLayer end -- ************************************************** -- Adds a tag to a layer -- ************************************************** function FO_Utilities:AddTag(tag, layer) tags = layer:UserTags() if tags == "" then tags = tag elseif not string.match(tags, tag) then tags = tags .. ", " .. tag -- Add tag to tags string tags = self:CleanString(tags) end layer:SetUserTags(tags) end -- ************************************************** -- Cleans string of unnecessary spaces and commas -- ************************************************** function FO_Utilities:CleanString(str) -- * Remove spaces before a comma: while string.match(str, " ,") do str = string.gsub(str, " , ", ",") end -- * Remove double commas: while string.match(str, ",,") do str = string.gsub(str, ",,", ",") end -- * Add spaces after comma's: str = string.gsub(str, ",",", ") -- * if string.sub(str,1, 1) == "," then -- * Check if the string starts with a comma str = string.sub(str, 2) -- * Remove the first character of the string because it's a comma end -- * Remove double spaces: while string.match(str, " ") do str = string.gsub(str, " ", " ") end -- * Remove space at the end of string: if string.sub(str, 0, 1) == " " then str = str:sub(2, -1) end -- * Remove space at the end of string: if string.sub(str, -1) == " " then str = str:sub(1, -2) end return str end -- ************************************************** -- Adds a tag to all selected layers -- ************************************************** function FO_Utilities:AddTagToSelectedLayers(tag, moho) --FOUT -- * FO_Utilities:AddTagToSelectedLayers("..tag..", moho)") local selCount = moho.document:CountSelectedLayers() for i = 0, selCount - 1 do local layer = moho.document:GetSelectedLayer(i) self:AddLayerTag(tag, layer, moho) end end -- ************************************************** -- Removes a tag to all selected layers -- ************************************************** function FO_Utilities:RemoveTagFromSelectedLayers(tag, moho) --FOUT local selCount = moho.document:CountSelectedLayers() for i = 0, selCount - 1 do local layer = moho.document:GetSelectedLayer(i) self:RemoveLayerTag(tag, layer, moho) end end -- ************************************************** -- Removes a tag from a layer -- ************************************************** function FO_Utilities:RemoveTag(tag, layer, moho) -- * FO_Utilities:RemoveTag("..tag..", "..layer:Name()..", moho)") local tags = layer:UserTags() local commaTag = "," .. tag local commaSpaceTag = ", " .. tag tags = tags:gsub(commaSpaceTag, "") -- Remove ", anim" from tags string tags = tags:gsub(commaTag, "") -- remove ",anim" from string, in case it doesn't have the space tags = tags:gsub(tag, "") -- remove tag from tags string if it wasn't already deleted with comma and/or space tags = self:CleanString(tags) layer:SetUserTags(tags) end -- *********************************************************************** -- Get shot name based on second to last underscore (removes _v000_X.moho) -- *********************************************************************** function FO_Utilities:ShotName(moho) local path = moho.document:Path() if path == nil then print ("path == nil") return nil end local fileName = self:FileName(path) local shotName = string.gsub(fileName, "_v%d%d%d_%u+.moho", "") return shotName end -- *********************************************************************** -- Removes (v000_X) from string -- *********************************************************************** function FO_Utilities:StripRigVersionFromString(str) return string.gsub(str, " [(][Vv]%d+_[A-z]+[)]", "") --" %(_v%d%d%d_%u+%)" end -- *********************************************************************** -- Strip everything before the last slash of a full filepath and return filename -- *********************************************************************** function FO_Utilities:FileName(path, stripExtension) -- extension = extension or true -- * Doesn't work!? path = string.gsub(path, "\\", "/") local lastslashpos = (path:reverse()):find("%/") -- * find last slash local fileName = (path:sub(-lastslashpos+1)) -- * filename only if stripExtension == true then -- * (stripExtension == nil/false will return filename WITH exentsion) -- * Find the position of the last dot in the filename local dotIndex = fileName:find("%.[^%.]*$") -- * If a dot is found, remove the extension if dotIndex then fileName = fileName:sub(1, dotIndex - 1) end end return fileName end -- ***************************************************************************************************** -- Filters layerpanel by tag and selects a layer if necessary and optionally set the timeline visibility -- ***************************************************************************************************** function FO_Utilities:FilterTag(tag, setTimelineVisibility, moho) if (setTimelineVisibility == false and moho:LayersWindowGetSearchContext() == 8 and moho:LayersWindowGetSearchContextValue() == tag) then moho:LayersWindowSetSearchContext(0) -- 0 = LAYERWND_SEARCHCONTEXT_ALL moho:LayersWindowSetSearchContextValue("") moho:ShowLayerInLayersPalette(moho.layer) else moho:LayersWindowSetSearchContext(8) -- 8 = LAYERWND_SEARCHCONTEXT_TAGCONTAIN moho:LayersWindowSetSearchContextValue(tag) local layer = moho.layer first_match = nil tags = layer:UserTags() if string.match(tags, tag) then first_match = layer end local layers = FO_Utilities:AllLayers(moho) local i for i = 1, #layers do layer = layers[i] tags = layer:UserTags() if string.match(tags, tag) then if first_match == nil then first_match = layer end if (setTimelineVisibility == true) then layer:SetShownOnTimeline(true) end elseif (setTimelineVisibility == true) then layer:SetShownOnTimeline(false) end end if first_match ~= nil then moho:SetSelLayer(first_match, false, false) end end end -- ************************************************** -- Filters layerpanel by group name -- ************************************************** function FO_Utilities:FilterGroupname(groupname, moho) if (moho:LayersWindowGetSearchContext() == 2) then moho:LayersWindowSetSearchContext(0) -- 0 = LAYERWND_SEARCHCONTEXT_ALL moho:LayersWindowSetSearchContextValue("") else moho:LayersWindowSetSearchContext(2) -- 2 = LAYERWND_SEARCHCONTEXT_GROUPNAMECONTAINS moho:LayersWindowSetSearchContextValue(groupname) end 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 -- ************************************************** -- Find index of element in table -- ************************************************** function table.find(table, element) for index, value in pairs(table) do if value == alement then return index end end end -- ************************************************** -- Deletes an element from a table -- ************************************************** function table.delete(tbl, element) for index, value in pairs(tbl) do if value == element then table.remove(tbl, index) end end end -- ************************************************** -- Removes element from table -- ************************************************** function table.removeElement(tab, element) for i, value in pairs(tab) do if value == element then table.remove(tab, i) return end end end -- ************************************************** -- Round a number by x amount of decimals -- ************************************************** function round(number, decimals) local power = 10^decimals return math.floor(number * power) / power end -- *********************************************************************** -- Show all child layers of 'layer' that are tagged "anim" on the timeline -- *********************************************************************** function FO_Utilities:ShowAnimChildLayersOnTimeline(layer) local tags = layer:UserTags() if (string.match(tags, FO_Utilities.rigTag) and not layer:SecondarySelection()) then return end for i = 0, layer:CountLayers() - 1 do local layer = layer:Layer(i) tags = layer:UserTags() local tags = layer:UserTags() if layer:IsGroupType() then self:ShowAnimChildLayersOnTimeline(layer) end if string.match(tags, FO_Utilities.animTag) and not string.match(tags, FO_Utilities.rigTag) then layer:SetShownOnTimeline(true) end end end -- *********************************************************************** -- Show all child layers of 'layer' that have keys -- *********************************************************************** function FO_Utilities:SecondarySelectKeyedChildLayers(moho, layer) local tags = layer:UserTags() if (string.match(tags, FO_Utilities.rigTag) and not layer:SecondarySelection()) then return end if layer:IsGroupType() then for i = 0, layer:CountLayers() - 1 do local layer = layer:Layer(i) tags = layer:UserTags() local tags = layer:UserTags() if layer:IsGroupType() then self:SecondarySelectKeyedChildLayers(moho, layer) end if FO_Utilities:LayerIsAnimated(moho, layer) and not string.match(tags, FO_Utilities.rigTag) then layer:SetSecondarySelection(true) -- moho:ShowLayerInLayersPalette(layer) -- * TODO, make optional? -- end end end end -- ************************************************** -- Recolor bones (The vanilla way) -- ************************************************** function FO_Utilities:RecolorizeBones(vanilla, moho) if moho.layer:UserComments() == "" then return end local skel = FO_Utilities:GetSkel(moho) if skel == nil then return end local customSkelActive = FO_Draw:WantCustomSkel(moho) for i = 0, skel:CountBones() - 1 do local bone = skel:Bone(i) local boneColorComment = FO_Utilities:LegacyBoneCommentColor(moho, i) if vanilla and not customSkelActive then if boneColorComment ~= 0 then bone:SetTags(boneColorComment) end end end local rigLayer = FO_Utilities:RigLayer(moho)--fix? if rigLayer ~= nil then Debug:Log("Removed comment from "..moho.layer:Name().." ("..moho.layer:UserComments()..")") rigLayer:SetUserComments("") -- * Force viewport to redraw itself: MOHO.Redraw() -- * Update UI so timeline channels show up and dissapear correctly: moho:UpdateUI() end end function FO_Utilities:Alert2(msg1, msg2, msg3) local type = LM.GUI.ALERT_WARNING if msg1 == "i" then type = LM.GUI.ALERT_INFO elseif msg1 == "!" then type = LM.GUI.ALERT_WARNING elseif msg1 == "?" then type = LM.GUI.ALERT_QUESTION end if msg3 ~= nil then msg3 = "\n"..msg3 end LM.GUI.Alert(icon, msg1, msg2, msg3, "OK", nil, nil) end -- ********************************************************* -- Simple alert dialog, takes many strings. -- Either as a table with strings, or as multiple arguments. -- Add a "i"/"?"/"!" to change the type of alertbox. -- ********************************************************* function FO_Utilities:Alert(...) local arg = {...} if (type(...) == "table") then arg = ... end local msg1 = nil local msg2 = nil local msg3 = nil local type = LM.GUI.ALERT_WARNING for i,v in ipairs(arg) do if v == "i" then type = LM.GUI.ALERT_INFO elseif v == "!" then type = LM.GUI.ALERT_WARNING elseif v == "?" then type = LM.GUI.ALERT_QUESTION elseif msg1 == nil then msg1 = v elseif msg2 == nil then msg2 = v elseif msg3 == nil then msg3 = "\n"..v else msg3 = msg3.."\n"..v end end LM.GUI.Alert(type, msg1, msg2, msg3, "OK", nil, nil) end -- **************************************************** -- Create simple question dialog with OK/Cancel buttons -- **************************************************** -- * An empty table to set up as a dialog subclass: FO_Utilities.AreYouSureDialogTable = {} -- * Dialog function: function FO_Utilities:AreYouSureDialog(moho, dialogTitle, messages) local d = LM.GUI.SimpleDialog(dialogTitle, self.AreYouSureDialogTable) local l = d:GetLayout() d.moho = moho local command = "" local align = LM.GUI.ALIGN_LEFT if messages == nil then return d end for i = 1, #messages do msg = messages[i] if i == #messages then if (string.match(string.lower(dialogTitle), "command")) then -- * SHOULD WRITE A NEAT SEPARATE FUNCTION... l:PushH() d.textBox = LM.GUI.TextControl(0, "Some text for a textbox to set its length!", 0, 0, "Command:") d.textBox:SetValue(command) l:AddChild(d.textBox, align) local tip = "(Copy/paste command into Windows Command Prompt)" if FO_Utilities:getOS() == "unix" then tip = "(Copy/paste command into MacOS Terminal)" end l:AddChild(LM.GUI.StaticText(tip)) l:Pop() end align = LM.GUI.ALIGN_RIGHT else command = command..msg end l:AddChild(LM.GUI.StaticText(msg), align) end return d end -- **************************************************** -- Create simple question dialog with text input -- **************************************************** -- * An empty table to set up as a dialog subclass: FO_Utilities.InputTextDialogTable = {} -- * Dialog function: function FO_Utilities:InputTextDialog(moho, dialogTitle, label, default, messages) local d = LM.GUI.SimpleDialog(dialogTitle, self.InputTextDialogTable) local l = d:GetLayout() d.moho = moho local align = LM.GUI.ALIGN_LEFT d.textBox = LM.GUI.TextControl(0, "Room for a loooooooooooooong string", 0, 0, label) d.textBox:SetValue(default) l:AddChild(d.textBox, align) if messages ~= nil then for i = 1, #messages do msg = messages[i] print (msg) l:AddChild(LM.GUI.StaticText(msg), align) end end return d end function FO_Utilities.InputTextDialogTable:OnOK() FO_Utilities.input = self.textBox:Value() end -- ************************************************* -- Create simple question dialog with Yes/No buttons -- ************************************************* function FO_Utilities:YesNoQuestion(...)--line1, line2, line3) local arg = {...} if (type(...) == "table") then arg = ... end local msg1 = nil local msg2 = nil local msg3 = nil local type = LM.GUI.ALERT_WARNING for i,v in ipairs(arg) do if v == "i" then type = LM.GUI.ALERT_INFO elseif v == "!" then type = LM.GUI.ALERT_WARNING elseif v == "?" then type = LM.GUI.ALERT_QUESTION elseif msg1 == nil then msg1 = v elseif msg2 == nil then msg2 = v elseif msg3 == nil then msg3 = "\n"..v else msg3 = msg3.."\n"..v end end -- local d = LM.GUI.Alert( type, msg1, msg2, msg3, "Yes", "No", nil ) -- * Returns 0 or 1 if d == 0 then return true else return false end end function FO_Utilities:MultipleChoiceQuestion(...)--question, choice0, choice1, choice2) --last three must be choices or nil local arg = {...} if (type(...) == "table") then arg = ... end if #arg < 4 then print("ERROR too little arguments, at least 4 needed, last 3 are buttons/answers (can be nil)") end local question = arg[1] local choice0 = arg[#arg-2] local choice1 = arg[#arg-1] local choice2 = arg[#arg] -- # Remove answers: table.remove(arg, #arg) table.remove(arg, #arg) table.remove(arg, #arg) local messages = arg -- print("question = "..question) -- print("choice0 = "..choice0) -- print("choice1 = "..choice1) -- print("choice2 = "..choice2) -- -- * Convert messages: local bigMessage = "" for i = 2, #messages do bigMessage = bigMessage..messages[i].."\n" end -- * Multiple choice dialog: local answer = LM.GUI.Alert( LM.GUI.ALERT_QUESTION, messages[1], bigMessage, nil, choice0, choice1, choice2) -- * Returns 0, 1 or 2 -- print("answer = "..tostring(answer)) return answer end -- ************************************************** -- Create directory -- ************************************************** function FO_Utilities:CreateDirectory(moho, directory) local directoryExists = false if moho ~= nil then directoryExists = FO_Utilities:FileExists(moho, directory) end if not directoryExists then if FO_Utilities:getOS() == "win" then directory = "\""..directory.."\"" os.execute("mkdir "..directory) else directory = string.gsub(directory, " ", "\\ ") os.execute("mkdir "..directory) end end end -- ************************************************** -- Get Operating System, returns "win" or "unix" -- ************************************************** function FO_Utilities:getOS() if os.getenv("OS") ~= nil then local opSys = string.lower(string.sub(os.getenv("OS"), 1, 3)) if opSys == "win" then return "win" else return "unix" end else return "unix" end end -- ************************************************** -- Get Moho Version -- ************************************************** function FO_Utilities:MohoVersion(moho) local helper -- * Uh if moho == nil then if MOHO.ScriptInterfaceHelper == nil then -- print("MOHO.ScriptInterfaceHelper == nil") return 0.0 -- * dumb broken temp solution hack TODO end helper = MOHO.ScriptInterfaceHelper:new_local() moho = helper:MohoObject() end if moho.AppVersion ~= nil then local sVersion = string.gsub(moho:AppVersion(), "^(%d+)(%.%d+)(%..+)", "%1%2") version = tonumber(sVersion) end if helper ~= nil then helper:delete() end return version end -- ************************************************** -- Open text file in text editor -- ************************************************** function FO_Utilities:EditTextFile(path, moho) local command local editor = "Sublime Text" if(FO_Utilities:getOS()=="win") then local editorpath = "C:\\Program Files\\Sublime Text 3\\sublime_text.exe" path = string.gsub(path, "/", "\\") -- * Reverse and double slashes for Windows command = "start \""..editor.."\" \""..editorpath.."\" \""..path.."\"" else path = string.gsub(path, " ", "\\ ") -- * Add backslash to space command = "open -a \""..editor.."\" "..path end FO_Utilities:Execute(command, false, false, moho) -- ************************************************************************ -- *** In "Sublime Text" config, add: 'open_files_in_new_window": true,' -- ************************************************************************ end -- ************************************************** -- Get bone's basename without all the bonetags -- ************************************************** function FO_Utilities:BaseName(boneName) local tags = FO_Utilities.boneTags local baseName = boneName for word in string.gmatch(boneName, "%S+") do for i = 1, #tags do local tag = tags[i] if string.match(word, tag) then baseName = string.gsub(baseName, tag, "") end end end baseName = FO_Utilities:CleanString(baseName) baseName = string.gsub(baseName, '[ \t]+%f[\r\n%z]', '') -- trim whitespace at start and end of string return baseName end -- ************************************************** -- Get distance between vectors -- ************************************************** function FO_Utilities:Distance(moho, vector1, vector2) local distance = vector1 - vector2 distance = math.abs(distance:Mag()) return distance end -- ************************************************** -- Check if bone has a smartbone action -- ************************************************** function FO_Utilities:IsASmartBone(moho, skel, bone) local name = bone:Name() if not moho.layer:HasAction(name) then return false end if string.find(name, "|") then -- ? return false end local boneID = skel:BoneID(bone) local layers = FO_Utilities:AllLayers(moho) for i = 1, #layers do local layer = layers[i] if layer:ControllingSkeleton() == skel then local parentBone = layer:LayerParentBone() if parentBone >= 0 then if parentBone == boneID then return false end else local meshLayer = moho:LayerAsVector(layer) if meshLayer then local mesh = meshLayer:Mesh() for p=0, mesh:CountPoints()-1 do if mesh:Point(p).fParent == boneID then return false end end end end end end return true end --- ************************************************** -- Checks if layer is animated -- ************************************************** function FO_Utilities:LayerIsAnimated(moho, layer) if not layer:IsReferencedLayer() then if not self:NonKeysTagLayer(layer, moho) then layer:ClearLayerKeyCount() if (layer:CountLayerKeys() > 2) then return true else return false end end end end -- ************************************************************************** -- Check if two values are equel: -- (Credits: A.Evseeva) -- ************************************************************************** function FO_Utilities:IsEqualValues(channel, val1, val2, accuracy) accuracy = accuracy or 0.0000001 local chanType = channel:ChannelType() if chanType == MOHO.CHANNEL_VAL then return (math.abs(val1 - val2) < accuracy) elseif chanType == MOHO.CHANNEL_VEC2 then return (((val1-val2):Mag()) < accuracy) elseif chanType == MOHO.CHANNEL_VEC3 then return (((val1-val2):Mag()) < accuracy) elseif chanType == MOHO.CHANNEL_COLOR then return val1.r == val2.r and val1.g == val2.g and val1.b == val2.b and val1.a == val2.a elseif chanType == MOHO.CHANNEL_BOOL then return (val1 == val2) elseif chanType == MOHO.CHANNEL_STRING then return (val1 == val2) end return false end -- ************************************************** -- Split String -- ************************************************** function string.split(s, delimiter) result = {} local first = true for match in (s..string.gsub(delimiter, "%%", "")):gmatch("(.-)"..delimiter) do if first or delimiter == " " then table.insert(result, match) first = false else table.insert(result, string.gsub(delimiter, "%%", "")..match) end end return result end -- ************************************************** -- Alternative (better?) Split String Function -- ************************************************** --[[ function string.split(text, sep, plain) -- * Emulate Python split function -- ########################################################################## -- # split() function for lua -- # Markus Nentwig 2007 -- # This code is in the public domain and provided without any warranty. -- ########################################################################## local result = {} local searchPos = 1 while true do local matchStart, matchEnd = string.find(text, sep, searchPos, plain) if matchStart and matchEnd >= matchStart then -- insert string up to separator into result table.insert(result, string.sub(text, searchPos, matchStart-1)) -- continue search after separator searchPos = matchEnd+1 else -- insert whole reminder as result table.insert(result, string.sub(text, searchPos)) break end end return result end --]] -- ************************************************** -- Capitalize first character of a string -- ************************************************** function string:CapitalizeFirst(str) return (str:gsub("^%l", string.upper)) end -- ************************************************** -- Collapse all groups in the layer panel -- ************************************************** function FO_Utilities:CollapseGroups(moho) local layers = FO_Utilities:AllLayers(moho) local i for i = 1, #layers do layer = layers[i] if layer:IsGroupType() == true then layer:Expand(false) end end end -- ************************************************************************** -- Dummy function to make sure no errors are thrown if Debug.lua is not found -- ************************************************************************** Debug = {} function Debug:Log() return end function FO_Utilities:InSmartBoneAction(moho) local action = moho.document:CurrentDocAction() if action ~= "" then -- * Only check if Action is a Smartbone-Action if we're in an Action: if moho.layer:IsSmartBoneAction(action) then return true end end return false end -- ************************************************** -- Removes 'forbidden' keys on bones such as: -- T/S on sb-dials -- R/S on sb-targets -- Any keys except a T on frame 1 for the GUI-bone -- ************************************************** function FO_Utilities:RemoveForbiddenBoneKeys(moho) local skel = FO_Utilities:GetSkel(moho) if skel == nil then return end local inSmartboneAction = self:InSmartBoneAction(moho) for i = 0, skel:CountBones() - 1 do local bone = skel:Bone(i) local boneName = bone:Name() if self:IsGUIBone(bone) then -- * GUI-bone bone.fAnimPos:ClearAfter(1) bone.fAnimAngle:ClearAfter(1) bone.fAnimScale:Clear(0) elseif not inSmartboneAction and bone.fShy then -- * Shy bones can be keyed in Smartbone actions, but not in the Mainline! bone.fAnimPos:Clear(0) bone.fAnimAngle:Clear(0) bone.fAnimScale:Clear(0) elseif FO_Utilities:IsSmartboneDial(skel, bone) then -- * Smartbone-dial bone.fAnimPos:Clear(0) bone.fAnimScale:Clear(0) elseif bone:IsZeroLength() and bone:IsLabelShowing() == true then -- * Smartbone-target bone.fAnimAngle:Clear(0) bone.fAnimScale:Clear(0) elseif string.match(bone:Name(), self.switchTag) then -- * Switch (slave) bones bone.fAnimPos:Clear(0) bone.fAnimAngle:Clear(0) bone.fAnimScale:Clear(0) end if LK_BoneSettings ~= nil then -- * Remove keys from fully constrained bones: if LK_BoneSettings:Constraint(bone.fAnimAngle) or bone.fMinConstraint == 0 and bone.fMaxConstraint == 0 then -- * TODO channel:Clear() BREAKS REFERENCED CHANNELS! FIX THIS! bone.fAnimAngle:Clear(0) -- * ??? 0 is frameZero argument, could go wrong? end if LK_BoneSettings:Constraint(bone.fAnimPos) then bone.fAnimPos:Clear(0) -- * ??? 0 is frameZero argument, could go wrong? end if LK_BoneSettings:Constraint(bone.fAnimScale) then bone.fAnimScale:Clear(0) -- * ??? 0 is frameZero argument, could go wrong? end end end end -- ************************************************** -- Paints keys the same color as the bones they belong too -- ************************************************** function FO_Utilities:PaintKeys(moho) if not self.colorKeys then return end local c, i local doc = moho.document local layer = moho.layer local cvStep local totalSel = 0 if layer:IsBoneType() then local boneLayer = moho:LayerAsBone(layer) local skel = boneLayer:Skeleton() if skel:CountBones() > 0 then -- if some ... local channels = {} for i = 0, skel:CountBones() - 1 do local bone = skel:Bone(i) local boneColor = bone:Tags() totalSel = 0 -- no keys so far for this bone c = 0 c = c + 1; channels[c] = skel:Bone(i).fAnimPos c = c + 1; channels[c] = skel:Bone(i).fAnimAngle c = c + 1; channels[c] = skel:Bone(i).fAnimScale c = c + 1; channels[c] = skel:Bone(i).fAnimParent c = c + 1; channels[c] = skel:Bone(i).fTargetBone c = c + 1; channels[c] = skel:Bone(i).fFlipH c = c + 1; channels[c] = skel:Bone(i).fFlipV c = c + 1; channels[c] = skel:Bone(i).fIKGlobalAngle c = c + 1; channels[c] = skel:Bone(i).fIKLock c = c + 1; channels[c] = skel:Bone(i).fIKParentTarget c = c + 1; channels[c] = skel:Bone(i).fPhysicsMotorSpeed for c = 1, #channels do local channel = channels[c] local endKey = channel:Duration() local thisKey = 0 -- the id of the key being processed local keyCount = channel:CountKeys() -- (-1 to ignore frame 0) local keysFound = 0 local frameNum = endKey while keysFound < keyCount do thisKey = channel:GetClosestKeyID(frameNum) local keyFrameNum = channel:GetKeyWhen(thisKey) keysFound = 1 + keysFound if (keyFrameNum ~= 0) then -- todo, check if this breaks referenced channels local interp = MOHO.InterpSetting:new_local() channel:GetKeyInterp(keyFrameNum, interp) interp.tags = boneColor channel:SetKeyInterp(keyFrameNum, interp) end frameNum = keyFrameNum - 1 end end end end end self:StepKeys(moho) --todo betere plek, en misschien totaal niet in zn eigen functie maar inbakken bij het zetten en cleanen van keys. -- Update UI --moho:UpdateSelectedChannels() -- ? --moho.layer:UpdateCurFrame() -- ? --moho:UpdateUI() end -- **************************************************************************************************************** -- *** Set keys from bones marked '!step' to step. *** -- **************************************************************************************************************** function FO_Utilities:StepKeys(moho) if LK_BoneSettings == nil then return end local c, i, j, k, n, p, t local doc = moho.document local layer = moho.layer local cvStep local totalSel = 0 if layer:IsBoneType() then local boneLayer = moho:LayerAsBone(layer) local skel = boneLayer:Skeleton() if skel:CountBones() > 0 then -- if some ... local channels = {} for i = 0, skel:CountBones() - 1 do local bone = skel:Bone(i) local boneName = bone:Name() if LK_BoneSettings:ForceStep(bone) then totalSel = 0 -- no keys so far for this bone c = 0 c = c + 1; channels[c] = skel:Bone(i).fAnimPos c = c + 1; channels[c] = skel:Bone(i).fAnimAngle c = c + 1; channels[c] = skel:Bone(i).fAnimScale c = c + 1; channels[c] = skel:Bone(i).fAnimParent c = c + 1; channels[c] = skel:Bone(i).fTargetBone c = c + 1; channels[c] = skel:Bone(i).fFlipH c = c + 1; channels[c] = skel:Bone(i).fFlipV c = c + 1; channels[c] = skel:Bone(i).fIKGlobalAngle c = c + 1; channels[c] = skel:Bone(i).fIKLock c = c + 1; channels[c] = skel:Bone(i).fIKParentTarget c = c + 1; channels[c] = skel:Bone(i).fPhysicsMotorSpeed for c = 1, #channels do local channel = channels[c] local endKey = channel:Duration() local thisKey = 0 -- the id of the key being processed local keyCount = channel:CountKeys() -- (-1 to ignore frame 0) local keysFound = 0 local frameNum = endKey while keysFound < keyCount do thisKey = channel:GetClosestKeyID(frameNum) local keyFrameNum = channel:GetKeyWhen(thisKey) keysFound = 1 + keysFound if (keyFrameNum ~= 0) then -- todo, check if this breaks referenced channels local interp = MOHO.InterpSetting:new_local() channel:GetKeyInterp(keyFrameNum, interp) interp.interpMode = 3 -- 3 = INTERP_STEP channel:SetKeyInterp(keyFrameNum, interp) end frameNum = keyFrameNum - 1 end end end end end end FO_Utilities:StepBools(moho) end -- ************************************************** -- Sets all bool keys to green/red -- ************************************************** function FO_Utilities:StepBools(moho, allLayers) allLayers = allLayers or false local layers = {} if allLayers then layers = FO_Utilities:AllLayers(moho) else for i = 0, moho.document:CountSelectedLayers() - 1 do local layer = moho.document:GetSelectedLayer(i) table.insert(layers, layer) end end for i = 1, #layers do local layer = layers[i] -- print(layer:Name()) -- for i = 0, moho.document:CountSelectedLayers() - 1 do -- local layer = moho.document:GetSelectedLayer(i) local chInfo = MOHO.MohoLayerChannel:new_local() -- * Iterate channels: for i = 0, layer:CountChannels()-1 do layer:GetChannelInfo(i, chInfo) if not chInfo.selectionBased then for j=0, chInfo.subChannelCount-1 do local subChannel = layer:Channel(i, j, moho.document) local channel = nil if subChannel:ChannelType() == MOHO.CHANNEL_BOOL then channel = moho:ChannelAsAnimBool(subChannel) if channel and not (chInfo.name:Buffer() == "All Channels") then local endKey = channel:Duration() local thisKey = 0 -- * ID of the key being processed. local keyCount = channel:CountKeys() -- * Including frame 0. local keysFound = 0 local frameNum = endKey while keysFound < keyCount do thisKey = channel:GetClosestKeyID(frameNum) frameNum = channel:GetKeyWhen(thisKey) if subChannel:ChannelType() == MOHO.CHANNEL_BOOL then local interp = MOHO.InterpSetting:new_local() channel:GetKeyInterp(frameNum, interp) interp.interpMode = 3 -- * 3 = INTERP_STEP if channel:GetValue(frameNum) then interp.tags = 4 -- * 4 = Green else interp.tags = 1 -- * 1 = Red end channel:SetKeyInterp(frameNum, interp) end keysFound = 1 + keysFound frameNum = frameNum - 1 end end end end end end end end function FO_Utilities:PointsColorKeys(moho) return -- * Disabled, buggy -- local mesh = moho:DrawingMesh() -- for i = 0, mesh:CountGroups()-1 do -- local group = mesh:Group(i) -- for j = 0, group:CountPoints()-1 do -- local groupName = group:Name() -- if groupName ~= "Shy" then -- local color = 12 -- for c = 1, #FO_Utilities.colorNames do -- local colorName = FO_Utilities.colorNames[c] -- if string.lower(groupName) == string.lower(colorName) then -- color = c -- end -- end -- local point = group:Point(j) -- local channels = {} -- table.insert(channels, point.fAnimPos) -- table.insert(channels, point.fWidth) -- -- * AS 11.0 -- table.insert(channels, point.fColor) -- table.insert(channels, point.fColorStrength) -- for j = 0, point:CountCurves()-1 do -- local curvePointID = mesh:PointID(point) --? -- local curve = point:Curve(j, curvePointID) --? -- -- curve, curvePointID = point:Curve(j, curvePointID) --? -- -- curve, curvePointID = point:Curve(j, 0) --? -- -- * AS 6.1 -- if curve.Curvature ~= nil then -- table.insert(channels, curve:Curvature(curvePointID)) -- end -- end -- -- * -- for i = 1, #channels do -- local channel = channels[i] -- -- -- local channel = channels[i] -- local endKey = channel:Duration() -- local thisKey = 0 -- * ID of the key being processed. -- local keyCount = channel:CountKeys() -- * Including frame 0. -- local keysFound = 0 -- local frameNum = endKey -- while keysFound < keyCount do -- thisKey = channel:GetClosestKeyID(frameNum) -- frameNum = channel:GetKeyWhen(thisKey) -- local interp = MOHO.InterpSetting:new_local() -- channel:GetKeyInterp(frameNum, interp) -- interp.tags = color -- channel:SetKeyInterp(frameNum, interp) -- keysFound = 1 + keysFound -- frameNum = frameNum - 1 -- end -- -- -- end -- end -- end -- end -- moho:UpdateUI() -- -- -- local mesh = moho:DrawingMesh() -- for i = 0, mesh:CountPoints() - 1 do -- local point = mesh:Point(i) -- local channels = {} -- table.insert(channels, point.fAnimPos) -- table.insert(channels, point.fWidth) -- -- * AS 11.0 -- table.insert(channels, point.fColor) -- table.insert(channels, point.fColorStrength) -- for j = 0, point:CountCurves()-1 do -- curve, curvePointID = point:Curve(j, curvePointID) -- -- * AS 6.1 -- if curve.Curvature ~= nil then -- table.insert(channels, curve:Curvature(curvePointID)) -- end -- end -- -- * -- for i = 1, #channels do -- local channel = channels[i] -- -- -- end -- end end -- ************************************************** -- Sets percentage keys to green/orange/red -- ************************************************** function FO_Utilities:PercentageKeyColors(moho) for i = 0, moho.document:CountSelectedLayers() - 1 do local layer = moho.document:GetSelectedLayer(i) channels = { layer.fAlpha } for i = 1, #channels do local channel = channels[i] local endKey = channel:Duration() local thisKey = 0 -- * ID of the key being processed. local keyCount = channel:CountKeys() -- * Including frame 0. local keysFound = 0 local frameNum = endKey while keysFound < keyCount do thisKey = channel:GetClosestKeyID(frameNum) frameNum = channel:GetKeyWhen(thisKey) local interp = MOHO.InterpSetting:new_local() channel:GetKeyInterp(frameNum, interp) local percentage = channel:GetValue(frameNum) * 100 percentage = math.floor(percentage + 0.5) if percentage == 100 then interp.tags = 4 -- * 4 = Green elseif percentage == 0 then interp.tags = 1 -- * 1 = Red elseif LK_LayerOpacity ~= nil and percentage == LK_LayerOpacity.presetValue then interp.tags = 6 -- * 6 = Purple else interp.tags = 2 -- * 2 = Orange end channel:SetKeyInterp(frameNum, interp) keysFound = 1 + keysFound frameNum = frameNum - 1 end end end end -- ************************************************** -- Sets a keyframe on all sub-switch-layers that are checked as visible in the timeline -- ************************************************** function FO_Utilities:KeyAllCheckedSubSwitches(moho) -- * Code doesn't work!? TODO local layers = FO_Utilities:AllLayers(moho) for i = 1, #layers do local layer = layers[i] local switchLayer = moho:LayerAsSwitch(layer) if (layer:IsAncestorSelected() == true and layer:IsShownOnTimeline() == true and switchLayer) then local switchValue = switchLayer:GetValue(moho.frame) switchLayer:SetValue(moho.frame, switchValue) end end end function FO_Utilities:IsSmartboneDial(skel, bone) return (bone:IsLabelShowing() == true and self:IsGUIBone(skel:Bone(bone.fParent))) end function FO_Utilities:IsSmartboneTarget(bone) return (bone:IsZeroLength() and bone:IsLabelShowing() == true) end function FO_Utilities:IsGUIBone(bone) if bone == nil then -- * In case we are checking if a bone's parent is a GUIbone and it has no parent. return false end return (bone:Name() == self.guiTag) end function FO_Utilities:IsPinBone(bone) return (bone.fLength == 0 and not bone:IsLabelShowing() == true) end function FO_Utilities:GuiID(skel) for i = 0, skel:CountBones() - 1 do local bone = skel:Bone(i) if FO_Utilities:IsGUIBone(bone) then return i end end return -1 end -- function FO_Utilities:IsHipBone(bone) -- return (string.match(bone:Name(), self.hipTag)) -- end -- ************************************************** -- Freeze shown bones -- ************************************************** function FO_Utilities:FreezeShownBones(moho) local skel = FO_Utilities:GetSkel(moho) if skel == nil then return end for i = 0, skel:CountBones() - 1 do local bone = skel:Bone(i) if not bone.fHidden then local setT = true local setS = true local setR = true -- * Smartbonedials: if FO_Utilities:IsSmartboneDial(skel, bone) then -- if FO_Utilities:IsSmartboneDial(skel, bone) then setS = false setT = false --[[ * Disabled smartbone-Targets: elseif bone:IsZeroLength() and bone:IsLabelShowing() == true then setS = false setR = false --]] end -- Check bonetags: local boneName = bone:Name() if (LK_BoneSettings:Constraint(bone.fAnimPos)) then setT = false end if (LK_BoneSettings:Constraint(bone.fAnimScale)) then setS = false end if (LK_BoneSettings:Constraint(bone.fAnimAngle)) then setR = false end -- Set allowed keys: if setT then local pos = bone.fAnimPos:GetValue(moho.frame) bone.fAnimPos:SetValue(moho.frame, pos) end if setS then local scale = bone.fAnimScale:GetValue(moho.frame) bone.fAnimScale:SetValue(moho.frame, scale) end if setR then local angle = bone.fAnimAngle:GetValue(moho.frame) bone.fAnimAngle:SetValue(moho.frame, angle) end end end end -- ************************************************** -- Rename bone including smartbone actions -- ************************************************** function FO_Utilities:RenameBoneAndAction(bone, newName, moho) local oldName = bone:Name() bone:SetName(newName) local layer = moho.layer if layer:HasAction(oldName) then layer:RenameAction(oldName, newName) end if layer:HasAction(oldName.." 2") then layer:RenameAction(oldName.." 2", newName.." 2") end end -- ************************************************** -- Find Nearest Point on a line -- ************************************************** function FO_Utilities:NearestPointOnLine(linePoint, lineDirection, pos) lineDirection:NormMe() local v = pos - linePoint local d = lineDirection:Dot(v) local nearestPos = linePoint + lineDirection * d return nearestPos end -- ************************************************** -- Try to go to the next (or previous) Switch Key -- ************************************************** function FO_Utilities:GoToNextSwitchKey(moho, previous) previous = previous or false local FBFLayer = nil -- * Reset if moho.layer:LayerType() == MOHO.LT_SWITCH then FBFLayer = moho:LayerAsSwitch(moho.layer )-- * Current layer is a switch! elseif moho.layer:AncestorSwitchLayer() ~= nil then FBFLayer = moho.layer:AncestorSwitchLayer() -- * Parent layer is switch! end if FBFLayer ~= nil then self:PrevNextSwitchKey(FBFLayer, moho.frame, previous, true, moho) end end -- ************************************************** -- Go to the next (or previous) switch key -- ************************************************** function FO_Utilities:PrevNextSwitchKey(switchLayer, startFrame, previous, gotoframe, moho) -- * local channel = switchLayer:SwitchValues() local endFrame = 1 if gotoframe or (channel:CountKeys() > 1) then endFrame = 0 end local found_key = false if not previous then local lastKey = channel:CountKeys() local lastKeyFrame = channel:GetKeyWhen(lastKey-1) endFrame = lastKeyFrame end -- * if (endFrame < moho.frame and not previous) then return found_key end -- local increment local timing_offset if endFrame > startFrame then increment = 1 else increment = -1 end startFrame = startFrame + increment -- * if switchLayer.TotalTimingOffset ~= nil then timing_offset = switchLayer:TotalTimingOffset() else timing_offset = 0 end -- * Go through next/previous frames and stop when a keyframe is found for frame = startFrame, endFrame, increment do -- * Going trough frames: " .. frame) if channel.HasKey ~= nil and channel:HasKey(frame) then found_key = true end if found_key then if gotoframe then moho:SetCurFrame(frame-timing_offset) end break end end return found_key end -- ************************************************** -- Expose bones by color and toggle back: -- ************************************************** function FO_Utilities:ToggleExposeColorBones(layer, skel, color) -- * Get hidden en visible bones: local hiddenBones = {} local visibleBones = {} for i = 0, skel:CountBones() - 1 do local bone = skel:Bone(i) local boneColor = bone:Tags() if not bone.fShy then if bone.fHidden then table.insert(hiddenBones, boneColor) elseif not bone.fHidden then table.insert(visibleBones, boneColor) end end end -- * local anyColorBoneExists = {} local anyColorBoneIsVisible = {} -- * for i = 0, 11 do -- * 11 = last color if table.contains(hiddenBones, i) then table.insert(anyColorBoneExists, i) elseif table.contains(visibleBones, i) then table.insert(anyColorBoneExists, i) table.insert(anyColorBoneIsVisible, i) end end -- * if table.contains(anyColorBoneExists, color) then -- IF minstens 1 bone van deze kleur (gekozen) bestaat THEN local expose = false for i = 0, 11 do if i ~= color then if table.contains(anyColorBoneIsVisible, i) then -- IF minstens 1 bone van deze kleur (any) is niet hidden THEN expose = true break end end end if not expose then for i = 0, skel:CountBones() - 1 do local bone = skel:Bone(i) if not bone.fShy then bone.fHidden = false elseif bone.fShy and layer:IsSmartBoneAction(action) and boneColor ~= color then bone.fHidden = false end end else for i = 0, skel:CountBones() - 1 do local bone = skel:Bone(i) local boneColor = bone:Tags() if boneColor == color and not bone.fShy then bone.fHidden = false else bone.fHidden = true end end end end end -- ******************************************************************** -- Get root bones (hip and feet, or simply all bones without a parent): -- ******************************************************************** function FO_Utilities:RootBones(skel, color) local rootBones = {} if #rootBones == 0 then for i = 0, skel:CountBones() - 1 do local bone = skel:Bone(i) if not bone.fHidden then local bone = skel:Bone(i) if bone.fParent == -1 then if color == nil or bone:Tags()==color then table.insert(rootBones, bone) end end end end end return rootBones end -- ************************************************** -- Toggles a tag in selected bones' names -- ************************************************** function FO_Utilities:ToggleTagSelectedBones(tag, moho) local skel = moho:Skeleton() local boneCount = skel:CountBones() local tagMatch = false local tagNotfound = false local doAddTag = false -- * Round 1 - Check if any of the selected bones already has the tag but other don't. Because in that case we should add the tag to all bones which don't have it yet.") for i = 0, boneCount - 1 do local bone = skel:Bone(i) if bone.fSelected then tags = bone:Name() if string.match(tags, tag) then tagMatch = true else tagNotfound = true end end end -- * Decide if we need to add or remove the tag from all bones") if tagNotfound == true then --or tagMatch == false then Debug:Log ("We have not found the tag in any of the selected bones' names.") doAddTag = true end -- * Round 2 - Actually do it per selected bone") for i = 0, boneCount - 1 do local bone = skel:Bone(i) if bone.fSelected then if doAddTag == false then -- Remove tag from bone FO_Utilities:RemoveTagFromBone(tag, bone, moho) elseif doAddTag == true then -- Add tag to each bone FO_Utilities:AddTagToBone(tag, bone, moho) end end end moho:UpdateUI() -- So the toolbar's widgets get updated. end -- ************************************************** -- Adds a tag to a bone's name -- ************************************************** function FO_Utilities:AddTagToBone(tag, bone, moho) -- moho eraf gehaald? local boneName = bone:Name() if boneName == "" then boneName = tag elseif not string.match(boneName, tag) then boneName = boneName .. " " .. tag -- Adds tag to boneName end boneName = FO_Utilities:CleanString(boneName) FO_Utilities:RenameBoneAndAction(bone, boneName, moho) end -- ************************************************** -- Removes a tag from a bone's name -- ************************************************** function FO_Utilities:RemoveTagFromBone(tag, bone, moho) local boneName = bone:Name() local spaceTag = " " .. tag boneName = boneName:gsub(spaceTag, "") boneName = boneName:gsub(tag, "") boneName = FO_Utilities:CleanString(boneName) FO_Utilities:RenameBoneAndAction(bone, boneName, moho) end -- ************************************************** -- Removes element from table -- ************************************************** function table.reverse(tab) -- * temp disabled, needs new lua version -- for i = 1, #tab//2, 1 do -- tab[i], tab[#tab-i+1] = tab[#tab-i+1], tab[i] -- end return tab end
FO_Utilities
Listed
Author: Lukas
View Script
Script type: Utility
Uploaded: Apr 19 2022, 07:03
Last modified: Dec 01 2023, 08:07
Script Version: 0
Utility file needed for scripts by Lukas Krepel, Frame Order
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: 4351
FO_Utilities
Listed
Author: Lukas
View Script
Script type: Utility
Uploaded: Apr 19 2022, 07:03
Last modified: Dec 01 2023, 08:07
Script Version: 0
Utility file needed for scripts by Lukas Krepel, Frame Order
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: 4351