SOLVED Add button to run a script

Thank you. Yes, the goal is to be able to execute multiple commands in one plugin so I was trying to find a way to connect the GeDialog with some commands.

I am new to programming and I have read through the GeDialog documentation and I can not figure out a way to connect the two concepts.

Is there some example code that creates a few buttons that run some commands?

Thank you for your help, I appreciate it.

@stereo_stan

As Ferdinand already said, you forgot to add the AddButton call, or rather (as I can see my line self.AddButton(ID_BUTTON3, c4d.BFH_SCALEFIT, name="Jump into code") in the script), you inserted it in the wrong place (it's now inside a comment section, where it doesn't do anything, and also in the wrong method.

Additionally, I need to tell you that you must define ID_BUTTON3 somewhere, or it will raise an error. And the snippet if ID == ID_BUTTON3: will not work since the parameter ID that I used is actually called messageId in your command method. Oops, and you duplicated the whole command method so now you have two of them and Python will only consider the latter and ignore the first.

Okay, I guess Maxon hates me for self promoting here, but this is not so much an API question now but a matter of plain Python understanding, so you may perhaps consider my Python/C4D API course under
https://www.patreon.com/cairyn
In section 10 I have multiple examples for dialog setup with layout, buttons, command method, text fields, radio buttons, separators, columns, etc.; too much to replicate here.

I have corrected the script for you a final time, removing all the comments and reducing the sample to the most necessary stuff:

import c4d
from c4d import gui

PLUGIN_ID = 1057171
ID_BUTTON3 = 1001

class ExampleDialog(c4d.gui.GeDialog):

    def CreateLayout(self):
        self.SetTitle("This is an example Dialog")
        self.AddButton(ID_BUTTON3, c4d.BFH_SCALEFIT, name="Jump into code")
        self.AddDlgGroup(c4d.DLG_OK | c4d.DLG_CANCEL)
        return True

    def Command(self, messageId, bc):
        if messageId == c4d.DLG_OK:
            print("User Click on yep")
            return True

        elif messageId == ID_BUTTON3:
            print ("Button clicked")
            return True    

        elif messageId == c4d.DLG_CANCEL:
            print("User Click on Cancel")
            self.Close()
            return True

        return True

class ExampleDialogCommand(c4d.plugins.CommandData):
    dialog = None
    
    def Execute(self, doc):
        if self.dialog is None:
            self.dialog = ExampleDialog()
        return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=PLUGIN_ID, defaultw=400, defaulth=32)

    def RestoreLayout(self, sec_ref):
        if self.dialog is None:
            self.dialog = ExampleDialog()

        return self.dialog.Restore(pluginid=PLUGIN_ID, secret=sec_ref)

if __name__ == "__main__":
    c4d.plugins.RegisterCommandPlugin(id=PLUGIN_ID,
                                      str="Py-CommandData Dialog",
                                      info=0,
                                      help="Display a basic GUI",
                                      dat=ExampleDialogCommand(),
                                      icon=None)

827c6608-acfc-4a41-b73c-a3348488d297-image.png

Hello @stereo_stan,

thank you for reaching out to us. I am not one hundred percent sure how your question is meant.

Scripts in the Python Scripting Manger in Cinema 4D can have their own button out of the box, you can even define the icon of that button. For details, please refer to the Icon section the Script Manger User Manual.

In case your question was more about execution, @Cairyn already gave you most of the info (thanks!). In principle you would do this with a CommandData or a NodeData plugin, depending on what you have in mind exactly. Define there a STRING element in their interface definition and flag it as being MULTISTRING and Python. Also add a button to the interface. Then, in your implementation, listen for the button being clicked and load the Python code from the code window you did create. You could of course also add logic to populate that code window from a file or cut out the code window altogether and always execute files. Executing arbitrary modules is more a Python than a Python SDK question and therefore out of scope of support, but I have shown it here. There are multiple libraries in the standard Python library you can use for this, runpy is the least complicated of them.

Cheers,
ferdinand

@stereo_stan I suppose you don't mean to run an external script (Python allows this through Execute and Evaluate functions) since you're a beginner. You just want to call a functionality from your script (the same). So, you just need the dialog.

The Github example (you found these, yes?) Memory Viewer contains a dialog that is called as part of the CommandData plugin. Let me quote this partially here (as it is (C)Maxon stuff anyway 😉 )

class MemoryViewerCommandData(c4d.plugins.CommandData):
    """
    Command Data class that holds the MemoryViewerDialog instance.
    """
    dialog = None

    def Execute(self, doc):
        """
        Called when the user Execute the command (CallCommand or a clicks on the Command from the plugin menu)
        :param doc: the current active document
        :type doc: c4d.documents.BaseDocument
        :return: True if the command success
        """
        # Creates the dialog if its not already exists
        if self.dialog is None:
            self.dialog = MemoryViewerDialog()

        # Opens the dialog
        return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=PLUGIN_ID, defaulth=400, defaultw=400)

    def RestoreLayout(self, sec_ref):
        """
        Used to restore an asynchronous dialog that has been placed in the users layout.
        :param sec_ref: The data that needs to be passed to the dlg (almost no use of it).
        :type sec_ref: PyCObject
        :return: True if the restore success
        """
        # Creates the dialog if its not already exists
        if self.dialog is None:
            self.dialog = MemoryViewerDialog()

        # Restores the layout
        return self.dialog.Restore(pluginid=PLUGIN_ID, secret=sec_ref)

This CommandData is relatively simple, it just opens your dialog. Noteworthy are that you need to open the dialog as DLG_TYPE_ASYNC, which will allow you to work in C4D while the dialog is open; and the implementation of a RestoreLayout function which C4D uses to open a dialog that is embedded in the layout.

The example also contains the MemoryViewerDialog class which is used in this CommandData, but you may not want to study that as it shows a number of different techniques (like GeUserArea) that you don't want yet, so I don't quote it. However, I saw in your other thread that you can construct a dialog class already, so I guess you do not need further pointers.

To get a button into your dialog, use AddButton in your dialog's CreateLayout method as often as you like, like that:
self.AddButton(ID_BUTTON3, c4d.BFH_SCALEFIT, name="Jump into code")
then evaluate the commands from that button in the dialog's Command method

    def Command(self, id, msg):
        if id==ID_BUTTON3:
            print ("Button clicked")
        return True

Thank you Cairyn and Ferdinand, this is all very helpful and I have a better understanding of different ways to approach this type of project.

Yes, I do not think I want to run an external script, just have one script that a user can perform a few different commands from. Essentially so they can just have one command icon ( script ) in their interface and when they click on that it opens up a GeDialog with a bunch of options they can run by clicking on buttons inside of the GeDialog vs having to click on a bunch of different scripts. I assume those buttons are essentially other scripts that are all bundled together that they would drop in their scripts folder.

I am so excited about all of the possibilities of adding programming into my daily work as a c4d animator-it is challenging to learn from a non-programming background but this forum has helped a lot.

Thanks again.

Hello @stereo_stan

unless I am overlooking something here, you can then simply drag and drop the script into one of Cinema's palettes as described in my previous posting. No extra steps required.

Cheers,
Ferdinand

One more question on this topic. I have tried to combine Cairyn's note on creating a button with the py-commanddata_dialogr13 script from Maxon github to create an async dialog.

For some reason, it will not add my button to the dialog-basically just trying to add buttons to the py-commanddata script. Do you have any possible suggestions on making something like this work? Basically would like an async dialog with buttons. Thank you for any advice.

"""
Copyright: MAXON Computer GmbH
Author: Maxime Adam

Description:
    - Creates a Dialog which display 2 buttons OK and Cancel.

Class/method highlighted:
    - c4d.plugins.CommandData
    - CommandData.Execute()
    - c4d.gui.GeDialog
    - GeDialog.CreateLayout()
    - GeDialog.Command()
"""
import c4d
from c4d import gui


# Be sure to use a unique ID obtained from www.plugincafe.com
PLUGIN_ID = 1057171



class ExampleDialog(c4d.gui.GeDialog):

    def CreateLayout(self):
        """This Method is called automatically when Cinema 4D Create the Layout (display) of the Dialog."""
        # Defines the title of the Dialog
        self.SetTitle("This is an example Dialog")

        # Creates a Ok and Cancel Button
        self.AddDlgGroup(c4d.DLG_OK | c4d.DLG_CANCEL)

        

        return True

    def Command(self, messageId, bc):
        """This Method is called automatically when the user clicks on a gadget and/or changes its value this function will be called.
        It is also called when a string menu item is selected.

        self.AddButton(ID_BUTTON3, c4d.BFH_SCALEFIT, name="Jump into code")

        Args:
            messageId (int): The ID of the gadget that triggered the event.
            bc (c4d.BaseContainer): The original message container.

        Returns:
            bool: False if there was an error, otherwise True.
        """
        # User click on Ok buttonG
        if messageId == c4d.DLG_OK:
            print("User Click on yep")
            return True

        if ID == ID_BUTTON3:
            print ("Button clicked")
            return True    

        # User click on Cancel button
        elif messageId == c4d.DLG_CANCEL:
            print("User Click on Cancel")

            # Close the Dialog
            self.Close()
            return True

        return True

    def Command(self, id, msg):
        if id == ID_BUTTON3:
            print ("Button clicked")
            return True    


class ExampleDialogCommand(c4d.plugins.CommandData):
    """Command Data class that holds the ExampleDialog instance."""
    dialog = None
    
    def Execute(self, doc):
        """Called when the user executes a command via either CallCommand() or a click on the Command from the extension menu.

        Args:
            doc (c4d.documents.BaseDocument): The current active document.

        Returns:
            bool: True if the command success.
        """
        # Creates the dialog if its not already exists
        if self.dialog is None:
            self.dialog = ExampleDialog()

        # Opens the dialog
        return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=PLUGIN_ID, defaultw=400, defaulth=32)

    def RestoreLayout(self, sec_ref):
        """Used to restore an asynchronous dialog that has been placed in the users layout.

        Args:
            sec_ref (PyCObject): The data that needs to be passed to the dialog.

        Returns:
            bool: True if the restore success
        """
        # Creates the dialog if its not already exists
        if self.dialog is None:
            self.dialog = ExampleDialog()

        # Restores the layout
        return self.dialog.Restore(pluginid=PLUGIN_ID, secret=sec_ref)


# main
if __name__ == "__main__":
    # Registers the plugin
    c4d.plugins.RegisterCommandPlugin(id=PLUGIN_ID,
                                      str="Py-CommandData Dialog",
                                      info=0,
                                      help="Display a basic GUI",
                                      dat=ExampleDialogCommand(),
                                      icon=None)

Heelo @stereo_stan,

@stereo_stan said in Add button to run a script:

or some reason, it will not add my button to the dialog-basically just trying to add buttons to the py-commanddata script.

The posted code does not add any button. This would have to happen in ExampleDialog.CreateLayout() which does only set the title of the dialog and adds a cancle group. Is that what you mean with "button"?

Cheers,
Ferdinand

@stereo_stan

As Ferdinand already said, you forgot to add the AddButton call, or rather (as I can see my line self.AddButton(ID_BUTTON3, c4d.BFH_SCALEFIT, name="Jump into code") in the script), you inserted it in the wrong place (it's now inside a comment section, where it doesn't do anything, and also in the wrong method.

Additionally, I need to tell you that you must define ID_BUTTON3 somewhere, or it will raise an error. And the snippet if ID == ID_BUTTON3: will not work since the parameter ID that I used is actually called messageId in your command method. Oops, and you duplicated the whole command method so now you have two of them and Python will only consider the latter and ignore the first.

Okay, I guess Maxon hates me for self promoting here, but this is not so much an API question now but a matter of plain Python understanding, so you may perhaps consider my Python/C4D API course under
https://www.patreon.com/cairyn
In section 10 I have multiple examples for dialog setup with layout, buttons, command method, text fields, radio buttons, separators, columns, etc.; too much to replicate here.

I have corrected the script for you a final time, removing all the comments and reducing the sample to the most necessary stuff:

import c4d
from c4d import gui

PLUGIN_ID = 1057171
ID_BUTTON3 = 1001

class ExampleDialog(c4d.gui.GeDialog):

    def CreateLayout(self):
        self.SetTitle("This is an example Dialog")
        self.AddButton(ID_BUTTON3, c4d.BFH_SCALEFIT, name="Jump into code")
        self.AddDlgGroup(c4d.DLG_OK | c4d.DLG_CANCEL)
        return True

    def Command(self, messageId, bc):
        if messageId == c4d.DLG_OK:
            print("User Click on yep")
            return True

        elif messageId == ID_BUTTON3:
            print ("Button clicked")
            return True    

        elif messageId == c4d.DLG_CANCEL:
            print("User Click on Cancel")
            self.Close()
            return True

        return True

class ExampleDialogCommand(c4d.plugins.CommandData):
    dialog = None
    
    def Execute(self, doc):
        if self.dialog is None:
            self.dialog = ExampleDialog()
        return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=PLUGIN_ID, defaultw=400, defaulth=32)

    def RestoreLayout(self, sec_ref):
        if self.dialog is None:
            self.dialog = ExampleDialog()

        return self.dialog.Restore(pluginid=PLUGIN_ID, secret=sec_ref)

if __name__ == "__main__":
    c4d.plugins.RegisterCommandPlugin(id=PLUGIN_ID,
                                      str="Py-CommandData Dialog",
                                      info=0,
                                      help="Display a basic GUI",
                                      dat=ExampleDialogCommand(),
                                      icon=None)

827c6608-acfc-4a41-b73c-a3348488d297-image.png

Thank you Cairyn and Ferdinand again. This is all making much more sense now, I appreciate you helping me understand how to correctly set up a simple interface. I do need to spend more time just learning the fundamentals of Python.

Hello @stereo_stan,

without further questions or replies, we will consider this topic as solved by Monday, the 30th and flag it accordingly.

Thank you for your understanding,
Ferdinand