When developing tools you often need to collect items from the scene which in some cases is easier said then done. MaxSQL provides a framework for querying all of Max like a database getting all the items you need with a few lines of mxs. Sometime it’s as simple as to get all cameras which you can of coarse simply do by:
items = cameras
Or if you want to get all teapots, use the:
items = getClassInstances Teapot
But if you want to get all materials used on those Teapots it becomes a bit more work, you have to iterate over all the teapots and collect the materials. And now lets say you want all bitmaps used anywhere on these teapots it requires even more code. Or even nastier if you just want all texturemaps, like noise, checker, etc that are used in the materials used on the teapots it starts to hurt with recursive functions and what not. These are just a few cases but there a lots of times where you’ll run into situations where it requires you to write some boring code to simply collect specific items. What MaxSQL provides is a unified way to get anything you need from anywhere you want.
When developing tools you often need to get some items from the scene. Sometime it’s as simple as to get all cameras which you can of coarse simply do by:
items = cameras
Or if you want to get all teapots, use the:
items = getClassInstances Teapot
But if you want to get all materials used on those Teapots it becomes a bit more work, you have to iterate over all the teapots and collect the materials. And now lets say you want all bitmaps used anywhere on these teapots it requires even more code. Or even nastier if you just want all texturemaps, like noise, checker, etc that are used in the materials used on the teapots it starts to hurt with recursive functions and what not. These are just a few cases but there a lots of times where you’ll run into situations where it requires you to write some boring code to simply collect specific items.
What MaxSQL provides is a unified way to get anything you need from anywhere you want.
So lets look at a few very basic examples before diving into the details:
q=maxSQL() -- create new struct q._select=Teapot; -- give me all the Teapots q._from = Objects; -- in this collection result = q._run()
or
q=maxSQL() -- create new struct q._select=bitmaptexture; -- give me all the bitmaptexture instances q._from=selection; --that are used on selected objects result = q._run()
or
q=maxSQL() -- create new struct q._select=bitmaptexture; -- give me all the bitmaptexture instances q._from=selection; --that are used on selected objects q._where= ( fn __ item = ( not doesfileexist item.fileName )) -- where the file does not exist; result = q._run()
Below is a step by step guide on how to get up to speed with all this.
1) Create a new maxSQL struct:
q = maxSQL()
q now is a struct that we can configure to perform a query. So the basic steps of using it are these:
1) create struct
2) set various properties to build up query
3) run it to get the result
Once a struct has been configured for a certain task you can run it again and again to get updated results. So this struct becomes a powerful selection tool and you can create multiple of them, all with different queries.
2) Now we tell it what we’re after
We do this setting the _select property and there are a two options:
q._select = <class>; --a single class, it will find all items in the scene of this class.
q._select = #(<class>,<class>,etc); --find items in the scene of all these classes
so:
q._select= PRS; -- get all PositionRotationScale controllers in the scene
q._select= #(bitmaptexture, noise); --find a bitmaptextures and noisemaps used the scene
And you also make it select anything that is extended from a certain class or classes, this is something you can’t do with Max’s ‘getClassInstances’.
q._select= texturemap; -- this will give you all maps present in the scene (bitmaptexture, noise, checker, etc)
q._select= materials; -- this will give you all materials in the scene (standard, coronamtl, vraymtl etc)
And you can just mix and match these in the arrays as well.
3) Now we tell it from where to start looking.
We do this by setting the _from property
q._from=<something> -- a single thing
q._from=#(<something>, <something else>) -- or an array of things.
q._from = fn _ ( <some code returning an item or array of items> ); --a function return things.
The difference between..
q._from = selection as array
and
q._from = fn _ ( selection as array );
.. is that the first one takes the selection when you set the _from property it and keeps those nodes, you’re passing an array of nodes to the struct. So every time you run the query it will use these same nodes. The seconds one, with the function, will execute that function every time you run the query and will get the current selected nodes in your scene. So depending on the usecase you can use either one. You can also set the _from property ‘manually’ again before you run the query.
The _from items have to exist in the scene. So if you want to use all PRS controllers as input for _from you could use something like this:
q._from = getClassInstances PRS;
Lambda function
The __ in the function definition is simply name that doesn’t get in the way of readability. Since we pass the function itself as a parameter to another function the name __ is never used. But Maxscript needs a name so I just picked __. So internally something like this is happening:
a = fn __ n = (print n;) a("hello lambda function")
_on
A special addition to _from is _on. With _on you can have it start looking from a specific property.
q=maxSQL() -- create new struct q._select= bitmaptextures ; -- give me all bitmaptexture maps. q._from= objects -- used on any object in the scene
if you run the query now you’d get all bitmap textures used anywhere on any standard material. But lets say we only want those on the diffuse map slots. For that we can add _on:
q._on="material.diffuseMap";
So it will take an item from <_from> , look at it’s ‘material’ property, then looks at its ‘texmapDiffuse’ property and from there will work it’s way up the entire hierarchy branch. I’m using materials here because it’s the most clear example but this will work with any type other classes as well. This will fail gracefully, if an object has no material property, or some material that doesn’t have the given properties it will just discard the item since it doesn’t satisfy the _on clause.
This is useful if you for example want to get all bitmaps used as normal maps and check if there gamma is set to 1. It will not only find bitmaps directly connected to the property but any bitmap in whatever branches of the given property.
4) Filtering (optional)
Chances are you’re not searching for all items everywhere all the time. To filter the results with certain conditions we have the _where property which has quite a few ways of using it. The main one is a simple lambda function that get the current item and returns either true or false.
q._where= ( fn __ item = ( <some code that returns true or false>) );
lets look at this example:
q=maxSQL() -- create new struct q._select=bitmaptexture; -- give me all the bitmaptexture instances q._from=objects ; --that are used on anything in this set of objects q._where= ( fn __ item = ( not doesfileexist item.fileName )) -- where the file does not exist; result = q._run() -- result now holds an array of all bitmaps with missing texture files.
So all items found are run through this function as the ‘item’ parameter, it the function returns false they are discarded, and when true they are kept.
(the __ in the function definition is explain in step 3)
‘item’ is the only required parameter but there are a few optional ones as well, these are all available params:
<item> is the current item
<items:> is an array containing all items that are being processed.
<i:> is the current count of kept items. (use this to limit the amount by using something like if i>10 then return false; )
<_from:> is the current from item
<_select:> is the current _select item
So the green ones are optional, you need to add them with a trailing semi-column. But as said you can just leave them out if you don’t need them.
q._where = ( fn __ item items: i: _select: _from: = ( print item; print items.count; print i; true ) )
Using only this lambda function is the most ‘raw’ option, there are some higher level ones as well:
q._where = #( "someproperty", fn _ val = ( <some code that returns true or false depending on val> );
This will look for the “someproperty” on each item and passes the value to the function for evaluation. Return true to keep the item, false to discard. If the property does not exist if will fail gracefully and discards the item. Example:
q._where = #( "color", fn _ val = ( if val.red > 128 ); --only return items that actually have a "color" property and it's red component value is above 128;
And if you only want to do quick property/value comparison you can use this:
q._where = #( "propertyname", <somevalue> ) -- keep items if "propertyname" == <somevalue>
By default the operand is ‘==’ , but you can specify an operand in the 3th element of the array:
q._where = #( "propertyname", <somevalue>, "!=" )
q._where = #( "propertyname", <somevalue>, "<" )
q._where = #( "propertyname", <somevalue> , ">" )
q._where = #( "propertyname", <somevalue>,"<=" )
q._where = #( "propertyname", <somevalue>,">=" )
You can also supply sub-properties, just join them with “.” as you would normally do
q._where = #( "material.diffusemap.coords.blur", 0 ,"!=" )
5) Ordering results (optional)
To order the results you can add an order property:
q._order = #( "propertyname", <#asc|#desc> )
Here are two examples, sort by name low-to-high and by ‘x’ property high-to-low:
q._order = #( "name", #asc )
q._order = #( "color", #desc)
Here you can also use deeper placed properties:
q._order = #( "transform.position.z", #asc)
6 Running the query
Once everything is setup you can simply run the query to get results:
result = q._run() --executes the query
And it stores the results internally as well for your convenience:
result = q._result -- get cached result from last run query
Once you’ve set up everything you can also override certain properties when you run it. These are all the option parameters:
q._run _select: _from: _where: _on: _order: _set: nocache:
So lets say you have build a query that gives you all the materials from all objects on an A-Z order way:
q=maxSQL() -- create new struct q._select=material; -- give me anything that is dirrived from the material class. (standardmtl, coronamtl, vraymtl, etc) q._from=objects; --that are used on any object q._order("name",#asc); -- order it by name A-Z q_.run()
Everytime you simply _run() it will give you updated results. But lets say for this one time you want the order reversed for some reason, simply use this:
q_.run order:#("name", #desc);
It will just use all the properties you’ve set earlier but it will now temporarily override _order with the one you’ve provided. if after this you use _run() without any parameters again you’ll get your original #asc ordered results again. You can add a nocache:true to that to prevent it from internally overwriting the cached result.
q_.run order:#("name", #desc) nocache:true;
7) Modifying properties (optional, use safety goggles)
You can also modify the found items. This is can really make or break your day so handle with care 🙂
q._set = fn __ item items: i: = ( <some code> )
the green paramaters are optional.
<item> is the current item being passed to the function.
<items> is the entire collection being proccessed.
<i> is the index of the current item in the items array.
(the __ in the function definition is explain in step 3)
So the example below will rename all your texturemaps to some thing like “map_7_of_9”
q=maxSQL() -- create new struct q._select=texturemap; -- give me all texturemaps (noise, checker, bitmaptexture, etc) q._from=objects; --that are used on any object q._set = fn __ item items: i: = ( item.name = "map_" + (i+1) as string+"_of_" + items.count as string) q._run()
8) some random examples
Below are some random examples that show various usage techniques :
q=maxSQL() -- create new struct q._select=PRS; -- give me all the PRS controllers q._from=cameras; --that are in this set of objects result = q._run() -- run query
q=maxSQL() -- create new struct q._select=bitmaptexture; -- give me all the bitmaptexture instances q._from=objects ; --that are used on anything in this set of objects q._where= ( fn __ item = ( not doesfileexist item.fileName )) -- where the file does not exist; result = q._run() -- run query, you'll get an array with all isntances that have missing bitmaps
q=maxSQL() -- create new struct q._select=texturemap; -- give me all the instances of texturemaps (noise, checker, bitmaptexture etc) q._from=objects ; --that are used on anything in this set of objects q._where= ( fn __ item = ( not (classof item == Noise AND item.color1== color 0 0 0 ))) -- but not the noise maps that have a black #1 color result = q._run()
q=maxSQL() -- create new struct q._select=texturemap; -- give me all the instances of texturemaps (noise, checker, bitmaptexture etc) q._from=objects ; --that are used on anything in this set of objects q._order = #( "name", #asc ) -- order them by name ascending ( #desc for the reverse) q._set = fn __ item i: = ( item.name = "map_" + i as string; ) result = q._run() -- run query, you'll get al maps renamed
q=maxSQL() -- create new struct q._select=material; -- give me anything that is a material q._from=selection ; --that are used on anything in this set of objects q._where= ( fn __ item = ( findstring (classof item as string ) "corona" != undefined )) -- that have the 'corona' in their classname result = q._run() -- run query, you'll get an array with all corona material instances used in the scene
q=maxSQL() -- create new struct q._select=#(texturemap, material); --give me all texturemaps and materials q._from=trackViewNodes[#sme][sme.activeView]; --from everything in the active SME (make sure there is one) q._where = ( fn __ item = ( a= ((sme.GetView (sme.activeView)).GetNodeByRef item); if isproperty a "selected" then a.selected else false)); -- that is selected in the SME result = q._run()
q=maxSQL() -- create new struct q._select= texturemap ; -- give me all texturemaps q._from=objects; -- from every object q._on="material.texmapdiffuse" -- that is on this object's property q._where = #( "name", "Map #17", "!=" ) -- where the maps's name is not 'max#17' q._order = #("coords.blur",#desc) -- order it by the amout of blur it gets result = q._run()