Source code for ddg.visualization.blender.context

import contextlib
from collections.abc import Iterator
from typing import Any, Literal, Optional

import bpy

# https://docs.blender.org/api/current/bpy_types_enum_items/object_mode_items.html#rna-enum-object-mode-items
Mode = Literal[
    "OBJECT",
    "EDIT",
    "POSE",
    "SCULPT",
    "VERTEX_PAINT",
    "WEIGHT_PAINT",
    "TEXTURE_PAINT",
    "PARTICLE_EDIT",
    "EDIT_GPENCIL",
    "SCULPT_GPENCIL",
    "PAINT_GPENCIL",
    "WEIGHT_GPENCIL",
    "VERTEX_GPENCIL",
]
# https://docs.blender.org/api/current/bpy_types_enum_items/mesh_select_mode_items.html#rna-enum-mesh-select-mode-items
MeshSelectMode = Literal["VERT", "EDGE", "FACE"]


[docs]@contextlib.contextmanager def mode( bobj: Any, *, mode: Mode = "OBJECT", mesh_select_mode: set[MeshSelectMode] = set(), exit_mode: Optional[Mode] = None, exit_mesh_select_mode: Optional[set[MeshSelectMode]] = None, ) -> Iterator[None]: """Context manager to set the active object and its modes. Sets mode `mode` with mesh select modes `mesh_select_mode` for an object `bobj` and upon exiting the context sets mode `exit_mode` and mesh select modes `exit_mesh_select_mode`. This is useful for example to perform changes in EDIT mode that require a switch to a different mode to be written to the object's data. Parameters ---------- bobj : bpy.types.Object The object that is set to be the active object. mode : str, default="OBJECT" The mode of `bobj` within the context. Must be one of "OBJECT", "EDIT", "POSE", "SCULPT", "VERTEX_PAINT", "WEIGHT_PAINT", "TEXTURE_PAINT", "PARTICLE_EDIT", "EDIT_GPENCIL", "SCULPT_GPENCIL", "PAINT_GPENCIL", "WEIGHT_GPENCIL", "VERTEX_GPENCIL". mesh_select_mode : set of { "VERT", "EDGE", "FACE" }, default=set() The mesh select modes of `bobj` within the context. This argument is ignored unless `mode == EDIT`. exit_mode : same type as `mode` or None, default=None The mode of `bobj` after exiting the context. If None, this will be the same as `mode`. exit_mesh_select_mode : same type as `mesh_select_mode` or None, default=None The mesh select modes of `bobj` after exiting the context. If None, this will be the same as `mesh_select_mode`. Examples -------- >>> import bpy >>> from ddg.visualization.blender.context import mode >>> >>> bpy.ops.mesh.primitive_cube_add() {'FINISHED'} >>> cube = bpy.context.object >>> with mode(cube, mode="EDIT", exit_mode="OBJECT"): ... print(f"Mode within the context: {cube.mode}") ... Mode within the context: EDIT >>> print(f"Mode after exiting the context: {cube.mode}") Mode after exiting the context: OBJECT Note that Blender doesn't write changes to edit meshes to the mesh until the object exits edit mode. >>> import bmesh >>> import numpy as np >>> >>> co_original = np.vstack([v.co for v in cube.data.vertices]) >>> with mode(cube, mode="EDIT", exit_mode="OBJECT"): ... bm = bmesh.from_edit_mesh(cube.data) ... for v in bm.verts: ... v.co *= 3 ... bmesh.update_edit_mesh(cube.data) ... co_in_context = np.vstack([v.co for v in cube.data.vertices]) ... ... # Note that even after calling bmesh.update_edit_mesh, ... # cube.data.vertices coordinates haven't changed. ... # The changes are visible in the UI however! ... assert np.allclose(co_original, co_in_context) ... Upon exiting the context, the mode is set to OBJECT mode. Since this is a different mode from EDIT mode, the changes made in EDIT mode are written to `cube.data`. >>> co_after_exit = np.vstack([v.co for v in cube.data.vertices]) >>> assert np.allclose(3 * co_original, co_after_exit) See Also -------- bpy.ops.object.mode_set Sets the object interaction mode. bpy.ops.object.mode_set_with_submode Sets the object interaction mode and optionally also the mesh select modes. Used internally by this context manager. bpy.types.Context.temp_override A context manager that temporarily changes the values of bpy.types.Context objects, e.g. bpy.context. """ # noqa: E501 exit_mode_ = mode if exit_mode is None else exit_mode exit_mesh_select_mode_ = ( mesh_select_mode if exit_mesh_select_mode is None else exit_mesh_select_mode ) try: bpy.context.view_layer.objects.active = bobj bpy.ops.object.mode_set_with_submode( mode=mode, toggle=False, mesh_select_mode=mesh_select_mode ) yield finally: bpy.context.view_layer.objects.active = bobj bpy.ops.object.mode_set_with_submode( mode=exit_mode_, toggle=False, mesh_select_mode=exit_mesh_select_mode_, )