Best practice using fx Dropbox to stop eternal, manual relinking

Wondering how to accomplish a certain animation task? Ask here.

Moderators: Víctor Paredes, Belgarath, slowtiger

Post Reply
batpete
Posts: 2
Joined: Fri Apr 22, 2022 12:45 pm

Best practice using fx Dropbox to stop eternal, manual relinking

Post by batpete »

Hi,

New Forum member here. I'm a Swedish animation producer developing a series for the Danish public broadcaster DR with Danish director Esben Toft Jacobsen and production company Tall and Small in Copenhagen.

We're working remotely syncing the material via Dropbox, using both Mac and PC. Linked files like BGs (PNG) and animatic files (mp4) can't remember the relative position to the moho-file, and everytime someone else needs to open a shot to do a fix, a render or work on it in other ways, we need to manually relink the files.

My thought is that Moho is looking for the files using absolute file positions, and this is different for each computer - not just between Mac and PC. I can't find any mentions to this issue or how to work around/solve it either here in the Forum or when just plain searching the web.

Does anyone have some light to shed on this and how to solve it?

For production an idea could be that remote users log in to a computer in the studio, working with files on the studio server, but it's not optimal.

Hoping the community can help me find a solution.

Best regards,
Petter
Swedish film producer with a passion for animation productions
Email: petter.lindblad@snowcloud.se
User avatar
synthsin75
Posts: 10253
Joined: Mon Jan 14, 2008 11:20 pm
Location: Oklahoma
Contact:

Re: Best practice using fx Dropbox to stop eternal, manual relinking

Post by synthsin75 »

The shared file, and it's external assets, should probably be saved using Gather Media. This is designed to keep track of the linked files relative to the .moho file.
I haven't tried it over file sharing, like Dropbox, though, but I think Greenlaw has.
User avatar
Lukas
Posts: 1336
Joined: Fri Apr 09, 2010 9:00 am
Location: Netherlands
Contact:

Re: Best practice using fx Dropbox to stop eternal, manual relinking

Post by Lukas »

We sync and share projects trough Nextcloud. Even mix Windows and Mac machines. The way Moho handles file paths is terrible for this because they are almost never relative but absolute (and this needs to be addressed!), but I scripted a solution that makes it work. You do need to run a script each time you open an unlinked file and first tell it not to locate images and there’s a bit more to consider. I’ll share more next week when I’m back at a computer.
User avatar
Lukas
Posts: 1336
Joined: Fri Apr 09, 2010 9:00 am
Location: Netherlands
Contact:

Re: Best practice using fx Dropbox to stop eternal, manual relinking

Post by Lukas »

Edit: ignore this post and read the next one. I'll leave this here just in case.

I've edited my script a bit to remove certain specifics. Install this as a menu script, save it as "LK_ChangeServerPaths.lua". You'll also need to install this utility file: FO_Utilities.lua

Code: Select all

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

ScriptName = "LK_ChangeServerPaths"

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

LK_ChangeServerPaths = {}

function LK_ChangeServerPaths:Name()
	return "Change Server Path"
end

function LK_ChangeServerPaths:Version()
	return "0.1"
end

function LK_ChangeServerPaths:Description()
	return "Change Server Path"
end

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

function LK_ChangeServerPaths:UILabel()
	return "Change Server Path"
end

LK_ChangeServerPaths.projectsServerPaths =	{
										"A:/", -- * 1
										"/Volumes/PROJECTS/", -- * 2
										"D:/dropbox/animations/", -- * 3
										"/Users/SomeGuy/Dropbox/Projects/", -- * 4
										"D:/lalala/", -- * 5
										}

LK_ChangeServerPaths.ResourcesServerPaths =	{
										"B:/", -- * 1
										"/Volumes/RESOURCES/", -- * 2
										"A:/Dropbox/Resources/", -- * 3
										"/Users/SomeGuy/Dropbox/Resources/", -- * 4
										"B:/a weird folder name/every pc is different/", -- * 5
									}

LK_ChangeServerPaths.choices =	{
										"Studio (Windows)", -- * 1
										"Studio (macOS)", -- * 2
										"SomePersonWorkingAtHome (Dropbox)", -- * 3
										"AnotherWorkingAtHome (Dropbox)", -- * 4
										"YepWorkingAtHome (Dropbox)", -- * 5
									}

LK_ChangeServerPaths.choice = 0

LK_ChangeServerPaths.projectsServerPath = nil
LK_ChangeServerPaths.resourcesServerPath = nil

LK_ChangeServerPaths.changedScale = false

function LK_ChangeServerPaths:LoadPrefs(prefs)
	self.choice = prefs:GetInt("LK_ChangeServerPaths.choice", 0)
	self.projectsServerPath = prefs:GetString("LK_ChangeServerPaths.projectsServerPath", nil)
	self.resourcesServerPath = prefs:GetString("LK_ChangeServerPaths.resourcesServerPath", nil)
end

function LK_ChangeServerPaths:SavePrefs(prefs)
    prefs:SetInt("LK_ChangeServerPaths.choice", self.choice)
    prefs:SetString("LK_ChangeServerPaths.projectsServerPath", self.projectsServerPath)
    prefs:SetString("LK_ChangeServerPaths.resourcesServerPath", self.resourcesServerPath)
end

-- **************************************************
-- Offset Amount Dialog
-- **************************************************

local LK_ServerPathDialog = {}

LK_ChangeServerPaths.images = 0
LK_ChangeServerPaths.changedImages = 0
LK_ChangeServerPaths.scripts = 0
LK_ChangeServerPaths.changedScripts = 0
LK_ChangeServerPaths.scaledLayerNames = {}
LK_ChangeServerPaths.undoPrepped = false

LK_ServerPathDialog.SELECTPATH =	MOHO.MSG_BASE + 100 -- 100 t/m 199 gereserveerd

function LK_ServerPathDialog:new(moho)
	local d = LM.GUI.SimpleDialog("Change project folder:", LK_ServerPathDialog)
	local l = d:GetLayout()
	d.moho = moho
	--
	local layer = moho.document:GetSelectedLayer()
	--
	d.explanation1 = LM.GUI.StaticText("Change the path for files on the Projects server, they will be replaced for every image layer.")
	d.projectsServerPathLabel = LM.GUI.DynamicText("LABEL:", 80)
	d.projectsServerPathLabel:SetValue("Projects path:")
	d.projectsServerPathTextBox = LM.GUI.DynamicText(LK_ChangeServerPaths.projectsServerPaths[LK_ChangeServerPaths.choice], 500)
	--
	d.resourcesLabel = LM.GUI.DynamicText("LABEL:", 80)
	d.resourcesLabel:SetValue("Resources path:")
	d.resourcesTextBox = LM.GUI.DynamicText(LK_ChangeServerPaths.ResourcesServerPaths[LK_ChangeServerPaths.choice], 500)
	--
	l:PushH()
	l:AddChild(d.explanation1)
	l:Pop() -- New line
	l:PushH()
	l:AddChild(d.projectsServerPathLabel)
	l:AddChild(d.projectsServerPathTextBox)
	--
	l:Pop() -- New line
	l:PushH()
	l:AddChild(d.resourcesLabel)
	l:AddChild(d.resourcesTextBox)
	--
	l:Pop() -- New line
	--
	l:PushH(LM.GUI.ALIGN_CENTER, -1)
	for i, string in ipairs(LK_ChangeServerPaths.projectsServerPaths) do
		l:AddChild(LM.GUI.Button(LK_ChangeServerPaths.choices[i], self.SELECTPATH + i))
	end
	l:Pop() -- New line
	-- This is where a "Cancel" and "OK" button show up
	return d
end

function LK_ServerPathDialog:HandleMessage(msg)
	if msg > self.SELECTPATH and msg < (self.SELECTPATH + 99) then
		local option = (msg - self.SELECTPATH)
		LK_ChangeServerPaths.choice = option
		local projectsPath = LK_ChangeServerPaths.projectsServerPaths[option]
		local resourcesPath = LK_ChangeServerPaths.ResourcesServerPaths[option]
		self.projectsServerPathTextBox:SetValue(projectsPath)
		self.resourcesTextBox:SetValue(resourcesPath)
	end
end

function LK_ServerPathDialog:OnOK()
	--
end


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

function LK_ChangeServerPaths:Run(moho)
	moho.document:PrepUndo(moho.layer, false)
	moho.document:SetDirty()
	self.choice = 0 -- DISABLE THIS LINE ONCE EVERYTHING IS WORKING CORRECTLY BY ADDING "--" IN FRONT OF IT
	if (self.choice == 0) then
		local dlog = LK_ServerPathDialog:new(moho)
		if (dlog:DoModal() == LM.GUI.MSG_CANCEL) then
			return
		end
	end
	--
	local newProjectsPath = LK_ChangeServerPaths.projectsServerPaths[self.choice]
	local newResourcesPath = LK_ChangeServerPaths.ResourcesServerPaths[self.choice]
	--
	self.projectsServerPath = newProjectsPath
	self.resourcesServerPath = newResourcesPath
	--
	newProjectsPath = self:CorrectSlashesPath(newProjectsPath)
	newResourcesPath = self:CorrectSlashesPath(newResourcesPath)
	--
	self.images = 0
	self.changedImages = 0
	self.scaledLayerNames = {}
	--
	self.scripts = 0
	self.changedScripts = 0
	--
	self.undoPrepped = false
	self.changedScale = false
	--
	local layers = FO_Utilities:AllLayers(moho)
	self.layersDone = {}
	for i = 1, #layers do
		local layer = layers[i]
		-- *** Images
		if (layer:LayerType() == MOHO.LT_IMAGE) then
			-- * Set Image Quality to high:
			LK_Render:SetHighImageQuality(moho, layer)
			-- print ("___"..layer:Name())
			self.images = self.images + 1
			-- *** Projects
			local newPath = newProjectsPath
			for j, string in ipairs(LK_ChangeServerPaths.projectsServerPaths) do
				if not table.contains(self.layersDone, layer) then
					if (j ~= LK_ChangeServerPaths.choice) then
						local oldPath = LK_ChangeServerPaths.projectsServerPaths[j]
						self:ChangeImagePath(moho, layer, oldPath, newPath)
					end
				end
			end
			--
			if not table.contains(self.layersDone, layer) then
				-- *** Resources
				local newPath = newResourcesPath
				for j, string in ipairs(LK_ChangeServerPaths.ResourcesServerPaths) do
					if not table.contains(self.layersDone, layer) then
						if (j ~= LK_ChangeServerPaths.choice) then
							local oldPath = LK_ChangeServerPaths.ResourcesServerPaths[j]
							self:ChangeImagePath(moho, layer, oldPath, newPath)
						end
					end
				end
			end
			--
		end
		-- *** Layerscripts
		local scriptPath = layer:LayerScript()
		if (scriptPath ~= "") then
			self.scripts = self.scripts + 1
			self:ChangeLayerscriptPath(moho, layer)
		end
	end
	if (self.changedImages > 0 or self.changedScripts > 0) then
		local alertMessage = "Changed paths for " .. self.changedImages .. " / " .. self.images .. " images and " .. self.changedScripts .. " / " .. self.scripts .. " layerscripts."
	    local scaleAlert = nil
	    if self.changedScale then
	    	scaleAlert = "WARNING, SCALE HAS BEEN CHANGED FOR "..#self.scaledLayerNames.." IMAGES TO FIX SIZE."
	    	local scaleData = "\n"
	    	for i = 1, #self.scaledLayerNames do
	    		-- print (i.."/"..#self.scaledLayerNames)
	    		local line = self.scaledLayerNames[i]
	    		-- print (line)
	    		scaleData = scaleData..line.."\n"
	    	end
	    	LM.GUI.Alert(LM.GUI.ALERT_WARNING, alertMessage, scaleAlert, scaleData, "OK", nil, nil)
	    else
	    	LM.GUI.Alert(LM.GUI.ALERT_INFO, alertMessage, scaleAlert, nil, "OK", nil, nil)
	    end
	end
end

-- **************************************************
-- Changing the path on an image layer
-- **************************************************
function LK_ChangeServerPaths:ChangeImagePath(moho, layer, oldPath, newPath)
	-- print (" - ChangeImagePath:"..layer:Name())
	oldPath = self:CorrectSlashesPath(oldPath)
	local imageLayer = moho:LayerAsImage(layer)
	local imagePath = (imageLayer:SourceImage())
	imagePath = string.gsub(imagePath, oldPath, newPath)
	-- * Check if path has changed:
	if (imagePath == imageLayer:SourceImage()) then
		return
	end
	-- * Check if file exists:
	if not FO_Utilities:FileExists(moho, imagePath) then
		print ("Unable to locate image file for layer ["..layer:Name().."] File doesn't exist: ["..imagePath.."]")
		return
	end
	-- print (layer:Name() .. " -> NEW IMAGE PATH: " .. imagePath)
	-- * Check old size:
	local oldHeight = imageLayer:Height()
	-- * Change image source:
	if not self.undoPrepped then
		moho.document:PrepUndo(moho.layer, true)
		moho.document:SetDirty()
		self.undoPrepped = true
	end
	imageLayer:SetSourceImage(imagePath)
	-- * Compare to new size:
	local newHeight = imageLayer:Height()
	-- * Check if difference is significant:
	local heightDifference = math.abs (oldHeight - newHeight)
	if heightDifference < 0.1 then
		heightDifference = 0
	end
	-- * Change size on frame 0 and timeline:
	if heightDifference ~= 0 then --newHeight ~= oldHeight then
		self.changedScale = true
		local heightChange = oldHeight / newHeight
		local scaleChannel = imageLayer.fScale
		local oldScale = scaleChannel:GetValue(0)
		local newScale = oldScale * heightChange
		-- * Main timeline scale channel for this image layer:
		local endKey = scaleChannel:Duration()
		local thisKey = 0
		local keyCount = scaleChannel:CountKeys()
		local keysFound = 0
		local frameNum = endKey
		while keysFound < keyCount do
			thisKey = scaleChannel:GetClosestKeyID(frameNum)
			keyFrameNum = scaleChannel:GetKeyWhen(thisKey)
			keysFound = 1 + keysFound
			-- * Scale it:
			local oldScale = scaleChannel:GetValue(keyFrameNum)
			local newScale = oldScale * heightChange
			scaleChannel:SetValue(keyFrameNum, newScale)
			frameNum = keyFrameNum - 1
		end
		-- * Do all action scale channels for this image layer too:
		for i = 0, scaleChannel:CountActions()-1 do
			local actionChannel = moho:ChannelAsAnimVec3(scaleChannel:Action(i))
			local endKey = actionChannel:Duration()
			local thisKey = 0
			local keyCount = actionChannel:CountKeys()-1 -- * -1 because we don't want to modify frame 0 in actions!
			local keysFound = 0
			local frameNum = endKey
			while keysFound < keyCount do
				thisKey = actionChannel:GetClosestKeyID(frameNum)
				keyFrameNum = actionChannel:GetKeyWhen(thisKey)
				keysFound = 1 + keysFound
				-- * Scale it:
				local oldScale = actionChannel:GetValue(keyFrameNum)
				local newScale = oldScale * heightChange
				actionChannel:SetValue(keyFrameNum, newScale)
				frameNum = keyFrameNum - 1
			end
		end
		table.insert(self.scaledLayerNames, "- "..layer:Name().." S.old: "..oldScale.y.." S.new: "..newScale.y.." H.diff: "..heightDifference)
	end
	self.changedImages = self.changedImages + 1
	table.insert(self.layersDone, layer)
end

-- **************************************************
-- Changing the path on an image layer
-- **************************************************
function LK_ChangeServerPaths:ChangeLayerscriptPath(moho, layer)
	local userPath = string.gsub(moho:UserAppDir(), '\\', '/')
	local scriptsFolder = "/Shared Resources/Embedded Scripts/"
	local newPath = userPath .. scriptsFolder
	newPath = self:CorrectSlashesPath(newPath)
	local scriptPath = layer:LayerScript()
	local embeddedscript = string.gsub(scriptPath, '\\', '/')
	local lastslashpos = (embeddedscript:reverse()):find("%/") -- find last slash
	embeddedscript = (embeddedscript:sub(-lastslashpos+1)) -- filename only
	oldPath = string.gsub(scriptPath, embeddedscript, "") -- path without filename
	oldPath = self:CorrectSlashesPath(oldPath)
	-- TODO: ALSO CHECK IF LAYERSCRIPT EXISTS IN NEW LOCATION
	if (oldPath ~= newPath) then
		scriptPath = string.gsub(scriptPath, oldPath, newPath) -- ***
		Debug:Log(layer:Name() .. " -> NEW LAYERSCRIPT PATH: " .. newPath .. embeddedscript)
		if not self.undoPrepped then
			moho.document:PrepUndo(moho.layer, true)
			moho.document:SetDirty()
			self.undoPrepped = true
		end
		layer:SetLayerScript(scriptPath)
		self.changedScripts = self.changedScripts + 1
	end
end

function LK_ChangeServerPaths:CorrectSlashesPath(path)
	if (FO_Utilities:getOS() == "unix") then
		path = string.gsub(path, "\\", "/")
	else
		path = string.gsub(path, "/", "\\")
	end
	return path
end

-- **************************************************
-- Change a single path
-- (Don't use this when looping trough layers)
-- (Used in other scripts like LK_RenderLocal)
-- **************************************************
function LK_ChangeServerPaths:FixPath(moho, path, choice)
	choice = choice or self.choice
	-- *** Projects
	local newProjectsPath = self.projectsServerPaths[choice]
	local newPath = newProjectsPath
	for i, string in ipairs(self.projectsServerPaths) do
	if not table.contains(self.layersDone, layer) then
			if (i ~= choice) then
				local oldPath = self.projectsServerPaths[i]
				path = string.gsub(path, oldPath, newPath)
			end
		end
	end
	local newResourcesPath = self.ResourcesServerPaths[choice]
	-- *** Resources
	local newPath = newResourcesPath
	for i, string in ipairs(self.ResourcesServerPaths) do
		if (i ~= choice) then
			local oldPath = self.ResourcesServerPaths[i]
			path = string.gsub(path, oldPath, newPath)
		end
	end
	return path
end
You'll need to edit this part:

Code: Select all

LK_ChangeServerPaths.projectsServerPaths =	{
										"A:/", -- * 1
										"/Volumes/PROJECTS/", -- * 2
										"D:/dropbox/animations/", -- * 3
										"/Users/SomeGuy/Dropbox/Projects/", -- * 4
										"D:/lalala/", -- * 5
										}

LK_ChangeServerPaths.ResourcesServerPaths =	{
										"B:/", -- * 1
										"/Volumes/RESOURCES/", -- * 2
										"A:/Dropbox/Resources/", -- * 3
										"/Users/SomeGuy/Dropbox/Resources/", -- * 4
										"B:/a weird folder name/every pc is different/", -- * 5
									}

LK_ChangeServerPaths.choices =	{
										"Studio (Windows)", -- * 1
										"Studio (macOS)", -- * 2
										"SomePersonWorkingAtHome (Dropbox)", -- * 3
										"AnotherWorkingAtHome (Dropbox)", -- * 4
										"YepWorkingAtHome (Dropbox)", -- * 5
									}
What you see here is 5 options for 5 different setups. Make sure the order is consistent. We use 2 different servers, but you might only need to use the Projects paths. Just fill in some nonsense for the Resources paths. (Make sure you have the same amount of entries in each list though, and the order should match. So person 4 is using projectpath 4 and resourcepath 4.)

Make sure you always import images in a 1080p project. (You can always render in 4K or whatever, but import everything in the same dimension or you'll be sorry down the line).

Whenever opening an unlinked broken moho file. Tell it "No" when a file is missing, and check the "Don't ask again" box. Then run this script.

Also, NEVER save a file with a few unlinked images. It will mess up the dimensions and it's impossible to get them back.

When everything is working for you, disable this line in the Run function:

Code: Select all

	self.choice = 0 -- DISABLE THIS LINE ONCE EVERYTHING IS WORKING CORRECTLY BY ADDING "--" IN FRONT OF IT
I hope at some point we'll get relative paths (either relative to a moho file's location, even if it means going 'up' a few folders before digging back into the folder structure, or relative to a customizable project(s) destination)

Use this script at your own risk and keep backups. It might resize images on frame 0 and all keys in all actions. We use it on a daily basis (in a mix of windows/mac machines that mount/sync from linux servers and it works flawlessly, but your setup and projects might be completely different.
Last edited by Lukas on Thu Apr 28, 2022 12:27 pm, edited 1 time in total.
User avatar
Lukas
Posts: 1336
Joined: Fri Apr 09, 2010 9:00 am
Location: Netherlands
Contact:

Re: Best practice using fx Dropbox to stop eternal, manual relinking

Post by Lukas »

Here's an easier to use version of the script. Ignore the previous post and use this instead. Install this as a menu script, save it as "LK_ChangeServerPaths.lua". You'll also need to install this utility file: FO_Utilities.lua

Code: Select all

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

ScriptName = "LK_ChangeServerPaths"

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

LK_ChangeServerPaths = {}

function LK_ChangeServerPaths:Name()
	return "Change Server Path"
end

function LK_ChangeServerPaths:Version()
	return "0.2"
end

function LK_ChangeServerPaths:Description()
	return "Change Server Path"
end

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

function LK_ChangeServerPaths:UILabel()
	return "Change Server Path"
end

LK_ChangeServerPaths.serverPaths =	{
										"Y:/",
										"/Volumes/Projects/",
										"D:/Dropbox/Projects/",
										"/Users/Crazyguy/Dropbox/Projects/",
										"D:/Projects/",
										"/Users/Bart/Dropbox/Projects_local/",
										"E:/MySyncedLocation/"
										}

LK_ChangeServerPaths.changedScale = false

-- **************************************************
-- Offset Amount Dialog
-- **************************************************

local LK_ServerPathDialog = {}

LK_ChangeServerPaths.images = 0
LK_ChangeServerPaths.changedImages = 0
LK_ChangeServerPaths.scripts = 0
LK_ChangeServerPaths.changedScripts = 0
LK_ChangeServerPaths.scaledLayerNames = {}
LK_ChangeServerPaths.undoPrepped = false

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

function LK_ChangeServerPaths:Run(moho)
	local newServerPath = nil
	for i = 1, #self.serverPaths do
		local serverPath =  self:CorrectSlashesPath(self.serverPaths[i])
		local docPath = moho.document:Path()
		if string.match(docPath, serverPath) then
			newServerPath = serverPath
		end
	end
	if newServerPath == nil then
		FO_Utilities:Alert("Can't determine current server path.", "Did you save your file on the server?", "?")
		print ("Possible server locations:")
		for i = 1, #self.serverPaths do

			print (" - '"..self.serverPaths[i].."'")
		end
		return
	end
	--
	moho.document:PrepUndo(moho.layer, false)
	moho.document:SetDirty()
	--
	newServerPath = self:CorrectSlashesPath(newServerPath)
	-- print ("newServerPath = "..newServerPath)
	--
	self.images = 0
	self.changedImages = 0
	self.scaledLayerNames = {}
	--
	self.scripts = 0
	self.changedScripts = 0
	--
	self.undoPrepped = false
	self.changedScale = false
	--
	local layers = FO_Utilities:AllLayers(moho)
	self.layersDone = {}
	for i = 1, #layers do
		local layer = layers[i]
		-- *** Images
		if (layer:LayerType() == MOHO.LT_IMAGE) then
			-- * Set Image Quality to high:
			LK_Render:SetHighImageQuality(moho, layer)
			-- print ("___"..layer:Name())
			self.images = self.images + 1
			-- * Server
			local newPath = newServerPath
			for j, string in ipairs(LK_ChangeServerPaths.serverPaths) do
				if not table.contains(self.layersDone, layer) then
					if not string.match(newServerPath, LK_ChangeServerPaths.serverPaths[j]) then
						local oldPath = LK_ChangeServerPaths.serverPaths[j]
						self:ChangeImagePath(moho, layer, oldPath, newPath)
					end
				end
			end
		end
		-- * Layerscripts
		local scriptPath = layer:LayerScript()
		if (scriptPath ~= "") then
			self.scripts = self.scripts + 1
			self:ChangeLayerscriptPath(moho, layer)
		end
	end
	if (self.changedImages > 0 or self.changedScripts > 0) then
		local alertMessage = "Changed paths for " .. self.changedImages .. " / " .. self.images .. " images and " .. self.changedScripts .. " / " .. self.scripts .. " layerscripts."
	    local scaleAlert = nil
	    if self.changedScale then
	    	scaleAlert = "WARNING, SCALE HAS BEEN CHANGED FOR "..#self.scaledLayerNames.." IMAGES TO FIX SIZE."
	    	local scaleData = "\n"
	    	for i = 1, #self.scaledLayerNames do
	    		-- print (i.."/"..#self.scaledLayerNames)
	    		local line = self.scaledLayerNames[i]
	    		-- print (line)
	    		scaleData = scaleData..line.."\n"
	    	end
	    	FO_Utilities:Alert(alertMessage, scaleAlert, scaleData)
	    else
	    	FO_Utilities:Alert(alertMessage, scaleAlert)
	    end
	else
		FO_Utilities:Alert("Nothing changed.", "i")
	end
end

-- **************************************************
-- Changing the path on an image layer
-- **************************************************
function LK_ChangeServerPaths:ChangeImagePath(moho, layer, oldPath, newPath)
	-- print (" - ChangeImagePath:"..layer:Name())
	oldPath = self:CorrectSlashesPath(oldPath)
	local imageLayer = moho:LayerAsImage(layer)
	local imagePath = (imageLayer:SourceImage())
	imagePath = string.gsub(imagePath, oldPath, newPath)
	-- * Check if path has changed:
	if (imagePath == imageLayer:SourceImage()) then
		return
	end
	-- * Check if file exists:
	if not FO_Utilities:FileExists(moho, imagePath) then
		print ("Unable to locate image file for layer ["..layer:Name().."] File doesn't exist: ["..imagePath.."]")
		return
	end
	-- print (layer:Name() .. " -> NEW IMAGE PATH: " .. imagePath)
	-- * Check old size:
	local oldHeight = imageLayer:Height()
	-- * Change image source:
	if not self.undoPrepped then
		moho.document:PrepUndo(moho.layer, true)
		moho.document:SetDirty()
		self.undoPrepped = true
	end
	imageLayer:SetSourceImage(imagePath)
	-- * Compare to new size:
	local newHeight = imageLayer:Height()
	-- * Check if difference is significant:
	local heightDifference = math.abs (oldHeight - newHeight)
	if heightDifference < 0.1 then
		heightDifference = 0
	end
	-- * Change size on frame 0 and timeline:
	if heightDifference ~= 0 then --newHeight ~= oldHeight then
		self.changedScale = true
		local heightChange = oldHeight / newHeight
		local scaleChannel = imageLayer.fScale
		local oldScale = scaleChannel:GetValue(0)
		local newScale = oldScale * heightChange
		-- * Main timeline scale channel for this image layer:
		local endKey = scaleChannel:Duration()
		local thisKey = 0
		local keyCount = scaleChannel:CountKeys()
		local keysFound = 0
		local frameNum = endKey
		while keysFound < keyCount do
			thisKey = scaleChannel:GetClosestKeyID(frameNum)
			keyFrameNum = scaleChannel:GetKeyWhen(thisKey)
			keysFound = 1 + keysFound
			-- * Scale it:
			local oldScale = scaleChannel:GetValue(keyFrameNum)
			local newScale = oldScale * heightChange
			scaleChannel:SetValue(keyFrameNum, newScale)
			frameNum = keyFrameNum - 1
		end
		-- * Do all action scale channels for this image layer too:
		for i = 0, scaleChannel:CountActions()-1 do
			local actionChannel = moho:ChannelAsAnimVec3(scaleChannel:Action(i))
			local endKey = actionChannel:Duration()
			local thisKey = 0
			local keyCount = actionChannel:CountKeys()-1 -- * -1 because we don't want to modify frame 0 in actions!
			local keysFound = 0
			local frameNum = endKey
			while keysFound < keyCount do
				thisKey = actionChannel:GetClosestKeyID(frameNum)
				keyFrameNum = actionChannel:GetKeyWhen(thisKey)
				keysFound = 1 + keysFound
				-- * Scale it:
				local oldScale = actionChannel:GetValue(keyFrameNum)
				local newScale = oldScale * heightChange
				actionChannel:SetValue(keyFrameNum, newScale)
				frameNum = keyFrameNum - 1
			end
		end
		table.insert(self.scaledLayerNames, "- "..layer:Name().." S.old: "..oldScale.y.." S.new: "..newScale.y.." H.diff: "..heightDifference)
	end
	self.changedImages = self.changedImages + 1
	table.insert(self.layersDone, layer)
end

-- **************************************************
-- Changing the path on an image layer
-- **************************************************
function LK_ChangeServerPaths:ChangeLayerscriptPath(moho, layer)
	local userPath = string.gsub(moho:UserAppDir(), '\\', '/')
	local scriptsFolder = "/Shared Resources/Embedded Scripts/"
	local newPath = userPath .. scriptsFolder
	newPath = self:CorrectSlashesPath(newPath)
	local scriptPath = layer:LayerScript()
	local embeddedscript = string.gsub(scriptPath, '\\', '/')
	local lastslashpos = (embeddedscript:reverse()):find("%/") -- find last slash
	embeddedscript = (embeddedscript:sub(-lastslashpos+1)) -- filename only
	oldPath = string.gsub(scriptPath, embeddedscript, "") -- path without filename
	oldPath = self:CorrectSlashesPath(oldPath)
	-- TODO: ALSO CHECK IF LAYERSCRIPT EXISTS IN NEW LOCATION
	if (oldPath ~= newPath) then
		scriptPath = string.gsub(scriptPath, oldPath, newPath) -- ***
		Debug:Log(layer:Name() .. " -> NEW LAYERSCRIPT PATH: " .. newPath .. embeddedscript)
		if not self.undoPrepped then
			moho.document:PrepUndo(moho.layer, true)
			moho.document:SetDirty()
			self.undoPrepped = true
		end
		layer:SetLayerScript(scriptPath)
		self.changedScripts = self.changedScripts + 1
	end
end

function LK_ChangeServerPaths:CorrectSlashesPath(path)
	if (FO_Utilities:getOS() == "unix") then
		path = string.gsub(path, "\\", "/")
	else
		path = string.gsub(path, "/", "\\")
	end
	return path
end

-- **************************************************
-- Change a single path
-- (Don't use this when looping trough layers)
-- (Used in other scripts like LK_RenderLocal)
-- **************************************************
function LK_ChangeServerPaths:FixPath(path, newServerPath, docPath)
	if newServerPath == nil then
		if docPath == nil then
			print ("need docPath")
			return
		end
		for i = 1, #self.serverPaths do
			local serverPath =  self:CorrectSlashesPath(self.serverPaths[i])
			if string.match(docPath, serverPath) then
				newServerPath = serverPath
			end
		end
	end
	if newServerPath ~= nil then
		for i, string in ipairs(self.serverPaths) do
			if not table.contains(self.layersDone, layer) then
				if not string.match(path, newServerPath) then
					local oldPath = self.serverPaths[i]
					path = string.gsub(path, oldPath, newServerPath)
				end
			end
		end
	end
	return path
end
Edit this part and make sure you have each different location in there that anyone on the project is using. All should point to the place where you're stuff is stored. For example a 'Projects' folder. It assumes everything is stored somewhere in that location, also the moho files itself.

Code: Select all

LK_ChangeServerPaths.serverPaths =	{
										"Y:/",
										"/Volumes/Projects/",
										"D:/Dropbox/Projects/",
										"/Users/Crazyguy/Dropbox/Projects/",
										"D:/Projects/",
										"/Users/Bart/Dropbox/Projects_local/",
										"E:/MySyncedLocation/"
										}
Tell it not to locate files when opening a shot with unlinked stuff. And run the script.

Let me know what works and what doesn't. We use it in production all the time, but your project setup might be completely different.
batpete
Posts: 2
Joined: Fri Apr 22, 2022 12:45 pm

Re: Best practice using fx Dropbox to stop eternal, manual relinking

Post by batpete »

Hi Lukas,

Thank you for the input and suggestion on how to create a work-around. We're gearing up for a production where we'll have several physical hubs/studios and a lot of remote artists on a longer project, and it seems a bit complicated way to go about it if the number of setups go up and artists coming and leaving the project.

I think I'll reach out to Lost Marble and here if there is a plan to add relative paths in the upcoming half-year that might help us. And I'll do a small test with local machines and having artists remote in to a workstation in one physical location. The latter would have all machines working against the same server and paths would be the same for everyone. Might be less smooth, but worth a test.

Best.
Petter
Swedish film producer with a passion for animation productions
Email: petter.lindblad@snowcloud.se
User avatar
Waldo2D
Posts: 16
Joined: Wed Dec 15, 2021 8:12 pm

Re: Best practice using fx Dropbox to stop eternal, manual relinking

Post by Waldo2D »

I use 'import by reference' all the time and relink to OTHER assets by breaking the links.
For now it's a manual labor.

In Autodesk Maya, '.ma' is 3D-file but also a text format where you can find and replace assets in the text-file.
So in one action, I can replace 3D-assets with other assets in the 3D-scene

Is there a way in Moho to relink assets with a script? Or is there another way?

Is this script the start of such a thing?
Luc

Lukas wrote: Tue Apr 26, 2022 12:24 pm Edit: ignore this post and read the next one. I'll leave this here just in case.

I've edited my script a bit to remove certain specifics. Install this as a menu script, save it as "LK_ChangeServerPaths.lua". You'll also need to install this utility file: FO_Utilities.lua

Code: Select all

-- **************************************************
-- Provide Moho with the name of this script object
-- **************************************************
[/quote]
User avatar
Greenlaw
Posts: 10382
Joined: Mon Jun 19, 2006 5:45 pm
Location: Los Angeles
Contact:

Re: Best practice using fx Dropbox to stop eternal, manual relinking

Post by Greenlaw »

batpete wrote: Fri Apr 22, 2022 3:06 pm We're working remotely syncing the material via Dropbox, using both Mac and PC...
I don't normally work directly out of Dropbox but here's' a word of warning:

I read in another thread that a user had trouble properly saving Moho files to Dropbox, and recently I saw this issue myself. Here's the problem:

If you have Autosave enabled, there's a bug where Moho will not properly save files to a Dropbox directory, and this can lead to corrupted files or lost data. In some cases, it's must a matter of changing the file's extension back to .moho but there's no guarantee that you will recover the latest version of your file.

There are two things you can do right now:

1. If you disable Autosave, Moho seems to save the file correctly. So, if you choose this approach, make sure all your artists have Autosave disabled.

2. IMO, the safest approach for the time being is to use Dropbox for archival/sharing of Moho projects, but have the artists work with a copy of a project located outside of the Dropbox. (Working on my own network independently of Dropbox is what I do anyway, but I also like to leave Autosave enabled.) When they are finished, the updated copy can be copied back into the shared Dropbox folder.

Hope this helps.
Post Reply