Fix #155797: WM: Break events handling after an undo/redo.

This commit introduces a new `WM_event_handling_break` API, that allows
some code executed during event handling (e.g. operators etc.) to
request the `WM_main` main event loop to stop processing the event
queues and to do a full loop (including redraw step) before resuming
handling of the events.

`test_undo.py` undo tests had to be modified to add a small delay when
batch-triggering undo/redo events, to give Blender enough time to
process all the relevant events and reach the expected state.

Co-authored-by: Campbell Barton <ideasman42@noreply.localhost>
Pull Request: https://projects.blender.org/blender/blender/pulls/156375
This commit is contained in:
Bastien Montagne 2026-03-31 10:42:04 +02:00
parent ba6cd98595
commit 52054aba9f
6 changed files with 80 additions and 0 deletions

View file

@ -295,13 +295,36 @@ def run(
TICKS = 4 # 3 works, 4 to be on the safe side.
# If we try to handle events this many times
TICKS_HANDLING_BREAK_MAX = 256
def event_step():
# Handle `is_event_handling_break` here so we don't incorrectly detect
# consecutive `is_event_handling_break` based on other functions exiting early.
is_event_handling_break = bpy.context.window_manager.is_event_handling_break
if is_event_handling_break:
if event_step._ticks_handling_break_consecutive > TICKS_HANDLING_BREAK_MAX:
raise RuntimeError(
"window_manager.is_event_handling_break set {:d} times, may be an event handling bug!".format(
TICKS_HANDLING_BREAK_MAX,
)
)
event_step._ticks_handling_break_consecutive += 1
else:
event_step._ticks_handling_break_consecutive = 0
# Run once 'TICKS' is reached.
if event_step._ticks < TICKS:
event_step._ticks += 1
return 0.0
event_step._ticks = 0
# Wait for any pending event queue break to be cleared before advancing,
# otherwise events injected by the next step may be deferred unexpectedly.
if is_event_handling_break:
return 0.0
if on_step_command_pre:
if event_step.run_events.gi_frame is not None:
import shlex
@ -353,6 +376,7 @@ def run(
event_step.run_events = iter(event_iter)
event_step._ticks = 0
event_step._ticks_handling_break_consecutive = 0
# Persistent so this keeps working when tests load a blend file.
bpy.app.timers.register(event_step, first_interval=0.0, persistent=True)