moho2Ae script

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

Moderators: Víctor Paredes, Belgarath, slowtiger

Post Reply
User avatar
Gherasimov Vasile
Posts: 5
Joined: Tue Jan 25, 2022 10:46 am

moho2Ae script

Post by Gherasimov Vasile »

Hello friends, I have a request for those who know coding. Please help me to adapt the given script to work with PSD files as well, if possible, so that when you open in AE, it also receives information about the location of the files, and then opens them in composition
image
https://prnt.sc/r-s10uVgUttx

Code: Select all

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

ScriptName = "CM_Moho2AE"

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

CM_Moho2AE = {}

function CM_Moho2AE:Name()
	return 'Export to After Effects jsx'
end

function CM_Moho2AE:Version()
	return '1.0'
end

function CM_Moho2AE:UILabel()
	return 'Export to After Effects jsx'
end

function CM_Moho2AE:Creator()
	return 'Community test'
end

function CM_Moho2AE:Description()
	return 'Create .jsx script which been called from AE imports all the bitmap images of moho bitmap layers and (optionaly) rendered sequences of moho vector layers, placing it into new composition in correct order and position (optionaly with layer animation too)'
end


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

function CM_Moho2AE:IsRelevant(moho)
	return true
end

function CM_Moho2AE:IsEnabled(moho)
	return true
end

-- **************************************************
-- CM_Moho2AEDialog
-- **************************************************

local CM_Moho2AEDialog = {}

function CM_Moho2AEDialog:new()
	local d = LM.GUI.SimpleDialog('Export to After Effects jsx', CM_Moho2AEDialog)
	local l = d:GetLayout()

	return d
end

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

-- map moho blending mode constants to AE blending mode constant names
CM_Moho2AE.blendingModesMap = {
	[MOHO.BM_ADD] = "BlendingMode.ADD",
	[MOHO.BM_COLOR] = "BlendingMode.COLOR",
	[MOHO.BM_DIFFERENCE] = "BlendingMode.DIFFERENCE",
	[MOHO.BM_HUE] = "BlendingMode.HUE",
	[MOHO.BM_LUMINOSITY] = "BlendingMode.LUMINOSITY",
	[MOHO.BM_MULTIPLY] = "BlendingMode.MULTIPLY",
	[MOHO.BM_OVERLAY] = "BlendingMode.OVERLAY",
	[MOHO.BM_SATURATION] = "BlendingMode.SATURATION",
	[MOHO.BM_SCREEN] = "BlendingMode.SCREEN"
}

function CM_Moho2AE:ResourcePath()
   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 = "(.*\\)[mM]enu\\.*"
   if slash == "/" then pattern = "(.*/)[mM]enu/.*" end

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

function CM_Moho2AE:Run(moho)
	--dialog call to uncomment later
	--[[
	local dlog = CM_Moho2AEDialog:new(moho)
	if (dlog:DoModal() == LM.GUI.MSG_CANCEL) then
		return
	end
	--]]
	
	moho.document:SetDirty()
	moho.document:PrepUndo(nil)
	
	-- read some common functions for jsx
	local jsxCommonFunctionsPath = self.ResourcePath().."common.jsx"
	local file = io.open(jsxCommonFunctionsPath, "r") 
    if not file then 	
		return LM.GUI.Alert(LM.GUI.ALERT_WARNING, "No " .. jsxCommonFunctionsPath)
	end
    local content = file:read "*a" -- *a or *all reads the whole file
    io.close(file)
	
	-- ask user for output file
	local path = LM.GUI.SaveFile("Select JSX File")
	if (path == "") then
		return
	end	
	if string.sub(path, -4) ~= ".jsx" then path = path..".jsx" end
	local f = io.open(path, "w")
	if (f == nil) then
		return
	end
	io.output(f)
	
	-- write some common functions for jsx
	io.write(content)
	
	
	-- write creation of main comp with this comp size and duration
	local name = string.sub(moho.document:Name(), 1, -6)
	self.w = moho.document:Width()
	self.h = moho.document:Height()
	self.fps = moho.document:Fps()
	self.dur = moho.document:EndFrame() - moho.document:StartFrame() + 1
	self.seconds = self.dur/self.fps
	io.write(string.format('var mainComp = app.project.items.addComp("%s", %s, %s, 1, %s, %s);\n', name, self.w, self.h, self.seconds, self.fps))
	io.write(string.format('var mainFolder = app.project.items.addFolder("%s");\n', name))
	io.write('mainComp.parentFolder = mainFolder;\n')
	self.globalVarNameIndex = 0
	
	io.write('var cameraNull = mainComp.layers.addNull();\ncameraNull.name = "camera";\n')
	io.write('var persNull = mainComp.layers.addNull();\npersNull.name = "pers";\n')
	self:BakeCamera(moho)
	
	for i = 0, moho.document:CountLayers() - 1 do
		local nextLayer = moho.document:Layer(i)
		local nextCompLayerName = self:ProcessAnyLayer(moho, nextLayer, 'mainComp')
	end
	
	io.close(f)
	
end

function CM_Moho2AE:BakeCamera(moho)
	--TODO: set animation for persNull and cameraNull layers, baking camera animation from Moho (see ae_export_2d_camera.lua for baking formulas)
end

function CM_Moho2AE:ProcessAnyLayer(moho, layer, parentCompVarName, folderName)
	if not folderName then folderName = "mainFolder" end
	
	local nextIndex = self.globalVarNameIndex + 1
	local name = 'layer' .. nextIndex
	self.globalVarNameIndex = self.globalVarNameIndex + 1
	
	local layerType = layer:LayerType()
	local newFootageItemVarName = nil
	if layerType == MOHO.LT_GROUP then
		--create folder for footage	
		local newFolderName = 'folder' .. nextIndex
		io.write(string.format('var %s = %s.items.addFolder("%s");\n', newFolderName, folderName, layer:Name()))		
		newFootageItemVarName = self:CreateGroupFootage(moho, layer)
		io.write(string.format('%s.parentFolder = %s;\n', newFootageItemVarName, folderName))
		for i=0, moho:LayerAsGroup(layer):CountLayers() - 1 do
			local nextLayer = moho:LayerAsGroup(layer):Layer(i)
			self:ProcessAnyLayer(moho, nextLayer, newFootageItemVarName, newFolderName)
		end
		io.write(string.format('if (%s.items.length == 0) %s.remove();\n', newFolderName, newFolderName))
	elseif layerType == MOHO.LT_IMAGE then
		newFootageItemVarName = self:CreateBitmapFootage(moho, layer)		
	elseif layerType == MOHO.LT_BONE then
		newFootageItemVarName = self:CreateSequenceFootage(moho, layer)
	end
	if not newFootageItemVarName then return nil end
	
	-- place resulting footage (composition, image or sequence) into parent composition

	if layerType == MOHO.LT_GROUP or layerType == MOHO.LT_IMAGE then
		io.write(string.format('var %s = %s.layers.add(%s);\n', name, parentCompVarName, newFootageItemVarName))		
		self:ApplyLayerTransform(moho, layer, name)
		if not layer:IsImmuneToCamera() then
			io.write(string.format('%s.parent = cameraNull;\n', name))
		end
	elseif layerType == MOHO.LT_BONE and #newFootageItemVarName > 0 then
		for k, v in pairs(newFootageItemVarName) do
			io.write(string.format('var %s = %s.layers.add(%s);\n', name..v, parentCompVarName, v))
			io.write(string.format('%s.parent = persNull;\n'), name..v)
		end
	end
	self:ApplyLayerBlending(moho, layer, name)
	if layerType == MOHO.LT_GROUP then
		-- set collapse transformations on
		io.write(string.format('%s.collapseTransformation = true;\n', name))
	end
	return name
end

function CM_Moho2AE:CreateGroupFootage(moho, layer)
	local nextIndex = self.globalVarNameIndex + 1
	local name = 'comp' .. nextIndex
	self.globalVarNameIndex = self.globalVarNameIndex + 1
	-- create new composition with same parameters as main one		
	io.write(string.format('var %s = app.project.items.addComp("%s", %s, %s, 1, %s, %s);\n', name, layer:Name(), self.w, self.h, self.seconds, self.fps))
	return name
end

function CM_Moho2AE:CreateBitmapFootage(moho, layer)
	local nextIndex = self.globalVarNameIndex + 1
	local name = 'img' .. nextIndex
	self.globalVarNameIndex = self.globalVarNameIndex + 1	
	-- import bitmap
	local imageLayer = moho:LayerAsImage(layer)
	local path = string.gsub(imageLayer:SourceImage(), "\\", "\\\\")
	local args = string.format('"%s"', path)
	if imageLayer:IsPSDImage() then 
		args = args .. string.format(', %s', imageLayer:PSDLayerOrderID() + 1)
	end
	-- call jsx function with these args (to import single bitmap or PSD file or use PSD layer allways imported for another layer of same moho project
	io.write(string.format('var %s = FindOrImport(%s);\n', name, args));
	return name
end

function CM_Moho2AE:CreateSequenceFootage(moho, layer)
	local characters = {}
	--TODO: find all the layercomps which include this layer
	--TODO: compute path to rendered images (ask user in first dialog and save to script preferences)
	--TODO: import sequence (if exists, or offline dummy if does not) to AE via jsx
	-- return array of new layers' names
	return characters
end

function CM_Moho2AE:ApplyLayerTransform(moho, layer, layerVarName)
	-- set ae anchor and position to moho values
	local aeAnchor = layer:Origin()
	aeAnchor.x = aeAnchor.x * self.h/2 
	aeAnchor.y = -aeAnchor.y * self.h/2 

	local aePos = layer.fTranslation:GetValue(1)
	aePos.x = aePos.x * self.h/2 + self.w/2 + aeAnchor.x
	aePos.y = - aePos.y * self.h/2 + self.h/2 + aeAnchor.y
	
	if layer:LayerType() == MOHO.LT_GROUP then 
		aeAnchor.x = aeAnchor.x + self.w/2
		aeAnchor.y = aeAnchor.y + self.h/2
	elseif layer:LayerType() == MOHO.LT_IMAGE then
		local imageLayer = moho:LayerAsImage(layer)
		aeAnchor.x = aeAnchor.x + imageLayer:PixelWidth()/2
		aeAnchor.y = aeAnchor.y + imageLayer:PixelHeight()/2
	end	
	io.write(string.format('%s.position.setValue([%s, %s]);\n', layerVarName, aePos.x, aePos.y))
	io.write(string.format('%s.property("Anchor Point").setValue([%s, %s]);\n', layerVarName, aeAnchor.x, aeAnchor.y))
	-- set ae rotation to moho values
	local aeAngle = -180 * layer.fRotationZ:GetValue(1)/math.pi
	io.write(string.format('%s.rotation.setValue(%s);\n', layerVarName, aeAngle))
	-- set ae scale to moho scale and flip values
	local aeScale = layer.fScale:GetValue(1) * 100
	if layer.fFlipH:GetValue(1) then aeScale.x = -aeScale.x end
	if layer.fFlipV:GetValue(1) then aeScale.y = -aeScale.y end
	io.write(string.format('%s.scale.setValue([%s, %s, %s]);\n', layerVarName, aeScale.x, aeScale.y, aeScale.z))
	
end

function CM_Moho2AE:ApplyLayerBlending(moho, layer, layerVarName)
	--TODO: set blending mode, transparency and visibility from moho layer to AE
end



https://drive.google.com/file/d/1bVB4Kw ... sp=sharing
script
User avatar
SimplSam
Posts: 1048
Joined: Thu Mar 13, 2014 5:09 pm
Location: London, UK
Contact:

Re: moho2Ae script

Post by SimplSam »

The PSDLayerOrderID function was removed from Moho 13.5. Therefore PSD's are pretty much unsupported in Moho scripting in the current release.

You may need to run the script in version Moho 12.5 in order to get it work.
Moho 14.1 » Win 11 Pro 64GB » NVIDIA GTX 1080ti 11GB
Moho 14.1 » Mac mini 2012 8GB » macOS 10.15 Catalina
Tube: SimplSam


Sam
User avatar
Gherasimov Vasile
Posts: 5
Joined: Tue Jan 25, 2022 10:46 am

Re: moho2Ae script

Post by Gherasimov Vasile »

SimplSam wrote: Wed Mar 22, 2023 3:04 pm The PSDLayerOrderID function was removed from Moho 13.5. Therefore PSD's are pretty much unsupported in Moho scripting in the current release.

You may need to run the script in version Moho 12.5 in order to get it work.
Thank you very much for your help
Post Reply