0

I am working on script/addon that will run bake operator every frame before rendering. The main purpose of the script is to bake diffuse shadows and color to an image so that I can use it in the emission shader of the hair particles, this is to render stylized hair. I need to bake image texture before calling render operator, but I can not figure out how to do so. I am trying to call ops.object.bake() in the render_pre handler but It does not work as it neglects the time that bake operator needs and proceds with rendering, wich basically crashes blender (i guess) One of the sollution I have in mind is to call render in object_bake_complete handler but In this case I need to create my own operator that will call bake and proceeds with rendering of the frame. I wanted to make it easy for the user to just press render_animation and it renders the animation but each frame before it starts rendering it bakes the texture.

import bpy

obj = bpy.context.active_object mat = obj.active_material baking_texture = mat.node_tree.nodes.active enabled_hair = []

Collect hair particles to turn off before baking

if obj: for modif in obj.modifiers: if modif.type == 'PARTICLE_SYSTEM' and modif.show_render: print(f'add particle to the list: {modif.name}') enabled_hair.append(modif)

Check if baking_texture is None or if its type is not 'TEX_IMAGE'

if baking_texture is None or baking_texture.type != 'TEX_IMAGE': # Collect image texture for baking for node in mat.node_tree.nodes: if node.type == 'TEX_IMAGE': if node.image is None: continue else: baking_texture = node break

This is where we turn off hair, bake the texture, turn on hair

def bake_texture(scene): global enabled_hair # Turn off hair for hair in enabled_hair: print(f'mute particles: {hair.name}') hair.show_render = False # Call bake texture print("Bake Texture") bpy.ops.object.bake('INVOKE_DEFAULT')

Set a flag to check if rendering has started

rendering_started = False #set flag to distinguish render from baking #baking = False

def start_rendering(scene): global rendering_started #global baking rendering_started = True #baking = True print("Start Rendering") bake_texture(scene)

def change_frame(scene): global rendering_started #global baking if not rendering_started: baking = True print("Frame changed") bake_texture(scene)

def finish_rendering(scene): print("finished rendering") global rendering_started rendering_started = False

def finish_baking(scene): global enabled_hair print("finished baking") # Turn on hair for hair in enabled_hair: print(f'un-mute particles: {hair.name}') hair.show_render = True

Remove the handler if it already exists

def uppend(target, handler): if handler in target: target.remove(handler) print(f'removed handler: {handler.name}') target.append(handler) print(f'added handler: {handler.name}')

Add the handler to both frame change and render events

if(enabled_hair and baking_texture): uppend(bpy.app.handlers.frame_change_post, change_frame) uppend(bpy.app.handlers.render_pre, start_rendering) uppend(bpy.app.handlers.render_complete, finish_rendering) uppend(bpy.app.handlers.render_cancel, finish_rendering) uppend(bpy.app.handlers.object_bake_complete, finish_baking) uppend(bpy.app.handlers.object_bake_cancel, finish_baking)

1 Answers1

0

Baking is basically rendering so it's tricky to call it from a script and carry on with another stuff after that, because rendering is... special. :D It's possible to do that using handlers and a modal operator. See this and I have this answer based on the first one. You would need to use application handlers meant for baking instead of rendering and figure out how to use your scripts logic in all of this.

Martynas Žiemys
  • 24,274
  • 2
  • 34
  • 77
  • I believe what you are saying is that I can not append an operation call before another operation call and I need to create my own operator that calls baking operator waits for it to finish and then call render operator and wait for it to finish? Because my main idea was to create an addon that runs in the background, and whenever user starts rendering (bpy.ops.render.render call) my addon automatically force my own way of rendering wich is first baking a frame and then rendering. I dont know if it even possible to pause operator to wait for another operator to finish before it can move on. – Michael Lebedev Feb 11 '24 at 19:30
  • No, this has nothing to do with operators running one after another. You can definitely do that with pretty much any operators, but rendering is different. I am not sure if you can do something before a specific operator runs(have a look at Message Bus, but I think that's only for after some operator changes some data-block), but you can replace a native operator and hotkeys with an add-on. Not sure if that's a good idea, but you can... – Martynas Žiemys Feb 11 '24 at 21:34
  • Well, there is a bpy.app.handlers.render_pre handler... I have a feeling it will not work to start another render process from it. I am not sure I get the code you have. It looks a bit like ChatGPT generated and makes little sense to me. Might be just me not understanding something though... Anyway, I think you will have to write a model operator if you want to run multiple renders from a script. That's assuming you want to see them in the UI as well (bpy.ops.render.render('INVOKE_DEFAULT')) and don't want the UI to freeze for the whole render process. – Martynas Žiemys Feb 11 '24 at 21:44
  • unfortunately operator do not wait for pre_handler, render is asynchronous thus if I run bake in pre_handler it will not wait and run render operator, which produce error, using modal might be the only solution in this case – Michael Lebedev Feb 15 '24 at 10:30