[Closed] Replace "Render button/max quick render" sequence with custom function?
I’ve almost completed a scripted plugin, but Max’s default render does not update the scene between frames as I need it to. I’ve been able to solve this by writing my own function to render animations, and would like to be able to drop it in place of the standard sequence.
Looking back at http://forums.cgsociety.org/showthread.php?f=98&t=636458 I discovered it is possible to halt and cancel Max’s render sequence. However, when I try to run the following, for example:
dialogMonitorOps.unregisterNotification id:#test
dialogMonitorOps.enabled = false
fn customRender = (
local hwnd = dialogMonitorOps.getWindowHandle()
local hwndText = uiAccessor.getWindowText hwnd
if classOf hwndText == string do (
local substr = substring hwndText 1 9
if substr == "Rendering" do (
-- Cancel standard render
uiAccessor.pressButtonByName hwnd "Cancel"
-- Substitute scripted render
render() -- PLACEHOLDER
)
)
true
)
dialogMonitorOps.enabled = true
dialogMonitorOps.interactive = false
dialogMonitorOps.registerNotification customRender id:#test
I get the following:
– Error occurred in customRender(); filename: C:\Users\Kevin\Documents\3dsMax\scripts\customRender.ms; position: 462; line: 16
– Frame:
– hwndText: “Rendering”
– substr: “Rendering”
– hwnd: 5444878P
To save the trouble of counting, that’s the line which in this case simply says render()
Obviously that works when run outside the function, and Max doesn’t seem to want to tell me what the error actually IS. Any tips on getting this working?
I’ve tried several different workarounds for this, all of them either giving me the same error as this one, or
– Runtime error: Unable to open renderer, possible recursion <<
Can anyone tell me if there is any way to do this???
I’d hoped this might be a possible workaround, but unfortunately it still returns an unidentified error.
global wasRender = false
dialogMonitorOps.unregisterNotification id:#test
dialogMonitorOps.enabled = false
fn check = (
if wasRender then (
render()
wasRender = false
)
else (
local hwnd = dialogMonitorOps.getWindowHandle()
local hwndText = uiAccessor.getWindowText hwnd
if classOf hwndText == string do (
local substr = substring hwndText 1 9
if substr == "Rendering" do (
uiAccessor.pressButtonByName hwnd "Cancel"
wasRender = true
)
)
)
return true
)
dialogMonitorOps.registerNotification check id:#test
dialogMonitorOps.enabled = true
Well, it’s ugly, but it will get the job done until something better comes along.
rollout RO_Delay "Preparing Render" (
timer clock "testClock" interval:1000
on clock tick do (
render()
destroyDialog RO_Delay
)
)
dialogMonitorOps.unregisterNotification id:#test
dialogMonitorOps.enabled = false
fn check = (
local hwnd = dialogMonitorOps.getWindowHandle()
local hwndText = uiAccessor.getWindowText hwnd
if classOf hwndText == string do (
local substr = substring hwndText 1 9
if substr == "Rendering" do (
uiAccessor.pressButtonByName hwnd "Cancel"
createDialog RO_Delay
)
)
return true
)
dialogMonitorOps.registerNotification check id:#test
dialogMonitorOps.enabled = true
http://forums.cgsociety.org/showthread.php?f=98&t=1434925
Feel free to take a crack at it if you like. I’ve thrown all the time at it that I’m going to, and have been told by more experienced people than myself that it can’t be done.
how is about just make several instances of the same light with all available shadow generators. And turn them ON/OFF using a parameter (instead of reassign a new generator).
In this case it will be truly animatable and renderable for every built-in renderer
It seems like it should be possible to use a #prerender callback to create those additional lights and set their shadow generator values prior to rendering any individual frames (and then removing them when the render is done). But I was never able to get that working. Can you look at this setup and see if you are able to get the createShadowLightArray function working with the callbacks?
fn createShadowLightArray ctrl src = (
L1 = copy src; L1.name = "L1"; L1.shadowGenerator = (Adv__Ray_Traced())
L2 = copy src; L2.name = "L2"; L2.shadowGenerator = (mental_ray_Shadow_Map())
L3 = copy src; L3.name = "L3"; L3.shadowGenerator = (Area_Shadows())
L4 = copy src; L4.name = "L4"; L4.shadowGenerator = (shadowMap())
L5 = copy src; L5.name = "L5"; L5.shadowGenerator = (raytraceShadow())
global lightArray = #(L1, L2, L3, L4, L5)
scr = ctrl.visibility.controller
for lit in lightArray do scr.AddNode lit.name lit
expr = "lightArray = #(L1, L2, L3, L4, L5)
"
expr += "for lit = 1 to lightArray.count do try (lightArray[lit].enabled = (ctrl.lit == lit); lightArray[lit].ishidden = (ctrl.lit != lit)) catch()
"
expr += "1"
scr.setExpression expr
return lightArray
)
fn destroyShadowLightArray ctrl lightArray = (
scr = ctrl.visibility.controller
for lit in lightArray do (
scr.deleteVariable lit.name
delete lit
)
scr.setExpression "1"
)
(
delete objects
box()
global testLight = omniLight pos:[0,0,50] castShadows:on
global ctrl = plane length:50 width:150 lengthSegs:1 widthSegs:1 wireColor:white --isSelected:true
ctrl.visibility = float_script()
scr = float_script()
ctrl.visibility.controller = scr
scr.AddNode "ctrl" ctrl
scr.setExpression "1"
ca = attributes ca attribID:#(0, 0) (
parameters params rollout:params (
lit type:#integer default:1 animatable:on ui:lightID
)
rollout params "Shadow Lights Control" (
radiobuttons lightID "Light ID" labels:#("1", "2", "3", "4", "5")
)
)
custAttributes.add ctrl ca #unique
callbacks.removeScripts id:#test
callbacks.addScript #preRender "testLight.enabled = false; for lit in lightArray do lit.enabled = true" id:#test
callbacks.addScript #postRender "testLight.enabled = true; destroyShadowLightArray ctrl lightArray" id:#test
seed 0
with animate on (
for j = 50 to 0 by (random -1 -5) do at time j ctrl.lit = random 1 5
)
completeRedraw()
/*
-- RUN PRIOR TO RENDERING
global lightArray = createShadowLightArray ctrl testLight
*/
)
I’ve done some tests again and I’ve found a few things that may be interesting,
1/ When you do:
L1 = copy src; L1.name = “L1”; L1.shadowGenerator = (Adv__Ray_Traced())
L2 = copy src; L2.name = “L2”; L2.shadowGenerator = (mental_ray_Shadow_Map())
L3 = copy src; L3.name = “L3”; L3.shadowGenerator = (Area_Shadows())
L4 = copy src; L4.name = “L4”; L4.shadowGenerator = (shadowMap())
L5 = copy src; L5.name = “L5”; L5.shadowGenerator = (raytraceShadow())
I don’t know why but when you assign the shadowGenerator to one of them, all the lights get this new value for shadowGenerator. A bug in copy lights?
So, you may create new ligths from scratch:
L1 = omniLight pos:src.pos castShadows:on; L1.name = “L1”; L1.shadowGenerator = (Adv__Ray_Traced())
L2 = omniLight pos:src.pos castShadows:on; L2.name = “L2”; L2.shadowGenerator = (mental_ray_Shadow_Map())
L3 = omniLight pos:src.pos castShadows:on; L3.name = “L3”; L3.shadowGenerator = (Area_Shadows())
L4 = omniLight pos:src.pos castShadows:on; L4.name = “L4”; L4.shadowGenerator = (shadowMap())
L5 = omniLight pos:src.pos castShadows:on; L5.name = “L5”; L5.shadowGenerator = (raytraceShadow())
2/ When I render the whole animation I have no chance… BUT, if I render from frame 1 to 50 AND the TimeSlider is at frame 1, it renders perfectly all the frames.
Don’t know if this can help you.
6th post down that was what I was trying to do actually, but I couldn’t figure out how to make it work right.
here is a setup that doesn’t need any callbacks to work:
ca_sg = attributes ca_sg attribID:#(111,222)
(
fn update =
(
if iskindof this.light RefTargMonitor do
(
format "% > %
" currenttime this.current
this.light.refTarg.shadowGenerator = this.shadowGenerator
1
)
0
)
parameters params
(
light type:#maxobject
current type:#integer default:1 animatable:on
shadowGenerators type:#maxobjecttab tabsize:3
shadowGenerator type:#maxObject
on shadowGenerator get val do
(
val = shadowGenerators[current]
)
)
)
delete objects
d = dummy()
custattributes.add d ca_sg
d.ca_sg.shadowGenerators = #(Area_Shadows(), ShadowMap(), RayTraceShadow())
lit = omnilight()
d.ca_sg.light = RefTargMonitor refTarg:lit
c = d.scale.controller = scale_script()
c.addobject "owner" (RefTargMonitor refTarg:d.ca_sg)
c.setexpression "owner.reftarg.update(); [1,1,1]"
at time 0 animate on d.current = 1
at time 10 animate on d.current = 2
at time 20 animate on d.current = 3
--lit.shadowGenerator
but it doesn’t solve your problem.
the ‘shadowGenerator’ parameter is not animatable. It’s being taken once works for whole rendering session. There is no way to change it on the fly
There is and there isn’t. The solution I am currently using is to cancel the built-in render sequence, and instead call a function which renders each frame separately. The end result is exactly what I want.
you should not care about overriding built-in render.
You just can simply say –
Want My Render – Use My Button.
That’s it
I’d considered it, but I’d rather have it run invisibly. This way people can use what they’re used to and start it from the same dialog where they set all the parameters. Besides, Max’s built-in render can be accessed so many different ways, and I was annoyed by the way it handles non-animatable parameters.
Also, it’s been an opportunity to learn some pretty interesting things.
So now the only thing I need to figure out how to implement is the “Current Task” progress bar as shown in the rendering dialog (i.e. Transforming Vertices/Rendering Image).
Unfortunately I’m not really sure where those values actually come from. Any tips on where I might be able to find this information?