PySide2 widget to drag and drop materials?

inquestudios
Enthusiast

PySide2 widget to drag and drop materials?

inquestudios
Enthusiast
Enthusiast

I'm trying to develop a simple tool that allows loading and saving of material libraries

AND also drag and drop materials from my widget to material editor and reverse.

What QMimeData type should I set to drop INTO material editor?

 

I have set def dropEvent to accept all drops right now, it even triggers when I drop data from external application like files from windows explorer. But when I try to drag from material editor slot, it only activates over material slots and viewport objects. How can I force my widget to accept material drops?

0 Likes
Reply
Accepted solutions (1)
3,264 Views
41 Replies
Replies (41)

inquestudios
Enthusiast
Enthusiast

In 2019 material browser it's not modal, it's fixed in 2024.

So, is there any way to disable material browser on left click?

The dumbest way is to close it with DialogMonitorOps, but it will close ALL material browsers when it's registered.

0 Likes

denisT.MaxDoctor
Advisor
Advisor

@inquestudios wrote:

In 2019 material browser it's not modal, it's fixed in 2024.


in 2020 it's already modal. 

Well... what functionality we want to keep for material buttons? Do you want to have complete control over the button's WinProc?

0 Likes

inquestudios
Enthusiast
Enthusiast

I need an iterface element that I can associate with material, select or deselect it, make a context menu, and drag material data to another 3ds MAX interfaces from there. I don't need material browser on it.
And looks like I will just refuse from using dragging. You were right, short answer is "I can't".
Thank you anyways, reusing MAXscript rollout as QT widget may be helpful in many other situations.

0 Likes

denisT.MaxDoctor
Advisor
Advisor

@inquestudios wrote:

I need an iterface element that I can associate with material, select or deselect it, make a context menu, and drag material data to another 3ds MAX interfaces from there. I don't need material browser on it.
And looks like I will just refuse from using dragging. You were right, short answer is "I can't".


but I didn't say it's not possible... 😉

denisTMaxDoctor_0-1720037095231.gif

 

inquestudios
Enthusiast
Enthusiast

OMG man, you know how to hold the intrigue. How can I recreate this?

0 Likes

denisT.MaxDoctor
Advisor
Advisor

@inquestudios wrote:

OMG man, you know how to hold the intrigue. How can I recreate this?


😊 

You need the HWND to hook the WinProc of the button's parent window. This is the initial Rollout. Python is enough for this, but you will need additional modules to work with WinAPI, such as win32gui and win32con, which are not included in the MAX default installation. 😋

0 Likes

inquestudios
Enthusiast
Enthusiast

I know how to install new libraries. Show me the code example already, please.
So I should know what documentation I have to RTFM

0 Likes

denisT.MaxDoctor
Advisor
Advisor

@inquestudios wrote:

I know how to install new libraries.


and how do you propose to deliver these libraries to the end user.... or is there only one user? ... just you? 😁

0 Likes

inquestudios
Enthusiast
Enthusiast

Not my headache, our team got another guy to develop installer shell.
I'm pretty sure he knows how to use PIP

0 Likes

denisT.MaxDoctor
Advisor
Advisor

@inquestudios wrote:

Show me the code example already, please.
So I should know what documentation I have to RTFM


For MAX, I write mostly in C++, but if I happen to have time, I can practice in Python. 😎

0 Likes

inquestudios
Enthusiast
Enthusiast

If you could show me how can I disable browser on materialbutton, it would be great.
If you will also show me how to turn this one into checkbutton and offset text to lower border, I'm in your debt.

No need to worry about context menus, this can be done in QT for sure, if I still have a right click handler in MAXscript.
And take your time, there's no haste at all.

0 Likes

denisT.MaxDoctor
Advisor
Advisor

@inquestudios wrote:

If you could show me how can I disable browser on materialbutton, it would be great.
If you will also show me how to turn this one into checkbutton and offset text to lower border, I'm in your debt.

No need to worry about context menus, this can be done in QT for sure, if I still have a right click handler in MAXscript.


Replacing the context menu of the material button is the hardest part, by the way. Because the "clip-boarded" material is not accessible. At least I couldn't find it. Maybe someone knows how to get the "copied" material (and other objects in MAX DAD)?

If you could show me how can I disable browser on materialbutton, it would be great.
To prevent the material button from a browser pop-up when clicked, you need to hook the WinProc of the material button's parent rollout and monitor for WM_COMMAND messages related to the material button. Specifically, you can intercept the WM_LBUTTONUP message to cancel the pop-up behavior. It's important to note that this modification will not affect the drag-and-drop functionality, as it relies on WM_LBUTTONDOWN messages.

 

I changed the scenario... it's better and more simple now.

0 Likes

inquestudios
Enthusiast
Enthusiast

Would be great if you supply some Python code, so I have some starting point to dig into this.

0 Likes

denisT.MaxDoctor
Advisor
Advisor
import pymxs, PySide2
rt = pymxs.runtime

##rt.clearListener()

mxsstr = '''
rollout mbtest "Matbuttons"
(
	materialButton mb1 "Test1" width:80 height:80 across:2
	materialButton mb2 "Test2" width:80 height:80 


	on mbtest open do 
	(
		mb1.material = Standard name:#MAT_RED diffuse:red
		mb2.material = Standard name:#MAT_GRN diffuse:green
	)
)
createdialog mbtest width:200
cui.RegisterDialogBar mbtest
'''


print(mxsstr)
rt.execute(mxsstr)

import ctypes, ctypes.wintypes

_LPARAM = ctypes.wintypes.LPARAM
_WPARAM = ctypes.wintypes.WPARAM
_HWND = ctypes.wintypes.HWND
_UINT = ctypes.wintypes.UINT
_LPCWSTR = ctypes.wintypes.LPCWSTR
_LONG_PTR = _LPARAM
_LRESULT = _LONG_PTR
_LPCWSTR = ctypes.wintypes.LPCWSTR

_WNDPROC = ctypes.WINFUNCTYPE(_LRESULT,  # return Value
                              _HWND,     # First Param, the handle
                              _UINT,     # second Param, message id
                              _WPARAM,   # third param, additional message info (depends on message id)
                              _LPARAM,   # fourth param, additional message info (depends on message id)
)


_SetWindowLongPtr = ctypes.windll.user32.SetWindowLongPtrA
_SetWindowLongPtr.argtypes = (_HWND, ctypes.c_int, _WNDPROC)
_SetWindowLongPtr.restypes = _WNDPROC

_CallWindowProc = ctypes.windll.user32.CallWindowProcA
_CallWindowProc.argtypes = (_WNDPROC, _HWND, _UINT, _WPARAM, _LPARAM)
_CallWindowProc.restypes = _LRESULT


_DefWindowProc = ctypes.windll.user32.DefWindowProcA

'''
@_WNDPROC
def _WndCallback(hwnd, msg, wparam, lparam):
    print(hwnd, msg, wparam, lparam)
    return _CallWindowProc(_old_wndproc, hwnd, msg, wparam, lparam)
'''	
  
# constants
GWLP_WNDPROC = -4
WM_COMMAND = 0x0111

class winproc_hook():
	def __init__(self, parent_hwnd, child_hwnd = None):
		self.window = parent_hwnd
		self.control = child_hwnd
		
		# need to hold a reference to WINFUNCTYPE wrappers
		self.new_winproc = _WNDPROC(self.winproc_monitor)
		self.old_winproc = _WNDPROC(_SetWindowLongPtr(self.window, GWLP_WNDPROC, self.new_winproc))

	def winproc_monitor(self, hWnd, uMsg, wParam, lParam):
		##print (uMsg,wParam,lParam)
		if (uMsg == WM_COMMAND): 
			return _DefWindowProc(hWnd, uMsg, wParam, lParam)
		return _CallWindowProc(self.old_winproc,  hWnd, uMsg, wParam, lParam)
		 
maxWindow = PySide2.QtWidgets.QWidget.find(rt.windows.getMaxHWnd())
def procCh(wd):
    chArr = wd.children()
    for cch in chArr:
        if type(cch) == PySide2.QtWidgets.QDockWidget:
            if cch.windowTitle() == 'Matbuttons':
                return cch
rw = procCh(maxWindow)
for i in rt.mbtest.controls:
    chwnd = i.hwnd[0]
    print('hwnd:{0} control:{1} (by name >> {2}) mat:{3}'.format(chwnd, i, getattr(rt.mbtest, getattr(i, 'name')) ,getattr(i,'Material')))
    ##print(''.format(chwnd, i, '==', getattr(rt.mbtest, getattr(i, 'name')) , 'mat:', getattr(i,'Material'))
stuctArr = rw.children()
class CBrowser(PySide2.QtWidgets.QMainWindow):
    def __init__(self, parent = maxWindow):
        super(CBrowser, self).__init__(parent)
        self.setCentralWidget(stuctArr[5])
        self.resize(850, 600)
cWindow = CBrowser()

xx = winproc_hook (getattr(rt.mbtest, 'hwnd'))

cWindow.show()

 

ok... looks like works... must be cleaned, but it's your problem now 😎

inquestudios
Enthusiast
Enthusiast

Looks a bit menacing right now 😥
Lot of new concepts to take up. But I'm sure this will pay off the effort.
Thank you so much for your help, I'll get back when there are any results or troubles.

0 Likes

denisT.MaxDoctor
Advisor
Advisor

@inquestudios wrote:

Looks a bit menacing right now 😥


I warned you... "as the days grew longer, the storms are stronger" 😉

denisT.MaxDoctor
Advisor
Advisor

@denisT.MaxDoctor wrote:

ok... looks like works... must be cleaned, but it's your problem now 😎


and cleaned more or less:

import pymxs, PySide2
import ctypes, ctypes.wintypes

from PySide2.QtWidgets import QWidget, QMainWindow

user32 = ctypes.windll.user32

rt = pymxs.runtime
MAXWINDOW = QWidget.find(rt.windows.getMaxHWnd())

# constants
GWLP_WNDPROC = -4
WM_COMMAND = 0x0111


LPARAM 	= ctypes.wintypes.LPARAM
WPARAM 	= ctypes.wintypes.WPARAM
HWND 	= ctypes.wintypes.HWND
UINT 	= ctypes.wintypes.UINT
LRESULT = LONG_PTR = LPARAM

WNDPROC = ctypes.WINFUNCTYPE(LRESULT,   # return Value
                              HWND,     # First Param, the handle
                              UINT,     # second Param, message id
                              WPARAM,   # third param, additional message info (depends on message id)
                              LPARAM,   # fourth param, additional message info (depends on message id)
)


SetWindowLong = user32.SetWindowLongPtrA
SetWindowLong.argtypes = (HWND, ctypes.c_int, WNDPROC)
SetWindowLong.restypes = WNDPROC

CallWindowProc = user32.CallWindowProcA
CallWindowProc.argtypes = (WNDPROC, HWND, UINT, WPARAM, LPARAM)
CallWindowProc.restypes = LRESULT

DefWindowProc = user32.DefWindowProcA
DefWindowProc.argtypes = (HWND, UINT, WPARAM, LPARAM)
DefWindowProc.restypes = LRESULT

GetParent = user32.GetParent

class winproc_hook():
	def __init__(self, parent_hwnd, child_hwnd = None):
		self.window = parent_hwnd
		self.control = child_hwnd
		
		# need to hold a reference to WINFUNCTYPE wrappers
		self.new_winproc = WNDPROC(self.winproc_monitor)
		self.old_winproc = WNDPROC(SetWindowLong(self.window, GWLP_WNDPROC, self.new_winproc))

	def winproc_monitor(self, hWnd, uMsg, wParam, lParam):
		##print (uMsg,wParam,lParam, self.control)
		if (uMsg == WM_COMMAND): 
			if (lParam == None) | (lParam == self.control):
				print('WM_COMMAND BLOCKED >>> hwnd:{0} control:{1}'.format(self.window, self.control))
				return DefWindowProc(hWnd, uMsg, wParam, lParam)
		return CallWindowProc(self.old_winproc,  hWnd, uMsg, wParam, lParam)
		
class rollout_holder(QMainWindow):
    def __init__(self, rollout):
		super(rollout_holder, self).__init__(MAXWINDOW)
		self.rollout_hwnd = getattr(rollout,'hwnd')
		self.rollout_host = QWidget.find(long(GetParent(self.rollout_hwnd)))
		_temp_dialog = self.rollout_host.parent()
				
		self.setCentralWidget(self.rollout_host)
		_temp_dialog.close()
		_temp_dialog.deleteLater()
		
		# only firts browser desabled
		controls = getattr(rollout,'controls');
		bt0 = getattr(controls[0],'hwnd')[0] 
		self.hook = winproc_hook(self.rollout_hwnd, bt0)


mxsstr = '''rollout rol "MXS Rollout" autolayoutonresize:on
(
	materialButton mb1 "Test1" width:64 height:64 across:2
	materialButton mb2 "Test2" width:64 height:64 

	on rol open do 
	(
		mb1.material = Standard name:#MAT_RED diffuse:red
		mb2.material = Standard name:#MAT_GRN diffuse:green
	)
)'''

_rol = rt.execute(mxsstr)
rt.createdialog(_rol)
rt.cui.RegisterDialogBar(_rol)

qt_host_wnd = rollout_holder(_rol)
#print (qt_host_wnd.rollout_hwnd, qt_host_wnd.hwnd, rt.windows.getMaxHWnd(), "**")

qt_host_wnd.show()



0 Likes

aikmana123d
Observer
Observer

Hello @denisT.MaxDoctor and everyone,

 

I want to share a cautionary tale about our recent experience with a freelance programmer named Andriy Pogribniy (@inquestudios here), who is seeking advice on this forum about a task he was doing for us.
Andriy, a Ukrainian developer, approached us for a position on our Connecter project. Though we weren't actively looking for new people to join the team, we decided to give him a chance. He mentioned being in a desperate financial situation, claiming he had no money to pay for his internet connection and only "one soup in his fridge". Feeling empathetic, we agreed to his weekly rate and even paid a week in advance to help him. We work with some Ukrainian 3D artists on our designconnected.com project, so I know how tough it is for everyone in this troubled country.
Initially, Andriy was communicative and responsive, promptly signing an NDA and providing his ID. We set up a Trello environment with all the necessary resources and conducted a call to explain our expectations and protocols. He seemed professional and eager to start.
In the beginning, Andriy released the first few iterations of the task on time. However, despite our straightforward, step-by-step resources, he struggled with some basic processes, like adding a new version to Trello. We assumed it was just about him getting familiar with our workflows and allowing him time to adapt.
Four weeks in, all paid timely, we reviewed his work and realized he hadn't followed the requirements. Instead, he completed random parts of the task. We arranged another call with him to emphasize the importance of adhering to our protocols. He apologized, attributing the issues to miscommunication, and assured us things would improve.
However, the following day, we found he had quit without notice, leaving a link to a YouTube meme titled "Do you mind if I leave?" (!) as his only communication. No email, no explanation – just vanished after four weeks of inconsistent work despite being paid regularly.
This lack of professionalism was shocking and unprecedented for us. We warn others to think twice before collaborating with Andriy or offering him advice on forums. Such behavior undermines trust and wastes valuable resources.


Feel free to contact me for verification.
Nick, CEO
n.katsarov[at]designconnected.com
connecterapp.com
designconnected.com
designconnected.agency

0 Likes

denisT.MaxDoctor
Advisor
Advisor

О! I was lucky enough to help and advise for free! 😅

inquestudios
Enthusiast
Enthusiast

You want to make it public? Ok, we can play this game together!
Inconsistent work, seriously? Do you even understand a bit of matters discussed up here to evaluate "inconsistency" of my work? During development process despite weekly reports and my daily demands of giving me adequate feedback, your team only sent me formal replies like "it's ok go on".
And after four weeks of development you eventually dumped me tons of claims and complaints, on the verge of insults and humiliation, because of just ONE lacking feature, while the product was just in the middle of development, no one told it is a final release. As if I'm not a developer working on your behalf, but a mangy cat that blowed in your slippers. During more than 20 years of my professional activities I collarorated with different clients, there was small and humble, like indie developers and students with pet projects, there was huge corporations like Samsung and HP, but none of them has shown so flagrant and snobbish lack of respect to my labor like you did. Working in such toxic and unfriendly environment was completely unacceptable for me, so yes, I just left. That's exacly what remote worker does when he's not happy with an employer. And YOU actually kicked me out of your Trello conference, I did not leave it myself. And YOU actually told me, that it's better to shut this project down than leaving it as is, aren't you? And YOU finally sent me an e-mail containg even more insults and threats, making any further discussion impossible.
You've got every single line of code you paid me for, and this code works as expected, so you have nothing to complain about.
The only problem is that you don't recognize this code real value, but it's not my problem, it's yours.
Next time you better be more polite with developers working on your projects, or you may find yourself with no one around.


P.S. And I never asked you to pay me upfront, it was your own initiative that I never asked for.
I actually asked you to STOP any upfront payments. What I was asking for is to stop fulling arond and give actual task already, because you gave me promise of work and wasted several weeks of time just for nothing. All claims about my bad financial situation was a total truth btw.

P.P.S. And that "Do you mind if I leave" from YouTube is not memetic in any way, this is an arthouse movie clip, it is not well known around. Looks like your ignorance goes far beyond software development.