SetString() to Update Dialog crash c4d
-
Hi,
i use a CommandData plugins to creat a button to active my tool plugins,and use tool.Message(123) to pass messages to tool plugins and refresh the gui parameters of tool plugins.
In practice it is:
When I click the button, the tool is activated, and some str parameters of the tool are set, and then the changed parameters are displayed. As shown below image1, I will set some strings to these 2 gui elementsimage1:
Here's a screen recording showing c4d crashing:
I tried to find the reason, I found that c4d does not crash when I activate the tool by click button, without setting the parameters of the tool!
two plugins use python!
Thanks for any help! -
Hey @chuanzhen,
Thank you for reaching out to us. While I understand the principal problem you have, I cannot give any concrete advice without the code. Please share a minimal setup of your project. You can use
sdk_support(at)maxon(dot)net
in case you do not want to make the code public.One thing though, you said you invoke
tool.Message(123)
which sounds dangerous. You should not send random message IDs and random data in that manner, as this can easily trip the C++ backend. You must at least use a unique plugin ID. I personally would recommend sending data withMSG_BASECONTAINER
though, assuming this is what you want to do here, as there are much less things which can go wrong this way.I am not even sure if sending data over a raw plugin ID is supported, at least in my quick test
t_data
wasNone
. You should of course also adhere to the threading restrictions, and based on what you do in yourToolData.Message
, you should either make sure that you are on the main thread when you do something that should only be done on the main thread, or alternatively make sure to invokemyToolData.Message
only from the main thread on the other end. But in aCommandData
you should most of the time be on the main thread anyways.Find a quick sketch of what I did at the end of the posting. Other than that, I can only recommend providing a code example to us.
Cheers,
FerdinandThe result:
The sending part, here as a script manger scrip:
import c4d def main() -> None: """ """ idSdkToolPlugin: int = 1054512 # The "Tool with GUI" ToolData plugin from the Python SDK idMyCommandDataPlugin: int = 1234567890 # A fictitious CommandData plugin to send data from. # Get the BasePlugin for the tool. myTool: c4d.plugins.BasePlugin = c4d.plugins.FindPlugin(idSdkToolPlugin, c4d.PLUGINTYPE_TOOL) if myTool is None: return # Setup some data to send. data: c4d.BaseContainer = c4d.BaseContainer(idMyCommandDataPlugin) data[0] = "Hello World" data[1] = 42 # Send the data. if not myTool.Message(c4d.MSG_BASECONTAINER, data): raise RuntimeError("Sending data failed.") print ("Sent data to tool.") if __name__ == "__main__": main()
The receiving part in the
ToolData
plugin:def Message(self, doc: c4d.documents.BaseDocument, data:c4d.BaseContainer, mid: int, t_data: any) -> bool: """Called when the tool receives messages. """ idMyCommandDataPlugin: int = 1234567890 # Check for a BaseContainer coming in with the ID of the CommandData plugin. if (mid == c4d.MSG_BASECONTAINER and isinstance(t_data, c4d.BaseContainer) and t_data.GetId() == idMyCommandDataPlugin): print (t_data[0]) print (t_data[1]) return True
-
Hey @ferdinand ,
Using the following code, the crash will also occurtest commanddata plugins code
import c4d import os import sys from c4d import gui,bitmaps,plugins,utils,modules,storage PLUGIN_ID = 10000000 #only for test SUB_DIALOG_ID = 1000001 SUB_DIALOG_GONE = 5000 SUB_DIALOG_GONE_NAMETEXT = 5001 SUB_DIALOG_GONE_NAME = 5002 SUB_DIALOG_GONE_ANIGROUP = 5003 SUB_DIALOG_GONE_ANIGROUP_ALLRANGE = 5004 SUB_DIALOG_GONE_ANIGROUP_PRERANGE = 5005 SUB_DIALOG_GTWO = 5010 SUB_DIALOG_GTWO_EDITIMAGE = 5011 SUB_DIALOG_GTWO_CUTIMAGE = 5012 SUB_DIALOG_IMAGE = 5020 SUB_DIALOG_SAVE = 5021 #active tool def UseSreenShot(active=False,path=None,name=None,size=None,cutimage=False,child_path="image"): tool_id = 1000010 doc = c4d.documents.GetActiveDocument() tool = plugins.FindPlugin(tool_id, c4d.PLUGINTYPE_TOOL) data = plugins.GetToolData(doc, tool_id) if active: doc.SetAction(tool_id) #c4d.CallCommand(1059888) # S_ScreenShot if path: data[1] = os.path.join(path,child_path) if name: data[2] = name if size: data[3] = size if cutimage: tool.Message(tool_id) # else: tool.Message(tool_id) # # class S_Add_Dialog(gui.GeDialog): name = None path = None size = None mode = 0 ua = None def CreateLayout(self): if self.mode == 3: self.SetTitle("Setting") else: self.SetTitle("New ") self.GroupBegin(SUB_DIALOG_GONE,c4d.BFH_SCALEFIT,3,1) self.AddStaticText(SUB_DIALOG_GONE_NAMETEXT,c4d.BFH_LEFT,100,10,"Name:") self.AddEditText(SUB_DIALOG_GONE_NAME,c4d.BFH_SCALEFIT,150,20) if self.mode == 1: # self.AddRadioGroup(SUB_DIALOG_GONE_ANIGROUP,c4d.BFH_RIGHT,2,1) self.AddChild(SUB_DIALOG_GONE_ANIGROUP,SUB_DIALOG_GONE_ANIGROUP_ALLRANGE,"All Range") self.AddChild(SUB_DIALOG_GONE_ANIGROUP, SUB_DIALOG_GONE_ANIGROUP_PRERANGE, "Preview Range") self.SetInt32(SUB_DIALOG_GONE_ANIGROUP,SUB_DIALOG_GONE_ANIGROUP_ALLRANGE) self.GroupEnd() self.GroupBegin(SUB_DIALOG_GTWO,c4d.BFH_SCALEFIT,1,1) self.AddButton(SUB_DIALOG_GTWO_CUTIMAGE,c4d.BFH_SCALEFIT,150,30,"Get Image") #self.AddButton(SUB_DIALOG_GTWO_CUTIMAGE, c4d.BFH_SCALEFIT, 150, 30, "Get Image") self.GroupEnd() #self.AddUserArea(SUB_DIALOG_IMAGE,c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT,100,100) #self.ua = UserArea_forAdd() #self.AttachUserArea(self.ua,SUB_DIALOG_IMAGE) self.AddButton(SUB_DIALOG_SAVE, c4d.BFH_SCALEFIT, 150, 30, "Save") return True def Command(self, id, msg): return True class S_Lib_Dialog(gui.GeDialog): sub_dialog = S_Add_Dialog() def CreateLayout(self): self.SetTitle("test") self.AddButton(1001,c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT,150,30,"test") return True def Command(self, id, msg): if id == 1001: #SUB_DIALOG_ID = 1000001 UseSreenShot(active=True, path='AAAA', name='BBB') # active tool,and send message self.sub_dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC,pluginid=PLUGIN_ID, defaultw=100, defaulth=100,subid=SUB_DIALOG_ID) return True def Restore(self, pluginid, secret): if secret['subid'] == SUB_DIALOG_ID: return self.sub_dialog.Restore(pluginid, secret) else: return super(S_Lib_Dialog, self).Restore(pluginid, secret) class S_Lib(plugins.CommandData): dialog = None def Execute(self,doc): # create the dialog if self.dialog is None: self.dialog = S_Lib_Dialog() return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=PLUGIN_ID, defaultw=800, defaulth=400) def RestoreLayout(self, sec_ref): # manage nonmodal dialog if self.dialog is None: self.dialog = S_Lib_Dialog() return self.dialog.Restore(pluginid=PLUGIN_ID, secret=sec_ref) # Execute main() if __name__=='__main__': path, fn = os.path.split(__file__) plugins.RegisterCommandPlugin(id=PLUGIN_ID, str="test command", info=0, help="", dat=S_Lib(), icon=None)
test tool plugins code:
import c4d import os from c4d import gui, plugins, bitmaps PLUGIN_ID = 1000010 #only for test def GetSafeWindow_Center(): doc = c4d.documents.GetActiveDocument() bd = doc.GetActiveBaseDraw() window_dict = bd.GetFrame() window_l = window_dict['cl'] window_r = window_dict['cr'] window_t = window_dict['ct'] window_b = window_dict['cb'] return bd, window_l, window_r, window_t, window_b def Draw2dbox(bd, x_min, x_max, y_min, y_max): p1 = c4d.Vector(x_min, y_min, 0) p2 = c4d.Vector(x_max, y_min, 0) p3 = c4d.Vector(x_max, y_max, 0) p4 = c4d.Vector(x_min, y_max, 0) bd.DrawLine2D(p1, p2) bd.DrawLine2D(p2, p3) bd.DrawLine2D(p3, p4) bd.DrawLine2D(p4, p1) def DrawBox(width_height, offset=c4d.Vector(0, 0, 0), color=c4d.Vector(1, 0, 0)): # draw box bd, safe_window_l, safe_window_r, safe_window_t, safe_window_b = GetSafeWindow_Center() safe_center = ((safe_window_r + safe_window_l) / 2 + offset[0], (safe_window_b + safe_window_t) / 2 + offset[1]) # width_half = width_height[0] / 2 height_half = width_height[1] / 2 x_min = safe_center[0] - width_half x_max = safe_center[0] + width_half y_min = safe_center[1] - height_half y_max = safe_center[1] + height_half # bd.SetMatrix_Screen() bd.SetPen(color) bd.DrawPoint2D(c4d.Vector(safe_center[0], safe_center[1], 0)) Draw2dbox(bd, x_min, x_max, y_min, y_max) class S_ScreenShot_Dialog(gui.SubDialog): parameters = None def __init__(self, para_in): self.parameters = para_in def update(self,command=False): if command: #update gui parameter if self.parameters['path']: self.SetString(1012,self.parameters['path']) if self.parameters['name']: self.SetString(1014,self.parameters['name']) def Command(self,id,msg): if id == 1033: return True if id == 1022 or id == 1032: # self.parameters['width'] = self.GetInt32(1022) self.parameters['height'] = self.GetInt32(1032) c4d.DrawViews(c4d.DRAWFLAGS_ONLY_BASEDRAW) return True if id == 1021: # self.SetInt32(1032, self.GetInt32(1022)) self.parameters['width'] = self.GetInt32(1022) self.parameters['height'] = self.GetInt32(1032) c4d.DrawViews(c4d.DRAWFLAGS_ONLY_BASEDRAW) return True if id == 1031: # self.SetInt32(1022, self.GetInt32(1032)) self.parameters['width'] = self.GetInt32(1022) self.parameters['height'] = self.GetInt32(1032) c4d.DrawViews(c4d.DRAWFLAGS_ONLY_BASEDRAW) return True if id == 1051: # self.parameters['offset'] = c4d.Vector(0) c4d.DrawViews(c4d.DRAWFLAGS_ONLY_BASEDRAW) return True if id == 1012: # self.parameters['path'] = self.GetString(1012) return True if id == 1034: # self.parameters['showbmp'] = self.GetBool(1034) return True return True def CreateLayout(self): self.GroupBegin(1010, c4d.BFH_SCALEFIT, 2, 1) self.GroupBorderSpace(10, 10, 10, 10) self.AddStaticText(1011, c4d.BFH_MASK, initw=50, inith=0, name="Path:") bc = c4d.BaseContainer() bc.SetBool(c4d.FILENAME_DIRECTORY,True) self.AddCustomGui(1012, c4d.CUSTOMGUI_FILENAME,"",c4d.BFH_SCALEFIT, 50, 20, bc) self.AddStaticText(1013, c4d.BFH_MASK, initw=50, inith=0, name="Name:") self.AddEditText(1014,c4d.BFH_MASK, initw=200, inith=0) self.GroupEnd() self.GroupBegin(1020, c4d.BFH_SCALEFIT, 2, 1) self.GroupBorderSpace(10, 10, 10, 10) self.GroupBegin(1100, c4d.BFH_SCALEFIT, 2, 1) self.AddButton(1021, c4d.BFH_MASK, initw=0, inith=0, name="Width:") self.AddEditNumberArrows(1022, c4d.BFH_MASK, initw=70, inith=0) self.AddButton(1031, c4d.BFH_MASK, initw=0, inith=0, name="Height") self.AddEditNumberArrows(1032, c4d.BFH_MASK, initw=70, inith=0) self.GroupEnd() self.AddButton(1051, c4d.BFH_LEFT, initw=100, inith=20, name="Center") self.GroupEnd() self.AddButton(1033, c4d.BFH_MASK, 100, 20, "Cut") self.AddCheckbox(1034,c4d.BFH_LEFT,100,10,"Show In Picture View ") return True def InitValues(self): self.SetInt32(1022, self.parameters['width']) self.SetInt32(1032, self.parameters['height']) self.SetBool(1034,self.parameters['showbmp']) if self.parameters['path']: self.SetString(1012, self.parameters['path']) return True class S_ScreenShot(plugins.ToolData): subdialog = None def __init__(self): self.data = {'width':512,'height':512,'offset':c4d.Vector(0),'path':'','name':'','showbmp':False} self.subdialog = S_ScreenShot_Dialog(self.data) #self.last_offset = c4d.Vector(0) def set_para_self(self,data): # if data[1]: self.data['path'] = data[1] # path if data[2]: self.data['name'] = data[2] # name def Message(self,doc, data, type, t_data): if type == PLUGIN_ID: #get commanddata plugins message self.set_para_self(data) # update tool gui parameter self.subdialog.update(True) return True return True def Draw(self, doc, data, bd, bh, bt, flags): DrawBox((self.data['width'],self.data['height']), self.data['offset']) return c4d.TOOLDRAW_NONE def MouseInput(self, doc, data, bd, win, msg): mx = msg[c4d.BFM_INPUT_X] my = msg[c4d.BFM_INPUT_Y] device = 0 #print msg[c4d.BFM_INPUT_CHANNEL],c4d.BFM_INPUT_MOUSELEFT,c4d.BFM_INPUT_MOUSERIGHT,c4d.BFM_INPUT_MOUSEMIDDLE if msg[c4d.BFM_INPUT_CHANNEL] == c4d.BFM_INPUT_MOUSELEFT: device = c4d.KEY_MLEFT elif msg[c4d.BFM_INPUT_CHANNEL] == c4d.BFM_INPUT_MOUSERIGHT: device = c4d.KEY_MRIGHT elif msg[c4d.BFM_INPUT_CHANNEL] == c4d.BFM_INPUT_MOUSEMIDDLE: device = c4d.KEY_MMIDDLE else: return True dx = 0.0 dy = 0.0 win.MouseDragStart(button=device, mx=int(mx), my=int(my), flags=c4d.MOUSEDRAGFLAGS_DONTHIDEMOUSE | c4d.MOUSEDRAGFLAGS_NOMOVE) result, dx, dy, channel = win.MouseDrag() while result == c4d.MOUSEDRAGRESULT_CONTINUE: mx += dx my += dy # continue if user doesnt move the mouse anymore if dx == 0.0 and dy == 0.0: result, dx, dy, channel = win.MouseDrag() continue if msg[c4d.BFM_INPUT_QUALIFIER] & c4d.QSHIFT: self.data['offset'] = c4d.Vector(dx, dy, 0) + self.data['offset'] if msg[c4d.BFM_INPUT_QUALIFIER] & c4d.QCTRL: # scale = dx * 0.0005 + 1.0 self.data['width'] *= scale self.data['height'] *= scale self.subdialog.update() #set camera parameter c4d.DrawViews(c4d.DA_ONLY_ACTIVE_VIEW | c4d.DA_NO_THREAD) result, dx, dy, channel = win.MouseDrag() return True def AllocSubDialog(self, bc): self.subdialog = S_ScreenShot_Dialog(self.data) return self.subdialog #always return new instance if __name__ == "__main__": dir, file = os.path.split(__file__) plugins.RegisterToolPlugin(id=PLUGIN_ID, str="tool_test", info=0, icon=None, help="", dat=S_ScreenShot())
This is the screen recording of the crash of the above code example:
-
Hi,
This is what is happening:
- you have your tool active, the SubDialog is created, and everything works fine.
- you switch to the move tool, an object inside the SubDialog is destroyed but not the SubDialog itself. You cannot use the subdialog anymore.
- you use the CommandData to activate the tool, snd data and try to update the DialogBox
- this is where it crashes because the SubDialog is not in a state allowing to use any GeDialog functionalities.
After searching a solution for a long time, it seems not possible to catch an event and/or check if the SubDialog can be used or not.
SetAction or GetAction to check if the current tool is yours cannot be used as it do not reflect the status of the SubDialog.
i have implemented the CoreMessage function to the SubDialog to catch the eventEVMSG_TOOLCHANGED
but that do not change anything.My next move would be to create the toolData in c++ using DescriptionToolData or even a toolData because there are some functions in c++ to check if the GeDialog is initialized or not.
Cheers
Manuel -
@m_magalhaes Thanks for the detailed explanation as to why it crashed!
-