Notifications
Clear all

[Closed] Mini Challenge: incremental unique Filenames

Approach:
You have a plugin that will generate a set of new nodes in the scene. In each complete calculation, you want to save these new nodes as an XRefScene before the next calculation can be done.
Your plugin must ask the user for a “base filename” for the XRefScenes (a new one or an existing one) and save each set of nodes as an XRefScene with this base filename with an incremental number of 3 digits.

Challenge:
Create a function to ask the user for a base filename and return the filename for the next XRefScene.

Examples:
You have in the folder “C:\TestNames” these filenames:
#(RefxTest-001.max, RefxTest-002.max, RefxTest-003_Test.max, Test-01.max, Test-02.max, kk.max, kk4.max, kk_XRef-01.max)

If the user chose:

  • RefxTest-001.max => the function should return: C:\TestNames\RefxTest-003.max
  • RefxTest-003_Test.max => C:\TestNames\RefxTest-003_Test001.max
  • kk.max => C:\TestNames\kk005.max
  • Test-01.max => C:\TestNames\Test-003.max
  • ppp.max (new filename) => C:\TestNames\ppp001.max

Here is my code. It works fine but I don’t like it at all. Any improvements will be welcomed.

 
fn getXRefSceneFilename =
(
	baseXRef = getMAXSaveFileName filename:"*.max" -- User File select. Weird as the function ask "do you want to replace it?"
	exportFilename = undefined
	
	if baseXRef != undefined do
	(
		baseName = getFilenameFile baseXRef
		basePathName = getFilenamePath baseXRef
		trimBaseName = trimRight baseName "1234567890"
		
		sameBaseName = getFiles (basePathName+trimBaseName+"*.max") -- Get files with same base name
		newIndex = 0
		
		if sameBaseName.count != 0 then -- Case the user selects an existing file
		(
			itemsToDelete = #()
			for i = 1 to sameBaseName.count do
			(
				tempTrimBaseName = trimRight (getFilenameFile sameBaseName[i]) "1234567890"
				if tempTrimBaseName.count != trimBaseName.count do append itemsToDelete i	-- Avoid filenames starting equal but that are different
			)
			
			for i = itemsToDelete.count to 1 by -1 do  -- Delete filenames starting  equal but that are different
			(
				deleteItem sameBaseName itemsToDelete[i]
			)
			
			fileIndex = for f in sameBaseName collect
			(
				tempBaseName = getFilenameFile f
				tempTrimBaseName = trimRight tempBaseName "1234567890"
				index = (substring tempBaseName (tempTrimBaseName.count+1)  -1) as integer
			)
			
			oldIndex = amax fileIndex
			newIndex = oldIndex +1
		)
		else -- Case the user writes a new filename
		(
			newIndex = 1
		)
		
		-- Convert maximum index to formatted string of 3 digits
		stringIndex = newIndex as String
		if stringIndex.count == 1 then stringIndex = "00"+stringIndex else if stringIndex.count == 2 then stringIndex = "0"+stringIndex
		
		exportFilename = basePathName + trimBaseName + stringIndex + ".max"
	)
	
	exportFilename

)


Perhaps another approach should be nicer, as giving allways the same base filename for XRefScenes generated by this plugin?!!

6 Replies

Based on the same code, something a bit smarter:


fn getXRefSceneFilename =
(
	baseXRef = getMAXSaveFileName filename:"*.max" -- User File select. Weird as the function ask "do you want to replace it?"
	exportFilename = undefined
	
	if baseXRef != undefined do
	(
		baseName = getFilenameFile baseXRef
		basePathName = getFilenamePath baseXRef
		trimBaseName = trimRight baseName "1234567890"
		
		sameBaseName = getFiles (basePathName+trimBaseName+"*.max") -- Get files with same base name
		newIndex = 0
		
		if sameBaseName.count != 0 then -- Case the user selects an existing file
		(
			fileIndex = for f in sameBaseName where	-- Avoid filenames starting equal but that are different
			(
				tempBaseName = getFilenameFile f
				tempTrimBaseName = trimRight tempBaseName "1234567890"
				check = tempTrimBaseName.count == trimBaseName.count 
			)
			collect
			(
				index = (substring tempBaseName (tempTrimBaseName.count+1)  -1) as integer
			)
			
			newIndex = (amax fileIndex) +1
		)
		else -- Case the user writes a new filename
		(
			newIndex = 1
		)
		
		-- Convert maximum index to formatted string of 3 digits
		stringIndex = newIndex as String
		if stringIndex.count == 1 then stringIndex = "00"+stringIndex else if stringIndex.count == 2 then stringIndex = "0"+stringIndex
		
		exportFilename = basePathName + trimBaseName + stringIndex + ".max"
	)
	
	exportFilename
)


You could use getMAXOpenFileName() instead to avoid the confirmation dialog.

You can simplify it by using formattedPrint()

stringIndex = formattedPrint newIndex format:"03d"
2 Replies
(@aaandres)
Joined: 11 months ago

Posts: 0

Thanks both, Denis and Jorge

I’ve tried this, but it doesn’t give the hability to write a new name. You have to chose an existing file. It’s a pity.

That’s cool. Thanks!

Thanks again. I will study your code.
At a first look, I miss the filename selection. I don’t like the advise “are you sure you want to overwrite…?”
Perhaps the only solution is to set allways the same basename in the max working folder… but I don’t like this either.

P.S.: you like structures, Denis!!

(@denist)
Joined: 11 months ago

Posts: 0

structures are like classes. i like classes

i have’t really been care about optimization and ‘purity’ of the mxs using writing this code.
so here is a .net-based mxs structure which can be easily extended to fit your needs:

global XRefFileSupport
(	
	struct XRefFileStruct
	(
	private
		dotnetstring = dotnetclass "System.String",
		dotnetpath = dotnetclass "System.IO.Path",
		dotnetdir = dotnetclass "System.IO.Directory",
		dotnetfile = dotnetclass "System.IO.File",
		dotregex = dotnetobject "System.Text.RegularExpressions.Regex" @"(\d+)$",
	public
		FileStruct =
		(
			struct FileStruct (file, path, name, base, id, type)
		),
		fn sortByID s1 s2 = (s1.id - s2.id),
		fn parseFilename filename = 
		(
			path = dotnetpath.GetDirectoryName filename
			name = dotnetpath.GetFileNameWithoutExtension filename
			type = dotnetpath.GetExtension filename
			
			ss = dotregex.Split name
			base = ss[1]
			id = if ss.count > 2 then ss[2] as integer else 0

			FileStruct file:filename path:path name:name base:base id:id type:type
		),
		fn buildFilename file = 
		(
			f = dotnetstring.Format "{0}\\{1}{2:d3}" file.path file.base file.id
			dotnetstring.Format "{0}{1}" f file.type -- in case you want to do anything special with extension
		),
		fn makeMask file = 
		(
			dotnetstring.Format "{0}*{1}" file.base file.type
		),
		fn findFilesAs file sorted:on =
		(
			mask = makeMask file
			files = dotnetdir.GetFiles file.path mask
			files = for f in files collect (parseFilename f)
			if sorted do qsort files sortByID
			files		
		),
		fn getAvailableIndex data = 
		(
			ids = (for f in data collect (f.id + 1)) as bitarray
			id = 1
			while ids[id] and id <= ids.count do id += 1
			id-1
		),
		fn getNextFile file increment:on = 
		(
			filesas = findFilesAs file
			_file = copy file
			if increment then
			(
				_file.id = filesas[filesas.count].id + 1
			)
			else
			(
				_file.id = getAvailableIndex filesas
			)
			buildFilename _file
		),
		fn getAllFiles path =
		(
			files = dotnetdir.GetFiles path
			for f in files collect (parseFilename f)
		)
	)
	XRefFileSupport = XRefFileStruct()
	ok
) 

using could be:

all = XRefFileSupport.getallfiles @"C:\Temp\"
ff = XRefFileSupport.findfilesas all[2]
XRefFileSupport.getnextfile ff[1] --increment:off 

PS. you are free to extend my code as well as fix bugs

  1. Buy the way, I’ve met such situations when a missing file with LOWER CASE extension (jpg, ies…) could be found in a folder, but but with UPPER CASE extension, and even after repathing I had missing for such files

  2. getallfiles – doesn’t give the posibility to use ProgressBar for searching.