1

Once setting an object mode with bpy.ops.object.mode_set(mode='EDIT'), can it be programmatically "locked" in that mode until the script explicitly sets another mode? Effectively locking out this button enter image description here or any other ways of changing modes.

I've tried forcing it back using a modal operator if it detects a different mode, but that seems clunky and I feel like there should be a better way?

if bpy.context.object.mode != 'EDIT':
   bpy.ops.object.mode_set(mode='EDIT')
Psyonic
  • 2,319
  • 8
  • 13

2 Answers2

3

Or... You could register an operator named object.mode_set that does nothing and unregister it afterwards:

import bpy
from bpy.props import (
    BoolProperty, 
    StringProperty,
)

class FakeModeSet(bpy.types.Operator): """Locked Object Mode""" bl_idname = "object.mode_set" bl_label = "Fake Mode Set Operator to lock all the mode setting" mode: StringProperty() # So it doesn't get angry when buttons from UI toggle: BoolProperty() # try to pass stuff to it

def execute(self, context):
    return {'FINISHED'}


bpy.utils.register_class(FakeModeSet) #bpy.utils.unregister_class(FakeModeSet)

Martynas Žiemys
  • 24,274
  • 2
  • 34
  • 77
  • This is exactly what I was looking for. Still don't really understand how it works though. Does it simply override the inbuilt object.mode_set operator? – Psyonic Sep 06 '22 at 08:17
  • 1
    Apparently. I just tried it and it works. I do not fully understand how the default one works after unregistering it, but it does. I guess the operator is not written in Python that's why. This wouldn't work with panels or headers or other stuff. If you for example register a new header for 3d view simply unregistering leaves it broken and the original Python code making the UI needs to be run to restore it. – Martynas Žiemys Sep 06 '22 at 12:52
2

Lock Edit Mode for 10 seconds

import bpy

def msgbus_callback(): print("mode changed") try: if bpy.context.object.mode != 'EDIT': bpy.ops.object.mode_set(mode='EDIT') except: pass

owner = "owner"

bpy.msgbus.subscribe_rna( key=(bpy.types.Object, 'mode'), owner=owner, args=(), notify=msgbus_callback )

def in_10_sec(): print("done") bpy.msgbus.clear_by_owner(owner)

bpy.app.timers.register(in_10_sec, first_interval=10)

Method 2: Use operator to block/allow some operator

Here is a operator to block object.mode_set and wm.search_menu

import bpy

def is_inside_viewport(event, context, header): r = context.region cy = r.height // 2 + r.y x = event.mouse_region_x y = event.mouse_region_y L = 0 R = r.width B = 0 T = r.height

if header.y > cy: # header is on top
    T -= header.height
else: # header is on bottom
    B += header.height

if x < L: return False
if x > R: return False
if y < B: return False
if y > T: return False
return True

class MY_OP(bpy.types.Operator): bl_idname = "view3d.my_operator" bl_label = "Operator"

def invoke(self, context, event):
    header = None
    for r in context.area.regions:
        if r.type == 'TOOL_HEADER':
            tool_header = r
        elif r.type == 'HEADER':
            header = r

    if header is None:
        print("header not find, cancelled")
        return {'CANCELLED'}

    self.header = header
    self.tool_header = tool_header
    context.window_manager.modal_handler_add(self)
    return {'RUNNING_MODAL'}

def modal(self, context, event):
    if event.type == 'ESC':
        print("operator finished")
        return {'FINISHED'}

    # check is the mouse under the viewport and not in header, but you can't use the header button
    if is_inside_viewport(event, context, self.header):
        is_mode_set = False
        for kc in context.window_manager.keyconfigs:
            for km in kc.keymaps:
                found_keymap = km.keymap_items.match_event(event)
                if found_keymap is not None:
                    name = found_keymap.idname
                    # print(name)
                    if name in {'object.mode_set', 'wm.search_menu'}:
                        is_mode_set = True
                        break

        if not is_mode_set:
            return {'PASS_THROUGH'}

    return {'RUNNING_MODAL'}


def register(): bpy.utils.register_class(MY_OP)

def unregister(): bpy.utils.unregister_class(MY_OP)

if name == "main": register()

enter image description here

X Y
  • 5,234
  • 1
  • 6
  • 20
  • Thanks @X Y, this basically uses the same method I'm already using, (if not "EDIT' then 'EDIT) I was hoping there was a way to "lock the mode" using some other means? +1 though because you taught me about bpy.msgbus :) – Psyonic Sep 05 '22 at 08:44
  • How about running a modal operator? User cannot change the mode until the operator finish. You also can allow special operation like rotate the viewport in the operator. – X Y Sep 05 '22 at 15:05