3
$\begingroup$

I'm trying to build an addon that makes it possible to batch render using Cycles directly from the UI. I've started from a script find a while ago here but I cannot find anymore to link for reference. Anyways, the script worked correctly but without UI so I've decided to port it into an addon that permits to make the same actions but when Blender is open.

Here the problem: my addon works. It correctly creates the panel, buttons etc, but when I click the render button Blender freezes and the only way to "recover" is by force-closing it. I've debugged the code line by line and find that the problem is when Blender calls the render.render operator (bpy.ops.render.render()), and that the same problem appears when you call the operator from Blender build-in Python terminal.

I know that the render.render operator is a C implementation and not a Python one, so maybe the problem resides in that, but the normal render button is implemented in the same way (it seems to me) and it works, and if in my addons I change the call to my operator render.batch with the call to render.render the behavior is normal (it renders the chosen camera), so I don't know what I'm doing wrong.

Below my new operator implementation, maybe someone knows how to help me.

class BatchRender(bpy.types.Operator):
"""Batch Render"""
bl_idname = "render.batch"
bl_label = "Subsequently render all selected cameras"
bl_options = {'REGISTER', 'UNDO'}

def execute(self, context):

    sceneKey = bpy.data.scenes.keys()[0]
    r = 0

    for item in cameras:
        if item.custom.isSelected:
            bpy.data.scenes[sceneKey].camera = item
            bpy.data.scenes[sceneKey].render.filepath = "//" + item.name + str(r)
            bpy.ops.render.render(write_still=True)
            r += 1
        else:
            self.report({'INFO'}, "No camera selected for batch rendering")

    return {'FINISHED'}

Some more little clarifications:

  • cameras is a list of the cameras present into the scene
  • custom.isSelected is a camera properties implemented by me before into the addons, is a boolean property and work correctly (I've tested line by line the code as I've said before)
$\endgroup$

2 Answers 2

3
$\begingroup$

That is because Blender operators are modal: They block code execution until finished. The execute function is called in the same thread in which the GUI is drawn - So your Blender GUI freezes until the render is finished. A similar question with a solution can be found here: Is it possible to make a sequence of renders and give the user the option to cancel the process through the UI at any given time?

$\endgroup$
0
$\begingroup$

I'm running into this same issue many years later, and have been unable to find a simple solution. After much toying around with the following approaches, I agree that 2xB's answer is correct.

  • Modal Operators to offload rendering: UI still freezes
  • Create background thread to offload rendering: Errors, freezes, or crashes
  • Put rendering on an app timer to separate rendering from the operator: UI still freezes

That said the third method at least delays rendering so I can display a popup message to the user to explain why their UI is freezing.

class DelayedRenderOP(bpy.types.Operator):
bl_idname = "example.delayed_render"
bl_label = "Delayed Render"

def execute(self, context):
    bpy.context.scene.render.image_settings.file_format = "PNG"
    
    bpy.context.scene.render.filepath = "C:\\example\\example.png"
    
    def render():
        bpy.ops.render.render(write_still=True)
    
    popupMessage(title = "Rendering", icon = "INFO", text = "Render in progress, \
    this may take several seconds. Blender will be unresponsive until this completes.")
    
    delayedFunc(render)
    
    return {"FINISHED"}

def delayedFunc(func):
    bpy.app.timers.register(func, first_interval=0.01)

_popupText = None
def popupMessage(title, icon, text):
    global _popupText
    _popupText = text
    bpy.context.window_manager.popup_menu(draw_func = drawPopup, title = title, icon = icon)
    _popupText = None

def drawPopup(self, context):
    self.layout.label(text = _popupText)

Note: There is likely a cleaner way to handle the text of the popup message, but this was the easiest solution I found.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .