ScriptSpot

Forum topic · General Scripting

Get Used Material ID List from EditablePoly without face check

By Matyrix · 2018-08-30

Downloads
Description

Hello,

Everybody on other forums use the check every face to read the Used Material IDs from an object, but there is a basic ID list in Editable Poly. I attached an image about it.

The only problem is that there is no chance to read this list from the Editable Poly.

Do somebody knows how can i read this list by script?

Comments (13)

.

jahman · 2018-08-31

You can also make an unsafe version of it and it will work at least two times faster.



if classof ::GetUsedMtlIDs != dotNetMethod do (

	local source = "using Autodesk.Max;
	using System;
	using System.Collections;
	using System.Collections.Generic;

	class MeshMaterialIDs
	{
	static private IGlobal global = GlobalInterface.Instance;

	static private IMesh GetMeshFromNode( IINode node )
	{            
		IObject obj = node.EvalWorldState( 0, true ).Obj;
		IClass_ID triClass = global.Class_ID.Create( 9, 0 );
		IClass_ID polyClass = global.Class_ID.Create( 1562457754, 0 );
		IClass_ID patchClass = global.Class_ID.Create( 4144, 0 );
						   
		if ( obj.IsSubClassOf( triClass ) )
		{
			return ((ITriObject)obj).Mesh;
		}
		else if ( obj.IsSubClassOf( polyClass ) )
		{                
			ITriObject mesh = (ITriObject)((IPolyObject)obj).ConvertToType( 0, triClass );
			return mesh.Mesh;
		}
		else if ( obj.IsSubClassOf( patchClass ) )
		{
			return ((IPatchObject)obj).GetMesh( 0 );
		}
		else if ( obj.CanConvertToType( triClass ) == 1 )
		{
			ITriObject tri = (ITriObject)obj.ConvertToType( 0, triClass );
			return tri.Mesh_;
		}
		else
		{
			return null;
		}
	}

	static public int[] GetUsedMtlIDs( uint handle )
	{
		IINode node = global.COREInterface.GetINodeByHandle( handle );
		IMesh mesh = GetMeshFromNode( node );

		if ( mesh == null ) return null;

		BitArray used_ids = new BitArray( ushort.MaxValue );
		List<int> material_ids = new List<int>( ushort.MaxValue );

		foreach ( IFace face in mesh.Faces )
		{
			if ( used_ids.Get( face.MatID ) == false )
			{
				material_ids.Add( face.MatID + 1 );
				used_ids.Set( face.MatID, true );
			}

		}

		return material_ids.ToArray();

	}

	}"
	
	csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
	compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
	compilerParams.ReferencedAssemblies.Add("System.dll");		
	compilerParams.ReferencedAssemblies.Add((getdir #maxroot) + "Autodesk.Max.dll");			
	compilerParams.GenerateInMemory = on
	compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
	
	
	if (compilerResults.Errors.Count > 0 ) then
	(
		local errs = stringstream ""
		for i = 0 to (compilerResults.Errors.Count-1) do
		(
			local err = compilerResults.Errors.Item[i]
			format "Error:% Line:% Column:% %\n" err.ErrorNumber err.Line err.Column err.ErrorText to:errs
		)
		format "%\n" errs
		undefined
	)

	local assembly = compilerResults.CompiledAssembly.CreateInstance "MeshMaterialIDs"	

	global GetUsedMtlIDs = assembly.GetUsedMtlIDs
		
)

-- use it like this
GetUsedMtlIDs $.inode.handle

Matyrix · 2018-08-31

jahman, you rock! i tried it out on a 4.000.000 tri object and take 1 second! fantastic, thanks a lot!
furthermore it works on edit poly and edit mesh as well

.

jahman · 2018-08-31

Glad to hear that. It should work ok with anything convertible to mesh.
upd
Here're my numbers :)
Numfaces 4224000
Time: 0.293sec. Mem: 544L
Unsafe Time: 0.058sec. Mem: 544L

Swordslayer · 2018-08-30

That only shows when MultiSubObject material is assigned (and only would display all the ids if the multimaterial had the right number of matching id materials).

Matyrix · 2018-08-30

Yes, you are right. When you have more very high detailed objects, it's the fastest way to drop a multi material to the objects with 100+ sub material, and read the ID-s, and restore the materials - instead of check every face with ID information.

Swordslayer · 2018-08-31

For personal use where you're pretty sure you don't exceed that, I agree; for a production-ready tool, you'd need 65536 submaterials (some pipelines use fixed id per material). But unless you're working with extra dense meshes, getting the ids used by ~10k mesh shouldn't take more than tenth of a second anyway.

.

jahman · 2018-08-31

There's also <IGameMesh>.GetActiveMatIDs method available, but unfortunately I couldn't find a way to use it from maxscript.

Matyrix · 2018-08-31

Wow, i think this is the right way, thanks!
It should be work somehow... I will read about it, hope can solve.

Swordslayer · 2018-08-31

(
	local compilerParams = dotNetObject "System.CodeDom.Compiler.CompilerParameters" #(
		"System.dll", "System.Core.dll", getDir #maxRoot + "Autodesk.Max.dll",
		getDir #maxRoot + "\bin\assemblies\Autodesk.Max.Wrappers.dll")
	compilerParams.GenerateInMemory = on

	local compilerResults = (dotNetObject "Microsoft.CSharp.CSharpCodeProvider").CompileAssemblyFromSource compilerParams #(
		"using System;
		using System.Linq;
		using System.Collections.Generic;
		using Autodesk.Max;
		using Autodesk.Max.Wrappers;

		internal static class TabExtensions {
			public static IEnumerable<T> ToEnumerable<T>(this ITab<T> tab) {
				for (int i = 0; i < tab.Count; i++)
					yield return tab[i];
			}
		}

		class Poly {
			internal static readonly IGlobal Global = GlobalInterface.Instance;

			public static int[] GetSelectedObjMatIDs() {
				var pIgame = Global.IGameInterface;
				pIgame.InitialiseIGame(true);
				pIgame.SetStaticFrame(0);

				using (var currNode = Global.COREInterface.GetSelNode(0))
				using (var gameNode = pIgame.GetIGameNode(currNode))
				using (var gameMesh = Global.IGameMesh.Marshal((gameNode.IGameObject as Autodesk.Max.Wrappers.IGameObject).INativeObject__NativePointer)) {
					return gameMesh.InitializeData ? gameMesh.ActiveMatIDs.ToEnumerable().Select(id => id + 1).ToArray() : new int[0];
				}
			}
		}"
	)
	::poly = compilerResults.CompiledAssembly.CreateInstance "Poly"
)

if selection.count == 1 and isKindOf $ GeometryClass do Poly.GetSelectedObjMatIDs()

jahman · 2018-08-31

.

.

jahman · 2018-08-31

Thanks a million Vojtěch.
I couldn't run it in 2014 (Global.IGameMesh is absent), but in 2016 works just fine. But it is surprisingly slow compared to what I've posted above. 4kk mesh processed in about 9-10sec

Swordslayer · 2018-08-31

Yeah, IGameMesh is more about convenience than speed, though I wouldn't have expected it to be that slow either. That sounds like even snapshotAsMesh+getFaceMatID in maxscript would be faster.

Matyrix · 2018-08-31

This tool works for specific workflow, you are right it's not a production-ready tool for everybody. Just for personal workflow.

One object is 5-10.000.000 polygon unfortunately.