Closed hannesdelbeke closed 1 year ago
(testing in krita to have access to lots of qt widgets setup)
can't seem to get windows only, also get tons of other widgets. closest is with if windowtitle()
but windows can not have titles. .window()
also returns tons of widgets.
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtCore import Qt
for widget in QApplication.topLevelWidgets():
if widget.windowTitle() and widget.windowFlags() & Qt.Window:
print(widget, widget.windowTitle(), widget.window().window())
possible option 2: use empty window as parent. make it transparent and frameless. but do children inherit this transparent style? that'd be undesired.
Would love to hear more about this, getting a bunch of bugs because of the wrapping. From what I can see (although I may be wrong) the only reason we do it is to allow the Qt window to be parented to the Blender window, allowing us to minimise the Blender window and the widget at the same time.
Could you make a separate issue for each bug? Would help a lot addressing them
also for your awareness. you can set the sys env var BQT_DISABLE_WRAP to 1 to disable wrapping blender in a QWindow. only disadvantage is tools wont stay in focus when clicking blender. but a lot of small bugs will dissapear
That's good to know, although we're looking to keep that focus unfortunately
❌⚠️ didn't get this to work, feels this could potentially work if we figure out how to prevent win from dissapearing. ctypes parenting proved hacky when testing with native blender windows though
I can show the qwidget in a sep window. not parented to blender.
But as soon as i use user32.SetParent
the widget instantly dissapears.
import sys
import ctypes
# import multiprocessing as mp
from PySide2.QtWidgets import QApplication, QWidget
def run_widget(handle):
app = QApplication.instance()
exec = False
if not app:
app = QApplication(sys.argv)#
exec = True
widget = QWidget()
widget.show()
if exec:
app.exec_()
user32 = ctypes.windll.user32
# def set_tool_window(hwnd):
# GWL_EXSTYLE=-20
# WS_EX_TOOLWINDOW=0x00000080
# user32.SetWindowLongPtrW(widget.winId(), GWL_EXSTYLE, WS_EX_TOOLWINDOW)
# this line makes the widget dissapear instantly.
user32.SetParent(widget.winId(), handle)
return widget
def do():
import bqt.blender_applications.win32_blender_application as win
blender_hwnd = win.get_blender_window()
#p = mp.Process(target=run_widget, args=(blender_hwnd,))
#p.start()
return run_widget(blender_hwnd)
the floatingwindows.py script contains various ctypes methods that might prove usefull.
#dupplicate into new windows
bpy.ops.screen.area_dupli('INVOKE_DEFAULT')
new_window = bpy.context.window_manager.windows[-1]
area = new_window.screen.areas[-1]
space_data = bpy.context.space_data
❌ bpy.ops.wm.window_new()
creates a new window that doesn't stay in foreground, and shows up in alt tab.
this means it's the same as not parenting our qt widgets to blender.
bpy.ops.wm.window_new() creates a new window, can we wrap this in qt. and do that for each widget we show? this would likely work but require devs to manually call a wrap method everytime they show a widget. max and unreal use this setup
played around with some wrapping. but feels a bit hacky
def da():
app = QApplication.instance()
exec = False
if not app:
app = QApplication(sys.argv)#
exec = True
bpy.ops.wm.window_new()
user32 = ctypes.windll.user32
win_h = user32.GetActiveWindow()
print("new window", win_h)
# set title, # todo doesnt work reliable
user32.SetWindowTextW(win_h, "Plugget Qt Manager")
blender_window = QWindow.fromWinId(win_h) # also sets flag to Qt.ForeignWindow
blender_widget = QWidget.createWindowContainer(blender_window)
blender_widget.show()
# tool window
widget = QWidget(blender_widget)
widget.setWindowFlags(widget.windowFlags() | Qt.Tool)
widget.show()
if exec:
app.exec_()
return widget, blender_widget, blender_window
❌ errors, wrapInstance
only works on objects that inherit from QObject
What if we use shiboken wrap? think this might allow to parent qwidgets to blender window without wrapping blender in a qwidget.
Creates a Python wrapper for a C++ object instantiated at a given memory address - the returned object type will be the same given by the user. The type must be a Shiboken type, the C++ object will not be destroyed when the returned Python object reach zero references. If the address is invalid or doesn’t point to a C++ object of given type the behavior is undefined.
asked the AI, not sure how accurate
shiboken.wrapInstance
andPySide5.QtWidgets.QWidget.createWindowContainer
are both methods for wrapping a C++ object in a Python object, but they have different use cases.
shiboken.wrapInstance
is a low-level function provided by the shiboken2 module that allows you to wrap any C++ object in a Python object. It is typically used when working with Qt objects that are not widgets, such asQAbstractItemModel
orQAbstractProxyModel
.
PySide5.QtWidgets.QWidget.createWindowContainer
is a higher-level function provided by thePySide5.QtWidgets
module that allows you to create aQWidget
container for aQWindow
. It is typically used when you want to display aQWindow
within aQWidget
hierarchy, such as when embedding a 3D view or a video player in a Qt application.
import ctypes
import shiboken2
from PyQt5.QtWidgets import QWidget
win_h = ctypes.windll.user32.GetActiveWindow() # Get the window handle for the active window
win_ptr = ctypes.windll.user32.GetWindowLongPtrW(win_h, ctypes.c_int(0)) # Get the pointer to the window
win_obj = shiboken2.wrapInstance(win_ptr, QWidget) # Wrap the pointer in a Python object
print(isinstance(win_obj, QWidget)) # Check
parenting a new blender window to blender main window with ctypes works, but is really buggy. window also dissapeared in background at some point, and main window was blocked
✅ this could be doable: manually manage windows in front or not
import ctypes
user32 = ctypes.windll.user32
w = user32.GetActiveWindow()
if blender is active, bring the blender_widget to foreground. set flag always on top? if qt widget that has rootnode blender_widget is active, same if any other widget is active, always on top is set to false allowing other windows in front
event loop could be in blender, but ideally event loop would be in qt. using e.g. qttimers. e.g. anim_bar since Blender operators that run constantly mess with debugging in VS code
Using our own manager seems to work quite well. if shows widgets in the foreground when any blender window is active, and hides them when blender looses focus. e.g. by going to another app. see test branch https://github.com/techartorg/bqt/tree/research/focus-no-wrap
we'd need to create some kind of "register/wrap widget" method, to add the widget to our manager.
custom widget focus manager:
QApp.blender_widget
, so 1 line to register instead should not be an issue.this has gone in with https://github.com/techartorg/bqt/releases/tag/1.1.0
it automatically works if you disabled qt wrapping for bqt by setting the env var BQT_DISABLE_WRAP
to 1
it assumes you parented all widgets with bqt.widget_manager.add(my_widget)
update: widgets can now be auto parented.
created new issue to clarify this process in code and docs https://github.com/techartorg/bqt/issues/80
interesting to note that this approach would not only work in Blender, but any software that can run python. so we could generalise bqt to work with anything, not just blender
right now we wrap blender in a qt window. this is the source of all our current issues.
unsure if context overrider for operators run from qt would be solved if not wrappedinvestigated, it doesn't solve https://github.com/techartorg/bqt/issues/62parent widgets to blender with ctypes
using ctypes on our widget windows might solve the parenting instead https://blenderartists.org/t/floating-windows/1163493/317
wrap widgets in a window managed by blender
Can we either extend Blender source code, creating an official PR in the blender repo to support empty windows. Or investigate what we already can do with the current exposed Python code.
auto parent widgets
Moved this to it's own issue https://github.com/techartorg/bqt/issues/71