MAXScript: How to find duplicate texturemap names via maxscript?


#1

I have a large scene that uses a huge number of texturemap (bitmap, mix, composite etc. etc.), but many different texturemap have the same name, although they are different texturemap (not instances). How can I find texturemap with duplicate names and assign a new name to such a texturemap?
I tried different methods using uniqueName, but this did not give the desired result - some texturemap are renamed again and still have the same names, although they are different texturemap. Is there some quick and reliable way to do this?

Here is one of the methods I tried, but it didn’t give the desired result:
here mapsArray - all texturemaps array from scene


fn renMapsName mapsArray = (
		uniques = #()
		dups = #()
		for i in mapsArray where not (appendifunique uniques i.name) do (
			appendIfUnique dups i
		)
		
		for n in dups do (
			n.name=uniqueName n.name
		)
	)

renMapsName mapsArray

#2

I don’t have a good scene to test it so can’t really tell how bad it performs :slight_smile: cause it renames all the maps that share same base name whenever two of them have duplicate names.


keys       = #()
textures   = #()
duplicates = #{}

base_keys     = #()
base_textures = #()

for tex_class in textureMap.classes do
(
	for tex in getClassInstances tex_class do
	(	
		key   = toLower tex.Name		
		index = findItem keys key
		
		if index > 0 then 
		(
			append textures[ index ] tex
			duplicates[ index ] = true
		)
		else
		(
			append keys key
			textures[ keys.Count ] = #( tex )	
		)

		base_key   = trimRight key "0123456789"
		base_index = findItem base_keys base_key
		
		if base_index > 0 then 
		(
			append base_textures[ base_index ] tex			
		)
		else
		(
			append base_keys base_key
			base_textures[ base_keys.Count ] = #( tex )	
		)
	)
)

for index in duplicates do
(

	base_name = toLower (trimRight textures[ index ][1].Name "0123456789")
	
	j = findItem base_keys base_name
		
	i = 0
	for tex in base_textures[ j ] do
	(		
		tex.Name = base_name + (formattedPrint (i += 1) format:"03d")		
	)
)

#3

Thanks Serejah, but your method is renaming ALL texturemaps from scene, but it is necessary that only those that have the same names are renamed.
For example, there is a Fallow texturemap named “Map # 001” that contains two texturemap with the same names “Map # 001”. After processing, these two maps should be assigned new names (ie “Map # 002” and “Map # 003”). Other texturemaps that already have unique names do not need to be renamed.
Thanks again!


#4

No, it doesn’t rename all texturemaps.
What it does, it finds all texturemaps than have same name prefix and rename these maps. It discards numbers suffix and rename maps staring from 001 to the end.

Example:

mAp #001    -- name prefix 'map #'
MAP #535    -- name prefix 'map #'
map #855393 -- name prefix 'map #'
mAp #001    -- duplicate name, name prefix 'map #'. All maps starting with 'map #' will be renamed
Smoke #42   -- name prefix 'Smoke #', not going to be renamed

and the result:

map #001
map #002
map #003
map #004
Smoke #42

Maybe it is better to collect all the scene maps names and then find a new name for each duplicate that doesn’t collide with the rest of the existing names. Instead of renaming all the maps sharing the same prefix.


#5

No, no. There is no need to search for texturemaps only by prefix, since many maps will have the same prefix, but the suffix will be different, which means they are already different names. It is necessary to rename only those texmaps that have completely identical names along with the prefix and suffix.

_ren_before

_ren_after


#6

Actually Max has a method to rename maps, but for some reason it always increments map suffixes :frowning:

details

just a quick demo:

g = (dotNetClass "Autodesk.Max.GlobalInterface").Instance
rename = g.coreinterface14.AssignNewName

textures = #()
for tex_class in textureMap.classes do
(
	join textures (getClassInstances tex_class)
)

for tex in textures do
(
	anim = g.animatable.getanimbyhandle (dotNetObject "system.uintptr" (getHandleByAnim tex))	
	rename anim
)

#7

This is probably correct in order to create a unique name. It seems to me that assignNewName not only changes the name, but also copies the map, which in this case does not need to be done.

anname

So I think it’s better to use uniqueName for renaming.
No problem to rename. The problem is to find and rename only those texmaps that have identical names but are not instances.


#8

you can collect all texmaps with the same name and then make unique array to discard instances. Or better use appendIfUnique when filling textures array


keys       = #()
textures   = #()
duplicates = #{}

for tex_class in textureMap.classes do
(
	for tex in getClassInstances tex_class do
	(	
		key   = toLower tex.Name		
		index = findItem keys key
		
		if index > 0 then 
		(
			append textures[ index ] tex
			duplicates[ index ] = true
		)
		else
		(
			append keys key
			textures[ keys.Count ] = #( tex )	
		)
	)
)


for index in duplicates do
(
	unique_duplicates = makeUniqueArray textures[ index ]
	
	if unique_duplicates.count > 1 do
	(
		map_name = unique_duplicates[1].Name
		base_name = trimRight map_name "0123456789"
		num_digits = map_name.Count - base_name.Count
		num = (substring map_name (1 + base_name.Count) num_digits) as integer
		
		format_string = "0" + num_digits as string + "d"
		i = num
		for j = 2 to unique_duplicates.Count do
		(		
                        -- replace below code with something like: new_name = MakeUniqueTexmapName base_name keys
                        new_name = base_name + (formattedPrint (i += 1) format:format_string)
                        -- to do: check that this new name isn't used by any texmap or mtl and make a new one if it does

			unique_duplicates[j].Name = new_name
                       -- to do: append new name to the array of used names i.e. append keys (tolower new_name) 
		)
	)
)

#9

Thank you so much Serejah! I will try this. It looks like your code should work as expected.:ok_hand:


#10

Hi Serejah!

I tested your code on several large scenes with maps with the dublicate names and found that some maps were given new names that other maps already have.
How can I prevent the duplication of new names assigned to maps?

Thank!


#11

I’ve added a few todo’s to the previously posted code.
Add a name maker function that will use keys array to check whether new name is already used by another texmap.
Use this function to make a new name for each duplicate and don’t forget to append this lowercased new name to the keys array


#12

Yes, I first wrote a question, and then I saw that comments were added to the source code. :slightly_smiling_face:
Thanks for the hint, I solved this problem!


#13

– duplicate deleted


#14

Hello Serejan!

I am still trying to optimize the code for renaming maps that have the same name.:roll_eyes:
On small scenes, everything works fine, but if you come across a large scene in which there are a huge number of maps with the same names (sometimes there are up to 20), the code takes a very long time (sometimes more than a minute) and at the end I get an error

-- Call stack overflow

I think this is because of the extra check for the duplicate name in the keys array.
Is it possible to somehow optimize the code so that it runs faster and does not give such an error?

Thanks again!

PS: For testing, I took one of the evermotion scenes, which has a lot of maps with duplicate names. You can also take this file for testing here: https://we.tl/t-wMjeo8BAJa (without textures, since the file will have a very large size with them)


#15

You’re lucky to wait just a minute :slight_smile:
Simple code like this takes 600 sec on my laptop in your scene (yeah it is that slow)

for tex_class in textureMap.classes do
(
	for tex in getClassInstances tex_class do ( /* do nothing */ )	
)

Every time you call getClassInstances under the hood it iterates over whole scene hierarchy to test and collect instances of a particular class. That’s why it is so slow (If I’m not mistaken).

sources


.

If you want to make it perform as fast as possible switch to c# or c++ and take a look at getClassInstances_cf source code. It is available in the SDK source files.

I doubt that there’s any better solution available in maxscript


#16

Where same map name make problem?


#17

I am trying to understand the logic to give some meaning to what we are doing …

What’s the point of keeping abstract names like: Map # 123, Map #1123125236, Map # 56, …? Even if they are unique?

I analyzed the names of the textures in your scene, so 99.9% of the names don’t make any sense. Wouldn’t it be easier to rename them all at once? At least we’ll track their count by increasing ID…


#18

Maybe you can use EnumerateAllAnimatables somehow, and filter only TextureMaps after that


#19

There is another idea … :slight_smile:

(
	dummy_anim = bezier_float()
	max_handle = gethandlebyanim dummy_anim
	texture_maps = for h = 1 to max_handle where iskindof (t = getanimbyhandle h) TextureMap collect t
)

#20

… and all together is:

(
	dummy_anim = bezier_float()
	max_handle = gethandlebyanim dummy_anim
	texs = for h = 1 to max_handle where iskindof (t = getanimbyhandle h) TextureMap collect t

	names = #()
	items = #()
	for t in texs do 
	(
		k = finditem names t.name 
		if k == 0 do
		(
			append names t.name
			append items #()
			k = names.count
		)
		append items[k] t  
	)
	for item in items do for k=2 to item.count do assignnewname item[k]
)