Notifications
Clear all

[Closed] need help with custom attributes

What I’m trying to do is set up some controls for a pair of materials. I’ve got a ui set up with spinners, etc that will adjust properties of both materials at the same time. That all works fine after running an initial script that creates the materials sets up all the connections. However, I want to be able to close down Max, restart it, open my scene file with those materials in it, and still have the CA be able to tweak the settings.

My first problem is that upon restarting max, I get a missing DLL error. I’m pretty certain this has something to do with where I saved the CA script file. I’ve tried it in maxRoot/scripts/myfolder, scripts/startup, stdplugins/stdscripts, and none of those seem to make any difference.

My second problem is that in the script I use variables to identify the materials I want to tweak. When I restart Max, those variable are undefined, and I’m not sure how to define those variables automatically, or how to “hard code” them into the scene (if that’s even possible). I will eventually be distributing this script publicly so I don’t want to rely on the user in any way to define variables. One option I was hoping to figure out is similar to using the $ for scene objects, but do materials have something similar? For instance, if you’re in the Material editor looking at the custom attribute rollout of a material, is there any way to script something like “foo = thisMaterial.diffusemap” where “thisMaterial” would be the same as using $ for scene objects?

Well… that’s enough for the moment. Any help would be seriously appreciated.
Thanks!!!

8 Replies

Another thought…

Can a custom attribute, upon being loaded in the scene query a material and read properties of the material and assign them to variables?

Or is there a whole other way to do this that I’m overlooking? As I only have about 4-5 months of scripting experience, this is entirely possible.

Ok, I figured out that using persistent globals is exactly what I needed to help me with my second problem. I’m still having trouble though getting my Custom Attribute script to load successfully though.

So even though my first script is setting the persistent globals in my scene file, when I close and restart Max, Max starts and evaluates the CA script which makes references to scene materials (by variable name) and it’s giving me an “unable to convert undefined to MaxObject” error. I’m assuming the reason I get the missing DLL error, is because when Max starts and finds an error, it doesn’t load the script, and thus my max scene file can’t find it.

I would assume most CA scripts refer to specific scene elements which are undefined at the time Max starts, no? So how do you handle this? Is there a way to have the CA script be evaluated when you load your scene instead of when Max starts? Or do I somehow need to define these scene elements (materials in my case) and then hopefully the persistent variables from my scene file overwrite those values??

Thanks!

Unfortunately the mxs help doesn’t provide clear information about custom scripted attributes. But scripted attributes are very similar to scripted plug-ins. You can handle them the same way.

Here is a simple snippet which shows how to do it:
(see Scripted Custom Attributes ans Global and Private Custom Attributes Definitions)


 global test_attrib = attributes "Test Data" attribID:#(0x1cabed9b, 0x5555de47)
 (
 	local loaded_time = "never been loaded"
 	parameters main rollout:params
 	(
 		unique_id type:#inttab tabSize:2 tabSizeVariable:off animatable:off 
 		extra_mat type:#material ui:ui_extra_mat
 		on extra_mat set val do 
 		(
 			this.params.ui_extra_mat.tooltip = if iskindof val Material then (classof val) as string else ""
 		)
 	)
 	rollout params "Parameters" 
 	(
 		materialbutton ui_extra_mat "" width:140
 	)
 	on create do format "create: %
" this
 	on postcreate do 
 	(
 		format "postcreate: %
" this
 		unique_id = genClassID returnValue:on
 	)
 	on load do 
 	(
 		format "load: %
" this
 		loaded_time = localtime
 	)
 	on postload do format "postload: %
" this
 )
 

save it as .ms file in …max\stdplugins\stdscripts

In this sample I defined the attribute as global. This attribute on postcreate event generates unique ID and stores it in param block.
On load event the attribute stores local time in local variable defined inside of the attribute’s body.

when you add this attribute to any node you will see the trace of create and postcreate events.
when you load (open file) or merge a node with this attribute you will see the trace of load and postload events.
Use these events to fill up, clean, update, double-check, etc. any parameters of attribute.

to add the attribute:


 (
 	delete objects
 	b = box isselected:on
 	em = EmptyModifier()
 	addmodifier b em
 	CustAttributes.add em test_attrib
 
 	format "id: %
loaded: %
" em.unique_id em.loaded_time 
 )
 

check the “how to make it better?” section on the maxscript help file, first title.

It says “avoid using persistent global variables; persistent globals: a good idea gone bad”

There you’ll find examples on how to use scripted controllers and custom attributes to replace the functionality of persistent variables. That should give you a new (and better) direction.

regards,

M

Ok, I’m still having problems getting errors after I reload max. I’ve recreated a simplified version of my script that I can share here and hopefully it will clear things up. The error I’m seeing is “Unknown property: “diffuseMap” in undefined”. The diffusemap property belongs to the variable “rTile” which has been already been defined in the scene as a persistent variable. So I don’t understand why it’s saying rTile is undefined. (The listener shows me that it IS defined). I read about the issues that can be caused by persistent variables but I’m hoping my situation doesn’t really apply and I can still use them. However, I’m fairly new to scripting, and since I don’t fully understand the issues with persistent variables, my hopes of using them may be unrealistic, or just plain stupid. Anyway, here it is…

[Edit] - It probably helps to clarify that this CA is assigned to the Material (rTile), not to an object or attribute holder.
testCA = attributes "tile color"
   
    (
   	parameters main rollout:tileControls
   	 (
   		  tileClr type:#color ui:tileClrPkr default:red
   		on tileClr set  newCol do 
   		(
   			rtile.diffusemap.brick_color =  newCol
   			showTextureMap rTile rTile.diffuseMap on
   		 )
   	 )
   	
   	
   	
   	rollout tileControls  "Tile Color Controls"
   	(
   		group "Tile Color Properties"
        		(
   		   colorpicker tileClrPkr "Pick tile color:"  color:red across:2 modal:false
   		)	
   	)
   	
   )
   
    custAttributes.add rtile testCA
   
   

try this:


testCA = attributes "tile color"
attribID:#(0x7f7438e5, 0x4515b94b)
   
(
	parameters main rollout:tileControls
	(
		tileClr type:#color ui:tileClrPkr default:red
	)
		rollout tileControls  "Tile Color Controls"
	(
		colorpicker tileClrPkr "Pick tile color:"  color:red across:2 modal:false

		on tileClrPkr changed newCol do
		(
			theNode = custAttributes.getowner this
			theNode.diffusemap.brick_color = newCol
			showTextureMap theNode theNode.diffuseMap on
		)
			
	)
)

Hey Lutteral,
Thanks for helping out with this… both here and on ScriptSpot. I’m beginning to get a better grasp on how CAs work. Your last suggestion of using “foo = custattributes.getowner this” worked. However, it worked for my simplified example and when I tried to apply that to my real project, I still get an error. Let me try to explain my goal here as simple as I can…

I have two materials and I want to control various parameters of the maps in each material with 1 single tool (custom attribute rollout).

To me, it makes most sense to place this tool as close as possible to the actual nodes I’m trying to control. So, I’m placing a CA on ONE of the materials. (As opposed to a scripted modifier on the geometry, or a CA on the geometry baseobject.

What I’ve most recently attempted is placing the main CA with all the texture controls on, we’ll call it “Mat1”, and placing another CA on “mat2”. In each CA OUTSIDE and BEFORE the param block and rollout there is a line defining the variable “global mat1 = custattributes.getowner this”. In the CA for Mat2, “global mat2 = custattributes.getowner this” is basically the only line, as the only purpose of this CA is define the variable “mat2”. However, also in the mat1 CA I’m attempting to control properties of mat2. For example, “mat2.diffuse = mat1.diffuse” so that the color of mat2 is always the same as the color of mat1. However, upon closing max and reopening the scene, it seems like mat2 is not being defined before the mat1 CA is evaluated, and it gives me the error “Unknown property: “diffuse” in undefined”.

I’m almost at a loss here. It seems like it should be a simple enough thing to do, right?? Drive parameters for multiple materials from within a single interface? Should I be taking a different approach??

I’m not quite sure, but I think that you should take another approach to solve your problem, as custom attributes are pretty much sticked to the node they belong.

In your example, let’s say you have 2 standard materials, and you want to control the diffuse color of both at the same time, I think that a much simpler approach should be assigning an instanced controller on them. You can do that with the standard max tools; go to the trackview, open the Medit Materials track, browse to shader basic parameters / diffuse color, right click the track, assign a bezier color, right click again, copy, and then paste on the diffuse color of the second material as an instance…

or if you want to do that in mxs, that could be something like:


myColorController = bezier_color()
meditMaterials[1].diffuse.controller = myColorController
meditMaterials[2].diffuse.controller = myColorController

this example ties the diffuse controllers of the first two materials on the material editor, assuming they are unique standard materials.
then, if you change the color of one of the materials, the other will upgrade as they share the same controller for the diffuse color. You can instance pretty much any controller for any property of the materials using this technique.

Hope this helps!

regards,
m

edit: I know my english is kind of crappy, sorry about that