Solved 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 elements

image1:
test.jpg

Here's a screen recording showing c4d crashing:bug.gif

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!

相信我,可以的!

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 event EVMSG_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

MAXON SDK Specialist

MAXON Registered Developer

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 with MSG_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 was None. You should of course also adhere to the threading restrictions, and based on what you do in your ToolData.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 invoke myToolData.Message only from the main thread on the other end. But in a CommandData 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,
Ferdinand

The result:
3accec83-d315-4858-a3a4-94849f0a7d3e-image.png

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

MAXON SDK Specialist
developers.maxon.net

Hey @ferdinand ,
Using the following code, the crash will also occur

test 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:

bug2.gif

相信我,可以的!

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 event EVMSG_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

MAXON SDK Specialist

MAXON Registered Developer

@m_magalhaes Thanks for the detailed explanation as to why it crashed!

相信我,可以的!