Hi @unti
From my side, I was also able to reproduce the Default right-click Menu Behavior in R20.
So the main issue regarding that is because you do GUI operation (ShowPopuDialog) into Message function which indeed not thread-safe according to the received message (this was also true in previous versions).
So to counter that, please check your message first and I would say listen to input only when Cinema 4D "invite you" to do it (aka during BFM_INTERACTSTART). Then make sure to call StopAllThread (Maybe another thread is also checking for input and want to display something?).
Then finally regarding your freeze beside the threading issue that could cause some corruption of the event queue. you call ShowPopupDialog with None, while the host GeDialog must be passed. It's needed to ensure the event queue is correctly forwarded from the dialog to the host of the modal popup dialog).
This way I was not anymore capable to make Cinema 4D freeze (you said crash, but personally I was never able to make it crash, only freeze, can you confirm? A crash is when C4D close, a freeze is when C4D hangs forever).
Here a complete example that works nicely on the previous version (R19/R20) and R21.
import c4d
class TestDialog(c4d.gui.GeDialog):
def __init__(self):
self._showPopup = False, 0, 0
def CreateLayout(self):
self.GroupBegin(10002, c4d.BFH_SCALEFIT| c4d.BFH_SCALEFIT, initw=100, inith=100)
self.GroupEnd()
return True
def IsPositionInGuiArea(self, x, y):
# Check click is within the GeDialog
if not 0 < x < self._x:
return False
if not 0 < y < self._y:
return False
return True
def Message(self, msg, result):
if msg.GetId() == c4d.BFM_ADJUSTSIZE:
self._x = msg[3] # Retrieve Y size of the GeDialog
self._y = msg[4] # Retrieve Y size of the GeDialog
# We are on the main thread here
elif msg.GetId() == c4d.BFM_INTERACTSTART:
c4d.StopAllThreads()
state = c4d.BaseContainer()
self.GetInputState(c4d.BFM_INPUT_MOUSE, c4d.BFM_INPUT_MOUSERIGHT, state)
if state.GetInt32(c4d.BFM_INPUT_VALUE) == True:
x = state.GetInt32(c4d.BFM_INPUT_X)
y = state.GetInt32(c4d.BFM_INPUT_Y)
g2l = self.Global2Local()
x += g2l['x']
y += g2l['y']
if self.IsPositionInGuiArea(x, y):
IDM_MENU1 = c4d.FIRST_POPUP_ID
IDM_MENU2 = c4d.FIRST_POPUP_ID + 1
IDM_MENU3 = c4d.FIRST_POPUP_ID + 2
menu = c4d.BaseContainer()
menu.InsData(IDM_MENU1, 'Menu 1')
menu.InsData(IDM_MENU2, 'Menu 2')
menu.InsData(IDM_MENU3, 'Menu 3')
l2s = self.Local2Screen()
print str(x+l2s['x']) + " :: " + str(y+l2s['y'])
self.KillEvents()
res = c4d.gui.ShowPopupDialog(cd=self, bc=menu, x=x+l2s['x'], y=y+l2s['y'])
return True
return c4d.gui.GeDialog.Message(self, msg, result)
if __name__ == '__main__':
global dlg
dlg = TestDialog()
dlg.Open(dlgtype=c4d.DLG_TYPE_ASYNC)
Finally, there was some change in the UI in R21 regarding how events are handled, to be brief previously Cinema 4D consumes OS events, while now it only listens to them but Cinema 4D UI was and is always processed in the main Thread and was never thread-safe (that's why some operations are not possible in a threaded environment) because the c4d API ensure you are in the correct thread before doing anything GUI related stuff.
Regarding your other issue about the console, as you may be aware R20 comes with a new Logger System.
And now the console only reads content from their Logger System.
The logger by itself is properly fed with data during a drag event (drag event = GUI interaction = main thread).
But as you are now aware drawing is done also in the main thread so if you do a While XXX in the dragging process the main thread doesn't have room to jump to the console drawing part.
Previously the logging (GePrint, Print) was also done in the main thread, so that means a redraw was done directly, but this slowdown a lot Cinema 4D UI.
So in R21, the data are correctly added with the correct time in the logger but it's just not displayed because now print only adds data to the logger. And the logger is displayed when its draw request has time to be processed in the main thread.
Not sure if it's clear, but just to recap, previously the console was refreshed in each print while now it requests redraw and redraw is done when the main thread got the time. (This is why previously printing 1000 value in the console was really slow and now pretty fast).
EDIT 11/03/20:
You could directly access the logger and passing the maxon.WRITEMETA.UI_SYNC_DRAW to force a redraw
import maxon
txt = "My Wonderfull Text"
pythonLogger = maxon.Loggers.Python()
pythonLogger.Write(maxon.TARGETAUDIENCE.ALL, txt, maxon.MAXON_SOURCE_LOCATION(1), maxon.WRITEMETA.UI_SYNC_DRAW)
Cheers,
Maxime.