Home Technical Talk

Maxscript: Arrange a list in descending order of material ID's assigned to an object

polycounter lvl 4
Offline / Send Message
Benjam polycounter lvl 4
Hey Polycounters,

I have a question/in need of some help. I have been wracking my brain trying to figure this one out for a while now.

So I have this tool that lists the stats of objects in the scene. The last column of my tool lists the number of materials you have assigned to the faces of each object. This works fine, however......the way in which I have done it does not allow me to sort them properly.

So how have I done it? Well, first I gather the objects in the scene into an array called mySelectedObjects, then I loop through each object selecting faces based on material ID's, if the number of faces selected is more than 0 then the material count is increased, if it is 0 then it is not. I do this for however many sub materials are in the multi-material (if any) then I iterate through the two arrays and place them in the listview.

The problem comes when I try to sort by material ID. Sorting by verts, faces etc. is easily done because I just pinch the stats using object.verts.count and then rearrange it that way. But because the material count array is just a set of numbers, I am running into problems. I can qsort the numbers array but I need to be able to sort the objects array with it.

The way I am doing it at the moment is SO SO SO SLOW and isn't really working for me....I am sorting the material array, then looping through it and then looping throuigh the objects array, counting the materials assigned and if it matches then put it in a temporary array in order and then use that to popuylate the list.

But it is getting very confusing and very slow. There must be an easier way!! Anyway here is the function for this:

Any help/tips would be greatly appreciated, this is the last thing I have to do before the tool is finished and I can release it!

Replies

  • Benjam
    Options
    Offline / Send Message
    Benjam polycounter lvl 4
    fn sortByMaterial =
    (
    --Sorts through the (now sorted) master material array and matches objects from the MSO array to the value
    	-- If a match is found then the object is placed into a new temporary array
    	-- The object is removed from the MSO array so that duplicates are not placed into the new temp array
    	-- The MSO array is then remade using the order from the tempArray
    	
    	local arrayTrack = 1 -- variable to hold the index number of the MSO array object
    	matId = 1
    	matNum = 0
    	
    	count = masterMatArray.count
    	
    	for i = 1 to count do
    	(
    		index = masterMatArray[i]
    		
    		for j in mySelectedObjects do
    		(
    			mat = j.material
    			
    			if(classof j !=  Editable_Poly and classof j != Editable_Mesh and classof  j != Editable_Patch) then
    			(
    				if(mat != undefined) then
    				(
    					matNum = 1
    				)
    				else 
    				(
    					matNum = 0
    				)
    				
    				arrayTrack += 1
    			)
    			else
    			(						
    				if(classof mat != MultiMaterial) then
    				(
    					if(mat != undefined) then
    					(
    						matNum = 1
    					)
    					else
    					(
    						matNum = 0
    					)
    				)
    				else
    				(
    					numberOfSubs = mat.numsubs
    					
    					if(classOf j == Editable_Mesh or classOf j == Editable_Patch) then
    					(
    -- 						print("Converted " + i as string)
    						copyOfObj = copy j
    						convertToPoly copyOfObj
    						j = copyOfObj
    						isCopied = true
    					)
    					
    					for k = 1 to numberOfSubs do
    					(
    						j.selectbymaterial matID
    							
    						faceArr = getFaceSelection j
    						--print("face array count is: " + faceArr.numberset as string)
    						
    						if(faceArr.numberset != 0) then
    						(
    							matNum += 1 
    						)
    						matID += 1
    					)
    				)
    				deselect j.faces
    				arrayTrack += 1
    			)
    			
    			if(matNum == index) then
    			(
    				append tempArray j
    			)
    			matNum = 0
    			matID = 1
    			
    			if(isCopied == true) then
    			(
    				delete j
    				isCopied = false
    			)
    		)
    	)
    	
    	mySelectedObjects = #()
    	for l in tempArray do
    	(
    		append mySelectedObjects l
    	)
    	tempArray = #()
    )
    
  • passerby
    Options
    Offline / Send Message
    passerby polycounter lvl 12
    why are you using 2 differnt arrays? why not a 2 dimensial array?
  • Benjam
    Options
    Offline / Send Message
    Benjam polycounter lvl 4
    I was wondering that myself but I fixed it in the end without having to use a 2d array. I set a user property to the object and assigned the materials count to that and read from that to get all my other settings. It's a lot faster and works well :)
  • passerby
    Options
    Offline / Send Message
    passerby polycounter lvl 12
    a list of code objects or a 2d array still is a little more ideal. so your not writing perstant data to the scene file.
  • Benjam
    Options
    Offline / Send Message
    Benjam polycounter lvl 4
    what do you mean writing persistent data to the scene file? If I can optimise it further then damn straight I will try.

    As it stands I gather a list of objects and apply the number of materials present on the object to a user defined property. Then when you go through and sort the objects in the list you are simply sorting the list data rather than the scene data. The only time the script gathers scene data is when the user clicks refresh or when the user clicks a deleted item in which case it automatically refreshes
  • Swordslayer
    Options
    Offline / Send Message
    Swordslayer interpolator
    Creating and assigning new user properties is such a persistent change and there's no need for it. I've already suggested (at tech-artists) you use a custom listview comparer as the info is already contained in the list itself, check the code again for other suggestions (functions getIDCount and getItems; the rest is just UI stuff).

    edit: For the sake of completeness and to avoid cross-referencing to another site:
    try destroyDialog sortByIDCount catch()
    rollout sortByIDCount "ID Count Test" width:225
    (
        dotNetControl dncObjList "System.Windows.Forms.ListView" width:200 height:200
        button btnFill "Fill It" width:200 height:25
    
        local dnListItem = dotNetClass "System.Windows.Forms.ListViewItem"
        local lastColumn, reversed = false
    
        fn compileListItemSorter =
        (
            source =  "using System;\n"
            source += "using System.Windows.Forms;\n"
            source += "using System.Collections;\n"
            source += "class ListViewItemComparer : IComparer\n"
            source += "{\n"
            source += "    private int c;\n"
            source += "    private bool num = false;\n"
            source += "    private int dir = 1;\n"
            source += "    public ListViewItemComparer() { c = 0; }\n"
            source += "    public ListViewItemComparer(int column, bool numeric, bool reverse)\n"
            source += "    { c = column; num = numeric; dir = reverse ? -1 : 1; }\n"
            source += "    public int Compare(object x, object y)\n"
            source += "    {\n"
            source += "        if (num) return Convert.ToInt32(((ListViewItem)x).SubItems[c].Text).CompareTo(\n"
            source += "            Convert.ToInt32(((ListViewItem)y).SubItems[c].Text)) * dir;\n"
            source += "        else return String.Compare(((ListViewItem)x).SubItems[c].Text,\n"
            source += "            ((ListViewItem)y).SubItems[c].Text) * dir;\n"
            source += "    }\n"
            source += "}"
    
            local csharpProvider = dotNetObject "Microsoft.CSharp.CSharpCodeProvider"
            local compilerParams = dotNetObject "System.CodeDom.Compiler.CompilerParameters"
            compilerParams.GenerateInMemory = true
            compilerParams.ReferencedAssemblies.Add("System.Windows.Forms.dll")
            compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
            compilerResults.CompiledAssembly
        )
    
        fn getIDCount obj ids:#{} =
        (
            local mesh = obj.mesh
            local faces = mesh.faces as bitarray
    
            for face in faces do
                append ids (getFaceMatID mesh face)
    
            delete mesh
            ids.numberSet
        )
    
        fn getItem obj channelCount =
        (
            local item = dotNetObject dnListItem obj
            item.SubItems.Add (channelCount as string)
            item
        )
    
        fn getItems objs =
            for obj in objs collect
                case classOf obj.material of
                (
                    MultiMaterial : getItem obj.name (getIDCount obj)
                    UndefinedClass : getItem obj.name 0
                    default : getItem obj.name 1
                )
    
        on sortByIDCount open do
        (
            dncObjList.View = dncObjList.View.Details
            dncObjList.BorderStyle = dncObjList.BorderStyle.FixedSingle
            dncObjList.FullRowSelect = true
            dncObjList.GridLines = true
    
            dncObjList.Columns.Add "Object Name" 100
            dncObjList.Columns.Add "ID Channels" 100
    
            compileListItemSorter()
        )
    
        on dncObjList columnClick columnHeader do
        (
            local column = columnHeader.Column
            if column == lastColumn then reversed = NOT reversed else reversed = false
    
            dncObjList.ListViewItemSorter = dotNetObject "ListViewItemComparer" \
                column (column == 1) reversed
            dncObjList.ListViewItemSorter = undefined
    
            lastColumn = column
        )
    
        on btnFill pressed do
        (
            dncObjList.Items.Clear()
            dncObjList.Items.AddRange (getItems selection)
        )
    )
    createDialog sortByIDCount
    

    I've added the reversed check to the comparer (of course, it's not to be taken to the letter - for example, it doesn't make much sense to compile the assembly on each run and so on and so on).
  • passerby
    Options
    Offline / Send Message
    passerby polycounter lvl 12
    Ya by persistent data I meant the data your putting in a user property. No need for it.
Sign In or Register to comment.