Solved c4d.gui.QuestionDialog() in Python plugin makes c4d crash every time

Hi there,

I'm working on a simple Tag plugin in Python, which must run under R21.

On the plugin itself, I'm checking inside Execute() if there is a change on the FILENAME field of my GUI. If there is a change, I'm checking the file extension and depending on this I'm proceeding to a conversion upon the agreement of the user to a question.

But... if seems that the simple call to c4d.gui.QuestionDialog() makes c4d crash instantly.

That's the facts.

Honestly, I'm using an identical call to this function elsewhere in the code in Message() without any issue.

Can this crash occurs because I'm using this in Execute() or can this be related to something I'm doing in a wrong way?
Of course I've check the documentation but I didn't notice anything relevant.

Here the excerpt of the code that makes c4d crash.

import sys
import os

import c4d
import time
import json

from c4d import gui, plugins, bitmaps

#../.. Inside the class which define the Tag plug-in


    def __init__ (self):
        pass



    def Init(self, node):
        return True


     def Execute(self, tag, doc, op, bt, priority, flags):

        # .../...

        currentFILE = self.currentTagData.GetFilename(c4d.mMOTIONFILE_PATH)
        if self.currentTagData.GetFilename(c4d.mMOTIONFILE_PATH) != self.previousFILE:
            self.previousFILE = currentFILE

            if currentFILE.lower().endswith(".json"): # Is a JSON?
                return True
            elif currentFILE.lower().endswith(".csv"): # Is a CSV?
                # CRASH HERE - Ask to convert the CSV file into a JSON one
                askConvertToJSON = c4d.gui.QuestionDialog("Would you like to convert this file to a compatible JSON?")
                if not askConvertToJSON:
                    return False
                return True
            else:
                raise ValueError("This file extensoin is not supported, please load a compatible JSON or CSV file")
                return False
        
        return True

Any helps are more than welcome.
Thanks!

Hello @mocoloco,

yes, finding the correct message to look for can be a bit tricky. In this case you should listen for MSG_DESCRIPTION_POSTSETPARAMETER, i.e., the message for when some parameter of a node has changed its value. You will find an example as a Python programming tag at the end of the posting which will translate quite directly to NodeData.Message(). It is only that the signature of the message function in a programming tag is slightly different.

Cheers,
Ferdinand

The scene file:
example.c4d

The code:

"""Example for reacting to parameter changes in a NodeData node.
"""

import c4d

def message(mid, mdata):
    """The equivalent to NodeData.Message().

    The signature of NodeData.Message() is a bit different, as it has slightly 
    different arguments and requires you to return something else than None.
    
    Args:
        mid (int): The message type
        mdata (any): The message data.
    """
    if mid == c4d.MSG_DESCRIPTION_POSTSETPARAMETER:

        # Here we are in a scripting object with a user data parameter, so
        # the parameter id is a bit more complex. We are checking if the
        # raising element was the element with the id (c4d.ID_USERDATA, 1),
        # i.e., the first user data element. In your case you would have to
        # only check the first DescLevel in the DescID passed as the message
        # data for MSG_DESCRIPTION_POSTSETPARAMETER.
        descId = mdata["descid"]
        if descId[0].id == c4d.ID_USERDATA and  descId[1].id == 1:
            # When we are in the main-thread, open a dialog.
            if c4d.threading.GeIsMainThread():
                # Op is predefined in a scripting object. In case of a
                # programming tag it is the tag itself. We simply query the
                # tag for the value with the DescId which has been sent has
                # the message data.
                file = op[descId]
                c4d.gui.MessageDialog("The the file is: {}".format(file))
def main():
    pass

MAXON SDK Specialist
developers.maxon.net

Hello @mocoloco,

welcome to the forum and the Cinema 4D community and thank you for reaching out to us.

The reason why your plugin is not working, is that your program design is a violation of Cinema 4D's threading restrictions. TagData.Execute is not being called from the main thread, i.e., is being called in parallel. Which makes GUI operations, e.g., opening a dialog, off limits there. For more information on things that are forbidden to do outside of the main thread, please read the mentioned article about threading restrictions. Since your code also does indicate that you are planning to do file operations in TagData.Execute: This will also cause problems when multiple threads try to operate on the same file.

You have move such operations to a method of NodeData which you know is being called (at least sometimes) on the main thread. A common candidate is NodeData.Message and listening for a button click. There you can then check for being on the main thread with c4d.threading.GeIsMainThread() and then do anything that would be unsafe to do in asynchronous execution, like modifying the scene graph or doing GUI operations.

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net

Thanks @ferdinana for your message and informations. As it is my first plugin I didn't dig that much in SDK and didn't noticed that..

My first try was to get that information on def Message(), but FILENAME declared on .res when clicked, doesn't fired any message while all other buttons are. This one is not maybe due to the fact that's not really a button, I'm gonna check if an another TYPE is changed with this particular FILENAME.

I will read all this and see if I can figured out with this.

Thanks a lot,
Christophe

After some test, I didn't find what message in NodeData check to see if there is any file change on the FILENAME GUI Item ID XXXX. That's frustrating.

I checked the following, that sounds relevant, but they unfortunetly aren't

    def Message(self, node, type, data):
        if type == c4d.MSG_DESCRIPTION_CHECKUPDATE:
        
        if type == c4d.MSG_DESCRIPTION_VALIDATE:
        
        if type == c4d.MSG_DESCRIPTION_EDIT_ENTRY:

Scanning datas and types didn't helped neither.

I'm just looking for a way to get a acknowledgment from the user prior doing an operation on the file selected... I thought that it could be simpler. If anybody ave a workaround for this and will to share it, please let me know!

Hello @mocoloco,

yes, finding the correct message to look for can be a bit tricky. In this case you should listen for MSG_DESCRIPTION_POSTSETPARAMETER, i.e., the message for when some parameter of a node has changed its value. You will find an example as a Python programming tag at the end of the posting which will translate quite directly to NodeData.Message(). It is only that the signature of the message function in a programming tag is slightly different.

Cheers,
Ferdinand

The scene file:
example.c4d

The code:

"""Example for reacting to parameter changes in a NodeData node.
"""

import c4d

def message(mid, mdata):
    """The equivalent to NodeData.Message().

    The signature of NodeData.Message() is a bit different, as it has slightly 
    different arguments and requires you to return something else than None.
    
    Args:
        mid (int): The message type
        mdata (any): The message data.
    """
    if mid == c4d.MSG_DESCRIPTION_POSTSETPARAMETER:

        # Here we are in a scripting object with a user data parameter, so
        # the parameter id is a bit more complex. We are checking if the
        # raising element was the element with the id (c4d.ID_USERDATA, 1),
        # i.e., the first user data element. In your case you would have to
        # only check the first DescLevel in the DescID passed as the message
        # data for MSG_DESCRIPTION_POSTSETPARAMETER.
        descId = mdata["descid"]
        if descId[0].id == c4d.ID_USERDATA and  descId[1].id == 1:
            # When we are in the main-thread, open a dialog.
            if c4d.threading.GeIsMainThread():
                # Op is predefined in a scripting object. In case of a
                # programming tag it is the tag itself. We simply query the
                # tag for the value with the DescId which has been sent has
                # the message data.
                file = op[descId]
                c4d.gui.MessageDialog("The the file is: {}".format(file))
def main():
    pass

MAXON SDK Specialist
developers.maxon.net

Wow, I couldn't have though that I have to watch c4d.MSG_DESCRIPTION_POSTSETPARAMETER.
I'm starting to understand the way to use threading as well, thanks a lot for the help and tips, all is working perfectly now.