[Closed] Some issues with my maxscript:
I have been posting a barrage of seemingly silly Q…This is what I have been up to. Its my first maxscript. I was just trying to get a maxscript to render based on display layers.
But I am running into some trouble… The script seems to run flawlessly the 3rd time I run if there are 2 layers and 4th time if there are 3 layers. Once it runs properly then it stays fine… But you shut down max and restart you will have to go through the same issues. Some global variable issue I suppose… The events are not registered properly maybe.
To run please create 2 objects with 2 layers. If there are no layers the UI will return blank.
In the UI you need to enter range..ex 1-12 and path..ex :E:/vij
Oh and as I said with 2 layers it will run perfectly the 3rd time you run the script. But remember to delete any folders prev generated.
Function LayerRender dirN fType=(
totalLayers = LayerManager.count - 1
for i = 0 to totalLayers do
(
(LayerManager.getLayer i).isHidden = true
)
layerNames = (for i = 1 to totalLayers collect (LayerManager.getLayer i).name)
j=1;
directoryName=""
for each in layerNames do
(
directoryName=dirN + "/" + each +"/"
makeDir directoryName
layer = LayerManager.getLayerFromName each
layer.current = true
lName = LayerManager.current.name
(LayerManager.getLayer j).on = true
lName = LayerManager.current.name
print "layer"
print each
v=execute(each+"Ed")
format "% = %
" (each+"Ed") (execute (each+"Ed"))
print v
rendSaveFile=true
rendTimeType=4
rendPickupFrames=v
rendOutputFilename=directoryName + "/" + lName + "." +fType
max quick render
(LayerManager.getLayer j).on = false
j=j+1
)
)
rci = rolloutCreator "myRollouts" "Layer Renderer"
rci.begin()
totalLayers = LayerManager.count - 1
layerNames = (for i = 1 to totalLayers collect (LayerManager.getLayer i).name)
rci.addControl #edittext "renderPaths" "Path"
for each in layerNames do
(
print each
rci.addControl #label each ("----" + each + "----")
rci.addControl #edittext (each + "S") "Range"
rci.addHandler (each + "S") #entered paramStr:"strv" codeStr:(each+"Ed=" + "strv")
)
rci.addControl #button "RenderButton" "--------------Render---------------"
rci.addHandler "RenderButton" #pressed codeStr:"LayerRender renderPaths.text \"jpg\""
createDialog (rci.end())
I know it takes patience to go through someone elses script and point out errors...
Can someone help…?
bobo, what do you think sir?
Hi Vij,
Your script fails initially due to non – initialization of the variable v, used to store render range. 2 steps can fix this :
use:
v = (execute("rci.def." + each + "S.text"));
instead of
v=execute(each+“Ed”)
&
comment out or Delete the line :
rci.addHandler (each + “S”) #entered paramStr:“strv” codeStr:(each+“Ed=” + “strv”)
personally, I would re-write the script from scratch, with more of a struct approach, so that
I keep the global space as clean as possible. Also, I’d prefer to use a ‘numbered’ approach more than a name based approach for the layer loops.
And as little use of the execute command as possible, infact, never use it ! Though, when working on a dynamically generated UI, its quite hard not to use it !
hope that helps
cheers
shibu
I would suggest trying to ignore any MEL knowledge you might have and attempt to think the Max way. While general mathematical knowledge can be useful when switchting between apps, designing UIs in MAXScript is very different from Maya, as are the behaviors of global variables and so on.
If I had to approach this from scratch, I would have opted for a non-dynamic GUI with a listbox or a DotNet listview showing the names of the layers and the ranges. I would also default all the ranges to whatever the current settings of the Render Dialog are because people are lazy and would hate to enter a range for each layer manually.
Then, when an item on the list is selected, just have ONE edit text field or a bunch of spinners to specify first, last and Nth frame and populate them based on the data corresponding to the selected layer. This way, if you have 100 layers, the UI will not grow outside of the desktop and the user will just have to scroll the listbox.
Also please read about Scope Of Variables in MAXScript and attempt not to declare anything as Global (explicitly or implicitly) unless really necessary. All your code is currently in the global scope which is a very bad practice. Every script should start with an opening parenthesis and ending with a closing one to ensure local scope for all variables and functions inside. And as mentioned already, using execute() should be a last resort and really not the way to do thing in MAXScript.
Another important note – other than in MEL, MAXScript can store ARBITRARY value types in arrays, including other arrays. So you can build your own data structure like
theLayerData = #(
#(“LayerName”, “1-10”),
#(“AnotherLayer”, “1,5,6”)
)
and access the second layer’s data like theLayerData[2], the name would be theLayerData[2][1], the range theLayerData[2][2] etc.
So you can store all the layers and their ranges in a convenient array of arrays and when you loop for rendering, just access the n-th sub-array and its elements to get all you need for the operation without needing to resolve the UI elements containing that data.
Here is my take on it, please use to get some ideas. Of course, you are free to develop any way you want, but this should provide an example of a UI that uses no execute() calls, has all data but the layer array and the rollout local and even stores the layer data (ranges!) with the MAX file – running the script a week later on the same scene will still remember what the ranges were!
I left most of your rendering code there as it was a good workaround to use the pickupframes and call the max quick render command. I added some error traps for directory checking and also made sure the Render Scene Dialog is closed as the settings wouldn’t stick otherwise (a MAX peculiarity).
macroScript LayerRenderer category:"Forum Help" --can remark this line for quick testing
(
global RenderByDisplayLayers_Rollout --the rollout as global to be able to close it
try(destroyDialog RenderByDisplayLayers_Rollout)catch() --try to close if opened
persistent global RenderByDisplayLayers_Data --persistent global var. to store data with the scene
--the non-dynamic UI
rollout RenderByDisplayLayers_Rollout "Layer Renderer"
(
button btn_getPath "Pick Output Directory..." width:180 --button to get an existing path
edittext edt_directory "Dir:" text:"c:\ emp\\" --the path where you can enter manually
button btn_update "Update Layer Data" width:180 --the button to reload all layers and reset ranges
listbox lbx_layers items:#() --the list of layers
edittext edt_range "Range:" --the field to enter the frame range
button btn_render "RENDER LAYERS" width:180 height:30 --the button to launch rendering
--function to collect layer names and ranges. Overwrites existing array
fn buildLayerData =
(
renderSceneDialog.commit() --update the Render Scene Dialog to get the actual range
RenderByDisplayLayers_Data = for i = 0 to LayerManager.count-1 collect #((LayerManager.getLayer i).name, rendPickupFrames)
)
--finction to populate the listbox
fn updateLayerDisplay =
(
lbx_layers.items = for i in RenderByDisplayLayers_Data collect (i[1] + " - [" + i[2] + "]")
)
--handler for populating the edit text when a layer is selected:
on lbx_layers selected itm do
(
edt_range.text = RenderByDisplayLayers_Data[itm][2]
)
--handler to update the selected layer's range when new text entered
on edt_range entered txt do
(
RenderByDisplayLayers_Data[lbx_layers.selection][2] = txt
updateLayerDisplay()
)
--handler to select an existing directory
on btn_getPath pressed do
(
local thePath = getSavePath initialdir:edt_directory.text
if thePath != undefined do edt_directory.text = thePath
)
--handler to render all layers
on btn_render pressed do
(
makeDir edt_directory.text all:true --try to make the directory
if doesFileExist edt_directory.text then --if it does exist,
(
renderSceneDialog.close() --close the Render Scene Dialog to be able to set it
local oldRange = rendPickupFrames --store the current range
rendSaveFile=true --enable saving
rendTimeType=4 --set type to pickup frames
--store old layer enabled states:
local oldStates = for i = 0 to layerManager.count-1 collect (layerManager.getLayer i).on
--set them all to off:
for i = 0 to layerManager.count-1 do (layerManager.getLayer i).on = false
--loop through the layers:
for i = 1 to RenderByDisplayLayers_Data.count do
(
theLayer = layerManager.getLayer (i-1) --get the layer (0-based for some crazy reason)
theLayer.on = true --enable the layer
rendPickupFrames=RenderByDisplayLayers_Data[i][2] --set the range
rendOutputFilename=edt_directory.text + "\\" + theLayer.name + ".jpg" --set the save file name
max quick render --render
theLayer.on = false --disable the layer
)--end i loop
--enable all layers according to the stored states
for i = 0 to layerManager.count-1 do (layerManager.getLayer i).on = oldStates[i+1]
rendPickupFrames = oldRange --reset the range to the old value
)
else --if dir. cannot be found, tell the user.
messagebox "Failed To Create Output Directory!" title:"Layer Renderer"
)
--handler for the update button - reloads all layers, resets ranges, displays in the listbox
on btn_update pressed do
(
buildLayerData()
updateLayerDisplay()
)
--When the UI opens,
on RenderByDisplayLayers_Rollout open do
(
--see if data already exists. If the array is undefined, build it:
if RenderByDisplayLayers_Data == undefined do buildLayerData()
--If the data exists but the layer count stored in the array is different from the count
--in the manager, also update:
if RenderByDisplayLayers_Data.count != layerManager.count do buildLayerData()
--and update the listbox
updateLayerDisplay()
)
)
createDialog RenderByDisplayLayers_Rollout 200 280 --create the dialog
)--end script
Some things that could be improved in my version:
*Respect the .renderable flag of the layer – if the layer is set to non-renderable, skip it.
*Store the last time settings before switching to pickup frames – if Single Frame or Scene Range were selected, restore them after finishing rendering.
*When updating layers, compare the names in the array and the manager and if a layer definition exists, don’t override its range. This would allow for adding/removing of layers without touching those already set up. Also, it would let you rebuild the array on startup everything without overriding the persisteng global content if it is already correct.
*Add some form of sanity check for the range string to ensure only valid characters have been entered.
thanks aahchoo…
bobo, did you just write it on the fly…really nice.
I saw your dvds for sale. Dont you have any online download page where I can pay and just download.
bobo
I tried extending your array but it doesnt seem to work. Just pasting part of your script
fn buildLayerData =
(
renderSceneDialog.commit()
camString= ""
sceneCam = cameras
camName=for o in sceneCam collect o.name
--for n in camName do
--camString=camString + n
RenderByDisplayLayers_Data = for i = 0 to LayerManager.count-1 collect #((LayerManager.getLayer i).name, rendPickupFrames,"viju")
)
fn updateLayerDisplay =
(
lbx_layers.items = for i in RenderByDisplayLayers_Data collect (i[1] + " - [" + i[2] + "]" + i[3])
print "layerItems"
print lbx_layers.items
)
Notice the string “vij” I added in RenderByDisplayLayers_Data. It doesnt seem to work when I try to access it.
Edit: It gives an error saying undefined…but then wrks when I click update layers button…
Basically Im trying to work towards including camera options in a multilist.
Not a bug, my “bad”.
The current code KEEPS the data from the previous run so you don’t have to enter ranges again and again – read the remarks in the on rollout open() handler! If you add new data to the array, you have to make sure the array is explicitly reset before running the new version of the script by evaluating in the Listener
RenderByDisplayLayers_Data = #()
The script will compare the length of this array (0) with the number of layers (1 or more) and automatically refresh the array with the new data because it will notice they are different.
If you prefer, you can replace the declaration of the array in the beginning of the script with the above line and the script will reset the array on each evaluation (at least while you are still developing it).
Hi, Bobo
I was surprised to see you use persisent global variables in your example code… aren’t you the one who wrote in the Max help docs that they’re “a good idea gone bad”?
No, that was a quote from Larry Minton
Persistent Globals were fixed in Max 8 and higher (for example the overwriting on merge was solved), but they were condemned mainly because they were intended for other types of use. For storing a couple of strings or even an array of nodes between sessions, they tend to work pretty nicely, at least in my experience. Of course, they require you to know what you are doing, but I feel rather comfortable using them…
Ah, that’s great to know. Thanks for sharing. I’d gotten into the habbit of using CA’s attached to the root node of the scene for all my persistent storage, avoiding the persistent globals religeously. Maybe I’ll revisit those again.
Thanks!