Introducing 3ds MAX to UE4 FBX and t3d Exporter MaxScript

Nevermind, just figured it out! All of my objects were imported from Rhino as block instances and came into 3DS with the same name. I guess the script needs each object to have its own name. I renamed them all using 3DS’s rename object command and had it number them all. Now it is working perfectly! Thanks for the great script!

I’m running 3ds Max 2016 and when trying to copy the geometrry’s positiins and paste in UE4.1.2 Max crashes every time.

Any help would be appreciated

Are you kidding?)) I have over 2000 objects on the scene, and for each box I need to choose mesh manually?))

I took an Unreal course where the guy modified this script to just copy and paste the models:

macroScript TS_UE4_ObjectExportTool category:“TS_Tools” tooltip:“Exports Objects to FBX for UE4” buttonText:“UE4 FBX Export”
macroScript TS_UE4_ObjectExportTool category:“TS_Tools” tooltip:“Exports Objects to FBX for UE4” buttonText:“UE4 FBX Export”
(
/*

www.TomShannon3D.com

Exports models to FBX one at a time. 
Each FBX file will be named after the Max Scene name
The models can be oriented to 0,0,0 on export (Rather than having them all stacked in MAX)
You can export the position of the models to the clipboard for easy placing in UE4 by pasting the clipboard contents then replacing the placeholder objects in UE4 with the correct objects.

Version 1.0 -	2014-07-22 : 	First version!
Version 1.1 -	2014-09-07 : 	Collision features and some useability fixes, renamed
								NEW: Allows users to explicitly set the export directory. This stays saved in the max file permanently and will be reloaded
								NEW: Users can reset the saved export path to use the default export path
								NEW: Exports UCX collision geometry! Just ensure your naming is correct, and the script will detect the apropriate UCX primitives for export. Note: they do NOT need to be selected or visible!

								Fixed: Export Window now identifies errors and offers some helpful tootips
								Fixed: Export path textfield identifies when no export path is available
								Fixed: Interface updates when the max file is loaded, saved or reset, showing the correct paths, etc.
								Fixed: Disabled Geometry-specific export options when the object position is being exported (To help notify the user as to what mode they are in)
								Fixed: Checks to see if export path is valid and asks user if they want to create the export path if it doesn't exist, rather than just making one
Version 1.1.1	2014-09-16 :	Fixed: Exporting objects now names objects correctly (Thanks, Maico G.!)
*/

try (destroyDialog UE4_Export_FBX_Rollout) catch ()

local ExportType = "Geometry" --legacy variable
local ExportObjects = #()
local OriginalSelection = #()

local defaultMeshPath = @"/Game/Export/%.%"

--Strings for building things
t3d_Header = 

"Begin Map
Begin Level
"
t3d_StaticMesh_Entry =
" Begin Actor Class=StaticMeshActor Name=% Archetype=StaticMeshActor’/Script/Engine.Default__StaticMeshActor’
Begin Object Class=StaticMeshComponent Name=StaticMeshComponent0 ObjName=StaticMeshComponent0 Archetype=StaticMeshComponent’/Script/Engine.Default__StaticMeshActor:StaticMeshComponent0’
End Object
Begin Object Name=StaticMeshComponent0
StaticMesh=StaticMesh’/Game/Export/%.%’
RelativeLocation=(X=%,Y=%,Z=%)
RelativeScale3D=(X=%,Y=%,Z=%)
RelativeRotation=(Pitch=%,Yaw=%,Roll=%)
CustomProperties
End Object
StaticMeshComponent=StaticMeshComponent0
Components(0)=StaticMeshComponent0
RootComponent=StaticMeshComponent0
ActorLabel="%"
End Actor
"

t3d_Footer =
" End Level
Begin Surface
End Surface
End Map
"

global UE4_Export_FBX_Rollout = rollout UE4_Export_FBX_Rollout "UE4 FBX Exporter Version 1.1.1"
(

	group  "Export Mode"
	(
		radioButtons UE4_ObjectType labels:#("Export Selected Objects to FBX Files","Copy Selected Object Transforms and Names to Clipboard") align:#left
	)
	
	group "Export Options"
	(
		checkbox UE4_UseGrid "Use user grid? (Grid must be named \"UE4_Grid\" to work.)" enabled:false checked:true
		checkbox UE4_MoveToOrigin "Move each object to 0,0,0?" enabled:true checked:true
		checkbox UE4_IncludeCollision "Include UCX Collision Shells?" across:2 checked:true
		hyperlink hl2 "For More Information Click Here" address:"https://docs.unrealengine.com/latest/INT/Engine/Content/FBX/StaticMeshes/index.html#collision" color:(color 0 0 255) visitedColor:(color 0 0 255)

			
		checkbox UE4_Dialog "Show FBX Export Dialog? (Will only be shown once per export)" checked:true 
		label label1 "Export Path:" align:#left
		edittext UE4_ExportPath_Text readonly:true  width:360 align:#left across:3 
		button UE4_SetExportPathButton "..."  height:17 width:17 offset:[174,0] tooltip:"Explicitly set the export path"
		button UE4_ClearExportPathButton "X"  height:17 width:17 offset:[60,0] tooltip:"Reset the export patht to be relative to this MAX scene file"
		button OpenExportPathButton "Open Export Path in Explorer"
	)
	
	listbox UE4_Listbox "Objects to be Exported:" readonly:true height:20
	button doit "Export Selected Objects" width:220 height:50	
	
	hyperlink hl1 "Help and Info" address:"http://www.tomshannon3d.com/2014/07/tstools-first-release-ue4-mass-fbx.html" align:#center color:(color 0 0 255) visitedColor:(color 0 0 255)
	
	local doGridTransform = false
	local exportObjects = #()
	local selnames = #()
	local ErrorArray = #()
	local exportPath = undefined
	
	local layerNames = #()
	local layerObjects = #()
	
	local packageName = "" --do i still need this?
	
	fn updateExportPath =
	(
		-- If exportPath AND maxFilePath aren't set
		if maxfilepath == "" and ExportPath == undefined then 
			UE4_ExportPath_Text.text = "Please save the scene or specify an export path explicitly."
		
		-- If exportPath has NOT been defined and the scene file HAS been saved, assume the export path
		if exportPath == undefined and maxfilepath != "" do 
		(
			exportPath = maxfilepath+@"export\"
			UE4_ExportPath_Text.text = exportPath
		)
			
		--If there's an export path defined in the scene, use that instead
		SavedExportPath = (getAppData rootNode 77900)
		if (SavedExportPath != undefined) then 
		(
			exportPath = SavedExportPath
			UE4_ExportPath_Text.text = exportPath
		) 
	)		
	
	--Ensure there's a valid export path before exporting. If it's not there, ask the user if they want to create the path or cancel
	--Returns true if the directory exists or is created
	--Returns false if the director doesn't exist and the user says NO
	fn checkExportPath exportPathName =
	(
		if doesfileexist (exportPathName as String) == false then 
		(
			makeTheDir = queryBox (exportPathName as String +"

Does not exist.
Create this directory now?") title:“Export Directory Does Not Exist” beep:true
if makeTheDir == true then
(
makeDir exportPathName
return true
)
else return false
)
else return true
)

	--Callback controls
	fn addCallbacks =
	(
		callbacks.addScript #selectionSetChanged "UE4_Export_FBX_Rollout.updateList()" id:#UE4_Export_FBX_Rollout_Update
		callbacks.addScript #filePostOpen "UE4_Export_FBX_Rollout.updateList resetExportPath:true" id:#UE4_Export_FBX_Rollout_Update
		callbacks.addScript #systemPostNew "UE4_Export_FBX_Rollout.updateList resetExportPath:true" id:#UE4_Export_FBX_Rollout_Update
		callbacks.addScript #systemPostReset "UE4_Export_FBX_Rollout.updateList resetExportPath:true" id:#UE4_Export_FBX_Rollout_Update
		callbacks.addScript #filePostSave "UE4_Export_FBX_Rollout.updateList()" id:#UE4_Export_FBX_Rollout_Update
	)
	
	fn removeCallbacks =
	(
		callbacks.RemoveScripts id:#UE4_Export_FBX_Rollout_Update
	)
	
	--return all collision shells matching the baseMesh's name
	fn findMatchingUCX baseMesh =
	(
		local matchingUCX = (execute ("$'UCX_"+baseMesh.name as string+"'*")) as array
		if matchingUCX != undefined then
			return matchingUCX
		else return false
	)
	
		
	
	--global updateList
	global updateList = fn updateList resetExportPath:false =
	(
		if resetExportPath==true do
			exportpath = undefined
		
		--Actual objects to be exported!
		exportObjects = #()
		
		--Names, etc to populate the selection list. This veruable name is bad now because it handles some error strings as well!
		local selnames = #()
		
		--run the export path function to ensure the path is as up to date as possible
		updateExportPath()
		
		--Check to see if there's a UE4_Grid object in the scene, if so set the options
		if $UE4_Grid == undefined then
		(
			UE4_UseGrid.checked = false
			UE4_UseGrid.enabled = false
		)
		else UE4_UseGrid.enabled = true
		
		--Make sure there's something in the selection
		theSelection = selection as array
		OriginalSelection = selection as Array
		ErrorArray = #()
		
		--Do some basic error checking:
		--Ensure something is selected
		if theSelection.count == 0 do
		(
			append ErrorArray "    --Nothing Selected! "
			append ErrorArray "        Please select some meshes to export."
		)
		--Ensure there's something selected that can be exported, also build the exportObjects array that's used for the actual export
		for obj in theSelection do
		(
			if (superclassof obj == GeometryClass and classof obj != Targetobject and matchpattern obj.name pattern:"UCX_*" == false) do 
			(
				objName = obj.name
				append exportObjects obj
			)
		)
		if exportObjects.count == 0 and theSelection.count != 0 do 
		(
			append ErrorArray "    --No valid Geometry selected! "
			append ErrorArray "        Please select some non-UCX meshes to export."
		)
		--Check the valididty of the export path
		if exportPath == undefined do 
		(
			append ErrorArray "    --No Export Path Defined! Please define an export path above."
			append ErrorArray "        Please define an export path above."
		)
		
		
		if errorArray.count == 0 then
		(
			if UE4_ObjectType.state == 1 then append selnames (exportPath) --display the export path in the window
			else append selnames ("To be copied to Clipboard:")
			
			if exportObjects.count == 0 then
			(
				UE4_Listbox.items = #("No Valid Objects Selected! Only non-UCX geometry can be exported.")
				return true
			)
			
			-- build the .fbx  list (THIS IS PURELY for display!)
			for obj in exportObjects do
			(
				objName = obj.name
				if UE4_ObjectType.state == 1 then
				(
					if UE4_UseGrid.checked == false then tempString = "  "+objName+".fbx @ "+obj.pos as string
						else tempString = "  "+objName+".fbx @ "+(obj.pos-$UE4_Grid.pos) as string+" relative to UE4_Grid.pos"
					if UE4_MoveToOrigin.checked == true do tempString = "  "+objName+".fbx @ 0,0,0"
						
				)
				if UE4_ObjectType.state == 2 then
				(
					if UE4_UseGrid.checked == false then tempString = "  "+objName+" @ "+obj.pos as string
						else tempString = "  "+objName+" @ "+(obj.pos-$UE4_Grid.pos) as string+" relative to UE4_Grid.pos"
				)
				
				append selnames tempString
				
				if UE4_IncludeCollision.checked == true and UE4_ObjectType.state == 1 do
				(
					for UCX in (findMatchingUCX obj) do append selnames ("    "+UCX.name as string)
				)
			)

			if (selnames.count > 0 )then
				UE4_Listbox.items = selnames
				
		)-- END if selection.count != 0 then
		else
		(
			append selnames ("There are errors; can't build the object list:" as string)
			join selnames ErrorArray
			UE4_Listbox.items = selnames
		)
		
	)-- end function updateList
	

	on UE4_ObjectType changed newState do
	(
		if Newstate == 1 then
		(
			UE4_UseGrid.enabled = true 
			UE4_MoveToOrigin.enabled = true
			UE4_IncludeCollision.enabled = true 
			UE4_Dialog.enabled = true 
		)
		else
		(
			UE4_UseGrid.enabled = false 
			UE4_MoveToOrigin.enabled = false
			UE4_IncludeCollision.enabled = false 
			UE4_Dialog.enabled = false 
		)
		updatelist()
	)
	
	
	on UE4_Export_FBX_Rollout open do
	(
		updateList()
		addCallbacks()
	)
	
	on UE4_Export_FBX_Rollout close do
	(
		removeCallbacks()
		UE4_Export_FBX_Rollout = null
	)
	
	on UE4_SetExportPathButton pressed do
	(
		--Open to a resonable location
		if (doesfileexist (exportPath as String)) then thePath = getSavepath initialDir:(exportPath)
		else thePath = getSavepath initialDir:(maxfilepath)
		print thePath
		
		--Once set, save the path to the scene's appdata
		if thePath != undefined then 
		(
			exportPath = thePath
			SetAppData rootNode 77900 exportPath
		)
			
		updateList()
	)
	
	on UE4_ClearExportPathButton pressed do
	(
		exportPath = undefined
		deleteAppData rootNode 77900
		updateList()
	)
	
	
	--Refresh the List/UI when otions are changed, etc.
	on UE4_UseGrid changed newState do updateList()
	on UE4_Dialog changed newState do updateList()
	on UE4_MoveToOrigin changed newState do updateList()
	on UE4_IncludeCollision changed newState do updateList()
	
	
	on OpenExportPathButton pressed do
	(
		if exportPath != undefined then
			if (checkExportPath exportPath == true) then shellLaunch "explorer.exe" exportPath
		else 
			messageBox "No export path is defined."
	)
	
	--
	--
	--//* THIS IS THE BIG BUTTON PRESS FUCTIONALITY!!
	--
	--
	on doit pressed do
	(
		updateList()
		
		if ErrorArray.count != 0 do
		(
			ErrorMessage = ""
			for e in errorArray do append ErrorMessage ("

"+e as string)
messagebox ("Cannot export at this time:
"+ ErrorMessage)
return false
)

		--Getting the options here for easy reference
		doGridTransform = UE4_UseGrid.checked
		doOrigin = UE4_MoveToOrigin.checked 
		dot3d = UE4_ObjectType.state == 2
		doGeom = UE4_ObjectType.state == 1
		doUCX = UE4_IncludeCollision.checked
		
		doDialog = UE4_Dialog.checked
		
		--Using the exportObjects generated by updateList to determine what to export
		--Probably don't need this anymore
		theSel = exportObjects
		
		--we need to 'pause' the callbacks in the script while we do this
		removeCallbacks()
		
		-- for speed
		max create mode
		
		--Here's where we create our selection arrays
		selectionArrays = #()
		
		-- Begin the t3d stringstream if we're doing that
		if (dot3d == true) do 
		(
			local t3dFile = stringstream ""
			format t3d_Header to:t3dFile
		)
		
		--ensure there's a directory to export to
		if (doGeom == true) do makeDir exportPath all:true
		
		--This is so we only show the FBX window the first time
		firstObject = true
		for obj in exportObjects do
		(
			objName = obj.name
			--select obj
			--Geometry/fbx
			if (doGeom == true) then
			(
				if doUCX then select (append (findMatchingUCX obj) obj)
				else select obj
				--print ("SLKAJSLDKJALSKDJALKSJDLAKSJDLAKSJDLKAJSDLKJASLDKJASLDJK")
				local objectShift = [0,0,0] --to be able to shift the collision shells if needed
				local exportFileName = exportPath+@"\"+objName+@".fbx"
				
				local objTransform = copy obj.transform
				
				--Get the shift based on user options
				if (doGridTransform == true and $UE4_Grid != undefined) then
				(
					objectShift = $UE4_Grid.pos
					
					if doOrigin == true do 
						objectShift = obj.pos - $UE4_Grid.pos
					
				)
				else if doOrigin == true then 
					objectShift = obj.pos
				
				--move everything
				for o in selection do 
					o.pos = o.pos - objectShift
				
				--Export selected!
				--Show the FBX dialog
				if firstObject == true and doDialog == true then 
					exportFile exportFileName selectedOnly:true
				
				else
					exportFile exportFileName #noPrompt selectedOnly:true
				
				firstObject = false
				
				--Move everything back
				for o in selection do 
					o.pos = o.pos + objectShift
				
			)		
			
			

			--.t3d --Now it just adds the text to the clipboard
			if (dot3d == true) then
			(
				
				--Default transformations
				local OBJRot = (matrix3 1).rotationpart as eulerangles 
				local OBJPos = [0,0,0]
				
				if (doGridTransform and $UE4_Grid != undefined) then
				(
					OBJRot = obj.transform.rotationpart as eulerangles 
					OBJPos = obj.pos - $UE4_Grid.pos
					
				)
				else
				(
					OBJRot = obj.transform.rotationpart as eulerangles 
					OBJPos = obj.pos
				)

–Name=EditorCube7
–RelativeLocation=(X=-2304.000000,Y=2047.999878,Z=298.000000)
–RelativeScale3D=(X=4.000000,Y=2.000000,Z=0.250000)
–RelativeRotation=(Pitch=0.000005,Yaw=180.000366,Roll=354.375000)
–ActorLabel=“EditorCube114”
if superclassof obj == GeometryClass do
format t3d_StaticMesh_Entry objName objName objName OBJPos.x (OBJPos.y*-1) OBJPos.z obj.scale.x obj.scale.y obj.scale.z (OBJRot.y*-1.0) (OBJRot.z*-1.0) OBJRot.x objName to:t3dFile
)
)

		--Close off the t3d file if it's being made
		if (dot3d == true) then 
		(
			
			format t3d_Footer to:t3dFile
			setClipBoardText  t3dFile
			free t3dfile
		)
		
		select exportObjects
		updateList()
		addCallbacks()
	) --end on doit pressed do
	
) -- end rollout
createDialog UE4_Export_FBX_Rollout width:420	

)

As I happen to know that there is a lot of People curently still using this script, I thought it prudent to post my personal edit update to TS_Tools 1.1 that makes this script compatible with UE5:
(The script is quite nice since it is much “cleaner” than something like datasmith)

https://drive.google.com/file/d/1BUcg_4JdquOsbIEnp2iqb-YN8cDaEtA7/view?usp=sharing

3 Likes

Thank you. You are my savior. OMG I seek this script for the last half of the year.

1 Like

Still working in 3ds max 2025. Thanks!