bpy_jupyter.services
bpy_jupyter.services
All independent, stateful utilities that ship with this extension.
bpy_jupyter.services.async_event_loop
Manages an asyncio
event loop, which allows running asynchronous extension code in the main thread of Blender.
Motivation
Blender's Python API is not thread safe, meaning that the main thread must be used to update all ex. properties, UI elements, etc. . At the same time, when Python code runs in Blender's main thread, the UI becomes unresponsive until it is finished. What to do?
Of course, many salient use cases require interacting with the main thread, yet do not constantly require the CPU's attention:
- Network Client: Waiting for responses from a network server, after making a request. - For instance, a progress bar that responds to updates from a cloud-service ex. a render farm, expensive physics simulation, etc. .
- Network Server: Waiting for clients to connect to us, - For instance, a mini web-service enabling IDE shortcuts that trigger actions within a running Blender.
- Input Processing: Waiting for input from some kind of input device. - For instance, an addon that maps game controller buttons to Blender properties, or MIDI sliders to rig properties.
- IPC: Waiting for status updates from an external process, started via ex.
subprocess
ormultiprocessing
. - For instance, monitoring a computationally heavy external command - or simply switching between light/dark mode in response to theming signals on a Linux system'sdbus
!
By inelegantly slapping an asyncio
event loop on top of Blender's main thread, any code that spends most of its time "just waiting" can now await
whatever it needs to, without blocking Blender's main thread.
Limitations
It's worth saying: Concurrency is not parallelism.
When using this system, all async
code still runs in the main thread.
If that code uses a lot of CPU processing, then it will block that main thread and freeze Blender's UI.
To run expensive code "in the background", one should use the right tools for the job, such as multiprocessing
.
The async
part only comes into play when ex. await
ing messages from that external process to update the extension's UI.
ATTRIBUTE | DESCRIPTION |
---|---|
EVENT_LOOP_TIMEOUT_SEC |
Number of seconds between each iteration of the
|
increment_event_loop
increment_event_loop() -> float
Run one iteration of the asyncio
event loop.
Mechanism
The hack at work here is thus: Blender's event loop is asked to increment the asyncio
event loop "very often".
Each time increment_event_loop
is called, all pending (non-await
ing) tasks will run until they either finish, or reach an await
.
This is the meaning of "one iteration", and this is achieved using loop.call_soon(loop.stop)
, then loop.run_forever()
.
Since the event loop retains its state, and ability to accept tasks, after loop.stop()
, doing this repeatedly amounts to a frequently invoked "pause and flush".
Notes
To enable, use bpy.app.timers.register(increment_event_loop, persistent=True)
.
RETURNS | DESCRIPTION |
---|---|
float
|
The number of seconds to wait before running this function again. |
Source code in bpy_jupyter/services/async_event_loop.py
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
|
start
start() -> None
Start the asyncio
event loop.
Notes
Registers increment_event_loop
to bpy.app.timers
.
DO NOT run if an event loop has already been started using start()
.
Source code in bpy_jupyter/services/async_event_loop.py
92 93 94 95 96 97 98 99 100 |
|
stop
stop()
Stop a running asyncio
event loop.
Notes
Unregisters increment_event_loop
from bpy.app.timers
.
DO NOT run if an event loop has not already been started using start()
.
Source code in bpy_jupyter/services/async_event_loop.py
103 104 105 106 107 108 109 110 111 |
|
bpy_jupyter.services.jupyter_kernel
Manages a global instance of an embedded ipython
kernel, as implemented by bpy_jupyter.utils.IPyKernel
.
Notes
Must be used together with bpy_jupyter.services.async_event_loop
, or some other implementation of an asyncio
event loop.
If this is not done, then the embedded kernel will be unable to act on incoming requests. Instead, such requests will hang forever / until timing out.
ATTRIBUTE | DESCRIPTION |
---|---|
IPYKERNEL |
An instance of the embedded
TYPE:
|
init
init(*, path_connection_file: Path) -> None
Initialize the IPyKernel using the given connection file path.
Notes
This is merely a setup function.
The kernel is not actually started until IPYKERNEL.start()
is called.
PARAMETER | DESCRIPTION |
---|---|
path_connection_file
|
Path to the kernel connection file.
TYPE:
|
Source code in bpy_jupyter/services/jupyter_kernel.py
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
|
is_kernel_running
is_kernel_running() -> bool
Whether the kernel is both initialized and running.
Notes
Use this to check the kernel state from poll()
methods, since it also takes the uninitialized IPYKERNEL is None
state into account.
RETURNS | DESCRIPTION |
---|---|
bool
|
Whether the underlying |
Source code in bpy_jupyter/services/jupyter_kernel.py
66 67 68 69 70 71 72 73 74 75 |
|
bpy_jupyter.registration
Manages the registration of Blender classes.
ATTRIBUTE | DESCRIPTION |
---|---|
_REGISTERED_CLASSES |
Blender classes currently registered by this addon, indexed by
|
register_classes
register_classes(
bl_classes: Sequence[type[BLClass]],
) -> None
Registers a list of Blender classes.
Notes
If a class is already registered (aka. its bl_idname
already has an entry), then its registration is skipped.
PARAMETER | DESCRIPTION |
---|---|
bl_classes
|
List of Blender classes to register.
TYPE:
|
Source code in bpy_jupyter/services/registration.py
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
unregister_classes
unregister_classes() -> None
Unregisters all previously registered Blender classes.
Source code in bpy_jupyter/services/registration.py
55 56 57 58 59 60 |
|