1
$\begingroup$

I'm very new to the Blender api and pardon this complex question.

I'm running a pyside2 window from blender which hosts a button. In this button I'm trying to get the currently selected object through the bpy.context.active_object. However, I'm getting the error AttributeError: 'Context' object has no attribute 'active_object'.

I've investigated and found out that this problem is isolated to the call within the button callback function. If I try to get the active object in my __init__ method it works just fine.

I print the values of the bpy.context both in the innit and in the callback, and they look different

Here's the smallest repro of the issue that I could do. Apologies as it is quite large.

NOTE: You need pyside2 installed and accessible in your sys.path to run this

import sys
import bpy

from PySide2 import QtWidgets, QtCore

class QtWindowEventLoop(bpy.types.Operator):
    """Allows PyQt or PySide to run inside Blender"""

    bl_idname = 'screen.qt_event_loop'
    bl_label = 'Qt Event Loop'

    def __init__(self, widget, *args, **kwargs):
        self._widget = widget
        self._args = args
        self._kwargs = kwargs

    def modal(self, context, event):
        wm = context.window_manager

        if not self.widget.isVisible():
            wm.event_timer_remove(self._timer)
            return {'FINISHED'}
        else:
            self.event_loop.processEvents()
            self.app.sendPostedEvents(None, 0)

        return {'PASS_THROUGH'}

    def execute(self, context):
        self.app = QtWidgets.QApplication.instance()

        if not self.app:
            self.app = QtWidgets.QApplication(sys.argv)

        self.event_loop = QtCore.QEventLoop()
        self.widget = self._widget(*self._args, **self._kwargs)

        wm = context.window_manager
        self._timer = wm.event_timer_add(1 / 120, window=context.window)
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}


class TestWindow(QtWidgets.QMainWindow):

    def __init__(self, parent=None):
        super(TestWindow, self).__init__(parent)
        self.tool_name = "My Blender test tool"
        self.add_ui()
        self.show()

    def add_ui(self):
        self.root_widget = QtWidgets.QWidget()
        self.root_layout = QtWidgets.QVBoxLayout()
        self.root_widget.setLayout(self.root_layout)
        self.setCentralWidget(self.root_widget)

        button = QtWidgets.QPushButton("button")
        self.context = bpy.context
        button.clicked.connect(self.button_func)
        print(dir(bpy.context))
        self.root_layout.addWidget(button)
        self.setLayout(self.root_layout)

    def button_func(self):
        print(dir(bpy.context))
        print(bpy.context.active_object)

class CustomWindowOperator(QtWindowEventLoop):
    bl_idname = "screen.custom_window"
    bl_label = "temp2.custom"

    def __init__(self):
        super().__init__(TestWindow)

def main():
    bpy.utils.register_class(QtWindowEventLoop)
    bpy.utils.register_class(CustomWindowOperator)
    bpy.ops.screen.custom_window() # starts the window

Here's the output that the two prints give Output above === is in the innit call, output below is inside the button callback

Anyone knows a way to circumvent this? Or how to set the context in any way?

Thanks in advance.

$\endgroup$
9
  • $\begingroup$ have u tried bpy.context.object? $\endgroup$ Jul 21, 2022 at 15:36
  • $\begingroup$ @HarryMcKenzie Yeah I tried it too but it is also unavailable unfortunately... $\endgroup$ Jul 22, 2022 at 6:42
  • $\begingroup$ i tried your code and it works perfectly. a qt message box pops up and when i click the button, it prints without errors. i'm running on Blender 2.93, 3.0, 3.1 and 3.2 without errors. i see you're using 2.91, maybe consider going to 2.93 or 3.2 $\endgroup$ Jul 22, 2022 at 7:14
  • $\begingroup$ Could you show me the output of when you run it please? Are you able to print the name of the active object? $\endgroup$ Jul 22, 2022 at 7:30
  • $\begingroup$ Tried using blender 3.1.2 and getting the same error still $\endgroup$ Jul 22, 2022 at 7:42

2 Answers 2

0
$\begingroup$

I've found the issue, and it's quite strange. If I spam the button, I sometimes get the desired result printed in the output, and the name of the selected object is shown.

It was really inconsistent though, almost random. I decided to try and remove the part of the code which keeps blender from freezing QtWindowEventLoop. It now works and gives me the selected object 100% of the time. But Blender is frozen while the QT window is active and the commands are only executed after the window has been closed. This is the new functional code:

import sys
import bpy

from PySide2 import QtWidgets, QtCore

class TestWindow(QtWidgets.QMainWindow):

    def __init__(self, parent=None):
        super(TestWindow, self).__init__(parent)
        self.tool_name = "My Blender test tool"
        self.add_ui()
        self.show()

    def add_ui(self):
        self.root_widget = QtWidgets.QWidget()
        self.root_layout = QtWidgets.QVBoxLayout()
        self.root_widget.setLayout(self.root_layout)
        self.setCentralWidget(self.root_widget)

        button = QtWidgets.QPushButton("button")
        self.context = bpy.context
        button.clicked.connect(self.button_func)
        print(dir(bpy.context))
        print("==============================================================")
        self.root_layout.addWidget(button)
        self.setLayout(self.root_layout)

    def button_func(self):
        print(dir(bpy.context))
        print(bpy.context.active_object)

def main():
        app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv)
        win = TestWindow()
        #win.main()
        app.exec_()
main()
$\endgroup$
0
$\begingroup$

You can decorate your callbacks with this piece of code.

I found this here: https://github.com/techartorg/bqt/commit/3a40d7f120ec7c9b9a4010b2b0ef97b9781c64ac

def context_window(func):
    """
    Support running operators from QT (ex. on button click).
    Decorator to override the context window for a function,
    """

    def wrapper(*args, **kwargs):
        with bpy.context.temp_override(window=bpy.context.window_manager.windows[0]):
            return func(*args, **kwargs)

    return wrapper

Obviously, this is empirical solution and i'm not sure if it will work all the time.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .