Driver expression over renderfarm (bpy.contex is not safe for renderfarm usage)

Hey there,

We render some blend files over our renderfarm with multiple scenes in them.
We need this so we can do custom aov’s with override material in each scene.

The scenes are called:
“scene” (beauty & crypto)
“normal” (custom normal pass)
“fresnel” (custom fresnel pass)

After the rendering, we pack all of them into an exr.

So in the fresnel pass, I would like to change some settings for the walls, because I also render AO in this scene. So I want to turn the “Shadow” toggle of the walls to True, while the scene “fresnel” is rendering.

This works with this driver expression:
1 if bpy.context.scene.name == ‘Fresnel’ else 0

Now the problem is, that there are multiple warnings beneath the expression:
-Slow Python expression
-WARNING: Driver expression may not work correctly
-TIP: bpy.contex is not safe for renderfarm usage

And yes, the expression works perfectly while rendering in gui mode, but when rendering over command line obviously it doesn’t work.

So my question is, is there a way to write this in a different way, so it will work over a renderfarm?

I use this at every cmd line argument, so this shouldn be the problem.

bpy.context.preferences.filepaths.use_scripts_auto_execute = True 

Cheers and thx

Daniel

Thinking out loud, but what if you use something like?

if bpy.data.scenes[0].name == "Fresnel" else 0

Instead of using bpy.context, use bpy.data, maybe it works :slight_smile:

Thank’s for your reply, I thought about that also, but the variable [0] stays the same in every scene, because the position 0 of Fresnel stays the same

Mmmh… I’m trying to find a solution but I don’t know how to check the active scene or the scene being rendered without bpy.context.

I’m sure @sybren knows the correct answer to this :slight_smile:

If drivers are problematic here, I would suggest adding some Python code to a text datablock that can toggle between the two states. You can then call it from the CLI after loading the file, depending on what you want to render. Such an approach may be a bit cumbersome to create, but it does have the benefit of giving an overview of all the settings that change between scenes.

Could be a good idea to have a pre-render and post-render script fields, so we can define a script that will be executed after or before the render, no matter if we are in the UI or in the CLI/Renderfarm, that could simplify a lot of things.

Maybe is even something I may be able to add, I’ll look at it :slight_smile:

You mean the bpy.app.handlers.render_xxx handlers Blender already has?

Nope :slight_smile:

I mean a pair of fields in the UI:

  • Pre-Render Script: you point a .py file will be executed before the render, without having to code anything and with the security that it goes with the scene, not with the CLI command

  • Post-Render Script: you point a .py file that will be executed after the render is finished and everything is saved, without having to code anything and with the security that it goes with the scene, not with the CLI command

So implementing those fields may be even easier with the handlers that are already there :slight_smile:

1 Like

I would vote against that. If you expect people to be able to write Python scripts in the first place, then adding a few lines to register a handler shouldn’t be a problem. If you expect people to not be able to write Python scripts, the entire thing is unusable anyway.

What’s so hard about adding this to a Python file? And why would you want to have the pre-render and post-render scripts placed in two different text blocks? I was thinking along the lines of this code:

import bpy

def set_scene_specific_stuff(scene: bpy.types.Scene, *, is_rendering: bool) -> None:
    is_fresnel = scene.name == 'Fresnel'
    # use is_fresnel and/or is_rendering to set certain properties

def render_init_func(scene: bpy.types.Scene) -> None:
    set_scene_specific_stuff(scene, is_rendering=True)

def render_complete_func(scene: bpy.types.Scene) -> None:
    set_scene_specific_stuff(scene, is_rendering=False)


if __name__ == '__main__':
    bpy.app.handlers.render_init.append(render_init_func)
    bpy.app.handlers.render_complete.append(render_complete_func)

I quite like the idea from @JuanGea because I’m pretty much a python noob, and your code @sybren already challenges me :smiley:

But thank you for setting this up, I will give it a try.

If I get this right, I start this code with the command line as a python file, and use the “is_fresnel” or “is_rendering” for setting my shadow toggles right?

Because I don’t expect people to be able to write a python script or even know python.

I expect to develop some tools that can do some preparation for the render for example and my artist to use those tools without having to touch a single line of code.

This exact coment:

Is common in many artists that are very good in their jobs but don’t know how to write a single line of code, and having pipeline tools that can be executed easily by artists is important.

The idea of having in separated fields is because there could be different tools for different situations and different moments, so an artist may need to share the initial script, but the final script could be different, so not ving forced to have a lot fo scripts to mix both situations is something good IMHO.

Python is great, but not everyone knows or want to know to script, giving those artists a way to use this kind of tool is something good, and making that option visible to the user is even better, how many artist know about “bpy.app.handlers.render_init” or “bpy.app.handlers.render_complete”?

For you it’s easy to use and understand, but for many people is a very complex task even to understand what “import bpy” is, and those fields would simplify that task and open the feature to a lot of users.

Read my post again: IF you assume people write Python, just give them Python code. Teach them how it works and that Python isn’t some scary monster. If you assume they do NOT know how to use Python code, don’t even bother selecting a Python text datablock, but let your tool hide away all complexity.

But the thing is that we cannot write a tool with UI and everything for each case.

If we hire an artist or a group of artists for a 2 weeks long project and they doesn’t know Python, I don’t have actual time to teach them or for them to learn Python IF they want to. Our projects are 2 weeks/ 3 weeks long in general, and on each project we need a different kind of artist and requirements.
Also, aditionally we can share our pre-render scripts to others and they could be able to use them even without knowing pythong. What I say it’s not a text block :slight_smile: I said a .py file to be executed, they don’t need to see a single line of code, everything is hidden because of the Pre/Post render UI I tell you :slight_smile:

Hey @sybren, I use the script with -P C:\Fresnel_render.py

I used the “is_rendering” in my driver, but I get the error:
Error in Driver: The following Python expression failed:
‘1 if is_rendering == True else 0’

How do I get the parameter into the driver?

Thanks Daniel

My approach was to avoid drivers. The properies that you would use that driver on, just set them directly in the set_scene_specific_stuff() function.

def set_scene_specific_stuff(scene: bpy.types.Scene, *, is_rendering: bool) -> None:
    is_fresnel = scene.name == 'Fresnel'

    if is_rendering:
        base_color = [0.3 * is_fresnel, 0, 0, 1]
    else:
        base_color = [1, 0.8, 0, 1]
    bpy.data.materials['Material'].node_tree.nodes['Principled BSDF'].inputs['Base Color'].default_value = base_color

Ahh thank’s now I got it to work.

I had to change if is_rendering: to if is_fresnel:

I have two questions, what does “*,” do?
And why do you set “-> None:” at the end of the definition?

Cheers and thanks a lot :slight_smile:
Daniel

I think I got it wrong. Now this should work but it isn’t.
Only the first query is working, so when “Fresnel” is the first scene it is true and when it is second or third it wont be true at all. What am I doing wrong?


def set_scene_specific_stuff(scene: bpy.types.Scene, *, is_rendering: bool) -> None:
    is_fresnel = scene.name == 'Fresnel'

    if is_rendering:
    	bpy.data.objects['Cube'].cycles_visibility.shadow = is_fresnel

    else:
    	bpy.data.objects['Cube'].cycles_visibility.shadow = False


def render_init_func(scene: bpy.types.Scene) -> None:
    set_scene_specific_stuff(scene, is_rendering=True)

def render_complete_func(scene: bpy.types.Scene) -> None:
    set_scene_specific_stuff(scene, is_rendering=False)


if __name__ == '__main__':
    bpy.app.handlers.render_init.append(render_init_func)
    bpy.app.handlers.render_complete.append(render_complete_func)

I have two questions, what does “*,” do?

That indicates that the following parameters are keyword-only. This means you have to pass is_rendering=True instead of just True. As a general rule I often require keywords for boolean parameters, because func(True, False, True) is way harder to read than func(is_rendering=True, as_background_job=False, compress=True).

And why do you set “-> None:” at the end of the definition?

That’s a type declaration, declaring that nothing is returned by that
function. I always declare types whenever possible/convenient.

What am I doing wrong?

Add some print() statements throughout your code. You’ll be able to see what it’s doing on the terminal.

As a side note, this code:

if is_rendering:
    bpy.data.objects['Cube'].cycles_visibility.shadow = is_fresnel

else:
    bpy.data.objects['Cube'].cycles_visibility.shadow = False

can be simplified to this, if you want:

bpy.data.objects['Cube'].cycles_visibility.shadow = is_fresnel and is_rendering
1 Like