On this page |
This class is a context manager which allows the user to press ⎋ Esc to interrupt a long-running operation, and can also display a progress percentage in the status line at the bottom of the main window.
You should use this in potentially long-running Python nodes (such as Python SOP) or shelf scripts.
For example if you have code looping over every point in the input geometry, if the geometry has millions of points the operation could take a long time.
You should perform the operation inside a hou.InterruptableOperation
block so the user can interrupt it, and so you can display progress.
Important: The script only checks if the user has pressed Escape when you call the context object’s updateProgress()
method.
So you need to add calls to updateProgress()
in the block, usually at the start of each iteration of a loop (even if you don’t provide a progress fraction).
(Calling updateProgress()
also gives Houdini a chance to respond to events, so it doesn’t appear to be non-responsive to the operating system. For example, regularly calling updateProgress()
prevents macOS from showing the “beachball” cursor.)
If the user interrupts the operation, the context block will exit with a hou.OperationInterrupted exception. However, you may not want to catch the exception in your script.
-
If you don’t catch the
hou.OperationInterrupted
exception in a shelf tool, the tool script will simply stop and Houdini will not display the exception to the user. If your script doesn’t require any cleanup, this is usually what the user would expect. -
If you don’t catch the exception in a Python node (for example, a Python SOP), Houdini will automatically display an error on the node (with the error text
Cooking was interrupted
). Again, this is typical of how nodes deal with long-running operations being interrupted.
To try this object out, you can create a shelf tool and paste the following into the tool script:
import time import hou try: with hou.InterruptableOperation("Waiting around doing nothing") as iop: for i in range(100): # Real work would go here, instead we'll just go to sleep time.sleep(0.1) # In a loop or series of steps, call updateProgress() on # the context object to check for interruption. You can optionally # pass a fraction to let the user know how much is done. # (The first argument is called "percentage" but it actually takes # a fraction.) iop.updateProgress(i / 100) except hou.OperationInterrupted: # The user pressed Esc (or the operation timed out if you used the # timeout_ms argument) hou.ui.displayMessage("Interrupted") else: # The operation finished without interruption hou.ui.displayMessage("Completed")
Nested operations ¶
You can nest multiple interruptable operations. For example:
# Start the overall, long operation. with hou.InterruptableOperation("Beginning operation", "Performing a Long Operation", open_interrupt_dialog=True) as iop: for i in range(num_tasks): # Update overall progress operation.updateLongProgress(i / num_tasks) # Start the sub-operation with hou.InterruptableOperation(f"Performing Task {i + 1}") as sub_iop: for j in range(num_subtasks): # Update sub-operation progress sub_iop.updateProgress(j / num_subtasks) # # PERFORM SUBTASK HERE. #
Timing out operations ¶
You can use the timeout_ms
argument when you create the context object to limit the amount of time an operation can take.
-
Hitting the time limit raises
hou.OperationInterrupted
, just like the user pressing ⎋ Esc. -
Just like the user pressing ⎋ Esc, the timeout is usually only checked when you call the
updateProgress()
method, so you need to call the method regularly inside the block.Some HOM functions may internally update progress, which can also trigger a check of the timeout. For example, you can wrap a call to
LopNode.stagePrimStats()
in ahou.InterruptableOperation
block with a timeout, and the timeout will interruptstagePrimStats()
because internallystagePrimStats()
updates Houdini as it works.
For example, this modified version of the example would normally run for 10 seconds, but will display Interrupted
after 1 second because of the timeout_ms=1000
argument:
import time import hou try: with hou.InterruptableOperation("Waiting around doing nothing", timeout_ms=1000) as iop: for i in range(100): # Real work would go here, instead we'll just go to sleep time.sleep(0.1) iop.updateProgress(i / 100) except hou.OperationInterrupted: # The user pressed Esc (or the operation timed out if you used the # timeout_ms argument) hou.ui.displayMessage("Interrupted") else: # The operation finished without interruption hou.ui.displayMessage("Completed")
Tips and notes ¶
-
By default, Houdini only shows the progress as text in the status line. If you pass
open_interrupt_dialog=True
when you create the object, Houdini will open a progress dialog that shows progress using a progress bar. You should generally open a dialog if the operation could take more than one or two seconds. -
The call to
updateProgress()
takes a small amount of time that can add up. Inside tight loops you may want to only call it, for example, every 10th iteration instead of every iteration. -
hou.InterruptableOperation
` does not work in the Python Shell. If you just want to try it out, use a shelf tool. -
Houdini may not display every progress update in the status bar. It updates the status bar on its own schedule for performance reasons.
-
Trying to create this object outside of a
with
statement will raise hou.OperationFailed.
Methods ¶
__init__(operation_name, long_operation_name=None, open_interrupt_dialog=False, timeout_ms=0)
Construct a new InterruptableOperation.
operation_name
A description of the interruptable operation that appears in the progress bar of the interrupt dialog.
long_operation_name
A description of the long, or higher-level, operation. If it is not
None
, a second progress bar appears on the interrupt dialog with the
long operation name in it.
open_interrupt_dialog
Determines whether the interrupt dialog should appear or not.
timeout_ms
An integer value specifying the number of milliseconds an operation
is allowed to spend before automatically being interrupted. The
timeout is only checked when the progress is updated using the
updateProgress
or updateLongProgress
methods (or the C++
equivalents when invoking native Houdini methods). Timeouts cannot
be “nested”. A timeout set on an “inner” operation, after an
“outer” operation already has a timeout, will be ignored.
An interrupt triggered due to a timeout is treated exactly like an interrupt triggered by the user, and so may cause nodes to enter an interrupted error state. If you wish to avoid this situation, be sure to cook any nodes whose data may be required before starting the interruptable operation with the timeout.
updateLongProgress(percentage=-1.0, long_op_status=None)
Update the progress percentage and status of the long, or high-level, operation. At the same time, check if the operation was interrupted by the user.
percentage
Despite the name, this argument takes a fraction from 0.0
to 1.0
, not a percentage.
If this number is negative, Houdini will not display the progress.
long_op_status
Text describing the current status of the long operation, if there is one. If pass a value for this argument, it overwrites the text in the 2nd progress bar of the interrupt dialog.
updateProgress(percentage=-1.0)
Update the progress percentage of the operation. At the same time, check if the operation was interrupted by the user.
percentage
Despite the name, this argument takes a fraction from 0.0
to 1.0
, not a percentage.
If this number is negative, Houdini will not display the progress.