Solved Add Commands to ShowPopupDialog

Hi,

Is there a way I can add commands to ShowPopupDialog?
In the documentation, it offers commands but built-in ones such as c4d.Ocube or c4d.IDM_COPY.

I want my own functions such as menu.InsData(some_function(), 'some_function'). But when I do so, it gives me an error.

It requires that the first parameter should be an integer(i.e. ID). Is there a way I can convert a function to an integer? Or am I approaching this entirely wrong?

Thank you for looking at my problem

Hi @bentraje this is not possible out of the box, you have to register a CommandData and the Execute method of this CommandData will be executed.

If you don't want to go to this way and keep using only a script, you can assign a unique ID for each menu entry, check for the clicked ID (contained in the return value of ShowPopupDialog) and then call the correct function.

Cheers,
Maxime.

@m_adam

Thanks for the response.

RE: you have to register a CommandData and the Execute method of this CommandData will be executed.
By register, I was thinking of the line c4d.plugins.RegisterCommandPlugin which is included for every single .pyp file. Does this mean I have to create several .pyp files for each separate command then map them in a universal .pyp file?

RE: check for the clicked ID (contained in the return value of ShowPopupDialog) and then call the correct function.

I have a working code below.
It works when you click on the Menu 1 it prints You selected Menu 1. in the console as a test.

My concern is I have it under the Message() and not under the Command() since it does not seem to recognize the ShowPopupDialog ids (or sub-ids).

Is this okay?

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.AddButton(id=20002, flags=c4d.BFH_CENTER, initw=50, inith=50, name="Button Me")
        self.GroupEnd()
        return True
    
    def IsPositionOnGadget(self, gadgetId, x, y):
        # Be sure that the windows is opened,
        # in our case since we call it in BFM_INTERACTSTART it's ok
        buttonData = self.GetItemDim(gadgetId)
        
        if not buttonData["x"] < x < buttonData["x"] + buttonData["w"]:
            return False

        if not buttonData["y"] < y < buttonData["y"] + buttonData["h"]:
            return False

        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.IsPositionOnGadget(gadgetId=20002, x=x, y=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()
                    self.res = c4d.gui.ShowPopupDialog(cd=self, bc=menu, x=x+l2s['x'], y=y+l2s['y'])
                    
                    if self.res == 900000:
                        print "You selected Menu 1"
                    elif self.res == 900001:
                        print "You selected Menu 2"
                    elif self.res == 900002:
                        print "You selected Menu 3"
                    else:
                        return
                    return True

        return c4d.gui.GeDialog.Message(self, msg, result)
    
    def Command(self, id, msg):
        
    
        return True
    
if __name__ == '__main__':
    global dlg
    dlg = TestDialog()
    dlg.Open(dlgtype=c4d.DLG_TYPE_ASYNC)

@bentraje
Hope this will help you out its a small demo I did.
Quick video on how it look inside Cinema 4d. Here
Download Demo Test : GitHub Link
Code:

# Imports
import os
import sys
import c4d
from c4d import plugins, gui, bitmaps, documents, storage, utils
from c4d.gui import GeDialog as WindowDialog
from c4d.plugins import CommandData, TagData, ObjectData

iPath = os.path.join(os.path.dirname(__file__), 'res', "yourIcon.png")

def CustomCommandPopupMenu():
    """" Popup Commands Ids Only Menu """
    menu = c4d.BaseContainer()
    menu.InsData(0, '') # Append separator
    menu.InsData(PLUG_2['ID'], "CMD")           # eg: menu.InsData(00000000, "CMD")
    menu.InsData(0, '') # Append separator
    menu.InsData(PLUG_3['ID'], "CMD")
    result = gui.ShowPopupDialog(cd=None, bc=menu, x=c4d.MOUSEPOS, y=c4d.MOUSEPOS)
    return True    

class CMDTool(CommandData):

    def __init__(self, CMD):
        super(CMDTool, self).__init__()
        self.CMDTool = CMD

    def Init(self, op):
        return True
    def Message(self, type, data):
        return True
  
    def Execute(self, doc):

        if self.CMDTool == "PCT":
            CustomCommandPopupMenu()

        if self.CMDTool == "PCT1":
            gui.MessageDialog("PlugCafeTool2")

        if self.CMDTool == "PCT2":
            gui.MessageDialog("PlugCafeTool2")

    def RestoreLayout(self, sec_ref):
        return True
    def ExecuteOptionID(self, doc, plugid, subid):
        gui.MessageDialog("PlugCafeTool2 Options")
        return True

# ----------------------------------------------------
#               Plugin Registration
# ----------------------------------------------------
#  // Plugin Flags Tpyes  //
class PluginFlags:
    """ Register Info Plugin Flags Tpyes """
    # Plugin General Flags :
    HidePlugin = c4d.PLUGINFLAG_HIDE
    HideTool = c4d.PLUGINFLAG_HIDEPLUGINMENU
    RefreshPlugin = c4d.PLUGINFLAG_REFRESHALWAYS
    SmallNodePlugin = c4d.PLUGINFLAG_SMALLNODE
    # Command Plugin Flags:
    OptionGear = c4d.PLUGINFLAG_COMMAND_OPTION_DIALOG   # A info flag / Command has additional options. The user can access them through a small gadget.
    # Tag Plugin Flags :
    TagVis = c4d.TAG_VISIBLE                            # The tag can be seen in the object manager.
    TagMul = c4d.TAG_MULTIPLE                           # Multiple copies of the tag allowed on a single object.
    TagHier = c4d.TAG_HIERARCHICAL                      # The tag works hierarchical, so that sub-objects inherit its properties (e.g. the material tag).
    TagExp = c4d.TAG_EXPRESSION                         # The tag is an expression.
    TagTem = c4d.TAG_TEMPORARY                          # Private.
    # Object Plugin Flags:
    oMod = c4d.OBJECT_MODIFIER                          # Modifier object. Deforms the surrounding object. (E.g. bend.)
    oHier = c4d.OBJECT_HIERARCHYMODIFIER                # Hierarchical modifier. Deforms the surrounding objects together with other instances in a hierarchy chain. Only the top-most instance of the plugin in a chain is called. (E.g. bones.)Hierarchical modifier. Deforms the surrounding objects together with other instances in a hierarchy chain. Only the top-most instance of the plugin in a chain is called. (E.g. bones.)
    oGen = c4d.OBJECT_GENERATOR                         # Generator object. Produces a polygonal or spline representation on its own. (E.g. primitive cube.)
    oInput = c4d.OBJECT_INPUT                           # Used in combination with OBJECT_GENERATOR. Specifies that the generator uses builds a polygon or spline, using its subobjects as input. (E.g. Sweep Subdivision Surface, Boolean.)
    oPart = c4d.OBJECT_PARTICLEMODIFIER                 # Particle modifier.
    oSpline = c4d.OBJECT_ISSPLINE                       # The object is a spline.
    oCamera = c4d.OBJECT_CAMERADEPENDENT                # Camera dependent.
    oPointObj = c4d.OBJECT_POINTOBJECT                  # Point Object.
    oPolyObj = c4d.OBJECT_POLYGONOBJECT                 # Polygon object.
PF = PluginFlags()
# // Register Plugin Add-on Tool Tpyes to Cinema 4D //
def RegisterCommandData(id, str_name, infoflags, iconName, helpInfo, dataClass):
    """ A CommandData Tool Plugin Register """
    DESCRIPTIONS = "" #ToolInfo_Description(helpInfo)
    plugin_Icon = c4d.bitmaps.BaseBitmap()
    plugin_Icon.InitWith(iconName)
    result = plugins.RegisterCommandPlugin(id=id,                       # Plugin register ID.
                                        str=str_name,                   # This is for the Plugin Name to show in the Plugins lic4d.storage.
                                        info=infoflags,                 # If you want a option button once you have a ExecuteOptionID in Data Class, 
                                                                        # then put in Flags info=c4d.PLUGINFLAG_COMMAND_OPTION_DIALOG|c4d.PLUGINFLAG_COMMAND_HOTKEY,
                                        icon=plugin_Icon,               # Plugin Icon Image.
                                        help=DESCRIPTIONS,              # The plugin help info is on what the plugin does.
                                        dat=dataClass)                  # The plugin data class.
    return True
# // Register Tools // 
PLUG_1 = {'ID':1050002, 'Icon':iPath, 'Name':"CommandTools Menu",'flags':0, 'Data':CMDTool("PCT"), 'Info':""}
PLUG_2 = {'ID':1051421, 'Icon':iPath, 'Name':"PlugCafeTool-1", 'flags':PF.HideTool, 'Data':CMDTool("PCT1"), 'Info':""}
PLUG_3 = {'ID':1054336, 'Icon':iPath, 'Name':"PlugCafeTool-2", 'flags':PF.HideTool|PF.OptionGear, 'Data':CMDTool("PCT2"), 'Info':""}

if __name__ == '__main__':
    dir, file = os.path.split(__file__)
    RegisterCommandData(PLUG_1["ID"], PLUG_1["Name"], PLUG_1["flags"], PLUG_1["Icon"], PLUG_1["Info"], PLUG_1["Data"])
    RegisterCommandData(PLUG_2["ID"], PLUG_2["Name"], PLUG_2["flags"], PLUG_2["Icon"], PLUG_2["Info"], PLUG_2["Data"])
    RegisterCommandData(PLUG_3["ID"], PLUG_3["Name"], PLUG_3["flags"], PLUG_3["Icon"], PLUG_3["Info"], PLUG_3["Data"])

cheers & good luck!
Ap Ashton

Hi @Ashton_FCS_PluginDev

Thanks for the demo. It works as advertised.
It certainly answers one of the two options presented by @m_adam on how to approach it.

  1. you have to register a CommandData
  2. you can assign a unique ID for each menu entry, check for the clicked ID

Hope you'll excuse me if I keep the thread open. I'm interested on @m_adam 's response on my execution of #2 option under the Message() (see above).

Thanks again for the response. Have a great day ahead!

As @Ashton_FCS_PluginDev demonstrates, you can call multiple RegisterXX in your pyp file.
So yes you have to register a CommandData for each in the first situation.

You don't have to react about it on the Message function since the whole script/Cinema 4D will be blocked until you clicked/selected answers and the clicked ID will be the return value of ShowPopupDialog but as explained and demonstrated in the ShowPopupDialog documentation.

Cheers,
Maxime.

@m_adam Thanks for further clarification.

@Ashton_FCS_PluginDev 's code works but I guess I'll just use the result of the ShowPopupDialog to trigger commands, as I added in the previous code.