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

ScriptName = "AE_MeshinstanceTool"

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

AE_MeshinstanceTool = {}

function AE_MeshinstanceTool:Name()
	return "Meshinstance Tool"
end

function AE_MeshinstanceTool:Version()
	return "1.27"
end

function AE_MeshinstanceTool:UILabel()
	return "Meshinstance tool"
end

function AE_MeshinstanceTool:Creator()
	return "Alexandra Evseeva"
end

function AE_MeshinstanceTool:Description()
	return "Interface for binding mesh layers (alternative to built-in internal reference system). Child layers repeat vertex and curvature animation of their parents"
end

function AE_MeshinstanceTool:LoadPrefs(prefs)
	self.allLayers = prefs:GetBool("AE_MeshinstanceTool.allLayers", false)
	self.preventSelection = prefs:GetBool("AE_MeshinstanceTool.preventSelection", true)
end

function AE_MeshinstanceTool:SavePrefs(prefs)
	prefs:SetBool("AE_MeshinstanceTool.allLayers", self.allLayers)
	prefs:SetBool("AE_MeshinstanceTool.preventSelection", self.preventSelection)
end

function AE_MeshinstanceTool:ResetPrefs()
	self.allLayers = false
	self.preventSelection = true
end

-- **************************************************
-- Is Relevant / Is Enabled
-- **************************************************

function AE_MeshinstanceTool:IsRelevant(moho)
	return true
end

function AE_MeshinstanceTool:IsEnabled(moho)
	if self.preventSelection and moho.frame > 0 and self:GetLayerParent(moho, moho.layer) then
		--self:SelectParentLayer(moho)
		local curParent = self:GetLayerParent(moho)
		if curParent and moho.layer ~= curParent then
			moho:SetSelLayer(curParent)
			moho:UpdateUI()
		end
	end
	return true
end

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

AE_MeshinstanceTool.srcKeyName = "AE_MeshinstSrc"
AE_MeshinstanceTool.srcPathKeyName = "AE_MeshinstSrcPath"
AE_MeshinstanceTool.scriptName = "ae_meshinstance.lua"
AE_MeshinstanceTool.allLayers = false
AE_MeshinstanceTool.realtimeOn = 0
AE_MeshinstanceTool.preventSelection = true

AE_MeshinstanceTool.MSG = MOHO.MSG_BASE
AE_MeshinstanceTool.REALTIME_BUTTON = AE_MeshinstanceTool.MSG + 1
AE_MeshinstanceTool.PARENT_BUTTON = AE_MeshinstanceTool.MSG + 2
AE_MeshinstanceTool.NEWPARENT_BUTTON = AE_MeshinstanceTool.MSG + 3
AE_MeshinstanceTool.NEWPARENT_BUTTON = AE_MeshinstanceTool.MSG + 4
AE_MeshinstanceTool.UPDATE_LINK = AE_MeshinstanceTool.MSG + 5
AE_MeshinstanceTool.ALLLAYERS_BUTTON = AE_MeshinstanceTool.MSG + 7
AE_MeshinstanceTool.DUPLAYER_BUTTON = AE_MeshinstanceTool.MSG + 8
--[[
AE_MeshinstanceTool.LOCKLAYER_BUTTON = AE_MeshinstanceTool.MSG + 9
--]]
AE_MeshinstanceTool.PREVENTSELECTION = AE_MeshinstanceTool.MSG + 10
AE_MeshinstanceTool.DUPGROUP_BUTTON = AE_MeshinstanceTool.MSG + 11
AE_MeshinstanceTool.PARENT_BUTTON_ALT = AE_MeshinstanceTool.MSG + 12

function AE_MeshinstanceTool:DoLayout(moho, layout)

	layout:AddChild(LM.GUI.StaticText("REALTIME: "))
	self.realtime_button = LM.GUI.Button("???", self.REALTIME_BUTTON)
	layout:AddChild(self.realtime_button)
	self.realtime_button:SetToolTip("Press to switch realtime on and off for all parented layers")
	layout:AddChild(LM.GUI.StaticText("Current layer's parent is "))
	self.parent_button = LM.GUI.Button("          NONE          ", self.PARENT_BUTTON, LM.GUI.ALIGN_CENTER)
	layout:AddChild(self.parent_button)
	self.parent_button:SetAlternateMessage(self.PARENT_BUTTON_ALT)
	self.parent_button:SetToolTip("Press to select parent layer")
	
	layout:AddChild(LM.GUI.StaticText("Set parent to "))
	self.newParent_button = LM.GUI.Button("          NONE          ", self.NEWPARENT_BUTTON)
	layout:AddChild(self.newParent_button)
	self.newParent_button:SetToolTip("Select two layers: parent first, child second (and active selected), then press this button")
	self.updateLink_button = LM.GUI.Button("UPDATE LINK", self.UPDATE_LINK)
	layout:AddChild(self.updateLink_button)
	self.updateLink_button:SetToolTip("Press to refresh linking for a single selected child layer")
	self.updateAll_button = LM.GUI.Button("UPDATE ALL", self.ALLLAYERS_BUTTON)
	layout:AddChild(self.updateAll_button)
	self.updateAll_button:SetToolTip("Press to refresh linking for all layers")	
	
	layout:AddChild(LM.GUI.StaticText("Create"))
	self.dupLayer_button = LM.GUI.Button("reference", self.DUPLAYER_BUTTON)
	layout:AddChild(self.dupLayer_button)
	self.dupLayer_button:SetToolTip("Duplicate group or layer creating linked layer(s)")
	self.dupGroup_button = LM.GUI.Button("copy", self.DUPGROUP_BUTTON)
	layout:AddChild(self.dupGroup_button)
	self.dupGroup_button:SetToolTip("Duplicate group creating a group with same link structure")	
	
	--[[
	self.lockLayer_button = LM.GUI.Button("unlock", self.LOCKLAYER_BUTTON)
	layout:AddChild(self.lockLayer_button)
	self.lockLayer_button:SetToolTip("Lock/unlock current layer")	
	--]]
	
	self.preventSelection_check = LM.GUI.CheckBox("Prevent child selection", self.PREVENTSELECTION)
	layout:AddChild(self.preventSelection_check)
	
end

function AE_MeshinstanceTool:UpdateWidgets(moho)

	if self.realtimeOn == 0  
		then self.realtime_button:SetLabel("???", false)
		elseif self.realtimeOn == 1 then self.realtime_button:SetLabel("ON", false)
		elseif self.realtimeOn == -1 then self.realtime_button:SetLabel("OFF", false)
	end

	local curParent = self:GetLayerParent(moho)

	if curParent then 
		self.parent_button:SetLabel(curParent:Name(),false)
		self.parent_button:Enable(true)
	else self.parent_button:SetLabel("NONE", false)
		self.parent_button:Enable(true)
		--[[
		
		-- folowing code slows down layer selecting, so it's commented out 
		-- and needs to be replaced with asking current layer for its children or their quantity at least
		-- writing these properties to parent layer during parenting
		
		self.parent_button:Enable(false)		
		for i, layer in AE_Utilities:IterateAllLayers(moho) do
			if self:GetLayerParent(moho, layer) == moho.layer then
				self.parent_button:Enable(true)
				break
			end
		end
		--]]
	end

	local newParent = self:GetNewParent(moho)
	if newParent then
		self.newParent_button:SetLabel(newParent:Name(), false)
		self.newParent_button:Enable(true)	
	else 
		self.newParent_button:SetLabel("NONE", false)
		if curParent then 
			self.newParent_button:Enable(true)	
		else 
			self.newParent_button:Enable(false)
		end
	end


	self.preventSelection_check:SetValue(self.preventSelection)

	--[[
	local label = moho.layer:IsLocked() and 'unlock' or 'lock'
	self.lockLayer_button:SetLabel(label, false)
	--]]
end

function AE_MeshinstanceTool:HandleMessage(moho, view, msg)
	if(msg == self.REALTIME_BUTTON) then	
		self.realtimeOn = self:SwitchRealtime(moho)
		self:UpdateWidgets(moho)
	elseif(msg == self.PARENT_BUTTON) then
		self:SelectParentLayer(moho, false)
	elseif(msg == self.PARENT_BUTTON_ALT) then
		--self:SelectParentLayer(moho, true)
		self:SelectAllGroup(moho)
	elseif(msg == self.NEWPARENT_BUTTON) then
		self:SetNewParentLayer(moho)
	elseif(msg == self.UPDATE_LINK) then
		self:UpdateLink(moho)
	elseif(msg == self.ALLLAYERS_BUTTON) then
		self:UpdateAllLayers(moho)
	elseif(msg == self.DUPLAYER_BUTTON) then
		moho.document:SetDirty()
		moho.document:PrepUndo()
		local oldLayer = moho.layer
		local newLayer = moho:DuplicateLayer(oldLayer)
		self:DuplicateLayer(moho, oldLayer, newLayer)
	elseif(msg == self.DUPGROUP_BUTTON) then
		moho.document:SetDirty()
		moho.document:PrepUndo()
		local oldLayer = moho.layer
		local newLayer = moho:DuplicateLayer(oldLayer)
		self:CopyGroupStructure(moho, oldLayer, newLayer)		
	--[[
	elseif (msg == self.LOCKLAYER_BUTTON) then
		moho.layer:SetLocked(not moho.layer:IsLocked())
		moho.layer:SetLabelColor(moho.layer:IsLocked() and -1 or 0)
		self:UpdateWidgets(moho)
	--]]
	elseif msg == self.PREVENTSELECTION then
		self.preventSelection = self.preventSelection_check:Value()
	end
end

function AE_MeshinstanceTool:OnMouseDown(moho, mouseEvent)
end

function AE_MeshinstanceTool:DrawMe(moho, view)
end

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

function AE_MeshinstanceTool:DoesFileExist(name)
	return os.rename(name, name) and true or false
end

function AE_MeshinstanceTool:ScriptSourcePath()
   local str = debug.getinfo(2, "S").source:sub(2)
   local _, slashPos = string.find(string.lower(str), "scripts") 
   local slash = string.sub(str, slashPos+1, slashPos+1)
   local pattern = "(.*\\)[tT]ool\\"
   if slash == "/" then pattern = "(.*/)[tT]ool/" end

   return (str:match(pattern) .. "ScriptResources".. slash .. "ae_meshinstance" .. slash), slash
end

function AE_MeshinstanceTool:GetScriptPath(moho)
	local docPath = string.sub(moho.document:Path(), 1, #moho.document:Path()-#moho.document:Name())
	local scriptPath = docPath .. self.scriptName
	if not self:DoesFileExist(scriptPath) then
		local sourcePath, slash = self:ScriptSourcePath()
		sourcePath = sourcePath..self.scriptName
		local cmdline = 'copy "' .. sourcePath .. '" "' .. scriptPath .. '"'
		if slash == "/" then cmdline = string.gsub(cmdline, "copy", "cp") end
		--print(cmdline)
		os.execute(cmdline)
	end
	return scriptPath
end

function AE_MeshinstanceTool:SwitchRealtime(moho)
	local scriptPath = self:GetScriptPath(moho)
	if self.realtimeOn > -1 then
		for i, layer in AE_Utilities:IterateAllLayers(moho) do
			self:TurnScriptOff(moho,layer)
		end
		return -1
	end
	for i, layer in AE_Utilities:IterateAllLayers(moho) do
		self:TurnScriptOn(moho,layer,scriptPath)
	end	
	return 1	
end

function AE_MeshinstanceTool:SelectAllGroup(moho)
	local curParent = self:GetLayerParent(moho)
	local layersToSelect = {}
	for i, layer in AE_Utilities:IterateAllLayers(moho) do
		local inGroup = false
		if curParent and curParent == layer then inGroup = true 
		else 
			layerParent = self:GetLayerParent(moho, layer)
			if curParent and curParent == layerParent then inGroup = true
			elseif layerParent == moho.layer then inGroup = true
			end
		end
		if inGroup then table.insert(layersToSelect, layer) end
	end
	for i, layer in pairs(layersToSelect) do 
		moho:SetSelLayer(layer, true) 
		moho:ShowLayerInLayersPalette(layer)
	end
	moho:UpdateUI()
	self:UpdateWidgets(moho)
end

function AE_MeshinstanceTool:SelectParentLayer(moho, addToSelection)
	local curParent = self:GetLayerParent(moho)
	if curParent then
		moho:SetSelLayer(curParent, addToSelection)
		moho:UpdateUI()
		moho:ShowLayerInLayersPalette(curParent)
		self:UpdateWidgets(moho)
	end
end

function AE_MeshinstanceTool:LinkLayers(moho, childLayer, parentLayer)
	local scriptInfo = childLayer:ScriptData()
	if not parentLayer then 
		scriptInfo:Remove(self.srcKeyName)
		scriptInfo:Remove(self.srcPathKeyName)
		--childLayer:SetLocked(false)
		if childLayer:LabelColor() == -1 then childLayer:SetLabelColor(0) end
		return 
	end	
	scriptInfo:Set(self.srcKeyName, parentLayer:UUID())
	scriptInfo:Set(self.srcPathKeyName, AE_Utilities:GetLayerRelativePath(moho, childLayer, parentLayer))	
end

function AE_MeshinstanceTool:SetNewParentLayer(moho)
	local newParentLayer = self:GetNewParent(moho)
	local scriptInfo = moho.layer:ScriptData()	
	
	if newParentLayer then
		local path = AE_Utilities:GetLayerRelativePath(moho, moho.layer, newParentLayer)
		local foundParent = AE_Utilities:GetLayerFromRelativePath(moho, moho.layer, path)
		if foundParent ~= newParentLayer then
			return LM.GUI.Alert(LM.GUI.ALERT_WARNING, string.format("Can not link %s to %s", moho.layer:Name(), newParentLayer:Name())) 
		end
	end
	
	self:LinkLayers(moho, moho.layer, newParentLayer)	
	
	if not newParentLayer then 
		--scriptInfo:Remove(self.srcKeyName)
		--scriptInfo:Remove(self.srcPathKeyName)
		self.parent_button:SetLabel("NONE",false)
		self.parent_button:Enable(false)		
		return 
	end
	
	--scriptInfo:Set(self.srcKeyName, newParentLayer:UUID())
	--scriptInfo:Set(self.srcPathKeyName, AE_Utilities:GetLayerRelativePath(moho, moho.layer, newParentLayer))
	self.parent_button:SetLabel(newParentLayer:Name(),false)
	self.parent_button:Enable(true)
	
end

function AE_MeshinstanceTool:UpdateBinding(moho, layer, parentLayer)
	local mesh = moho:LayerAsVector(layer)
	local parentMesh = moho:LayerAsVector(parentLayer)
	if not mesh or not parentMesh then return end
	mesh = mesh:Mesh()
	parentMesh = parentMesh:Mesh()
	for p = 0, math.min(parentMesh:CountPoints(), mesh:CountPoints())-1 do
		mesh:Point(p).fParent = parentMesh:Point(p).fParent
	end
end

function AE_MeshinstanceTool:UpdateLink(moho)
	--print("Update link entered")
	
	local parentLayer = self:GetLayerParent(moho, moho.layer)
	if parentLayer then self:UpdateBinding(moho, moho.layer, parentLayer) end
	
	if not self:IsScriptOn(moho, moho.layer) then
		local scriptPath = self:GetScriptPath(moho)
		self:TurnScriptOn(moho,moho.layer, scriptPath)
	end
	self:TurnScriptOff(moho,moho.layer)
	moho.layer:UpdateCurFrame()
	moho:UpdateUI()
end

function AE_MeshinstanceTool:UpdateAllLayers(moho)
	--print("Update link entered")
	if not self:IsScriptOn(moho, moho.layer) then
		local scriptPath = self:GetScriptPath(moho)
		for i, layer in AE_Utilities:IterateAllLayers(moho) do
			local parentLayer = self:GetLayerParent(moho,layer)
			if parentLayer == layer then 
				LM.GUI.Alert(LM.GUI.ALERT_WARINING, "Removed self-reference from " .. AE_Utilities:GetLayerRelativePath(moho, nil, layer))
				self:LinkLayers(moho, layer, nil)				
			elseif parentLayer then
				--print("updating ", layer:Name(), " from ", parentLayer:Name())
				self:LinkLayers(moho, layer, parentLayer)
				self:UpdateBinding(moho, layer, parentLayer)				
				self:TurnScriptOn(moho, layer, scriptPath)	
				self:TurnScriptOff(moho, layer)	
				layer:UpdateCurFrame()				
			end
		end

	end
	moho:UpdateUI()
end

function AE_MeshinstanceTool:GetLayerParent(moho, layer)
	if not layer then layer = moho.layer end
	local scriptInfo = layer:ScriptData()
	local currentSrcUUID = scriptInfo:GetString(self.srcKeyName)
	if not currentSrcUUID then return nil end
	for i, nextLayer in AE_Utilities:IterateAllLayers(moho) do
		if nextLayer:UUID() == currentSrcUUID then
			local path = AE_Utilities:GetLayerRelativePath(moho, layer, nextLayer)
			scriptInfo:Set(self.srcPathKeyName, path)
			return nextLayer
		end
	end
	local path = scriptInfo:GetString(self.srcPathKeyName)
	local pathLayer = AE_Utilities:GetLayerFromRelativePath(moho, layer, path)
	if pathLayer then
		scriptInfo:Set(self.srcKeyName, pathLayer:UUID())
		return pathLayer
	end
	return nil
end

function AE_MeshinstanceTool:GetNewParent(moho)
	if not moho.document:CountSelectedLayers()==2 then return nil end
	for i, nextLayer in AE_Utilities:IterateAllLayers(moho) do
		if nextLayer:SecondarySelection() and nextLayer ~= moho.layer then 
			return nextLayer
		end
	end
	return nil
end

function AE_MeshinstanceTool:ReplaceChannel(moho, channel, srcChannel)
	local derChannel = AE_Utilities:GetDerivedChannel(moho, channel)
	local derSrcChannel = AE_Utilities:GetDerivedChannel(moho, srcChannel)
	derChannel:Clear()
	for k=0, derSrcChannel:CountKeys()-1 do
		local value = derSrcChannel:GetValueByID(k)
		local when = derSrcChannel:GetKeyWhen(k)
		local interp = MOHO.InterpSetting:new_local()
		derSrcChannel:GetKeyInterpByID(k, interp)
		derChannel:SetValue(when, value)
		derChannel:SetKeyInterp(when, interp)
	end
end

function AE_MeshinstanceTool:TurnScriptOn(moho, layer, scriptPath)
	local scriptInfo = layer:ScriptData()
	if scriptInfo:HasKey(self.srcKeyName) then
		layer:SetLayerScript(scriptPath)
		--print("script turned on for ", layer:Name())
	end
end

function AE_MeshinstanceTool:TurnScriptOff(moho, layer)
	if self:IsScriptOn(moho,layer) then
		layer:SetLayerScript(nil)
		--print("script turned off for ", layer:Name())
		local scriptInfo = layer:ScriptData()
		if scriptInfo:HasKey(self.srcKeyName) then
			local srcUUID = scriptInfo:GetString(self.srcKeyName)
			local srcLayer = AE_Utilities:GetLayerByUUID(moho, srcUUID)
			if srcLayer then
				for subID, chID, channel, chInfo in AE_Utilities:IterateAllChannels(moho, layer) do			
					if not chInfo.selectionBased then
						if chInfo.channelID == CHANNEL_CURVE or chInfo.channelID == CHANNEL_POINT then									
							local srcChInfo = MOHO.MohoLayerChannel:new_local()									
							srcLayer:GetChannelInfo(chID, srcChInfo)
							local srcChannel = srcLayer:Channel(chID, subID, moho.document)
							if not srcChannel then return end
							self:ReplaceChannel(moho, channel, srcChannel)
							for a=0, srcChannel:CountActions()-1 do
								if srcChannel:Action(a):Duration() > 0 then
									local actionName = srcChannel:ActionName(a)
									local actChannel = channel:ActionByName(actionName)
									if not actChannel then
										channel:ActivateAction(actionName)
										actChannel = channel:ActionByName(actionName)
									end
									self:ReplaceChannel(moho, actChannel, srcChannel:Action(a))
								end
							end
						end
					end
				end				
			end
		end
	end
end

function AE_MeshinstanceTool:IsScriptOn(moho, layer)
	local scriptPath = layer:LayerScript()
	if scriptPath and string.sub(scriptPath, -#self.scriptName) == self.scriptName then 
		return true 
	end
	return false
end

function AE_MeshinstanceTool:DuplicateLayer(moho, oldLayer, newLayer)
	if oldLayer:IsGroupType() then
		if not newLayer:IsGroupType() then return end
		oldLayer = moho:LayerAsGroup(oldLayer)
		newLayer = moho:LayerAsGroup(newLayer)
		if oldLayer:CountLayers() ~= newLayer:CountLayers() then return end
		for i=0, oldLayer:CountLayers()-1 do
			self:DuplicateLayer(moho, oldLayer:Layer(i), newLayer:Layer(i))
		end
	else
		if not moho:LayerAsVector(oldLayer) or not moho:LayerAsVector(newLayer) then return end
		self:LinkLayers(moho, newLayer, oldLayer)
		--[[
		newLayer:SetLocked(true)
		newLayer:SetLabelColor(-1)
		--]]
	end
end

function AE_MeshinstanceTool:CopyGroupStructure(moho, oldLayer, newLayer, oldRoot)
	if not oldRoot then oldRoot = oldLayer end
	if oldLayer:IsGroupType() then
		if not newLayer:IsGroupType() then return end
		oldLayer = moho:LayerAsGroup(oldLayer)
		newLayer = moho:LayerAsGroup(newLayer)
		if oldLayer:CountLayers() ~= newLayer:CountLayers() then return end
		for i=0, oldLayer:CountLayers()-1 do
			self:CopyGroupStructure(moho, oldLayer:Layer(i), newLayer:Layer(i), oldRoot)
		end
	else
		if not moho:LayerAsVector(oldLayer) or not moho:LayerAsVector(newLayer) then return end
		local oldParent = self:GetLayerParent(moho, oldLayer)
		if not oldParent then return end
		if not AE_Utilities:IsAncestor(oldRoot, oldParent) then return end
		local path = AE_Utilities:GetLayerRelativePath(moho, oldLayer, oldParent)
		local checkPath = AE_Utilities:GetLayerFromRelativePath(moho, oldLayer, path)
		if checkPath ~= oldParent then 
			return LM.GUI.Alert(LM.GUI.ALERT_WARNING, string.format("Have a problem with parenting %s to %s. Link will not be copied", oldLayer:Name(), oldParent:Name()))
		end
		local newParent = AE_Utilities:GetLayerFromRelativePath(moho, newLayer, path)
		if not newParent then
			return LM.GUI.Alert(LM.GUI.ALERT_WARNING, string.format("Have a problem with parenting %s to %s. Link will not be copied", oldLayer:Name(), oldParent:Name()))
		end
		self:LinkLayers(moho, newLayer, newParent)
	end
end

Icon
AE Meshinstance
Listed

Script type: Tool

Uploaded: Feb 13 2021, 07:09

Last modified: Apr 30 2021, 07:45

Link mesh layers with ability to turn the link on and off
Movement of points and bezier handles are linked, actions and bone bindings are sinchronized, all the other channels are not linked. Meshes must have equal numbers of points and curve segments. 
In "realtime" mode child layers carry layer script, put into project save folder. In "off" mode layers are not linked but can be sinchronized with button press.

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: 235