Source code for houdini_core_tools.context_managers

"""Houdini related context managers."""

# Future
from __future__ import annotations

# Standard Library
from collections.abc import Generator, Iterable
from contextlib import ContextDecorator, contextmanager
from typing import TYPE_CHECKING

# Houdini Core Tools
from houdini_core_tools import exceptions

# Houdini
import hou

if TYPE_CHECKING:
    from types import TracebackType


# Classes


[docs] class emit_varchange(ContextDecorator): """Emit a varchange call at the end of the scope. In the event of nested uses, only the outermost will execute the varchange. >>> with emit_varchange(): ... hou.hscript("set FOO=456") ... >>> # A varchange call will have been emitted for FOO. """ _ACTIVE = False def __init__(self) -> None: self._should_run = False def __enter__(self) -> None: self._should_run = not emit_varchange._ACTIVE if self._should_run: emit_varchange._ACTIVE = True def __exit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, ) -> None: if self._should_run: hou.hscript("varchange") emit_varchange._ACTIVE = False
[docs] class temporarily_unlock_parameters(ContextDecorator): """Context manager to temporarily unlock parameters and re-lock them on exit. >>> my_parameter = hou.parm("/obj/geo1/scale") >>> with temporarily_unlock_parameters(my_parameter): ... my_parameter.set(3) ... Args: parms_to_unlock: The parameter(s) to unlock. """ def __init__(self, parms_to_unlock: hou.Parm | hou.ParmTuple | Iterable[hou.Parm]) -> None: # If the item is a parm tuple then convert it to its constituent hou.Parm # objects. if isinstance(parms_to_unlock, hou.ParmTuple): parms_to_unlock = tuple(parms_to_unlock) # type: ignore if not isinstance(parms_to_unlock, Iterable): parms_to_unlock = [parms_to_unlock] self.parms = parms_to_unlock # Store the current status, so we can restore it if necessary. self.locked_states = [parm.isLocked() for parm in parms_to_unlock] def __enter__(self) -> None: self._set_lock_state(locked=False) def __exit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, ) -> None: self._set_lock_state(locked=True) def _set_lock_state(self, *, locked: bool) -> None: """Set the parms lock states to the desired value, if necessary. Args: locked: Whether the parms should be locked. """ for parm, parm_is_locked in zip(self.parms, self.locked_states): if parm_is_locked: try: parm.lock(locked) # This might be raised if the parameter is inside a locked HDA # or not included in a take. Since Houdini messages never contain # useful information like the offending parameter we'll re-raise # an exception with a better message. except hou.PermissionError as inst: raise hou.PermissionError( # noqa: TRY003 f"Error setting lock status for {parm.path()}" ) from inst
# Functions
[docs] @contextmanager def context_container(category: hou.NodeTypeCategory, *, destroy: bool = True) -> Generator[hou.OpNode, None, None]: """Context manager that provides an appropriate node to create a node under. If the container type needs to be created it will be, then it will be destroyed after the scope of the manager if `destroy` is True. >>> with context_container(hou.sopNodeTypeCategory()) as container: ... container.createNode("box") Args: category: The node type category of the node to create. destroy: Whether to destroy the node or not. Returns: An appropriate parent node to create a node of the desired type under. Raises: UnsupportedCategoryError: Raised if the category does not correspond to a known/supported type. """ category_name = category.name() # Types which can map directly to default scene nodes. direct_mappings = { "Driver": hou.node("/out"), "Lop": hou.node("/stage"), "Object": hou.node("/obj"), "Shop": hou.node("/shop"), "Vop": hou.node("/mat"), } container = direct_mappings.get(category_name) # If there was a direct mapping then use it. if container is not None: yield container # Otherwise, check for specific contexts and create the requisite node # of a matching context. else: match category_name: case "Cop": container = hou.node("/img").createNode("copnet") case "Cop2": container = hou.node("/img").createNode("img") case "Dop": container = hou.node("/obj").createNode("dopnet") case "Sop": container = hou.node("/obj").createNode("geo") case "Top": container = hou.node("/obj").createNode("topnet") # If a known context cannot be found, raise an error. case _: raise exceptions.UnsupportedCategoryError(category) yield container # Destroy the created container. if destroy: container.destroy()
[docs] @contextmanager def restore_current_selection() -> Generator[None, None, None]: """Restore the current selection after the block has exited. The selection will be restored in the event an exception occurs inside the block. >>> with restore_current_selection(): ... # Perform actions that could change the selection. ... """ # Stash for restoring afterwards. selected = hou.selectedItems() hou.clearAllSelected() try: yield # Use finally so the selection is always restored even if errors occur. finally: hou.clearAllSelected() # Restore the selection. for item in selected: item.setSelected(True)
[docs] @contextmanager def set_current_node( node: hou.Node, ) -> Generator[None, None, None]: """Temporarily set the current for the scope of the block. The current node is that returned by `hou.pwd()`. The current mode will be restored in the event an exception occurs inside the block. >>> with set_current_node(hou.node("/obj")): ... # Perform actions where hou.pwd() is used. ... Args: node: The temporary node. """ current_node = hou.pwd() hou.setPwd(node) try: yield finally: hou.setPwd(current_node)
[docs] @contextmanager def set_temporary_update_mode( update_mode: hou.updateMode, ) -> Generator[None, None, None]: """Temporarily set the UI update mode for the scope of the block. The update mode will be restored in the event an exception occurs inside the block. >>> with set_temporary_update_mode(hou.updateMode.Manual): ... # Perform actions while in manual mode. ... Args: update_mode: The temporary update mode. """ # Get the current update mode to restore it after. current_mode = hou.updateModeSetting() hou.setUpdateMode(update_mode) # type: ignore try: yield finally: # Restore the previous update mode. hou.setUpdateMode(current_mode)