6

Is it possible to catch reports from Operators in a Python script?

For example, the Operator bpy.ops.object.modifier_apply() shows errors in the console when something goes wrong. I'd be happy to have a try... catch... method to handle those.

Same with some addons like 3D Print Toolbox who send back useful info in the console and UI, but I can't seem to find a way to catch those. Is this possible?

Update:
To give a concrete example, this file uses a boolean modifier on two objects and when applied, the Operator returns a lot of WARNING or errors in the console. How could I catch those?

xuv
  • 179
  • 10
  • This isn't really a blender question so much as a python question. What you're looking for is error handling in python, and here's the docs about that: https://docs.python.org/3/tutorial/errors.html – TLousky Jan 14 '16 at 23:51
  • @TLousky Sorry if my question is badly formulated. But Operators can report data or info or messages when they fail, operate or succeed. So I'm not maybe going to use try... catch, but if I could somehow access those reports, that would be useful. – xuv Jan 15 '16 at 00:31
  • OK, if the operator failed but did not trigger an exception that stops the script, you will be able to see the result in the operator's return value: res = bpy.ops.some_operator(). Common values are {'FINISHED'}, {'CANCELED'} and {'RUNNING_MODAL'}, among others. – TLousky Jan 15 '16 at 00:33
  • @TLousky: Ok. Thx. Yes, that works. But I'm not interested in what an operator returns, but at the calls to the report() function from those Operators. For a particular and specific example, in the 3D Print Toolbox addon, at line 477, the Operator.report() function is called. That's what I'm trying to catch. – xuv Jan 15 '16 at 00:56
  • related: http://blender.stackexchange.com/questions/18874/return-values-of-higher-ui-functions – p2or Jan 15 '16 at 10:58
  • Also related to http://blender.stackexchange.com/questions/26098/python-modifier-error-handling – xuv Jan 15 '16 at 16:22
  • Unfortunately, in that previous related link, the try:... except:... around the bpy.ops.modifier_apply() does not work for me. The error is returned but the except is not called. – xuv Jan 15 '16 at 16:33
  • Also related to: http://blender.stackexchange.com/questions/6119/suppress-output-of-python-operators-bpy-ops – ideasman42 Jan 20 '16 at 08:38
  • I consider this a mild defect in the architecture of blender's operator mechanism. You are encouraged to write operators in such a manner that their logic is difficult to reuse. I once wrote an operator that called a helper function that threw exceptions which were caught by the operator wrapper and turned into report()s. When I tried to contribute the source code they told me not to use and catch exceptions, thus making my code much clumsier to reuse. – Mutant Bob Jan 20 '16 at 16:01

2 Answers2

11

For Operator Error Reports

Catching Errors is handled separately, this example catches the error report and prints it as a string.

import bpy
try:
    bpy.ops.object.vertex_group_add()
except RuntimeError as ex:
    error_report = "\n".join(ex.args)
    print("Caught error:", error_report)

For Operator Info Reports

Python can temporarily redirect the stdout using contextlib, this is ideal since you may not want to suppress output for _all_ scripts, just selectively redirect some output.

eg:

import bpy

import io
from contextlib import redirect_stdout

stdout = io.StringIO()
with redirect_stdout(stdout):
    bpy.ops.mesh.remove_doubles()

If you want you can read the output back out or use it however you like.

stdout.seek(0)
output = stdout.read()
print("Report was %r" % output)
ideasman42
  • 47,387
  • 10
  • 141
  • 223
0

This is a hack I found to solve parts of this problem. It will work with Operators using the report() function to display data back to the user but don't provide a way to catch that data in other ways.

Those reports are both displayed in the Information window and in the console. So the trick I found is to do temporarily reroute the stdout from Python to a text file and re-read the text file afterwards.

# Opens a text file to write the reports
def openLogs():
    logfile = open( bpy.path.abspath('//') + "log.txt", "r+")
    # reroute stdout to the logfile
    sys.stdout = logfile
    return logfile

# Reads the file content and closes it
def readAndCloseLogs( logfile ):
    logfile.seek(0)
    logs = logfile.readlines()
    logfile.close()
    # Return stdout to its normal state
    sys.stdout = sys.__stdout__
    return logs

# Then wrap your operator between these two functions
logfile = openLogs()
# bpy.ops... # Put your operator here 
logs = readAndCloseLogs( logfile )

# The logs contains what the Operator has reported
xuv
  • 179
  • 10