[3DsMax] robust texture pathing?

Max seems horrible at using relative texture paths.

our max files are moving to new work spaces as we sort out our P4 workflow and texture refs are being lost.

our asset directories look like

../source_art/characters/myGuy/mesh/myguy.max
../source_art/characters/myGuy/textures/myguy_diffuse.tif ...ect

but when the path to …/source_art changes (i.e., a new workspace is created in P4) max looses track of all the textures.

It’s a headache I’ve faced before and not found a robust solution for.
google hits at yield a wide variety of responses, mostly one-asset-at-a-time band aid solutions.
I’d like something bulletproof , reliable, and script-able.

I am thinking of dropping the mesh vs texture folder arrangement and just keeping the textures in the maxfilepath folder so Max can see them.
but I have noticed that when you do this, the mxs reference (obj.material.diffuseMap.filename) still points to the old location.
Thus is important because I need a reliable reference to the source textures file locations.

Just a thought, but did you consider a callback on scene open that checks if the relative path of referenced textures are correct? Also if you’re remapping p4 workspaces, you can check if the texture file reference exists, if not point to the proper path. This however assumes you have a consistent folder structure for your project.

You should add the relative texture path ( relative to the max file ) to your user paths-> External Files pathlist
I see your structure references the texture in a “.textures/” sub-path (relative to the max file location ,
so adding a “. extures” entry in “Configure User Paths->External Files” tab should solve your problem

thanks for your replies
@ spacefrog :
I tried adding the relative path to the External files in Configure User Paths GUI but it didn’t work.
So I started looking for a mistaken assumption in my situation.

I checked the user paths with some code:


for i = 1 to mapPaths.count() do
(
	m=mapPaths.get i
	format "%
" m
)

it returned a path relative to my C:\ drive rather than relative to the maxfilepath.

I think the project settings for our assets were never changed from Max’s Default settings.
So the “relative to what?” target is incorrect.

If run this mxs:



pathConfig.setCurrentProjectFolder maxfilepath
mapPaths.add "../texture"

I can see the correct paths in the mapPath struct, but still they do not load and max continues to see them as missing.

I;m moving on to the AtsOps interface in mxs to see what it can yield .

The wayward paths that spawned the “missing file” alerts seemed to be stored (or at least accessed) in the Asset Tracker (in mxs AtsOps Interface) so utilizing its functions provided a workable solution.



/*
	newDir replaces the "mesh" maxfilepath with "texture"
	so "C:/project/art/characters/myCharacter/mesh/ " becomes "C:/project/art/characters/myCharacter/textures/"
        if not found in /textures/ then the global /art/ directory is searched.
        this covers  expected locations of the textures in anyone's local workspace
*/

fn repathBitmaps newDir:(substitutestring maxfilepath "mesh" "texture") =
(
	local arr=#()
	ATSOps.GetFilesByFileSystemStatus #Missing &arr--gather missing filpaths into the array arr
	if arr.count>0 then(
		format " repathMissingBitmaps to: %
" newDir
		--use maxfilepath to find the local path to /art/ 
		slices=filterstring maxfilepath "\\"
		str = ""
		artPath=""
		index=finditem slices "art"
		for i = 1 to index do
		(
			artPath+=slices[i]
			artPath+="\\"
		)
		for asset in arr do
		(
			format "		missing:%
" asset
			AtsOps.clearSelection()
			AtsOps.selectFiles #(asset)
			local filenameOfAsset = (DotNetClass "System.IO.Path").GetFileName asset
			local di = DotNetObject "System.IO.DirectoryInfo" newDir --look in ../textures/
			local files = di.GetFiles filenameOfAsset (DotNetClass "System.IO.SearchOption").AllDirectories
			if files[1] !=undefined then (
				--if found , replace the path in the Asset Tracker
				local newFileName = files[1].directoryName
				AtsOps.SetPathOnSelection newFileName
				format "		FOUND:%
" newFileName
			)else(
				--not in ../textures/ so look globally in .../art/
				di = DotNetObject "System.IO.DirectoryInfo" artPath
				artfiles = di.GetFiles filenameOfAsset (DotNetClass "System.IO.SearchOption").AllDirectories
				for each in artfiles do
				if artfiles[1] !=undefined then (
					newFileName = artfiles[1].directoryName
					AtsOps.SetPathOnSelection newFileName
					format "		FOUND:%
" newFileName 	
				)else(
					format "		NOT FOUND:%
"asset	
				)
			)
			ATSOps.refresh()
		)
	)	
)